1 /***************************************************************************
2 * Copyright (C) 2008-2012 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
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. *
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. *
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 ***************************************************************************/
29 #include "playlist_editor.h"
31 #include "regex_filter.h"
33 #include "statusbar.h"
34 #include "tag_editor.h"
35 #include "utility/comparators.h"
38 using namespace std::placeholders
;
40 using Global::MainHeight
;
41 using Global::MainStartY
;
43 PlaylistEditor
*myPlaylistEditor
= new PlaylistEditor
;
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));
81 Content
->setItemDisplayer(std::bind(Display::Songs
, _1
, this, Config
.song_list_format
));
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
);
106 std::wstring
PlaylistEditor::title()
108 return L
"Playlist editor";
111 void PlaylistEditor::refresh()
113 Playlists
->display();
114 mvvline(MainStartY
, RightColumnStartX
-1, 0, MainHeight
);
118 void PlaylistEditor::switchTo()
120 using Global::myScreen
;
121 using Global::myLockedScreen
;
123 if (myScreen
== this)
130 updateInactiveScreen(this);
132 if (hasToBeResized
|| myLockedScreen
)
135 if (myScreen
!= this && myScreen
->isTabbable())
136 Global::myPrevScreen
= myScreen
;
139 markSongsInPlaylist(contentProxyList());
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
)
176 it
->setBold(myPlaylist
->checkForSong(*song
));
178 for (; song
!= list
.end(); ++song
)
179 Content
->addItem(*song
, myPlaylist
->checkForSong(*song
));
181 if (Config
.titles_visibility
)
183 title
= "Playlist content";
185 title
+= unsignedLongIntTo
<std::string
>::apply(list
.size());
187 if (list
.size() == 1)
191 title
.resize(Content
->getWidth());
193 Content
->setTitle(title
);
198 if (w
== Content
&& Content
->reallyEmpty())
200 Content
->setHighlightColor(Config
.main_highlight_color
);
201 Playlists
->setHighlightColor(Config
.active_column_color
);
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");
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
)
233 if (w
== Playlists
&& !Playlists
->empty())
235 if (Mpd
.LoadPlaylist(Playlists
->current().value()))
237 Statusbar::msg("Playlist \"%s\" loaded", Playlists
->current().value().c_str());
239 myPlaylist
->PlayNewlyAddedSongs();
242 else if (w
== Content
&& !Content
->empty())
243 myPlaylist
->Add(Content
->current().value(), add_n_play
);
246 w
->scroll(NC::wDown
);
249 void PlaylistEditor::enterPressed()
254 void PlaylistEditor::spacePressed()
256 if (Config
.space_selects
)
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
);
276 AddToPlaylist(false);
279 void PlaylistEditor::mouseButtonPressed(MEVENT me
)
281 if (!Playlists
->empty() && Playlists
->hasCoords(me
.x
, me
.y
))
285 if (previousColumnAvailable())
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();
297 if (pos
< Playlists
->size()-1)
298 Playlists
->scroll(NC::wUp
);
302 Screen
<NC::Window
>::mouseButtonPressed(me
);
305 else if (!Content
->empty() && Content
->hasCoords(me
.x
, me
.y
))
309 if (nextColumnAvailable())
314 if (size_t(me
.y
) < Content
->size() && (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
)))
317 if (me
.bstate
& BUTTON1_PRESSED
)
319 size_t pos
= Content
->choice();
321 if (pos
< Content
->size()-1)
322 Content
->scroll(NC::wUp
);
328 Screen
<NC::Window
>::mouseButtonPressed(me
);
332 /***********************************************************************/
334 bool PlaylistEditor::allowsFiltering()
339 std::string
PlaylistEditor::currentFilter()
343 filter
= RegexFilter
<std::string
>::currentFilter(*Playlists
);
344 else if (w
== Content
)
345 filter
= RegexFilter
<MPD::Song
>::currentFilter(*Content
);
349 void PlaylistEditor::applyFilter(const std::string
&filter
)
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
)
360 auto rx
= RegexFilter
<MPD::Song
>(filter
, Config
.regex_type
, SongEntryMatcher
);
361 Content
->filter(Content
->begin(), Content
->end(), rx
);
365 /***********************************************************************/
367 bool PlaylistEditor::allowsSearching()
372 bool PlaylistEditor::search(const std::string
&constraint
)
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
);
388 void PlaylistEditor::nextFound(bool wrap
)
391 Playlists
->nextFound(wrap
);
392 else if (w
== Content
)
393 Content
->nextFound(wrap
);
396 void PlaylistEditor::prevFound(bool wrap
)
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();
410 ptr
= contentProxyList();
414 bool PlaylistEditor::allowsSelection()
419 void PlaylistEditor::reverseSelection()
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
;
432 bool any_selected
= false;
433 for (auto it
= Playlists
->begin(); it
!= Playlists
->end(); ++it
)
435 if (it
->isSelected())
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());
463 /***********************************************************************/
465 bool PlaylistEditor::previousColumnAvailable()
469 if (!Playlists
->reallyEmpty())
475 void PlaylistEditor::previousColumn()
479 Content
->setHighlightColor(Config
.main_highlight_color
);
482 Playlists
->setHighlightColor(Config
.active_column_color
);
486 bool PlaylistEditor::nextColumnAvailable()
490 if (!Content
->reallyEmpty())
496 void PlaylistEditor::nextColumn()
500 Playlists
->setHighlightColor(Config
.main_highlight_color
);
503 Content
->setHighlightColor(Config
.active_column_color
);
507 /***********************************************************************/
510 void PlaylistEditor::Locate(const std::string
&name
)
515 for (size_t i
= 0; i
< Playlists
->size(); ++i
)
517 if (name
== (*Playlists
)[i
].value())
519 Playlists
->highlight(i
);
529 std::string
SongToString(const MPD::Song
&s
)
532 if (Config
.columns_in_playlist_editor
)
533 result
= s
.toString(Config
.song_in_columns_to_string_format
, Config
.tags_separator
);
535 result
= s
.toString(Config
.song_list_format_dollar_free
, Config
.tags_separator
);
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
));