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>
30 #include "playlist_editor.h"
32 #include "regex_filter.h"
34 #include "statusbar.h"
35 #include "tag_editor.h"
36 #include "utility/comparators.h"
38 #include "screen_switcher.h"
40 using Global::MainHeight
;
41 using Global::MainStartY
;
43 PlaylistEditor
*myPlaylistEditor
;
47 const auto fetch_delay
= boost::posix_time::milliseconds(500);
49 size_t LeftColumnStartX
;
50 size_t LeftColumnWidth
;
51 size_t RightColumnStartX
;
52 size_t RightColumnWidth
;
54 std::string
SongToString(const MPD::Song
&s
);
55 bool PlaylistEntryMatcher(const boost::regex
&rx
, const std::string
&playlist
);
56 bool SongEntryMatcher(const boost::regex
&rx
, const MPD::Song
&s
);
60 PlaylistEditor::PlaylistEditor()
61 : m_timer(boost::posix_time::from_time_t(0))
63 LeftColumnWidth
= COLS
/3-1;
64 RightColumnStartX
= LeftColumnWidth
+1;
65 RightColumnWidth
= COLS
-LeftColumnWidth
-1;
67 Playlists
= NC::Menu
<std::string
>(0, MainStartY
, LeftColumnWidth
, MainHeight
, Config
.titles_visibility
? "Playlists" : "", Config
.main_color
, NC::Border::None
);
68 Playlists
.setHighlightColor(Config
.active_column_color
);
69 Playlists
.cyclicScrolling(Config
.use_cyclic_scrolling
);
70 Playlists
.centeredCursor(Config
.centered_cursor
);
71 Playlists
.setSelectedPrefix(Config
.selected_item_prefix
);
72 Playlists
.setSelectedSuffix(Config
.selected_item_suffix
);
73 Playlists
.setItemDisplayer([](NC::Menu
<std::string
> &menu
) {
74 menu
<< Charset::utf8ToLocale(menu
.drawn()->value());
77 Content
= NC::Menu
<MPD::Song
>(RightColumnStartX
, MainStartY
, RightColumnWidth
, MainHeight
, Config
.titles_visibility
? "Playlist content" : "", Config
.main_color
, NC::Border::None
);
78 Content
.setHighlightColor(Config
.main_highlight_color
);
79 Content
.cyclicScrolling(Config
.use_cyclic_scrolling
);
80 Content
.centeredCursor(Config
.centered_cursor
);
81 Content
.setSelectedPrefix(Config
.selected_item_prefix
);
82 Content
.setSelectedSuffix(Config
.selected_item_suffix
);
83 switch (Config
.playlist_editor_display_mode
)
85 case DisplayMode::Classic
:
86 Content
.setItemDisplayer(boost::bind(Display::Songs
, _1
, contentProxyList(), Config
.song_list_format
));
88 case DisplayMode::Columns
:
89 Content
.setItemDisplayer(boost::bind(Display::SongsInColumns
, _1
, contentProxyList()));
96 void PlaylistEditor::resize()
98 size_t x_offset
, width
;
99 getWindowResizeParams(x_offset
, width
);
101 LeftColumnStartX
= x_offset
;
102 LeftColumnWidth
= width
/3-1;
103 RightColumnStartX
= LeftColumnStartX
+LeftColumnWidth
+1;
104 RightColumnWidth
= width
-LeftColumnWidth
-1;
106 Playlists
.resize(LeftColumnWidth
, MainHeight
);
107 Content
.resize(RightColumnWidth
, MainHeight
);
109 Playlists
.moveTo(LeftColumnStartX
, MainStartY
);
110 Content
.moveTo(RightColumnStartX
, MainStartY
);
115 std::wstring
PlaylistEditor::title()
117 return L
"Playlist editor";
120 void PlaylistEditor::refresh()
123 mvvline(MainStartY
, RightColumnStartX
-1, 0, MainHeight
);
127 void PlaylistEditor::switchTo()
129 SwitchTo::execute(this);
130 markSongsInPlaylist(contentProxyList());
135 void PlaylistEditor::update()
137 if (Playlists
.reallyEmpty() || m_playlists_update_requested
)
139 m_playlists_update_requested
= false;
140 Playlists
.clearSearchResults();
141 withUnfilteredMenuReapplyFilter(Playlists
, [this]() {
143 Mpd
.GetPlaylists([this, &idx
](std::string playlist
) {
144 if (idx
< Playlists
.size())
145 Playlists
[idx
].value() = playlist
;
147 Playlists
.addItem(playlist
);
150 if (idx
< Playlists
.size())
151 Playlists
.resizeList(idx
);
152 std::sort(Playlists
.beginV(), Playlists
.endV(),
153 LocaleBasedSorting(std::locale(), Config
.ignore_leading_the
));
158 if (!Playlists
.empty()
159 && ((Content
.reallyEmpty() && Global::Timer
- m_timer
> fetch_delay
) || m_content_update_requested
)
162 m_content_update_requested
= false;
163 Content
.clearSearchResults();
164 withUnfilteredMenuReapplyFilter(Content
, [this]() {
166 Mpd
.GetPlaylistContent(Playlists
.current().value(), [this, &idx
](MPD::Song s
) {
167 if (idx
< Content
.size())
169 Content
[idx
].value() = s
;
170 Content
[idx
].setBold(myPlaylist
->checkForSong(s
));
173 Content
.addItem(s
, myPlaylist
->checkForSong(s
));
176 if (idx
< Content
.size())
177 Content
.resizeList(idx
);
179 if (Config
.titles_visibility
)
181 title
= "Playlist content";
183 title
+= boost::lexical_cast
<std::string
>(Content
.size());
185 if (Content
.size() == 1)
189 title
.resize(Content
.getWidth());
191 Content
.setTitle(title
);
196 if (isActiveWindow(Content
) && Content
.reallyEmpty())
198 Content
.setHighlightColor(Config
.main_highlight_color
);
199 Playlists
.setHighlightColor(Config
.active_column_color
);
203 if (Playlists
.empty() && Content
.reallyEmpty())
205 Content
.Window::clear();
206 Content
.Window::display();
210 bool PlaylistEditor::isContentFiltered()
212 if (Content
.isFiltered())
214 Statusbar::print("Function currently unavailable due to filtered playlist content");
220 ProxySongList
PlaylistEditor::contentProxyList()
222 return ProxySongList(Content
, [](NC::Menu
<MPD::Song
>::Item
&item
) {
223 return &item
.value();
227 void PlaylistEditor::AddToPlaylist(bool add_n_play
)
231 if (isActiveWindow(Playlists
) && !Playlists
.empty())
234 withUnfilteredMenu(Content
, [&]() {
235 success
= addSongsToPlaylist(Content
.beginV(), Content
.endV(), add_n_play
, -1);
237 Statusbar::printf("Playlist \"%1%\" loaded%2%",
238 Playlists
.current().value(), withErrors(success
)
241 else if (isActiveWindow(Content
) && !Content
.empty())
242 addSongToPlaylist(Content
.current().value(), add_n_play
);
245 w
->scroll(NC::Scroll::Down
);
248 void PlaylistEditor::enterPressed()
253 void PlaylistEditor::spacePressed()
255 if (Config
.space_selects
)
257 if (isActiveWindow(Playlists
))
259 if (!Playlists
.empty())
261 Playlists
.current().setSelected(!Playlists
.current().isSelected());
262 Playlists
.scroll(NC::Scroll::Down
);
265 else if (isActiveWindow(Content
))
267 if (!Content
.empty())
269 Content
.current().setSelected(!Content
.current().isSelected());
270 Content
.scroll(NC::Scroll::Down
);
275 AddToPlaylist(false);
278 void PlaylistEditor::mouseButtonPressed(MEVENT me
)
280 if (!Playlists
.empty() && Playlists
.hasCoords(me
.x
, me
.y
))
282 if (!isActiveWindow(Playlists
))
284 if (previousColumnAvailable())
289 if (size_t(me
.y
) < Playlists
.size() && (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
)))
291 Playlists
.Goto(me
.y
);
292 if (me
.bstate
& BUTTON3_PRESSED
)
294 size_t pos
= Playlists
.choice();
296 if (pos
< Playlists
.size()-1)
297 Playlists
.scroll(NC::Scroll::Up
);
301 Screen
<WindowType
>::mouseButtonPressed(me
);
304 else if (!Content
.empty() && Content
.hasCoords(me
.x
, me
.y
))
306 if (!isActiveWindow(Content
))
308 if (nextColumnAvailable())
313 if (size_t(me
.y
) < Content
.size() && (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
)))
316 if (me
.bstate
& BUTTON1_PRESSED
)
318 size_t pos
= Content
.choice();
320 if (pos
< Content
.size()-1)
321 Content
.scroll(NC::Scroll::Up
);
327 Screen
<WindowType
>::mouseButtonPressed(me
);
331 /***********************************************************************/
333 bool PlaylistEditor::allowsFiltering()
338 std::string
PlaylistEditor::currentFilter()
341 if (isActiveWindow(Playlists
))
342 filter
= RegexFilter
<std::string
>::currentFilter(Playlists
);
343 else if (isActiveWindow(Content
))
344 filter
= RegexFilter
<MPD::Song
>::currentFilter(Content
);
348 void PlaylistEditor::applyFilter(const std::string
&filter
)
352 if (isActiveWindow(Playlists
))
354 Playlists
.clearFilter();
355 Playlists
.clearFilterResults();
357 else if (isActiveWindow(Content
))
359 Content
.clearFilter();
360 Content
.clearFilterResults();
366 if (isActiveWindow(Playlists
))
369 auto rx
= RegexFilter
<std::string
>(
370 boost::regex(filter
, Config
.regex_type
), PlaylistEntryMatcher
);
371 Playlists
.filter(Playlists
.begin(), Playlists
.end(), rx
);
373 else if (isActiveWindow(Content
))
376 auto rx
= RegexFilter
<MPD::Song
>(
377 boost::regex(filter
, Config
.regex_type
), SongEntryMatcher
);
378 Content
.filter(Content
.begin(), Content
.end(), rx
);
381 catch (boost::bad_expression
&) { }
384 /***********************************************************************/
386 bool PlaylistEditor::allowsSearching()
391 bool PlaylistEditor::search(const std::string
&constraint
)
393 if (constraint
.empty())
395 if (isActiveWindow(Playlists
))
396 Playlists
.clearSearchResults();
397 else if (isActiveWindow(Content
))
398 Content
.clearSearchResults();
404 if (isActiveWindow(Playlists
))
406 auto rx
= RegexFilter
<std::string
>(
407 boost::regex(constraint
, Config
.regex_type
), PlaylistEntryMatcher
);
408 result
= Playlists
.search(Playlists
.begin(), Playlists
.end(), rx
);
410 else if (isActiveWindow(Content
))
412 auto rx
= RegexFilter
<MPD::Song
>(
413 boost::regex(constraint
, Config
.regex_type
), SongEntryMatcher
);
414 result
= Content
.search(Content
.begin(), Content
.end(), rx
);
418 catch (boost::bad_expression
&)
424 void PlaylistEditor::nextFound(bool wrap
)
426 if (isActiveWindow(Playlists
))
427 Playlists
.nextFound(wrap
);
428 else if (isActiveWindow(Content
))
429 Content
.nextFound(wrap
);
432 void PlaylistEditor::prevFound(bool wrap
)
434 if (isActiveWindow(Playlists
))
435 Playlists
.prevFound(wrap
);
436 else if (isActiveWindow(Content
))
437 Content
.prevFound(wrap
);
440 /***********************************************************************/
442 ProxySongList
PlaylistEditor::proxySongList()
444 auto ptr
= ProxySongList();
445 if (isActiveWindow(Content
))
446 ptr
= contentProxyList();
450 bool PlaylistEditor::allowsSelection()
455 void PlaylistEditor::reverseSelection()
457 if (isActiveWindow(Playlists
))
458 reverseSelectionHelper(Playlists
.begin(), Playlists
.end());
459 else if (isActiveWindow(Content
))
460 reverseSelectionHelper(Content
.begin(), Content
.end());
463 MPD::SongList
PlaylistEditor::getSelectedSongs()
465 MPD::SongList result
;
466 if (isActiveWindow(Playlists
))
468 bool any_selected
= false;
469 for (auto &e
: Playlists
)
474 Mpd
.GetPlaylistContent(e
.value(), vectorMoveInserter(result
));
477 // if no item is selected, add songs from right column
478 if (!any_selected
&& !Content
.empty())
480 withUnfilteredMenu(Content
, [this, &result
]() {
481 result
.insert(result
.end(), Content
.beginV(), Content
.endV());
485 else if (isActiveWindow(Content
))
487 for (auto &e
: Content
)
489 result
.push_back(e
.value());
490 // if no item is selected, add current one
491 if (result
.empty() && !Content
.empty())
492 result
.push_back(Content
.current().value());
497 /***********************************************************************/
499 bool PlaylistEditor::previousColumnAvailable()
501 if (isActiveWindow(Content
))
503 if (!Playlists
.reallyEmpty())
509 void PlaylistEditor::previousColumn()
511 if (isActiveWindow(Content
))
513 Content
.setHighlightColor(Config
.main_highlight_color
);
516 Playlists
.setHighlightColor(Config
.active_column_color
);
520 bool PlaylistEditor::nextColumnAvailable()
522 if (isActiveWindow(Playlists
))
524 if (!Content
.reallyEmpty())
530 void PlaylistEditor::nextColumn()
532 if (isActiveWindow(Playlists
))
534 Playlists
.setHighlightColor(Config
.main_highlight_color
);
537 Content
.setHighlightColor(Config
.active_column_color
);
541 /***********************************************************************/
543 void PlaylistEditor::updateTimer()
545 m_timer
= Global::Timer
;
548 void PlaylistEditor::Locate(const std::string
&name
)
551 for (size_t i
= 0; i
< Playlists
.size(); ++i
)
553 if (name
== Playlists
[i
].value())
555 Playlists
.highlight(i
);
565 std::string
SongToString(const MPD::Song
&s
)
568 switch (Config
.playlist_display_mode
)
570 case DisplayMode::Classic
:
571 result
= s
.toString(Config
.song_list_format_dollar_free
, Config
.tags_separator
);
573 case DisplayMode::Columns
:
574 result
= s
.toString(Config
.song_in_columns_to_string_format
, Config
.tags_separator
);
580 bool PlaylistEntryMatcher(const boost::regex
&rx
, const std::string
&playlist
)
582 return boost::regex_search(playlist
, rx
);
585 bool SongEntryMatcher(const boost::regex
&rx
, const MPD::Song
&s
)
587 return boost::regex_search(SongToString(s
), rx
);