more PascalCase to camelCase conversions
[ncmpcpp.git] / src / playlist_editor.cpp
blob1f593e93c5740919fe4833774848c38a0d5d1a79
1 /***************************************************************************
2 * Copyright (C) 2008-2012 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>
22 #include <algorithm>
24 #include "charset.h"
25 #include "display.h"
26 #include "global.h"
27 #include "helpers.h"
28 #include "playlist.h"
29 #include "playlist_editor.h"
30 #include "mpdpp.h"
31 #include "regex_filter.h"
32 #include "status.h"
33 #include "statusbar.h"
34 #include "tag_editor.h"
35 #include "utility/comparators.h"
36 #include "title.h"
38 using namespace std::placeholders;
40 using Global::MainHeight;
41 using Global::MainStartY;
43 PlaylistEditor *myPlaylistEditor = new PlaylistEditor;
45 namespace {//
47 size_t LeftColumnStartX;
48 size_t LeftColumnWidth;
49 size_t RightColumnStartX;
50 size_t RightColumnWidth;
52 std::string SongToString(const MPD::Song &s);
53 bool PlaylistEntryMatcher(const Regex &rx, const std::string &playlist);
54 bool SongEntryMatcher(const Regex &rx, const MPD::Song &s);
58 void PlaylistEditor::init()
60 LeftColumnWidth = COLS/3-1;
61 RightColumnStartX = LeftColumnWidth+1;
62 RightColumnWidth = COLS-LeftColumnWidth-1;
64 Playlists = new NC::Menu<std::string>(0, MainStartY, LeftColumnWidth, MainHeight, Config.titles_visibility ? "Playlists" : "", Config.main_color, NC::brNone);
65 Playlists->setHighlightColor(Config.active_column_color);
66 Playlists->cyclicScrolling(Config.use_cyclic_scrolling);
67 Playlists->centeredCursor(Config.centered_cursor);
68 Playlists->setSelectedPrefix(Config.selected_item_prefix);
69 Playlists->setSelectedSuffix(Config.selected_item_suffix);
70 Playlists->setItemDisplayer(Display::Default<std::string>);
72 Content = new NC::Menu<MPD::Song>(RightColumnStartX, MainStartY, RightColumnWidth, MainHeight, Config.titles_visibility ? "Playlist content" : "", Config.main_color, NC::brNone);
73 Content->setHighlightColor(Config.main_highlight_color);
74 Content->cyclicScrolling(Config.use_cyclic_scrolling);
75 Content->centeredCursor(Config.centered_cursor);
76 Content->setSelectedPrefix(Config.selected_item_prefix);
77 Content->setSelectedSuffix(Config.selected_item_suffix);
78 if (Config.columns_in_playlist_editor)
79 Content->setItemDisplayer(std::bind(Display::SongsInColumns, _1, this));
80 else
81 Content->setItemDisplayer(std::bind(Display::Songs, _1, this, Config.song_list_format));
83 w = Playlists;
84 isInitialized = 1;
87 void PlaylistEditor::resize()
89 size_t x_offset, width;
90 getWindowResizeParams(x_offset, width);
92 LeftColumnStartX = x_offset;
93 LeftColumnWidth = width/3-1;
94 RightColumnStartX = LeftColumnStartX+LeftColumnWidth+1;
95 RightColumnWidth = width-LeftColumnWidth-1;
97 Playlists->resize(LeftColumnWidth, MainHeight);
98 Content->resize(RightColumnWidth, MainHeight);
100 Playlists->moveTo(LeftColumnStartX, MainStartY);
101 Content->moveTo(RightColumnStartX, MainStartY);
103 hasToBeResized = 0;
106 std::wstring PlaylistEditor::title()
108 return L"Playlist editor";
111 void PlaylistEditor::refresh()
113 Playlists->display();
114 mvvline(MainStartY, RightColumnStartX-1, 0, MainHeight);
115 Content->display();
118 void PlaylistEditor::switchTo()
120 using Global::myScreen;
121 using Global::myLockedScreen;
123 if (myScreen == this)
124 return;
126 if (!isInitialized)
127 init();
129 if (myLockedScreen)
130 updateInactiveScreen(this);
132 if (hasToBeResized || myLockedScreen)
133 resize();
135 if (myScreen != this && myScreen->isTabbable())
136 Global::myPrevScreen = myScreen;
137 myScreen = this;
138 drawHeader();
139 markSongsInPlaylist(contentProxyList());
140 refresh();
143 void PlaylistEditor::update()
145 if (Playlists->reallyEmpty() || playlistsUpdateRequested)
147 playlistsUpdateRequested = false;
148 Playlists->clearSearchResults();
149 withUnfilteredMenuReapplyFilter(*Playlists, [this]() {
150 auto list = Mpd.GetPlaylists();
151 std::sort(list.begin(), list.end(),
152 LocaleBasedSorting(std::locale(), Config.ignore_leading_the));
153 auto playlist = list.begin();
154 if (Playlists->size() > list.size())
155 Playlists->resizeList(list.size());
156 for (auto it = Playlists->begin(); it != Playlists->end(); ++it, ++playlist)
157 it->value() = *playlist;
158 for (; playlist != list.end(); ++playlist)
159 Playlists->addItem(*playlist);
161 Playlists->refresh();
164 if (!Playlists->empty() && (Content->reallyEmpty() || contentUpdateRequested))
166 contentUpdateRequested = false;
167 Content->clearSearchResults();
168 withUnfilteredMenuReapplyFilter(*Content, [this]() {
169 auto list = Mpd.GetPlaylistContent(Playlists->current().value());
170 auto song = list.begin();
171 if (Content->size() > list.size())
172 Content->resizeList(list.size());
173 for (auto it = Content->begin(); it != Content->end(); ++it, ++song)
175 it->value() = *song;
176 it->setBold(myPlaylist->checkForSong(*song));
178 for (; song != list.end(); ++song)
179 Content->addItem(*song, myPlaylist->checkForSong(*song));
180 std::string title;
181 if (Config.titles_visibility)
183 title = "Playlist content";
184 title += " (";
185 title += unsignedLongIntTo<std::string>::apply(list.size());
186 title += " item";
187 if (list.size() == 1)
188 title += ")";
189 else
190 title += "s)";
191 title.resize(Content->getWidth());
193 Content->setTitle(title);
195 Content->display();
198 if (w == Content && Content->reallyEmpty())
200 Content->setHighlightColor(Config.main_highlight_color);
201 Playlists->setHighlightColor(Config.active_column_color);
202 w = Playlists;
205 if (Playlists->empty() && Content->reallyEmpty())
207 Content->Window::clear();
208 Content->Window::display();
212 bool PlaylistEditor::isContentFiltered()
214 if (Content->isFiltered())
216 Statusbar::msg("Function currently unavailable due to filtered playlist content");
217 return true;
219 return false;
222 std::shared_ptr<ProxySongList> PlaylistEditor::contentProxyList()
224 return mkProxySongList(*Content, [](NC::Menu<MPD::Song>::Item &item) {
225 return &item.value();
229 void PlaylistEditor::AddToPlaylist(bool add_n_play)
231 MPD::SongList list;
233 if (w == Playlists && !Playlists->empty())
235 if (Mpd.LoadPlaylist(Playlists->current().value()))
237 Statusbar::msg("Playlist \"%s\" loaded", Playlists->current().value().c_str());
238 if (add_n_play)
239 myPlaylist->PlayNewlyAddedSongs();
242 else if (w == Content && !Content->empty())
243 myPlaylist->Add(Content->current().value(), add_n_play);
245 if (!add_n_play)
246 w->scroll(NC::wDown);
249 void PlaylistEditor::enterPressed()
251 AddToPlaylist(true);
254 void PlaylistEditor::spacePressed()
256 if (Config.space_selects)
258 if (w == Playlists)
260 if (!Playlists->empty())
262 Playlists->current().setSelected(!Playlists->current().isSelected());
263 Playlists->scroll(NC::wDown);
266 else if (w == Content)
268 if (!Content->empty())
270 Content->current().setSelected(!Content->current().isSelected());
271 Content->scroll(NC::wDown);
275 else
276 AddToPlaylist(false);
279 void PlaylistEditor::mouseButtonPressed(MEVENT me)
281 if (!Playlists->empty() && Playlists->hasCoords(me.x, me.y))
283 if (w != Playlists)
285 if (previousColumnAvailable())
286 previousColumn();
287 else
288 return;
290 if (size_t(me.y) < Playlists->size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
292 Playlists->Goto(me.y);
293 if (me.bstate & BUTTON3_PRESSED)
295 size_t pos = Playlists->choice();
296 spacePressed();
297 if (pos < Playlists->size()-1)
298 Playlists->scroll(NC::wUp);
301 else
302 Screen<NC::Window>::mouseButtonPressed(me);
303 Content->clear();
305 else if (!Content->empty() && Content->hasCoords(me.x, me.y))
307 if (w != Content)
309 if (nextColumnAvailable())
310 nextColumn();
311 else
312 return;
314 if (size_t(me.y) < Content->size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
316 Content->Goto(me.y);
317 if (me.bstate & BUTTON1_PRESSED)
319 size_t pos = Content->choice();
320 spacePressed();
321 if (pos < Content->size()-1)
322 Content->scroll(NC::wUp);
324 else
325 enterPressed();
327 else
328 Screen<NC::Window>::mouseButtonPressed(me);
332 /***********************************************************************/
334 bool PlaylistEditor::allowsFiltering()
336 return true;
339 std::string PlaylistEditor::currentFilter()
341 std::string filter;
342 if (w == Playlists)
343 filter = RegexFilter<std::string>::currentFilter(*Playlists);
344 else if (w == Content)
345 filter = RegexFilter<MPD::Song>::currentFilter(*Content);
346 return filter;
349 void PlaylistEditor::applyFilter(const std::string &filter)
351 if (w == Playlists)
353 Playlists->showAll();
354 auto rx = RegexFilter<std::string>(filter, Config.regex_type, PlaylistEntryMatcher);
355 Playlists->filter(Playlists->begin(), Playlists->end(), rx);
357 else if (w == Content)
359 Content->showAll();
360 auto rx = RegexFilter<MPD::Song>(filter, Config.regex_type, SongEntryMatcher);
361 Content->filter(Content->begin(), Content->end(), rx);
365 /***********************************************************************/
367 bool PlaylistEditor::allowsSearching()
369 return true;
372 bool PlaylistEditor::search(const std::string &constraint)
374 bool result = false;
375 if (w == Playlists)
377 auto rx = RegexFilter<std::string>(constraint, Config.regex_type, PlaylistEntryMatcher);
378 result = Playlists->search(Playlists->begin(), Playlists->end(), rx);
380 else if (w == Content)
382 auto rx = RegexFilter<MPD::Song>(constraint, Config.regex_type, SongEntryMatcher);
383 result = Content->search(Content->begin(), Content->end(), rx);
385 return result;
388 void PlaylistEditor::nextFound(bool wrap)
390 if (w == Playlists)
391 Playlists->nextFound(wrap);
392 else if (w == Content)
393 Content->nextFound(wrap);
396 void PlaylistEditor::prevFound(bool wrap)
398 if (w == Playlists)
399 Playlists->prevFound(wrap);
400 else if (w == Content)
401 Content->prevFound(wrap);
404 /***********************************************************************/
406 std::shared_ptr<ProxySongList> PlaylistEditor::getProxySongList()
408 auto ptr = nullProxySongList();
409 if (w == Content)
410 ptr = contentProxyList();
411 return ptr;
414 bool PlaylistEditor::allowsSelection()
416 return true;
419 void PlaylistEditor::reverseSelection()
421 if (w == Playlists)
422 reverseSelectionHelper(Playlists->begin(), Playlists->end());
423 else if (w == Content)
424 reverseSelectionHelper(Content->begin(), Content->end());
427 MPD::SongList PlaylistEditor::getSelectedSongs()
429 MPD::SongList result;
430 if (w == Playlists)
432 bool any_selected = false;
433 for (auto it = Playlists->begin(); it != Playlists->end(); ++it)
435 if (it->isSelected())
437 any_selected = true;
438 auto songs = Mpd.GetPlaylistContent(it->value());
439 result.insert(result.end(), songs.begin(), songs.end());
442 // we don't check for empty result here as it's possible that
443 // all selected playlists are empty.
444 if (!any_selected && !Content->empty())
446 withUnfilteredMenu(*Content, [this, &result]() {
447 result.insert(result.end(), Content->beginV(), Content->endV());
451 else if (w == Content)
453 for (auto it = Content->begin(); it != Content->end(); ++it)
454 if (it->isSelected())
455 result.push_back(it->value());
456 // if no item is selected, add current one
457 if (result.empty() && !Content->empty())
458 result.push_back(Content->current().value());
460 return result;
463 /***********************************************************************/
465 bool PlaylistEditor::previousColumnAvailable()
467 if (w == Content)
469 if (!Playlists->reallyEmpty())
470 return true;
472 return false;
475 void PlaylistEditor::previousColumn()
477 if (w == Content)
479 Content->setHighlightColor(Config.main_highlight_color);
480 w->refresh();
481 w = Playlists;
482 Playlists->setHighlightColor(Config.active_column_color);
486 bool PlaylistEditor::nextColumnAvailable()
488 if (w == Playlists)
490 if (!Content->reallyEmpty())
491 return true;
493 return false;
496 void PlaylistEditor::nextColumn()
498 if (w == Playlists)
500 Playlists->setHighlightColor(Config.main_highlight_color);
501 w->refresh();
502 w = Content;
503 Content->setHighlightColor(Config.active_column_color);
507 /***********************************************************************/
510 void PlaylistEditor::Locate(const std::string &name)
512 if (!isInitialized)
513 init();
514 update();
515 for (size_t i = 0; i < Playlists->size(); ++i)
517 if (name == (*Playlists)[i].value())
519 Playlists->highlight(i);
520 Content->clear();
521 break;
524 switchTo();
527 namespace {//
529 std::string SongToString(const MPD::Song &s)
531 std::string result;
532 if (Config.columns_in_playlist_editor)
533 result = s.toString(Config.song_in_columns_to_string_format, Config.tags_separator);
534 else
535 result = s.toString(Config.song_list_format_dollar_free, Config.tags_separator);
536 return result;
539 bool PlaylistEntryMatcher(const Regex &rx, const std::string &playlist)
541 return rx.match(playlist);
544 bool SongEntryMatcher(const Regex &rx, const MPD::Song &s)
546 return rx.match(SongToString(s));