status: store status fields seperately
[ncmpcpp.git] / src / display.cpp
blob45d892bf21f4c33a4bae55fbf1d10599041208de
1 /***************************************************************************
2 * Copyright (C) 2008-2014 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
4 * *
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. *
9 * *
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. *
14 * *
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 ***************************************************************************/
21 #include <cassert>
23 #include "browser.h"
24 #include "charset.h"
25 #include "display.h"
26 #include "helpers.h"
27 #include "song_info.h"
28 #include "playlist.h"
29 #include "global.h"
30 #include "tag_editor.h"
31 #include "utility/string.h"
32 #include "utility/type_conversions.h"
34 using Global::myScreen;
36 namespace {//
38 const wchar_t *toColumnName(char c)
40 switch (c)
42 case 'l':
43 return L"Time";
44 case 'f':
45 return L"Filename";
46 case 'D':
47 return L"Directory";
48 case 'a':
49 return L"Artist";
50 case 'A':
51 return L"Album Artist";
52 case 't':
53 return L"Title";
54 case 'b':
55 return L"Album";
56 case 'y':
57 return L"Date";
58 case 'n': case 'N':
59 return L"Track";
60 case 'g':
61 return L"Genre";
62 case 'c':
63 return L"Composer";
64 case 'p':
65 return L"Performer";
66 case 'd':
67 return L"Disc";
68 case 'C':
69 return L"Comment";
70 case 'P':
71 return L"Priority";
72 default:
73 return L"?";
77 template <typename T>
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;
90 if (separate_albums)
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();
102 if (is_now_playing)
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)
116 if (*it == '$')
118 ++it;
119 if (it == line.end()) // end of format
121 menu << '$';
122 break;
124 else if (isdigit(*it)) // color
126 if (!discard_colors)
127 menu << NC::Color(*it-'0');
129 else if (*it == 'R') // right align
131 NC::Buffer buf;
132 buf << " ";
133 stringToBuffer(++it, line.end(), buf);
134 if (discard_colors)
135 buf.removeProperties();
136 size_t x_off = menu.getWidth() - wideLength(ToWString(buf.str()));
137 if (is_now_playing)
138 x_off -= Config.now_playing_suffix_length;
139 if (is_selected)
140 x_off -= Config.selected_item_suffix_length;
141 menu << NC::XY(x_off, y) << buf;
142 break;
144 else // not a color nor right align, just a random character
145 menu << *--it;
147 else if (*it == MPD::Song::FormatEscapeCharacter)
149 ++it;
150 // treat '$' as a normal character if song format escape char is prepended to it
151 if (it == line.end() || *it != '$')
152 --it;
153 menu << *it;
155 else
156 menu << *it;
158 if (is_now_playing)
159 menu << Config.now_playing_suffix;
160 if (separate_albums)
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())
168 return;
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);
173 int width;
174 int y = menu.getY();
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
180 int x = menu.getX();
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;
185 else
186 width = it->fixed ? it->width : it->width * menu.getWidth() * 0.01;
187 // columns with relative width may shrink to 0, omit them
188 if (width == 0)
189 continue;
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.
192 if (it != last)
193 --width;
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.
203 int offset = 0;
204 if (is_now_playing)
205 offset += Config.now_playing_prefix_length;
206 if (is_selected)
207 offset += Config.selected_item_prefix_length;
208 if (width-offset < 0)
210 remained_width -= width + 1;
211 menu.goToXY(width, y);
212 menu << ' ';
213 continue;
215 width -= offset;
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 (*) */)
221 break;
223 std::wstring tag;
224 for (size_t i = 0; i < it->type.length(); ++i)
226 MPD::Song::GetFunction get = charToGetFunction(it->type[i]);
227 assert(get);
228 tag = ToWString(Charset::utf8ToLocale(s.getTags(get, Config.tags_separator)));
229 if (!tag.empty())
230 break;
232 if (tag.empty() && it->display_empty_tag)
233 tag = ToWString(Config.empty_tag);
234 wideCut(tag, width);
236 if (!discard_colors && it->color != NC::Color::Default)
237 menu << it->color;
239 int x_off = 0;
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);
247 menu << tag;
248 menu.goToXY(x + width, y);
249 if (it != last)
251 // add missing width's part and restore the value.
252 menu << ' ';
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
263 // returns there).
264 if (is_now_playing)
266 int np_x = menu.getWidth() - Config.now_playing_suffix_length;
267 if (is_selected)
268 np_x -= Config.selected_item_suffix_length;
269 menu.goToXY(np_x, y);
270 menu << Config.now_playing_suffix;
272 if (is_selected)
273 menu.goToXY(menu.getWidth() - Config.selected_item_suffix_length, y);
275 if (separate_albums)
276 menu << NC::Format::NoUnderline;
281 std::string Display::Columns(size_t list_width)
283 std::string result;
284 if (Config.columns.empty())
285 return result;
287 int width;
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;
296 else
297 width = it->fixed ? it->width : it->width * list_width * 0.01;
298 // columns with relative width may shrink to 0, omit them
299 if (width == 0)
300 continue;
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.
303 if (it != last)
304 --width;
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 (*) */)
308 break;
310 std::wstring name;
311 if (it->name.empty())
313 size_t j = 0;
314 while (true)
316 name += toColumnName(it->type[j]);
317 ++j;
318 if (j < it->type.length())
319 name += '/';
320 else
321 break;
324 else
325 name = it->name;
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));
334 else
336 result += Charset::utf8ToLocale(ToString(name));
337 result += std::string(x_off, KEY_SPACE);
340 if (it != last)
342 // add missing width's part and restore the value.
343 remained_width -= width+1;
344 result += ' ';
348 return result;
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);
361 #ifdef HAVE_TAGLIB_H
362 void Display::Tags(NC::Menu<MPD::MutableSong> &menu)
364 const MPD::MutableSong &s = menu.drawn()->value();
365 if (s.isModified())
366 menu << Config.modified_item_prefix;
367 size_t i = myTagEditor->TagTypes->choice();
368 if (i < 11)
370 ShowTag(menu, Charset::utf8ToLocale(s.getTags(SongInfo::Tags[i].Get, Config.tags_separator)));
372 else if (i == 12)
374 if (s.getNewName().empty())
375 menu << Charset::utf8ToLocale(s.getName());
376 else
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();
387 switch (item.type)
389 case MPD::itDirectory:
390 menu << "["
391 << Charset::utf8ToLocale(getBasename(item.name))
392 << "]";
393 break;
394 case MPD::itSong:
395 switch (Config.browser_display_mode)
397 case DisplayMode::Classic:
398 showSongs(menu, *item.song, pl, Config.song_list_format);
399 break;
400 case DisplayMode::Columns:
401 showSongsInColumns(menu, *item.song, pl);
402 break;
404 break;
405 case MPD::itPlaylist:
406 menu << Config.browser_playlist_prefix
407 << Charset::utf8ToLocale(getBasename(item.name));
408 break;
412 void Display::SEItems(NC::Menu<SEItem> &menu, const ProxySongList &pl)
414 const SEItem &si = menu.drawn()->value();
415 if (si.isSong())
417 switch (Config.search_engine_display_mode)
419 case DisplayMode::Classic:
420 showSongs(menu, si.song(), pl, Config.song_list_format);
421 break;
422 case DisplayMode::Columns:
423 showSongsInColumns(menu, si.song(), pl);
424 break;
427 else
428 menu << si.buffer();