1 /***************************************************************************
2 * Copyright (C) 2008-2014 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 ***************************************************************************/
22 #include <boost/bind.hpp>
23 #include <boost/date_time/posix_time/posix_time.hpp>
31 #include "playlist_editor.h"
33 #include "regex_filter.h"
35 #include "statusbar.h"
36 #include "tag_editor.h"
37 #include "utility/comparators.h"
39 #include "screen_switcher.h"
41 using Global::MainHeight
;
42 using Global::MainStartY
;
44 PlaylistEditor
*myPlaylistEditor
;
48 const int pe_timeout
= 250;
49 const auto fetch_delay
= boost::posix_time::milliseconds(pe_timeout
);
51 size_t LeftColumnStartX
;
52 size_t LeftColumnWidth
;
53 size_t RightColumnStartX
;
54 size_t RightColumnWidth
;
56 std::string
SongToString(const MPD::Song
&s
);
57 bool PlaylistEntryMatcher(const boost::regex
&rx
, const std::string
&playlist
);
58 bool SongEntryMatcher(const boost::regex
&rx
, const MPD::Song
&s
);
62 PlaylistEditor::PlaylistEditor()
63 : m_timer(boost::posix_time::from_time_t(0))
65 LeftColumnWidth
= COLS
/3-1;
66 RightColumnStartX
= LeftColumnWidth
+1;
67 RightColumnWidth
= COLS
-LeftColumnWidth
-1;
69 Playlists
= NC::Menu
<std::string
>(0, MainStartY
, LeftColumnWidth
, MainHeight
, Config
.titles_visibility
? "Playlists" : "", Config
.main_color
, NC::Border::None
);
70 Playlists
.setHighlightColor(Config
.active_column_color
);
71 Playlists
.cyclicScrolling(Config
.use_cyclic_scrolling
);
72 Playlists
.centeredCursor(Config
.centered_cursor
);
73 Playlists
.setSelectedPrefix(Config
.selected_item_prefix
);
74 Playlists
.setSelectedSuffix(Config
.selected_item_suffix
);
75 Playlists
.setItemDisplayer([](NC::Menu
<std::string
> &menu
) {
76 menu
<< Charset::utf8ToLocale(menu
.drawn()->value());
79 Content
= NC::Menu
<MPD::Song
>(RightColumnStartX
, MainStartY
, RightColumnWidth
, MainHeight
, Config
.titles_visibility
? "Playlist content" : "", Config
.main_color
, NC::Border::None
);
80 Content
.setHighlightColor(Config
.main_highlight_color
);
81 Content
.cyclicScrolling(Config
.use_cyclic_scrolling
);
82 Content
.centeredCursor(Config
.centered_cursor
);
83 Content
.setSelectedPrefix(Config
.selected_item_prefix
);
84 Content
.setSelectedSuffix(Config
.selected_item_suffix
);
85 switch (Config
.playlist_editor_display_mode
)
87 case DisplayMode::Classic
:
88 Content
.setItemDisplayer(boost::bind(Display::Songs
, _1
, contentProxyList(), Config
.song_list_format
));
90 case DisplayMode::Columns
:
91 Content
.setItemDisplayer(boost::bind(Display::SongsInColumns
, _1
, contentProxyList()));
98 void PlaylistEditor::resize()
100 size_t x_offset
, width
;
101 getWindowResizeParams(x_offset
, width
);
103 LeftColumnStartX
= x_offset
;
104 LeftColumnWidth
= width
/3-1;
105 RightColumnStartX
= LeftColumnStartX
+LeftColumnWidth
+1;
106 RightColumnWidth
= width
-LeftColumnWidth
-1;
108 Playlists
.resize(LeftColumnWidth
, MainHeight
);
109 Content
.resize(RightColumnWidth
, MainHeight
);
111 Playlists
.moveTo(LeftColumnStartX
, MainStartY
);
112 Content
.moveTo(RightColumnStartX
, MainStartY
);
117 std::wstring
PlaylistEditor::title()
119 return L
"Playlist editor";
122 void PlaylistEditor::refresh()
125 drawSeparator(RightColumnStartX
-1);
129 void PlaylistEditor::switchTo()
131 SwitchTo::execute(this);
132 markSongsInPlaylist(contentProxyList());
137 void PlaylistEditor::update()
139 if (Playlists
.reallyEmpty() || m_playlists_update_requested
)
141 m_playlists_update_requested
= false;
142 Playlists
.clearSearchResults();
143 withUnfilteredMenuReapplyFilter(Playlists
, [this]() {
145 Mpd
.GetPlaylists([this, &idx
](std::string playlist
) {
146 if (idx
< Playlists
.size())
147 Playlists
[idx
].value() = playlist
;
149 Playlists
.addItem(playlist
);
152 if (idx
< Playlists
.size())
153 Playlists
.resizeList(idx
);
154 std::sort(Playlists
.beginV(), Playlists
.endV(),
155 LocaleBasedSorting(std::locale(), Config
.ignore_leading_the
));
160 if (!Playlists
.empty()
161 && ((Content
.reallyEmpty() && Global::Timer
- m_timer
> fetch_delay
) || m_content_update_requested
)
164 m_content_update_requested
= false;
165 Content
.clearSearchResults();
166 withUnfilteredMenuReapplyFilter(Content
, [this]() {
168 Mpd
.GetPlaylistContent(Playlists
.current().value(), [this, &idx
](MPD::Song s
) {
169 if (idx
< Content
.size())
171 Content
[idx
].value() = s
;
172 Content
[idx
].setBold(myPlaylist
->checkForSong(s
));
175 Content
.addItem(s
, myPlaylist
->checkForSong(s
));
178 if (idx
< Content
.size())
179 Content
.resizeList(idx
);
181 if (Config
.titles_visibility
)
183 title
= "Playlist content";
185 title
+= boost::lexical_cast
<std::string
>(Content
.size());
187 if (Content
.size() == 1)
191 title
.resize(Content
.getWidth());
193 Content
.setTitle(title
);
198 if (isActiveWindow(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 int PlaylistEditor::windowTimeout()
214 if (Content
.reallyEmpty())
217 return Screen
<WindowType
>::windowTimeout();
220 bool PlaylistEditor::isContentFiltered()
222 if (Content
.isFiltered())
224 Statusbar::print("Function currently unavailable due to filtered playlist content");
230 ProxySongList
PlaylistEditor::contentProxyList()
232 return ProxySongList(Content
, [](NC::Menu
<MPD::Song
>::Item
&item
) {
233 return &item
.value();
237 void PlaylistEditor::AddToPlaylist(bool add_n_play
)
241 if (isActiveWindow(Playlists
) && !Playlists
.empty())
244 withUnfilteredMenu(Content
, [&]() {
245 success
= addSongsToPlaylist(Content
.beginV(), Content
.endV(), add_n_play
, -1);
247 Statusbar::printf("Playlist \"%1%\" loaded%2%",
248 Playlists
.current().value(), withErrors(success
)
251 else if (isActiveWindow(Content
) && !Content
.empty())
252 addSongToPlaylist(Content
.current().value(), add_n_play
);
255 w
->scroll(NC::Scroll::Down
);
258 void PlaylistEditor::enterPressed()
263 void PlaylistEditor::spacePressed()
265 if (Config
.space_selects
)
267 if (isActiveWindow(Playlists
))
269 if (!Playlists
.empty())
271 Playlists
.current().setSelected(!Playlists
.current().isSelected());
272 Playlists
.scroll(NC::Scroll::Down
);
275 else if (isActiveWindow(Content
))
277 if (!Content
.empty())
279 Content
.current().setSelected(!Content
.current().isSelected());
280 Content
.scroll(NC::Scroll::Down
);
285 AddToPlaylist(false);
288 void PlaylistEditor::mouseButtonPressed(MEVENT me
)
290 if (!Playlists
.empty() && Playlists
.hasCoords(me
.x
, me
.y
))
292 if (!isActiveWindow(Playlists
))
294 if (previousColumnAvailable())
299 if (size_t(me
.y
) < Playlists
.size() && (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
)))
301 Playlists
.Goto(me
.y
);
302 if (me
.bstate
& BUTTON3_PRESSED
)
304 size_t pos
= Playlists
.choice();
306 if (pos
< Playlists
.size()-1)
307 Playlists
.scroll(NC::Scroll::Up
);
311 Screen
<WindowType
>::mouseButtonPressed(me
);
314 else if (!Content
.empty() && Content
.hasCoords(me
.x
, me
.y
))
316 if (!isActiveWindow(Content
))
318 if (nextColumnAvailable())
323 if (size_t(me
.y
) < Content
.size() && (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
)))
326 if (me
.bstate
& BUTTON1_PRESSED
)
328 size_t pos
= Content
.choice();
330 if (pos
< Content
.size()-1)
331 Content
.scroll(NC::Scroll::Up
);
337 Screen
<WindowType
>::mouseButtonPressed(me
);
341 /***********************************************************************/
343 bool PlaylistEditor::allowsFiltering()
348 std::string
PlaylistEditor::currentFilter()
351 if (isActiveWindow(Playlists
))
352 filter
= RegexFilter
<std::string
>::currentFilter(Playlists
);
353 else if (isActiveWindow(Content
))
354 filter
= RegexFilter
<MPD::Song
>::currentFilter(Content
);
358 void PlaylistEditor::applyFilter(const std::string
&filter
)
362 if (isActiveWindow(Playlists
))
364 Playlists
.clearFilter();
365 Playlists
.clearFilterResults();
367 else if (isActiveWindow(Content
))
369 Content
.clearFilter();
370 Content
.clearFilterResults();
376 if (isActiveWindow(Playlists
))
379 auto rx
= RegexFilter
<std::string
>(
380 boost::regex(filter
, Config
.regex_type
), PlaylistEntryMatcher
);
381 Playlists
.filter(Playlists
.begin(), Playlists
.end(), rx
);
383 else if (isActiveWindow(Content
))
386 auto rx
= RegexFilter
<MPD::Song
>(
387 boost::regex(filter
, Config
.regex_type
), SongEntryMatcher
);
388 Content
.filter(Content
.begin(), Content
.end(), rx
);
391 catch (boost::bad_expression
&) { }
394 /***********************************************************************/
396 bool PlaylistEditor::allowsSearching()
401 bool PlaylistEditor::search(const std::string
&constraint
)
403 if (constraint
.empty())
405 if (isActiveWindow(Playlists
))
406 Playlists
.clearSearchResults();
407 else if (isActiveWindow(Content
))
408 Content
.clearSearchResults();
414 if (isActiveWindow(Playlists
))
416 auto rx
= RegexFilter
<std::string
>(
417 boost::regex(constraint
, Config
.regex_type
), PlaylistEntryMatcher
);
418 result
= Playlists
.search(Playlists
.begin(), Playlists
.end(), rx
);
420 else if (isActiveWindow(Content
))
422 auto rx
= RegexFilter
<MPD::Song
>(
423 boost::regex(constraint
, Config
.regex_type
), SongEntryMatcher
);
424 result
= Content
.search(Content
.begin(), Content
.end(), rx
);
428 catch (boost::bad_expression
&)
434 void PlaylistEditor::nextFound(bool wrap
)
436 if (isActiveWindow(Playlists
))
437 Playlists
.nextFound(wrap
);
438 else if (isActiveWindow(Content
))
439 Content
.nextFound(wrap
);
442 void PlaylistEditor::prevFound(bool wrap
)
444 if (isActiveWindow(Playlists
))
445 Playlists
.prevFound(wrap
);
446 else if (isActiveWindow(Content
))
447 Content
.prevFound(wrap
);
450 /***********************************************************************/
452 ProxySongList
PlaylistEditor::proxySongList()
454 auto ptr
= ProxySongList();
455 if (isActiveWindow(Content
))
456 ptr
= contentProxyList();
460 bool PlaylistEditor::allowsSelection()
465 void PlaylistEditor::reverseSelection()
467 if (isActiveWindow(Playlists
))
468 reverseSelectionHelper(Playlists
.begin(), Playlists
.end());
469 else if (isActiveWindow(Content
))
470 reverseSelectionHelper(Content
.begin(), Content
.end());
473 MPD::SongList
PlaylistEditor::getSelectedSongs()
475 MPD::SongList result
;
476 if (isActiveWindow(Playlists
))
478 bool any_selected
= false;
479 for (auto &e
: Playlists
)
484 Mpd
.GetPlaylistContent(e
.value(), vectorMoveInserter(result
));
487 // if no item is selected, add songs from right column
488 if (!any_selected
&& !Content
.empty())
490 withUnfilteredMenu(Content
, [this, &result
]() {
491 result
.insert(result
.end(), Content
.beginV(), Content
.endV());
495 else if (isActiveWindow(Content
))
497 for (auto &e
: Content
)
499 result
.push_back(e
.value());
500 // if no item is selected, add current one
501 if (result
.empty() && !Content
.empty())
502 result
.push_back(Content
.current().value());
507 /***********************************************************************/
509 bool PlaylistEditor::previousColumnAvailable()
511 if (isActiveWindow(Content
))
513 if (!Playlists
.reallyEmpty())
519 void PlaylistEditor::previousColumn()
521 if (isActiveWindow(Content
))
523 Content
.setHighlightColor(Config
.main_highlight_color
);
526 Playlists
.setHighlightColor(Config
.active_column_color
);
530 bool PlaylistEditor::nextColumnAvailable()
532 if (isActiveWindow(Playlists
))
534 if (!Content
.reallyEmpty())
540 void PlaylistEditor::nextColumn()
542 if (isActiveWindow(Playlists
))
544 Playlists
.setHighlightColor(Config
.main_highlight_color
);
547 Content
.setHighlightColor(Config
.active_column_color
);
551 /***********************************************************************/
553 void PlaylistEditor::updateTimer()
555 m_timer
= Global::Timer
;
558 void PlaylistEditor::Locate(const std::string
&name
)
561 for (size_t i
= 0; i
< Playlists
.size(); ++i
)
563 if (name
== Playlists
[i
].value())
565 Playlists
.highlight(i
);
575 std::string
SongToString(const MPD::Song
&s
)
578 switch (Config
.playlist_display_mode
)
580 case DisplayMode::Classic
:
581 result
= s
.toString(Config
.song_list_format_dollar_free
, Config
.tags_separator
);
583 case DisplayMode::Columns
:
584 result
= s
.toString(Config
.song_in_columns_to_string_format
, Config
.tags_separator
);
590 bool PlaylistEntryMatcher(const boost::regex
&rx
, const std::string
&playlist
)
592 return boost::regex_search(playlist
, rx
);
595 bool SongEntryMatcher(const boost::regex
&rx
, const MPD::Song
&s
)
597 return boost::regex_search(SongToString(s
), rx
);