Make 'update_environment' action also update local mpd status
[ncmpcpp.git] / src / display.cpp
blob4f91290f09b4f94b2813ced98c445032a46b174e
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 "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, 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 if (next->song() != nullptr && next->song()->getAlbum() != s.getAlbum())
91 separate_albums = true;
94 if (separate_albums)
96 menu << NC::Format::Underline;
97 mvwhline(menu.raw(), menu.getY(), 0, NC::Key::Space, menu.getWidth());
100 is_selected = menu.drawn()->isSelected();
101 discard_colors = Config.discard_colors_if_item_is_selected && is_selected;
103 int song_pos = s.getPosition();
104 is_now_playing = Status::State::player() != MPD::psStop
105 && 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 menu_width = menu.getWidth();
148 if (is_now_playing)
150 menu_width -= Config.now_playing_prefix_length;
151 menu_width -= Config.now_playing_suffix_length;
153 if (is_selected)
155 menu_width -= Config.selected_item_prefix_length;
156 menu_width -= Config.selected_item_suffix_length;
159 int width;
160 int y = menu.getY();
161 int remained_width = menu_width;
163 std::vector<Column>::const_iterator it, last = Config.columns.end() - 1;
164 for (it = Config.columns.begin(); it != Config.columns.end(); ++it)
166 // check current X coordinate
167 int x = menu.getX();
168 // column has relative width and all after it have fixed width,
169 // so stretch it so it fills whole screen along with these after.
170 if (it->stretch_limit >= 0) // (*)
171 width = remained_width - it->stretch_limit;
172 else
173 width = it->fixed ? it->width : it->width * menu_width * 0.01;
174 // columns with relative width may shrink to 0, omit them
175 if (width == 0)
176 continue;
177 // if column is not last, we need to have spacing between it
178 // and next column, so we substract it now and restore later.
179 if (it != last)
180 --width;
182 // if column doesn't fit into screen, discard it and any other after it.
183 if (remained_width-width < 0 || width < 0 /* this one may come from (*) */)
184 break;
186 std::wstring tag;
187 for (size_t i = 0; i < it->type.length(); ++i)
189 MPD::Song::GetFunction get = charToGetFunction(it->type[i]);
190 assert(get);
191 tag = ToWString(Charset::utf8ToLocale(s.getTags(get)));
192 if (!tag.empty())
193 break;
195 if (tag.empty() && it->display_empty_tag)
196 tag = ToWString(Config.empty_tag);
197 wideCut(tag, width);
199 if (!discard_colors && it->color != NC::Color::Default)
200 menu << it->color;
202 int x_off = 0;
203 // if column uses right alignment, calculate proper offset.
204 // otherwise just assume offset is 0, ie. we start from the left.
205 if (it->right_alignment)
206 x_off = std::max(0, width - int(wideLength(tag)));
208 whline(menu.raw(), NC::Key::Space, width);
209 menu.goToXY(x + x_off, y);
210 menu << tag;
211 menu.goToXY(x + width, y);
212 if (it != last)
214 // add missing width's part and restore the value.
215 menu << ' ';
216 remained_width -= width+1;
219 if (!discard_colors && it->color != NC::Color::Default)
220 menu << NC::Color::End;
223 if (is_now_playing)
224 menu << Config.now_playing_suffix;
225 if (separate_albums)
226 menu << NC::Format::NoUnderline;
231 std::string Display::Columns(size_t list_width)
233 std::string result;
234 if (Config.columns.empty())
235 return result;
237 int width;
238 int remained_width = list_width;
239 std::vector<Column>::const_iterator it, last = Config.columns.end() - 1;
240 for (it = Config.columns.begin(); it != Config.columns.end(); ++it)
242 // column has relative width and all after it have fixed width,
243 // so stretch it so it fills whole screen along with these after.
244 if (it->stretch_limit >= 0) // (*)
245 width = remained_width - it->stretch_limit;
246 else
247 width = it->fixed ? it->width : it->width * list_width * 0.01;
248 // columns with relative width may shrink to 0, omit them
249 if (width == 0)
250 continue;
251 // if column is not last, we need to have spacing between it
252 // and next column, so we substract it now and restore later.
253 if (it != last)
254 --width;
256 // if column doesn't fit into screen, discard it and any other after it.
257 if (remained_width-width < 0 || width < 0 /* this one may come from (*) */)
258 break;
260 std::wstring name;
261 if (it->name.empty())
263 size_t j = 0;
264 while (true)
266 name += toColumnName(it->type[j]);
267 ++j;
268 if (j < it->type.length())
269 name += '/';
270 else
271 break;
274 else
275 name = it->name;
276 wideCut(name, width);
278 int x_off = std::max(0, width - int(wideLength(name)));
279 if (it->right_alignment)
281 result += std::string(x_off, NC::Key::Space);
282 result += Charset::utf8ToLocale(ToString(name));
284 else
286 result += Charset::utf8ToLocale(ToString(name));
287 result += std::string(x_off, NC::Key::Space);
290 if (it != last)
292 // add missing width's part and restore the value.
293 remained_width -= width+1;
294 result += ' ';
298 return result;
301 void Display::SongsInColumns(NC::Menu<MPD::Song> &menu, const SongList &list)
303 showSongsInColumns(menu, menu.drawn()->value(), list);
306 void Display::Songs(NC::Menu<MPD::Song> &menu, const SongList &list, const Format::AST<char> &ast)
308 showSongs(menu, menu.drawn()->value(), list, ast);
311 #ifdef HAVE_TAGLIB_H
312 void Display::Tags(NC::Menu<MPD::MutableSong> &menu)
314 const MPD::MutableSong &s = menu.drawn()->value();
315 if (s.isModified())
316 menu << Config.modified_item_prefix;
317 size_t i = myTagEditor->TagTypes->choice();
318 if (i < 11)
320 ShowTag(menu, Charset::utf8ToLocale(s.getTags(SongInfo::Tags[i].Get)));
322 else if (i == 12)
324 if (s.getNewName().empty())
325 menu << Charset::utf8ToLocale(s.getName());
326 else
327 menu << Charset::utf8ToLocale(s.getName())
328 << Config.color2
329 << " -> "
330 << NC::FormattedColor::End(Config.color2)
331 << Charset::utf8ToLocale(s.getNewName());
334 #endif // HAVE_TAGLIB_H
336 void Display::Items(NC::Menu<MPD::Item> &menu, const SongList &list)
338 const MPD::Item &item = menu.drawn()->value();
339 switch (item.type())
341 case MPD::Item::Type::Directory:
342 menu << "["
343 << Charset::utf8ToLocale(getBasename(item.directory().path()))
344 << "]";
345 break;
346 case MPD::Item::Type::Song:
347 switch (Config.browser_display_mode)
349 case DisplayMode::Classic:
350 showSongs(menu, item.song(), list, Config.song_list_format);
351 break;
352 case DisplayMode::Columns:
353 showSongsInColumns(menu, item.song(), list);
354 break;
356 break;
357 case MPD::Item::Type::Playlist:
358 menu << Config.browser_playlist_prefix
359 << Charset::utf8ToLocale(getBasename(item.playlist().path()));
360 break;
364 void Display::SEItems(NC::Menu<SEItem> &menu, const SongList &list)
366 const SEItem &si = menu.drawn()->value();
367 if (si.isSong())
369 switch (Config.search_engine_display_mode)
371 case DisplayMode::Classic:
372 showSongs(menu, si.song(), list, Config.song_list_format);
373 break;
374 case DisplayMode::Columns:
375 showSongsInColumns(menu, si.song(), list);
376 break;
379 else
380 menu << si.buffer();