change version to 0.7.3
[ncmpcpp.git] / src / display.cpp
blobca21c8482850f5dcc762ca6d43bf6dec645ee486
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 "format_impl.h"
27 #include "helpers.h"
28 #include "menu_impl.h"
29 #include "song_info.h"
30 #include "playlist.h"
31 #include "global.h"
32 #include "tag_editor.h"
33 #include "utility/string.h"
34 #include "utility/type_conversions.h"
36 using Global::myScreen;
38 namespace {
40 const wchar_t *toColumnName(char c)
42 switch (c)
44 case 'l':
45 return L"Time";
46 case 'f':
47 return L"Filename";
48 case 'D':
49 return L"Directory";
50 case 'a':
51 return L"Artist";
52 case 'A':
53 return L"Album Artist";
54 case 't':
55 return L"Title";
56 case 'b':
57 return L"Album";
58 case 'y':
59 return L"Date";
60 case 'n': case 'N':
61 return L"Track";
62 case 'g':
63 return L"Genre";
64 case 'c':
65 return L"Composer";
66 case 'p':
67 return L"Performer";
68 case 'd':
69 return L"Disc";
70 case 'C':
71 return L"Comment";
72 case 'P':
73 return L"Priority";
74 default:
75 return L"?";
79 template <typename T>
80 void setProperties(NC::Menu<T> &menu, const MPD::Song &s, const SongList &list,
81 bool &separate_albums, bool &is_now_playing, bool &is_selected, bool &discard_colors)
83 size_t drawn_pos = menu.drawn() - menu.begin();
84 separate_albums = false;
85 if (Config.playlist_separate_albums)
87 auto next = list.beginS() + drawn_pos + 1;
88 if (next != list.endS())
90 auto next_s = next->get<Bit::Song>();
91 if (next_s != nullptr && next_s->getAlbum() != s.getAlbum())
92 separate_albums = true;
95 if (separate_albums)
97 menu << NC::Format::Underline;
98 mvwhline(menu.raw(), menu.getY(), 0, NC::Key::Space, menu.getWidth());
101 is_selected = menu.drawn()->isSelected();
102 discard_colors = Config.discard_colors_if_item_is_selected && is_selected;
104 int song_pos = drawn_pos;
105 is_now_playing = Status::State::player() != MPD::psStop && myPlaylist->isActiveWindow(menu)
106 && song_pos == Status::State::currentSongPosition();
107 if (is_now_playing)
108 menu << Config.now_playing_prefix;
111 template <typename T>
112 void showSongs(NC::Menu<T> &menu, const MPD::Song &s, const SongList &list, const Format::AST<char> &ast)
114 bool separate_albums, is_now_playing, is_selected, discard_colors;
115 setProperties(menu, s, list, separate_albums, is_now_playing, is_selected, discard_colors);
117 const size_t y = menu.getY();
118 NC::Buffer right_aligned;
119 Format::print(ast, menu, &s, &right_aligned,
120 discard_colors ? Format::Flags::Tag | Format::Flags::OutputSwitch : Format::Flags::All
122 if (!right_aligned.str().empty())
124 size_t x_off = menu.getWidth() - wideLength(ToWString(right_aligned.str()));
125 if (is_now_playing)
126 x_off -= Config.now_playing_suffix_length;
127 if (is_selected)
128 x_off -= Config.selected_item_suffix_length;
129 menu << NC::TermManip::ClearToEOL << NC::XY(x_off, y) << right_aligned;
132 if (is_now_playing)
133 menu << Config.now_playing_suffix;
134 if (separate_albums)
135 menu << NC::Format::NoUnderline;
138 template <typename T>
139 void showSongsInColumns(NC::Menu<T> &menu, const MPD::Song &s, const SongList &list)
141 if (Config.columns.empty())
142 return;
144 bool separate_albums, is_now_playing, is_selected, discard_colors;
145 setProperties(menu, s, list, separate_albums, is_now_playing, is_selected, discard_colors);
147 int width;
148 int y = menu.getY();
149 int remained_width = menu.getWidth();
150 std::vector<Column>::const_iterator it, last = Config.columns.end() - 1;
151 for (it = Config.columns.begin(); it != Config.columns.end(); ++it)
153 // check current X coordinate
154 int x = menu.getX();
155 // column has relative width and all after it have fixed width,
156 // so stretch it so it fills whole screen along with these after.
157 if (it->stretch_limit >= 0) // (*)
158 width = remained_width - it->stretch_limit;
159 else
160 width = it->fixed ? it->width : it->width * menu.getWidth() * 0.01;
161 // columns with relative width may shrink to 0, omit them
162 if (width == 0)
163 continue;
164 // if column is not last, we need to have spacing between it
165 // and next column, so we substract it now and restore later.
166 if (it != last)
167 --width;
169 if (it == Config.columns.begin() && (is_now_playing || is_selected))
171 // here comes the shitty part. if we applied now playing or selected
172 // prefix, first column's width needs to be properly modified, so
173 // next column is not affected by them. if prefixes fit, we just
174 // subtract their width from allowed column's width. if they don't,
175 // then we pretend that they do, but we adjust current cursor position
176 // so part of them will be overwritten by next column.
177 int offset = 0;
178 if (is_now_playing)
179 offset += Config.now_playing_prefix_length;
180 if (is_selected)
181 offset += Config.selected_item_prefix_length;
182 if (width-offset < 0)
184 remained_width -= width + 1;
185 menu.goToXY(width, y);
186 menu << ' ';
187 continue;
189 width -= offset;
190 remained_width -= offset;
193 // if column doesn't fit into screen, discard it and any other after it.
194 if (remained_width-width < 0 || width < 0 /* this one may come from (*) */)
195 break;
197 std::wstring tag;
198 for (size_t i = 0; i < it->type.length(); ++i)
200 MPD::Song::GetFunction get = charToGetFunction(it->type[i]);
201 assert(get);
202 tag = ToWString(Charset::utf8ToLocale(s.getTags(get)));
203 if (!tag.empty())
204 break;
206 if (tag.empty() && it->display_empty_tag)
207 tag = ToWString(Config.empty_tag);
208 wideCut(tag, width);
210 if (!discard_colors && it->color != NC::Color::Default)
211 menu << it->color;
213 int x_off = 0;
214 // if column uses right alignment, calculate proper offset.
215 // otherwise just assume offset is 0, ie. we start from the left.
216 if (it->right_alignment)
217 x_off = std::max(0, width - int(wideLength(tag)));
219 whline(menu.raw(), NC::Key::Space, width);
220 menu.goToXY(x + x_off, y);
221 menu << tag;
222 menu.goToXY(x + width, y);
223 if (it != last)
225 // add missing width's part and restore the value.
226 menu << ' ';
227 remained_width -= width+1;
230 if (!discard_colors && it->color != NC::Color::Default)
231 menu << NC::Color::End;
234 // here comes the shitty part, second chapter. here we apply
235 // now playing suffix or/and make room for selected suffix
236 // (as it will be applied in Menu::Refresh when this function
237 // returns there).
238 if (is_now_playing)
240 int np_x = menu.getWidth() - Config.now_playing_suffix_length;
241 if (is_selected)
242 np_x -= Config.selected_item_suffix_length;
243 menu.goToXY(np_x, y);
244 menu << Config.now_playing_suffix;
246 if (is_selected)
247 menu.goToXY(menu.getWidth() - Config.selected_item_suffix_length, y);
249 if (separate_albums)
250 menu << NC::Format::NoUnderline;
255 std::string Display::Columns(size_t list_width)
257 std::string result;
258 if (Config.columns.empty())
259 return result;
261 int width;
262 int remained_width = list_width;
263 std::vector<Column>::const_iterator it, last = Config.columns.end() - 1;
264 for (it = Config.columns.begin(); it != Config.columns.end(); ++it)
266 // column has relative width and all after it have fixed width,
267 // so stretch it so it fills whole screen along with these after.
268 if (it->stretch_limit >= 0) // (*)
269 width = remained_width - it->stretch_limit;
270 else
271 width = it->fixed ? it->width : it->width * list_width * 0.01;
272 // columns with relative width may shrink to 0, omit them
273 if (width == 0)
274 continue;
275 // if column is not last, we need to have spacing between it
276 // and next column, so we substract it now and restore later.
277 if (it != last)
278 --width;
280 // if column doesn't fit into screen, discard it and any other after it.
281 if (remained_width-width < 0 || width < 0 /* this one may come from (*) */)
282 break;
284 std::wstring name;
285 if (it->name.empty())
287 size_t j = 0;
288 while (true)
290 name += toColumnName(it->type[j]);
291 ++j;
292 if (j < it->type.length())
293 name += '/';
294 else
295 break;
298 else
299 name = it->name;
300 wideCut(name, width);
302 int x_off = std::max(0, width - int(wideLength(name)));
303 if (it->right_alignment)
305 result += std::string(x_off, NC::Key::Space);
306 result += Charset::utf8ToLocale(ToString(name));
308 else
310 result += Charset::utf8ToLocale(ToString(name));
311 result += std::string(x_off, NC::Key::Space);
314 if (it != last)
316 // add missing width's part and restore the value.
317 remained_width -= width+1;
318 result += ' ';
322 return result;
325 void Display::SongsInColumns(NC::Menu<MPD::Song> &menu, const SongList &list)
327 showSongsInColumns(menu, menu.drawn()->value(), list);
330 void Display::Songs(NC::Menu<MPD::Song> &menu, const SongList &list, const Format::AST<char> &ast)
332 showSongs(menu, menu.drawn()->value(), list, ast);
335 #ifdef HAVE_TAGLIB_H
336 void Display::Tags(NC::Menu<MPD::MutableSong> &menu)
338 const MPD::MutableSong &s = menu.drawn()->value();
339 if (s.isModified())
340 menu << Config.modified_item_prefix;
341 size_t i = myTagEditor->TagTypes->choice();
342 if (i < 11)
344 ShowTag(menu, Charset::utf8ToLocale(s.getTags(SongInfo::Tags[i].Get)));
346 else if (i == 12)
348 if (s.getNewName().empty())
349 menu << Charset::utf8ToLocale(s.getName());
350 else
351 menu << Charset::utf8ToLocale(s.getName())
352 << Config.color2 << " -> " << NC::Color::End
353 << Charset::utf8ToLocale(s.getNewName());
356 #endif // HAVE_TAGLIB_H
358 void Display::Items(NC::Menu<MPD::Item> &menu, const SongList &list)
360 const MPD::Item &item = menu.drawn()->value();
361 switch (item.type())
363 case MPD::Item::Type::Directory:
364 menu << "["
365 << Charset::utf8ToLocale(getBasename(item.directory().path()))
366 << "]";
367 break;
368 case MPD::Item::Type::Song:
369 switch (Config.browser_display_mode)
371 case DisplayMode::Classic:
372 showSongs(menu, item.song(), list, Config.song_list_format);
373 break;
374 case DisplayMode::Columns:
375 showSongsInColumns(menu, item.song(), list);
376 break;
378 break;
379 case MPD::Item::Type::Playlist:
380 menu << Config.browser_playlist_prefix
381 << Charset::utf8ToLocale(getBasename(item.playlist().path()));
382 break;
386 void Display::SEItems(NC::Menu<SEItem> &menu, const SongList &list)
388 const SEItem &si = menu.drawn()->value();
389 if (si.isSong())
391 switch (Config.search_engine_display_mode)
393 case DisplayMode::Classic:
394 showSongs(menu, si.song(), list, Config.song_list_format);
395 break;
396 case DisplayMode::Columns:
397 showSongsInColumns(menu, si.song(), list);
398 break;
401 else
402 menu << si.buffer();