actions: use unique_ptr for storing actions
[ncmpcpp.git] / src / display.cpp
blobe50915bf7ccdcebcc66ac4c67c295107e733e121
1 /***************************************************************************
2 * Copyright (C) 2008-2016 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 = s.getPosition();
105 is_now_playing = Status::State::player() != MPD::psStop
106 && myPlaylist->isActiveWindow(menu)
107 && song_pos == Status::State::currentSongPosition();
108 if (is_now_playing)
109 menu << Config.now_playing_prefix;
112 template <typename T>
113 void showSongs(NC::Menu<T> &menu, const MPD::Song &s, const SongList &list, const Format::AST<char> &ast)
115 bool separate_albums, is_now_playing, is_selected, discard_colors;
116 setProperties(menu, s, list, separate_albums, is_now_playing, is_selected, discard_colors);
118 const size_t y = menu.getY();
119 NC::Buffer right_aligned;
120 Format::print(ast, menu, &s, &right_aligned,
121 discard_colors ? Format::Flags::Tag | Format::Flags::OutputSwitch : Format::Flags::All
123 if (!right_aligned.str().empty())
125 size_t x_off = menu.getWidth() - wideLength(ToWString(right_aligned.str()));
126 if (is_now_playing)
127 x_off -= Config.now_playing_suffix_length;
128 if (is_selected)
129 x_off -= Config.selected_item_suffix_length;
130 menu << NC::TermManip::ClearToEOL << NC::XY(x_off, y) << right_aligned;
133 if (is_now_playing)
134 menu << Config.now_playing_suffix;
135 if (separate_albums)
136 menu << NC::Format::NoUnderline;
139 template <typename T>
140 void showSongsInColumns(NC::Menu<T> &menu, const MPD::Song &s, const SongList &list)
142 if (Config.columns.empty())
143 return;
145 bool separate_albums, is_now_playing, is_selected, discard_colors;
146 setProperties(menu, s, list, separate_albums, is_now_playing, is_selected, discard_colors);
148 int width;
149 int y = menu.getY();
150 int remained_width = menu.getWidth();
151 std::vector<Column>::const_iterator it, last = Config.columns.end() - 1;
152 for (it = Config.columns.begin(); it != Config.columns.end(); ++it)
154 // check current X coordinate
155 int x = menu.getX();
156 // column has relative width and all after it have fixed width,
157 // so stretch it so it fills whole screen along with these after.
158 if (it->stretch_limit >= 0) // (*)
159 width = remained_width - it->stretch_limit;
160 else
161 width = it->fixed ? it->width : it->width * menu.getWidth() * 0.01;
162 // columns with relative width may shrink to 0, omit them
163 if (width == 0)
164 continue;
165 // if column is not last, we need to have spacing between it
166 // and next column, so we substract it now and restore later.
167 if (it != last)
168 --width;
170 if (it == Config.columns.begin() && (is_now_playing || is_selected))
172 // here comes the shitty part. if we applied now playing or selected
173 // prefix, first column's width needs to be properly modified, so
174 // next column is not affected by them. if prefixes fit, we just
175 // subtract their width from allowed column's width. if they don't,
176 // then we pretend that they do, but we adjust current cursor position
177 // so part of them will be overwritten by next column.
178 int offset = 0;
179 if (is_now_playing)
180 offset += Config.now_playing_prefix_length;
181 if (is_selected)
182 offset += Config.selected_item_prefix_length;
183 if (width-offset < 0)
185 remained_width -= width + 1;
186 menu.goToXY(width, y);
187 menu << ' ';
188 continue;
190 width -= offset;
191 remained_width -= offset;
194 // if column doesn't fit into screen, discard it and any other after it.
195 if (remained_width-width < 0 || width < 0 /* this one may come from (*) */)
196 break;
198 std::wstring tag;
199 for (size_t i = 0; i < it->type.length(); ++i)
201 MPD::Song::GetFunction get = charToGetFunction(it->type[i]);
202 assert(get);
203 tag = ToWString(Charset::utf8ToLocale(s.getTags(get)));
204 if (!tag.empty())
205 break;
207 if (tag.empty() && it->display_empty_tag)
208 tag = ToWString(Config.empty_tag);
209 wideCut(tag, width);
211 if (!discard_colors && it->color != NC::Color::Default)
212 menu << it->color;
214 int x_off = 0;
215 // if column uses right alignment, calculate proper offset.
216 // otherwise just assume offset is 0, ie. we start from the left.
217 if (it->right_alignment)
218 x_off = std::max(0, width - int(wideLength(tag)));
220 whline(menu.raw(), NC::Key::Space, width);
221 menu.goToXY(x + x_off, y);
222 menu << tag;
223 menu.goToXY(x + width, y);
224 if (it != last)
226 // add missing width's part and restore the value.
227 menu << ' ';
228 remained_width -= width+1;
231 if (!discard_colors && it->color != NC::Color::Default)
232 menu << NC::Color::End;
235 // here comes the shitty part, second chapter. here we apply
236 // now playing suffix or/and make room for selected suffix
237 // (as it will be applied in Menu::Refresh when this function
238 // returns there).
239 if (is_now_playing)
241 int np_x = menu.getWidth() - Config.now_playing_suffix_length;
242 if (is_selected)
243 np_x -= Config.selected_item_suffix_length;
244 menu.goToXY(np_x, y);
245 menu << Config.now_playing_suffix;
247 if (is_selected)
248 menu.goToXY(menu.getWidth() - Config.selected_item_suffix_length, y);
250 if (separate_albums)
251 menu << NC::Format::NoUnderline;
256 std::string Display::Columns(size_t list_width)
258 std::string result;
259 if (Config.columns.empty())
260 return result;
262 int width;
263 int remained_width = list_width;
264 std::vector<Column>::const_iterator it, last = Config.columns.end() - 1;
265 for (it = Config.columns.begin(); it != Config.columns.end(); ++it)
267 // column has relative width and all after it have fixed width,
268 // so stretch it so it fills whole screen along with these after.
269 if (it->stretch_limit >= 0) // (*)
270 width = remained_width - it->stretch_limit;
271 else
272 width = it->fixed ? it->width : it->width * list_width * 0.01;
273 // columns with relative width may shrink to 0, omit them
274 if (width == 0)
275 continue;
276 // if column is not last, we need to have spacing between it
277 // and next column, so we substract it now and restore later.
278 if (it != last)
279 --width;
281 // if column doesn't fit into screen, discard it and any other after it.
282 if (remained_width-width < 0 || width < 0 /* this one may come from (*) */)
283 break;
285 std::wstring name;
286 if (it->name.empty())
288 size_t j = 0;
289 while (true)
291 name += toColumnName(it->type[j]);
292 ++j;
293 if (j < it->type.length())
294 name += '/';
295 else
296 break;
299 else
300 name = it->name;
301 wideCut(name, width);
303 int x_off = std::max(0, width - int(wideLength(name)));
304 if (it->right_alignment)
306 result += std::string(x_off, NC::Key::Space);
307 result += Charset::utf8ToLocale(ToString(name));
309 else
311 result += Charset::utf8ToLocale(ToString(name));
312 result += std::string(x_off, NC::Key::Space);
315 if (it != last)
317 // add missing width's part and restore the value.
318 remained_width -= width+1;
319 result += ' ';
323 return result;
326 void Display::SongsInColumns(NC::Menu<MPD::Song> &menu, const SongList &list)
328 showSongsInColumns(menu, menu.drawn()->value(), list);
331 void Display::Songs(NC::Menu<MPD::Song> &menu, const SongList &list, const Format::AST<char> &ast)
333 showSongs(menu, menu.drawn()->value(), list, ast);
336 #ifdef HAVE_TAGLIB_H
337 void Display::Tags(NC::Menu<MPD::MutableSong> &menu)
339 const MPD::MutableSong &s = menu.drawn()->value();
340 if (s.isModified())
341 menu << Config.modified_item_prefix;
342 size_t i = myTagEditor->TagTypes->choice();
343 if (i < 11)
345 ShowTag(menu, Charset::utf8ToLocale(s.getTags(SongInfo::Tags[i].Get)));
347 else if (i == 12)
349 if (s.getNewName().empty())
350 menu << Charset::utf8ToLocale(s.getName());
351 else
352 menu << Charset::utf8ToLocale(s.getName())
353 << Config.color2 << " -> " << NC::Color::End
354 << Charset::utf8ToLocale(s.getNewName());
357 #endif // HAVE_TAGLIB_H
359 void Display::Items(NC::Menu<MPD::Item> &menu, const SongList &list)
361 const MPD::Item &item = menu.drawn()->value();
362 switch (item.type())
364 case MPD::Item::Type::Directory:
365 menu << "["
366 << Charset::utf8ToLocale(getBasename(item.directory().path()))
367 << "]";
368 break;
369 case MPD::Item::Type::Song:
370 switch (Config.browser_display_mode)
372 case DisplayMode::Classic:
373 showSongs(menu, item.song(), list, Config.song_list_format);
374 break;
375 case DisplayMode::Columns:
376 showSongsInColumns(menu, item.song(), list);
377 break;
379 break;
380 case MPD::Item::Type::Playlist:
381 menu << Config.browser_playlist_prefix
382 << Charset::utf8ToLocale(getBasename(item.playlist().path()));
383 break;
387 void Display::SEItems(NC::Menu<SEItem> &menu, const SongList &list)
389 const SEItem &si = menu.drawn()->value();
390 if (si.isSong())
392 switch (Config.search_engine_display_mode)
394 case DisplayMode::Classic:
395 showSongs(menu, si.song(), list, Config.song_list_format);
396 break;
397 case DisplayMode::Columns:
398 showSongsInColumns(menu, si.song(), list);
399 break;
402 else
403 menu << si.buffer();