1 /***************************************************************************
2 * Copyright (C) 2008-2014 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
27 #include "song_info.h"
30 #include "tag_editor.h"
31 #include "utility/string.h"
32 #include "utility/type_conversions.h"
34 using Global::myScreen
;
38 const wchar_t *toColumnName(char c
)
51 return L
"Album Artist";
78 void setProperties(NC::Menu
<T
> &menu
, const MPD::Song
&s
, const ProxySongList
&pl
, bool &separate_albums
,
79 bool &is_now_playing
, bool &is_selected
, bool &discard_colors
)
81 size_t drawn_pos
= menu
.drawn() - menu
.begin();
82 separate_albums
= false;
83 if (Config
.playlist_separate_albums
)
85 size_t next_pos
= drawn_pos
+1;
86 auto next
= next_pos
< pl
.size() ? pl
.getSong(next_pos
) : 0;
87 if (next
&& next
->getAlbum() != s
.getAlbum())
88 separate_albums
= true;
92 menu
<< NC::Format::Underline
;
93 mvwhline(menu
.raw(), menu
.getY(), 0, KEY_SPACE
, menu
.getWidth());
96 is_selected
= menu
.drawn()->isSelected();
97 discard_colors
= Config
.discard_colors_if_item_is_selected
&& is_selected
;
99 int song_pos
= menu
.isFiltered() ? s
.getPosition() : drawn_pos
;
100 is_now_playing
= Status::State::player() != MPD::psStop
&& myPlaylist
->isActiveWindow(menu
)
101 && song_pos
== Status::State::currentSongPosition();
103 menu
<< Config
.now_playing_prefix
;
106 template <typename T
>
107 void showSongs(NC::Menu
<T
> &menu
, const MPD::Song
&s
, const ProxySongList
&pl
, const std::string
&format
)
109 bool separate_albums
, is_now_playing
, is_selected
, discard_colors
;
110 setProperties(menu
, s
, pl
, separate_albums
, is_now_playing
, is_selected
, discard_colors
);
112 size_t y
= menu
.getY();
113 std::string line
= Charset::utf8ToLocale(s
.toString(format
, Config
.tags_separator
, "$"));
114 for (auto it
= line
.begin(); it
!= line
.end(); ++it
)
119 if (it
== line
.end()) // end of format
124 else if (isdigit(*it
)) // color
127 menu
<< NC::Color(*it
-'0');
129 else if (*it
== 'R') // right align
133 stringToBuffer(++it
, line
.end(), buf
);
135 buf
.removeProperties();
136 size_t x_off
= menu
.getWidth() - wideLength(ToWString(buf
.str()));
138 x_off
-= Config
.now_playing_suffix_length
;
140 x_off
-= Config
.selected_item_suffix_length
;
141 menu
<< NC::XY(x_off
, y
) << buf
;
144 else // not a color nor right align, just a random character
147 else if (*it
== MPD::Song::FormatEscapeCharacter
)
150 // treat '$' as a normal character if song format escape char is prepended to it
151 if (it
== line
.end() || *it
!= '$')
159 menu
<< Config
.now_playing_suffix
;
161 menu
<< NC::Format::NoUnderline
;
164 template <typename T
>
165 void showSongsInColumns(NC::Menu
<T
> &menu
, const MPD::Song
&s
, const ProxySongList
&pl
)
167 if (Config
.columns
.empty())
170 bool separate_albums
, is_now_playing
, is_selected
, discard_colors
;
171 setProperties(menu
, s
, pl
, separate_albums
, is_now_playing
, is_selected
, discard_colors
);
175 int remained_width
= menu
.getWidth();
176 std::vector
<Column
>::const_iterator it
, last
= Config
.columns
.end() - 1;
177 for (it
= Config
.columns
.begin(); it
!= Config
.columns
.end(); ++it
)
179 // check current X coordinate
181 // column has relative width and all after it have fixed width,
182 // so stretch it so it fills whole screen along with these after.
183 if (it
->stretch_limit
>= 0) // (*)
184 width
= remained_width
- it
->stretch_limit
;
186 width
= it
->fixed
? it
->width
: it
->width
* menu
.getWidth() * 0.01;
187 // columns with relative width may shrink to 0, omit them
190 // if column is not last, we need to have spacing between it
191 // and next column, so we substract it now and restore later.
195 if (it
== Config
.columns
.begin() && (is_now_playing
|| is_selected
))
197 // here comes the shitty part. if we applied now playing or selected
198 // prefix, first column's width needs to be properly modified, so
199 // next column is not affected by them. if prefixes fit, we just
200 // subtract their width from allowed column's width. if they don't,
201 // then we pretend that they do, but we adjust current cursor position
202 // so part of them will be overwritten by next column.
205 offset
+= Config
.now_playing_prefix_length
;
207 offset
+= Config
.selected_item_prefix_length
;
208 if (width
-offset
< 0)
210 remained_width
-= width
+ 1;
211 menu
.goToXY(width
, y
);
216 remained_width
-= offset
;
219 // if column doesn't fit into screen, discard it and any other after it.
220 if (remained_width
-width
< 0 || width
< 0 /* this one may come from (*) */)
224 for (size_t i
= 0; i
< it
->type
.length(); ++i
)
226 MPD::Song::GetFunction get
= charToGetFunction(it
->type
[i
]);
228 tag
= ToWString(Charset::utf8ToLocale(s
.getTags(get
, Config
.tags_separator
)));
232 if (tag
.empty() && it
->display_empty_tag
)
233 tag
= ToWString(Config
.empty_tag
);
236 if (!discard_colors
&& it
->color
!= NC::Color::Default
)
240 // if column uses right alignment, calculate proper offset.
241 // otherwise just assume offset is 0, ie. we start from the left.
242 if (it
->right_alignment
)
243 x_off
= std::max(0, width
- int(wideLength(tag
)));
245 whline(menu
.raw(), KEY_SPACE
, width
);
246 menu
.goToXY(x
+ x_off
, y
);
248 menu
.goToXY(x
+ width
, y
);
251 // add missing width's part and restore the value.
253 remained_width
-= width
+1;
256 if (!discard_colors
&& it
->color
!= NC::Color::Default
)
257 menu
<< NC::Color::End
;
260 // here comes the shitty part, second chapter. here we apply
261 // now playing suffix or/and make room for selected suffix
262 // (as it will be applied in Menu::Refresh when this function
266 int np_x
= menu
.getWidth() - Config
.now_playing_suffix_length
;
268 np_x
-= Config
.selected_item_suffix_length
;
269 menu
.goToXY(np_x
, y
);
270 menu
<< Config
.now_playing_suffix
;
273 menu
.goToXY(menu
.getWidth() - Config
.selected_item_suffix_length
, y
);
276 menu
<< NC::Format::NoUnderline
;
281 std::string
Display::Columns(size_t list_width
)
284 if (Config
.columns
.empty())
288 int remained_width
= list_width
;
289 std::vector
<Column
>::const_iterator it
, last
= Config
.columns
.end() - 1;
290 for (it
= Config
.columns
.begin(); it
!= Config
.columns
.end(); ++it
)
292 // column has relative width and all after it have fixed width,
293 // so stretch it so it fills whole screen along with these after.
294 if (it
->stretch_limit
>= 0) // (*)
295 width
= remained_width
- it
->stretch_limit
;
297 width
= it
->fixed
? it
->width
: it
->width
* list_width
* 0.01;
298 // columns with relative width may shrink to 0, omit them
301 // if column is not last, we need to have spacing between it
302 // and next column, so we substract it now and restore later.
306 // if column doesn't fit into screen, discard it and any other after it.
307 if (remained_width
-width
< 0 || width
< 0 /* this one may come from (*) */)
311 if (it
->name
.empty())
316 name
+= toColumnName(it
->type
[j
]);
318 if (j
< it
->type
.length())
326 wideCut(name
, width
);
328 int x_off
= std::max(0, width
- int(wideLength(name
)));
329 if (it
->right_alignment
)
331 result
+= std::string(x_off
, KEY_SPACE
);
332 result
+= Charset::utf8ToLocale(ToString(name
));
336 result
+= Charset::utf8ToLocale(ToString(name
));
337 result
+= std::string(x_off
, KEY_SPACE
);
342 // add missing width's part and restore the value.
343 remained_width
-= width
+1;
351 void Display::SongsInColumns(NC::Menu
< MPD::Song
>& menu
, const ProxySongList
&pl
)
353 showSongsInColumns(menu
, menu
.drawn()->value(), pl
);
356 void Display::Songs(NC::Menu
< MPD::Song
>& menu
, const ProxySongList
&pl
, const std::string
&format
)
358 showSongs(menu
, menu
.drawn()->value(), pl
, format
);
362 void Display::Tags(NC::Menu
<MPD::MutableSong
> &menu
)
364 const MPD::MutableSong
&s
= menu
.drawn()->value();
366 menu
<< Config
.modified_item_prefix
;
367 size_t i
= myTagEditor
->TagTypes
->choice();
370 ShowTag(menu
, Charset::utf8ToLocale(s
.getTags(SongInfo::Tags
[i
].Get
, Config
.tags_separator
)));
374 if (s
.getNewName().empty())
375 menu
<< Charset::utf8ToLocale(s
.getName());
377 menu
<< Charset::utf8ToLocale(s
.getName())
378 << Config
.color2
<< " -> " << NC::Color::End
379 << Charset::utf8ToLocale(s
.getNewName());
382 #endif // HAVE_TAGLIB_H
384 void Display::Items(NC::Menu
<MPD::Item
> &menu
, const ProxySongList
&pl
)
386 const MPD::Item
&item
= menu
.drawn()->value();
389 case MPD::itDirectory
:
391 << Charset::utf8ToLocale(getBasename(item
.name
))
395 switch (Config
.browser_display_mode
)
397 case DisplayMode::Classic
:
398 showSongs(menu
, *item
.song
, pl
, Config
.song_list_format
);
400 case DisplayMode::Columns
:
401 showSongsInColumns(menu
, *item
.song
, pl
);
405 case MPD::itPlaylist
:
406 menu
<< Config
.browser_playlist_prefix
407 << Charset::utf8ToLocale(getBasename(item
.name
));
412 void Display::SEItems(NC::Menu
<SEItem
> &menu
, const ProxySongList
&pl
)
414 const SEItem
&si
= menu
.drawn()->value();
417 switch (Config
.search_engine_display_mode
)
419 case DisplayMode::Classic
:
420 showSongs(menu
, si
.song(), pl
, Config
.song_list_format
);
422 case DisplayMode::Columns
:
423 showSongsInColumns(menu
, si
.song(), pl
);