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 "regex_filter.h"
32 #include "utility/comparators.h"
34 using namespace std::placeholders
;
36 using Global::MainHeight
;
37 using Global::MainStartY
;
39 Playlist
*myPlaylist
= new Playlist
;
41 bool Playlist::ReloadTotalLength
= 0;
42 bool Playlist::ReloadRemaining
= false;
46 NC::Menu
< std::pair
<std::string
, MPD::Song::GetFunction
> > *SortDialog
= 0;
47 size_t SortDialogHeight
;
49 const size_t SortOptions
= 10;
50 const size_t SortDialogWidth
= 30;
52 std::string
songToString(const MPD::Song
&s
);
53 bool playlistEntryMatcher(const Regex
&rx
, const MPD::Song
&s
);
59 Items
= new NC::Menu
<MPD::Song
>(0, MainStartY
, COLS
, MainHeight
, Config
.columns_in_playlist
&& Config
.titles_visibility
? Display::Columns(COLS
) : "", Config
.main_color
, NC::brNone
);
60 Items
->cyclicScrolling(Config
.use_cyclic_scrolling
);
61 Items
->centeredCursor(Config
.centered_cursor
);
62 Items
->setHighlightColor(Config
.main_highlight_color
);
63 Items
->setSelectedPrefix(Config
.selected_item_prefix
);
64 Items
->setSelectedSuffix(Config
.selected_item_suffix
);
65 if (Config
.columns_in_playlist
)
66 Items
->setItemDisplayer(std::bind(Display::SongsInColumns
, _1
, *this));
68 Items
->setItemDisplayer(std::bind(Display::Songs
, _1
, *this, Config
.song_list_format
));
72 SortDialogHeight
= std::min(int(MainHeight
), 17);
74 SortDialog
= new NC::Menu
< std::pair
<std::string
, MPD::Song::GetFunction
> >((COLS
-SortDialogWidth
)/2, (MainHeight
-SortDialogHeight
)/2+MainStartY
, SortDialogWidth
, SortDialogHeight
, "Sort songs by...", Config
.main_color
, Config
.window_border
);
75 SortDialog
->cyclicScrolling(Config
.use_cyclic_scrolling
);
76 SortDialog
->centeredCursor(Config
.centered_cursor
);
77 SortDialog
->setItemDisplayer(Display::Pair
<std::string
, MPD::Song::GetFunction
>);
79 SortDialog
->addItem(std::make_pair("Artist", &MPD::Song::getArtist
));
80 SortDialog
->addItem(std::make_pair("Album", &MPD::Song::getAlbum
));
81 SortDialog
->addItem(std::make_pair("Disc", &MPD::Song::getDisc
));
82 SortDialog
->addItem(std::make_pair("Track", &MPD::Song::getTrack
));
83 SortDialog
->addItem(std::make_pair("Genre", &MPD::Song::getGenre
));
84 SortDialog
->addItem(std::make_pair("Date", &MPD::Song::getDate
));
85 SortDialog
->addItem(std::make_pair("Composer", &MPD::Song::getComposer
));
86 SortDialog
->addItem(std::make_pair("Performer", &MPD::Song::getPerformer
));
87 SortDialog
->addItem(std::make_pair("Title", &MPD::Song::getTitle
));
88 SortDialog
->addItem(std::make_pair("Filename", &MPD::Song::getURI
));
89 SortDialog
->addSeparator();
90 SortDialog
->addItem(std::make_pair("Sort", static_cast<MPD::Song::GetFunction
>(0)));
91 SortDialog
->addItem(std::make_pair("Cancel", static_cast<MPD::Song::GetFunction
>(0)));
98 void Playlist::SwitchTo()
100 using Global::myScreen
;
101 using Global::myLockedScreen
;
102 using Global::myInactiveScreen
;
104 if (myScreen
== this)
113 UpdateInactiveScreen(this);
115 if (hasToBeResized
|| myLockedScreen
)
118 if (myScreen
!= this && myScreen
->isTabbable())
119 Global::myPrevScreen
= myScreen
;
121 EnableHighlighting();
122 if (w
!= Items
) // even if sorting window is active, background has to be refreshed anyway
124 Global::RedrawHeader
= true;
127 void Playlist::Resize()
129 size_t x_offset
, width
;
130 GetWindowResizeParams(x_offset
, width
);
131 Items
->resize(width
, MainHeight
);
132 Items
->moveTo(x_offset
, MainStartY
);
134 Items
->setTitle(Config
.columns_in_playlist
&& Config
.titles_visibility
? Display::Columns(Items
->getWidth()) : "");
135 if (w
== SortDialog
) // if sorting window is active, playlist needs refreshing
138 SortDialogHeight
= std::min(int(MainHeight
), 17);
139 if (Items
->getWidth() >= SortDialogWidth
&& MainHeight
>= 5)
141 SortDialog
->resize(SortDialogWidth
, SortDialogHeight
);
142 SortDialog
->moveTo(x_offset
+(width
-SortDialogWidth
)/2, (MainHeight
-SortDialogHeight
)/2+MainStartY
);
144 else // if screen is too low to display sorting window, fall back to items list
150 std::basic_string
<my_char_t
> Playlist::Title()
152 std::basic_string
<my_char_t
> result
= U("Playlist ");
153 if (ReloadTotalLength
|| ReloadRemaining
)
154 itsBufferedStats
= TotalLength();
155 result
+= Scroller(TO_WSTRING(itsBufferedStats
), itsScrollBegin
, COLS
-result
.length()-(Config
.new_design
? 2 : Global::VolumeState
.length()));
159 void Playlist::EnterPressed()
164 Mpd
.PlayID(Items
->current().value().getID());
166 else if (w
== SortDialog
)
168 size_t pos
= SortDialog
->choice();
170 size_t beginning
= 0;
171 size_t end
= Items
->size();
172 if (Items
->hasSelected())
174 std::vector
<size_t> list
;
175 Items
->getSelected(list
);
176 beginning
= *list
.begin();
177 end
= *list
.rbegin()+1;
180 if (pos
> SortOptions
)
182 if (pos
== SortOptions
+2) // cancel
190 ShowMessage("Move tag types up and down to adjust sort order");
194 MPD::SongList playlist
;
195 playlist
.reserve(end
-beginning
);
196 for (size_t i
= beginning
; i
< end
; ++i
)
197 playlist
.push_back((*Items
)[i
].value());
199 std::function
<void(MPD::SongList::iterator
, MPD::SongList::iterator
)> iter_swap
, quick_sort
;
200 auto song_cmp
= [](const MPD::Song
&a
, const MPD::Song
&b
) -> bool {
201 CaseInsensitiveStringComparison cmp
;
202 for (size_t i
= 0; i
< SortOptions
; ++i
)
203 if (int ret
= cmp(a
.getTags((*SortDialog
)[i
].value().second
), b
.getTags((*SortDialog
)[i
].value().second
)))
205 return a
.getPosition() < b
.getPosition();
207 iter_swap
= [&playlist
](MPD::SongList::iterator a
, MPD::SongList::iterator b
) {
208 std::iter_swap(a
, b
);
209 Mpd
.Swap(a
-playlist
.begin(), b
-playlist
.begin());
211 quick_sort
= [this, &song_cmp
, &quick_sort
, &iter_swap
](MPD::SongList::iterator first
, MPD::SongList::iterator last
) {
214 MPD::SongList::iterator pivot
= first
+rand()%(last
-first
);
215 iter_swap(pivot
, last
-1);
218 MPD::SongList::iterator tmp
= first
;
219 for (MPD::SongList::iterator i
= first
; i
!= pivot
; ++i
)
220 if (song_cmp(*i
, *pivot
))
222 iter_swap(tmp
, pivot
);
224 quick_sort(first
, tmp
);
225 quick_sort(tmp
+1, last
);
229 ShowMessage("Sorting...");
230 Mpd
.StartCommandsList();
231 quick_sort(playlist
.begin(), playlist
.end());
232 if (Mpd
.CommitCommandsList())
233 ShowMessage("Playlist sorted");
238 void Playlist::SpacePressed()
240 if (w
== Items
&& !Items
->empty())
242 Items
->current().setSelected(!Items
->current().isSelected());
243 Items
->scroll(NC::wDown
);
247 void Playlist::MouseButtonPressed(MEVENT me
)
249 if (w
== Items
&& !Items
->empty() && Items
->hasCoords(me
.x
, me
.y
))
251 if (size_t(me
.y
) < Items
->size() && (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
)))
254 if (me
.bstate
& BUTTON3_PRESSED
)
258 Screen
<NC::Window
>::MouseButtonPressed(me
);
260 else if (w
== SortDialog
&& SortDialog
->hasCoords(me
.x
, me
.y
))
262 if (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
))
264 SortDialog
->Goto(me
.y
);
265 if (me
.bstate
& BUTTON3_PRESSED
)
269 Screen
<NC::Window
>::MouseButtonPressed(me
);
273 /***********************************************************************/
275 bool Playlist::allowsFiltering()
280 std::string
Playlist::currentFilter()
284 filter
= RegexFilter
<MPD::Song
>::currentFilter(*Items
);
288 void Playlist::applyFilter(const std::string
&filter
)
293 auto rx
= RegexFilter
<MPD::Song
>(filter
, Config
.regex_type
, playlistEntryMatcher
);
294 Items
->filter(Items
->begin(), Items
->end(), rx
);
298 /***********************************************************************/
300 bool Playlist::allowsSearching()
305 bool Playlist::search(const std::string
&constraint
)
310 auto rx
= RegexFilter
<MPD::Song
>(constraint
, Config
.regex_type
, playlistEntryMatcher
);
311 result
= Items
->search(Items
->begin(), Items
->end(), rx
);
316 void Playlist::nextFound(bool wrap
)
319 Items
->nextFound(wrap
);
322 void Playlist::prevFound(bool wrap
)
325 Items
->prevFound(wrap
);
328 /***********************************************************************/
330 std::shared_ptr
<ProxySongList
> Playlist::getProxySongList()
332 auto ptr
= nullProxySongList();
334 ptr
= mkProxySongList(*Items
, [](NC::Menu
<MPD::Song
>::Item
&item
) {
335 return &item
.value();
340 MPD::Song
*Playlist::getSong(size_t pos
)
344 ptr
= &(*Items
)[pos
].value();
348 MPD::Song
*Playlist::currentSong()
353 return getSong(Items
->choice());
356 bool Playlist::allowsSelection()
361 void Playlist::reverseSelection()
363 reverseSelectionHelper(Items
->begin(), Items
->end());
366 MPD::SongList
Playlist::getSelectedSongs()
368 MPD::SongList result
;
371 for (auto it
= Items
->begin(); it
!= Items
->end(); ++it
)
372 if (it
->isSelected())
373 result
.push_back(it
->value());
374 if (result
.empty() && !Items
->empty())
375 result
.push_back(Items
->current().value());
380 /***********************************************************************/
383 bool Playlist::isFiltered()
385 if (Items
->isFiltered())
387 ShowMessage("Function currently unavailable due to filtered playlist");
393 void Playlist::MoveSelectedItems(Movement where
)
395 if (Items
->empty() || isFiltered())
402 if (Items
->hasSelected())
404 std::vector
<size_t> list
;
405 myPlaylist
->Items
->getSelected(list
);
406 if (list
.front() > 0)
408 Mpd
.StartCommandsList();
409 std::vector
<size_t>::const_iterator it
= list
.begin();
410 for (; it
!= list
.end(); ++it
)
411 Mpd
.Move(*it
-1, *it
);
412 if (Mpd
.CommitCommandsList())
414 Items
->at(list
.back()).setSelected(false);
415 Items
->at(list
.front()-1).setSelected(true);
416 Items
->highlight(list
[(list
.size()-1)/2]-1);
422 size_t pos
= myPlaylist
->Items
->choice();
425 if (Mpd
.Move(pos
-1, pos
))
426 Items
->scroll(NC::wUp
);
433 if (Items
->hasSelected())
435 std::vector
<size_t> list
;
436 Items
->getSelected(list
);
438 if (list
.back() < Items
->size()-1)
440 Mpd
.StartCommandsList();
441 std::vector
<size_t>::const_reverse_iterator it
= list
.rbegin();
442 for (; it
!= list
.rend(); ++it
)
443 Mpd
.Move(*it
, *it
+1);
444 if (Mpd
.CommitCommandsList())
446 Items
->at(list
.front()).setSelected(false);
447 Items
->at(list
.back()+1).setSelected(true);
448 Items
->highlight(list
[(list
.size()-1)/2]+1);
454 size_t pos
= Items
->choice();
455 if (pos
< Items
->size()-1)
457 if (Mpd
.Move(pos
, pos
+1))
458 Items
->scroll(NC::wDown
);
466 void Playlist::Sort()
470 if (Items
->getWidth() < SortDialogWidth
|| MainHeight
< 5)
471 ShowMessage("Screen is too small to display dialog window");
479 void Playlist::Reverse()
483 ShowMessage("Reversing playlist order...");
484 size_t beginning
= -1, end
= -1;
485 for (size_t i
= 0; i
< Items
->size(); ++i
)
487 if (Items
->at(i
).isSelected())
489 if (beginning
== size_t(-1))
494 if (beginning
== size_t(-1)) // no selected items
499 Mpd
.StartCommandsList();
500 for (size_t i
= beginning
, j
= end
-1; i
< (beginning
+end
)/2; ++i
, --j
)
502 if (Mpd
.CommitCommandsList())
503 ShowMessage("Playlist reversed");
506 void Playlist::AdjustSortOrder(Movement where
)
512 size_t pos
= SortDialog
->choice();
513 if (pos
> 0 && pos
< SortOptions
)
515 SortDialog
->Swap(pos
, pos
-1);
516 SortDialog
->scroll(NC::wUp
);
522 size_t pos
= SortDialog
->choice();
523 if (pos
< SortOptions
-1)
525 SortDialog
->Swap(pos
, pos
+1);
526 SortDialog
->scroll(NC::wDown
);
533 void Playlist::EnableHighlighting()
535 Items
->setHighlighting(1);
539 bool Playlist::SortingInProgress()
541 return w
== SortDialog
;
544 std::string
Playlist::TotalLength()
546 std::ostringstream result
;
548 if (ReloadTotalLength
)
551 for (size_t i
= 0; i
< Items
->size(); ++i
)
552 itsTotalLength
+= (*Items
)[i
].value().getDuration();
553 ReloadTotalLength
= 0;
555 if (Config
.playlist_show_remaining_time
&& ReloadRemaining
&& !Items
->isFiltered())
557 itsRemainingTime
= 0;
558 for (size_t i
= NowPlaying
; i
< Items
->size(); ++i
)
559 itsRemainingTime
+= (*Items
)[i
].value().getDuration();
560 ReloadRemaining
= false;
563 result
<< '(' << Items
->size() << (Items
->size() == 1 ? " item" : " items");
565 if (Items
->isFiltered())
568 size_t real_size
= Items
->size();
569 Items
->showFiltered();
570 if (Items
->size() != real_size
)
571 result
<< " (out of " << Mpd
.GetPlaylistLength() << ")";
576 result
<< ", length: ";
577 ShowTime(result
, itsTotalLength
, Config
.playlist_shorten_total_times
);
579 if (Config
.playlist_show_remaining_time
&& itsRemainingTime
&& !Items
->isFiltered() && Items
->size() > 1)
581 result
<< " :: remaining: ";
582 ShowTime(result
, itsRemainingTime
, Config
.playlist_shorten_total_times
);
588 const MPD::Song
*Playlist::NowPlayingSong()
590 bool was_filtered
= Items
->isFiltered();
592 const MPD::Song
*s
= isPlaying() ? &Items
->at(NowPlaying
).value() : 0;
594 Items
->showFiltered();
598 bool Playlist::Add(const MPD::Song
&s
, bool in_playlist
, bool play
, int position
)
600 if (Config
.ncmpc_like_songs_adding
&& in_playlist
)
602 unsigned hash
= s
.getHash();
605 for (size_t i
= 0; i
< Items
->size(); ++i
)
607 if (Items
->at(i
).value().getHash() == hash
)
617 Mpd
.StartCommandsList();
618 for (size_t i
= 0; i
< Items
->size(); ++i
)
620 if ((*Items
)[i
].value().getHash() == hash
)
623 Items
->deleteItem(i
);
627 Mpd
.CommitCommandsList();
633 int id
= Mpd
.AddSong(s
, position
);
636 ShowMessage("Added to playlist: %s", s
.toString(Config
.song_status_format_no_colors
).c_str());
646 bool Playlist::Add(const MPD::SongList
&l
, bool play
, int position
)
651 Mpd
.StartCommandsList();
654 for (auto it
= l
.begin(); it
!= l
.end(); ++it
)
655 if (Mpd
.AddSong(*it
) < 0)
660 for (auto j
= l
.rbegin(); j
!= l
.rend(); ++j
)
661 if (Mpd
.AddSong(*j
, position
) < 0)
664 if (!Mpd
.CommitCommandsList())
667 PlayNewlyAddedSongs();
671 void Playlist::PlayNewlyAddedSongs()
673 bool is_filtered
= Items
->isFiltered();
675 size_t old_size
= Items
->size();
677 if (old_size
< Items
->size())
680 Items
->showFiltered();
683 void Playlist::SetSelectedItemsPriority(int prio
)
685 std::vector
<size_t> list
;
686 myPlaylist
->Items
->getSelected(list
);
688 list
.push_back(Items
->choice());
689 Mpd
.StartCommandsList();
690 for (std::vector
<size_t>::const_iterator it
= list
.begin(); it
!= list
.end(); ++it
)
691 Mpd
.SetPriority((*Items
)[*it
].value(), prio
);
692 if (Mpd
.CommitCommandsList())
693 ShowMessage("Priority set");
696 bool Playlist::checkForSong (const MPD::Song
&s
)
698 for (size_t i
= 0; i
< Items
->size(); ++i
)
699 if (s
.getHash() == (*Items
)[i
].value().getHash())
706 std::string
songToString(const MPD::Song
&s
)
709 if (Config
.columns_in_playlist
)
710 result
= s
.toString(Config
.song_in_columns_to_string_format
);
712 result
= s
.toString(Config
.song_list_format_dollar_free
);
716 bool playlistEntryMatcher(const Regex
&rx
, const MPD::Song
&s
)
718 return rx
.match(songToString(s
));