Config: reformat all descriptions to fill 80 columns
[ncmpcpp.git] / src / display.cpp
blob329b061b1c1c8237e4a5993b75f09ff13085c54b
1 /***************************************************************************
2 * Copyright (C) 2008-2017 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 "curses/menu_impl.h"
24 #include "screens/browser.h"
25 #include "charset.h"
26 #include "display.h"
27 #include "format_impl.h"
28 #include "helpers.h"
29 #include "screens/song_info.h"
30 #include "screens/playlist.h"
31 #include "global.h"
32 #include "screens/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,
82 bool &is_in_playlist, bool &discard_colors)
84 size_t drawn_pos = menu.drawn() - menu.begin();
85 separate_albums = false;
86 if (Config.playlist_separate_albums)
88 auto next = list.beginS() + drawn_pos + 1;
89 if (next != list.endS())
91 if (next->song() != nullptr && next->song()->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 int song_pos = s.getPosition();
102 is_now_playing = Status::State::player() != MPD::psStop
103 && myPlaylist->isActiveWindow(menu)
104 && song_pos == Status::State::currentSongPosition();
105 if (is_now_playing)
106 menu << Config.now_playing_prefix;
108 is_in_playlist = !myPlaylist->isActiveWindow(menu)
109 && myPlaylist->checkForSong(s);
110 if (is_in_playlist)
111 menu << NC::Format::Bold;
113 is_selected = menu.drawn()->isSelected();
114 discard_colors = Config.discard_colors_if_item_is_selected && is_selected;
117 template <typename T>
118 void unsetProperties(NC::Menu<T> &menu, bool separate_albums, bool is_now_playing,
119 bool is_in_playlist)
121 if (is_in_playlist)
122 menu << NC::Format::NoBold;
124 if (is_now_playing)
125 menu << Config.now_playing_suffix;
127 if (separate_albums)
128 menu << NC::Format::NoUnderline;
131 template <typename T>
132 void showSongs(NC::Menu<T> &menu, const MPD::Song &s, const SongList &list, const Format::AST<char> &ast)
134 bool separate_albums, is_now_playing, is_selected, is_in_playlist, discard_colors;
135 setProperties(menu, s, list, separate_albums, is_now_playing, is_selected,
136 is_in_playlist, discard_colors);
138 const size_t y = menu.getY();
139 NC::Buffer right_aligned;
140 Format::print(ast, menu, &s, &right_aligned,
141 discard_colors ? Format::Flags::Tag | Format::Flags::OutputSwitch : Format::Flags::All
143 if (!right_aligned.str().empty())
145 size_t x_off = menu.getWidth() - wideLength(ToWString(right_aligned.str()));
146 if (is_now_playing)
147 x_off -= Config.now_playing_suffix_length;
148 if (is_selected)
149 x_off -= Config.selected_item_suffix_length;
150 menu << NC::TermManip::ClearToEOL << NC::XY(x_off, y) << right_aligned;
153 unsetProperties(menu, separate_albums, is_now_playing, is_in_playlist);
156 template <typename T>
157 void showSongsInColumns(NC::Menu<T> &menu, const MPD::Song &s, const SongList &list)
159 if (Config.columns.empty())
160 return;
162 bool separate_albums, is_now_playing, is_selected, is_in_playlist, discard_colors;
163 setProperties(menu, s, list, separate_albums, is_now_playing, is_selected,
164 is_in_playlist, discard_colors);
166 int menu_width = menu.getWidth();
167 if (is_now_playing)
169 menu_width -= Config.now_playing_prefix_length;
170 menu_width -= Config.now_playing_suffix_length;
172 if (is_selected)
174 menu_width -= Config.selected_item_prefix_length;
175 menu_width -= Config.selected_item_suffix_length;
178 int width;
179 int y = menu.getY();
180 int remained_width = menu_width;
182 std::vector<Column>::const_iterator it, last = Config.columns.end() - 1;
183 for (it = Config.columns.begin(); it != Config.columns.end(); ++it)
185 // check current X coordinate
186 int x = menu.getX();
187 // column has relative width and all after it have fixed width,
188 // so stretch it so it fills whole screen along with these after.
189 if (it->stretch_limit >= 0) // (*)
190 width = remained_width - it->stretch_limit;
191 else
192 width = it->fixed ? it->width : it->width * menu_width * 0.01;
193 // columns with relative width may shrink to 0, omit them
194 if (width == 0)
195 continue;
196 // if column is not last, we need to have spacing between it
197 // and next column, so we substract it now and restore later.
198 if (it != last)
199 --width;
201 // if column doesn't fit into screen, discard it and any other after it.
202 if (remained_width-width < 0 || width < 0 /* this one may come from (*) */)
203 break;
205 std::wstring tag;
206 for (size_t i = 0; i < it->type.length(); ++i)
208 MPD::Song::GetFunction get = charToGetFunction(it->type[i]);
209 assert(get);
210 tag = ToWString(Charset::utf8ToLocale(s.getTags(get)));
211 if (!tag.empty())
212 break;
214 if (tag.empty() && it->display_empty_tag)
215 tag = ToWString(Config.empty_tag);
216 wideCut(tag, width);
218 if (!discard_colors && it->color != NC::Color::Default)
219 menu << it->color;
221 int x_off = 0;
222 // if column uses right alignment, calculate proper offset.
223 // otherwise just assume offset is 0, ie. we start from the left.
224 if (it->right_alignment)
225 x_off = std::max(0, width - int(wideLength(tag)));
227 whline(menu.raw(), NC::Key::Space, width);
228 menu.goToXY(x + x_off, y);
229 menu << tag;
230 menu.goToXY(x + width, y);
231 if (it != last)
233 // add missing width's part and restore the value.
234 menu << ' ';
235 remained_width -= width+1;
238 if (!discard_colors && it->color != NC::Color::Default)
239 menu << NC::Color::End;
242 unsetProperties(menu, separate_albums, is_now_playing, is_in_playlist);
247 std::string Display::Columns(size_t list_width)
249 std::string result;
250 if (Config.columns.empty())
251 return result;
253 int width;
254 int remained_width = list_width;
255 std::vector<Column>::const_iterator it, last = Config.columns.end() - 1;
256 for (it = Config.columns.begin(); it != Config.columns.end(); ++it)
258 // column has relative width and all after it have fixed width,
259 // so stretch it so it fills whole screen along with these after.
260 if (it->stretch_limit >= 0) // (*)
261 width = remained_width - it->stretch_limit;
262 else
263 width = it->fixed ? it->width : it->width * list_width * 0.01;
264 // columns with relative width may shrink to 0, omit them
265 if (width == 0)
266 continue;
267 // if column is not last, we need to have spacing between it
268 // and next column, so we substract it now and restore later.
269 if (it != last)
270 --width;
272 // if column doesn't fit into screen, discard it and any other after it.
273 if (remained_width-width < 0 || width < 0 /* this one may come from (*) */)
274 break;
276 std::wstring name;
277 if (it->name.empty())
279 size_t j = 0;
280 while (true)
282 name += toColumnName(it->type[j]);
283 ++j;
284 if (j < it->type.length())
285 name += '/';
286 else
287 break;
290 else
291 name = it->name;
292 wideCut(name, width);
294 int x_off = std::max(0, width - int(wideLength(name)));
295 if (it->right_alignment)
297 result += std::string(x_off, NC::Key::Space);
298 result += Charset::utf8ToLocale(ToString(name));
300 else
302 result += Charset::utf8ToLocale(ToString(name));
303 result += std::string(x_off, NC::Key::Space);
306 if (it != last)
308 // add missing width's part and restore the value.
309 remained_width -= width+1;
310 result += ' ';
314 return result;
317 void Display::SongsInColumns(NC::Menu<MPD::Song> &menu, const SongList &list)
319 showSongsInColumns(menu, menu.drawn()->value(), list);
322 void Display::Songs(NC::Menu<MPD::Song> &menu, const SongList &list, const Format::AST<char> &ast)
324 showSongs(menu, menu.drawn()->value(), list, ast);
327 #ifdef HAVE_TAGLIB_H
328 void Display::Tags(NC::Menu<MPD::MutableSong> &menu)
330 const MPD::MutableSong &s = menu.drawn()->value();
331 if (s.isModified())
332 menu << Config.modified_item_prefix;
333 size_t i = myTagEditor->TagTypes->choice();
334 if (i < 11)
336 ShowTag(menu, Charset::utf8ToLocale(s.getTags(SongInfo::Tags[i].Get)));
338 else if (i == 12)
340 if (s.getNewName().empty())
341 menu << Charset::utf8ToLocale(s.getName());
342 else
343 menu << Charset::utf8ToLocale(s.getName())
344 << Config.color2
345 << " -> "
346 << NC::FormattedColor::End(Config.color2)
347 << Charset::utf8ToLocale(s.getNewName());
350 #endif // HAVE_TAGLIB_H
352 void Display::Items(NC::Menu<MPD::Item> &menu, const SongList &list)
354 const MPD::Item &item = menu.drawn()->value();
355 switch (item.type())
357 case MPD::Item::Type::Directory:
358 menu << "["
359 << Charset::utf8ToLocale(getBasename(item.directory().path()))
360 << "]";
361 break;
362 case MPD::Item::Type::Song:
363 switch (Config.browser_display_mode)
365 case DisplayMode::Classic:
366 showSongs(menu, item.song(), list, Config.song_list_format);
367 break;
368 case DisplayMode::Columns:
369 showSongsInColumns(menu, item.song(), list);
370 break;
372 break;
373 case MPD::Item::Type::Playlist:
374 menu << Config.browser_playlist_prefix
375 << Charset::utf8ToLocale(getBasename(item.playlist().path()));
376 break;
380 void Display::SEItems(NC::Menu<SEItem> &menu, const SongList &list)
382 const SEItem &si = menu.drawn()->value();
383 if (si.isSong())
385 switch (Config.search_engine_display_mode)
387 case DisplayMode::Classic:
388 showSongs(menu, si.song(), list, Config.song_list_format);
389 break;
390 case DisplayMode::Columns:
391 showSongsInColumns(menu, si.song(), list);
392 break;
395 else
396 menu << si.buffer();