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 size_t LeftColumnStartX
;
49 size_t LeftColumnWidth
;
50 size_t RightColumnStartX
;
51 size_t RightColumnWidth
;
53 std::string
SongToString(const MPD::Song
&s
);
54 bool PlaylistEntryMatcher(const boost::regex
&rx
, const std::string
&playlist
);
55 bool SongEntryMatcher(const boost::regex
&rx
, const MPD::Song
&s
);
59 PlaylistEditor::PlaylistEditor()
60 : m_timer(boost::posix_time::from_time_t(0))
61 , m_window_timeout(Config
.data_fetching_delay
? 250 : 500)
62 , m_fetching_delay(boost::posix_time::milliseconds(Config
.data_fetching_delay
? 250 : -1))
64 LeftColumnWidth
= COLS
/3-1;
65 RightColumnStartX
= LeftColumnWidth
+1;
66 RightColumnWidth
= COLS
-LeftColumnWidth
-1;
68 Playlists
= NC::Menu
<std::string
>(0, MainStartY
, LeftColumnWidth
, MainHeight
, Config
.titles_visibility
? "Playlists" : "", Config
.main_color
, NC::Border::None
);
69 Playlists
.setHighlightColor(Config
.active_column_color
);
70 Playlists
.cyclicScrolling(Config
.use_cyclic_scrolling
);
71 Playlists
.centeredCursor(Config
.centered_cursor
);
72 Playlists
.setSelectedPrefix(Config
.selected_item_prefix
);
73 Playlists
.setSelectedSuffix(Config
.selected_item_suffix
);
74 Playlists
.setItemDisplayer([](NC::Menu
<std::string
> &menu
) {
75 menu
<< Charset::utf8ToLocale(menu
.drawn()->value());
78 Content
= NC::Menu
<MPD::Song
>(RightColumnStartX
, MainStartY
, RightColumnWidth
, MainHeight
, Config
.titles_visibility
? "Playlist content" : "", Config
.main_color
, NC::Border::None
);
79 Content
.setHighlightColor(Config
.main_highlight_color
);
80 Content
.cyclicScrolling(Config
.use_cyclic_scrolling
);
81 Content
.centeredCursor(Config
.centered_cursor
);
82 Content
.setSelectedPrefix(Config
.selected_item_prefix
);
83 Content
.setSelectedSuffix(Config
.selected_item_suffix
);
84 switch (Config
.playlist_editor_display_mode
)
86 case DisplayMode::Classic
:
87 Content
.setItemDisplayer(boost::bind(Display::Songs
, _1
, contentProxyList(), Config
.song_list_format
));
89 case DisplayMode::Columns
:
90 Content
.setItemDisplayer(boost::bind(Display::SongsInColumns
, _1
, contentProxyList()));
97 void PlaylistEditor::resize()
99 size_t x_offset
, width
;
100 getWindowResizeParams(x_offset
, width
);
102 LeftColumnStartX
= x_offset
;
103 LeftColumnWidth
= width
/3-1;
104 RightColumnStartX
= LeftColumnStartX
+LeftColumnWidth
+1;
105 RightColumnWidth
= width
-LeftColumnWidth
-1;
107 Playlists
.resize(LeftColumnWidth
, MainHeight
);
108 Content
.resize(RightColumnWidth
, MainHeight
);
110 Playlists
.moveTo(LeftColumnStartX
, MainStartY
);
111 Content
.moveTo(RightColumnStartX
, MainStartY
);
116 std::wstring
PlaylistEditor::title()
118 return L
"Playlist editor";
121 void PlaylistEditor::refresh()
124 drawSeparator(RightColumnStartX
-1);
128 void PlaylistEditor::switchTo()
130 SwitchTo::execute(this);
131 markSongsInPlaylist(contentProxyList());
136 void PlaylistEditor::update()
138 if (Playlists
.reallyEmpty() || m_playlists_update_requested
)
140 m_playlists_update_requested
= false;
141 Playlists
.clearSearchResults();
142 withUnfilteredMenuReapplyFilter(Playlists
, [this]() {
144 Mpd
.GetPlaylists([this, &idx
](std::string playlist
) {
145 if (idx
< Playlists
.size())
146 Playlists
[idx
].value() = playlist
;
148 Playlists
.addItem(playlist
);
151 if (idx
< Playlists
.size())
152 Playlists
.resizeList(idx
);
153 std::sort(Playlists
.beginV(), Playlists
.endV(),
154 LocaleBasedSorting(std::locale(), Config
.ignore_leading_the
));
159 if ((Content
.reallyEmpty() && Global::Timer
- m_timer
> m_fetching_delay
)
160 || m_content_update_requested
)
162 m_content_update_requested
= false;
163 if (Playlists
.empty())
167 Content
.clearSearchResults();
168 withUnfilteredMenuReapplyFilter(Content
, [this]() {
170 Mpd
.GetPlaylistContent(Playlists
.current().value(), [this, &idx
](MPD::Song s
) {
171 if (idx
< Content
.size())
173 Content
[idx
].value() = s
;
174 Content
[idx
].setBold(myPlaylist
->checkForSong(s
));
177 Content
.addItem(s
, myPlaylist
->checkForSong(s
));
180 if (idx
< Content
.size())
181 Content
.resizeList(idx
);
183 if (Config
.titles_visibility
)
185 title
= "Playlist content";
187 title
+= boost::lexical_cast
<std::string
>(Content
.size());
189 if (Content
.size() == 1)
193 title
.resize(Content
.getWidth());
195 Content
.setTitle(title
);
201 if (isActiveWindow(Content
) && Content
.reallyEmpty())
203 Content
.setHighlightColor(Config
.main_highlight_color
);
204 Playlists
.setHighlightColor(Config
.active_column_color
);
208 if (Playlists
.empty() && Content
.reallyEmpty())
210 Content
.Window::clear();
211 Content
.Window::display();
215 int PlaylistEditor::windowTimeout()
217 if (Content
.reallyEmpty())
218 return m_window_timeout
;
220 return Screen
<WindowType
>::windowTimeout();
223 bool PlaylistEditor::isContentFiltered()
225 if (Content
.isFiltered())
227 Statusbar::print("Function currently unavailable due to filtered playlist content");
233 ProxySongList
PlaylistEditor::contentProxyList()
235 return ProxySongList(Content
, [](NC::Menu
<MPD::Song
>::Item
&item
) {
236 return &item
.value();
240 void PlaylistEditor::AddToPlaylist(bool add_n_play
)
242 if (isActiveWindow(Playlists
) && !Playlists
.empty())
245 Mpd
.GetPlaylistContent(Playlists
.current().value(), vectorMoveInserter(list
));
246 bool success
= addSongsToPlaylist(list
.begin(), list
.end(), 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
&& !Playlists
.empty())
489 Mpd
.GetPlaylistContent(Playlists
.current().value(), vectorMoveInserter(result
));
491 else if (isActiveWindow(Content
))
493 for (auto &e
: Content
)
495 result
.push_back(e
.value());
496 // if no item is selected, add current one
497 if (result
.empty() && !Content
.empty())
498 result
.push_back(Content
.current().value());
503 /***********************************************************************/
505 bool PlaylistEditor::previousColumnAvailable()
507 if (isActiveWindow(Content
))
509 if (!Playlists
.reallyEmpty())
515 void PlaylistEditor::previousColumn()
517 if (isActiveWindow(Content
))
519 Content
.setHighlightColor(Config
.main_highlight_color
);
522 Playlists
.setHighlightColor(Config
.active_column_color
);
526 bool PlaylistEditor::nextColumnAvailable()
528 if (isActiveWindow(Playlists
))
530 if (!Content
.reallyEmpty())
536 void PlaylistEditor::nextColumn()
538 if (isActiveWindow(Playlists
))
540 Playlists
.setHighlightColor(Config
.main_highlight_color
);
543 Content
.setHighlightColor(Config
.active_column_color
);
547 /***********************************************************************/
549 void PlaylistEditor::updateTimer()
551 m_timer
= Global::Timer
;
554 void PlaylistEditor::Locate(const std::string
&name
)
557 for (size_t i
= 0; i
< Playlists
.size(); ++i
)
559 if (name
== Playlists
[i
].value())
561 Playlists
.highlight(i
);
571 std::string
SongToString(const MPD::Song
&s
)
574 switch (Config
.playlist_display_mode
)
576 case DisplayMode::Classic
:
577 result
= s
.toString(Config
.song_list_format_dollar_free
, Config
.tags_separator
);
579 case DisplayMode::Columns
:
580 result
= s
.toString(Config
.song_in_columns_to_string_format
, Config
.tags_separator
);
586 bool PlaylistEntryMatcher(const boost::regex
&rx
, const std::string
&playlist
)
588 return boost::regex_search(playlist
, rx
);
591 bool SongEntryMatcher(const boost::regex
&rx
, const MPD::Song
&s
)
593 return boost::regex_search(SongToString(s
), rx
);