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 (!Playlists
.empty()
160 && ((Content
.reallyEmpty() && Global::Timer
- m_timer
> m_fetching_delay
) || m_content_update_requested
)
163 m_content_update_requested
= false;
164 Content
.clearSearchResults();
165 withUnfilteredMenuReapplyFilter(Content
, [this]() {
167 Mpd
.GetPlaylistContent(Playlists
.current().value(), [this, &idx
](MPD::Song s
) {
168 if (idx
< Content
.size())
170 Content
[idx
].value() = s
;
171 Content
[idx
].setBold(myPlaylist
->checkForSong(s
));
174 Content
.addItem(s
, myPlaylist
->checkForSong(s
));
177 if (idx
< Content
.size())
178 Content
.resizeList(idx
);
180 if (Config
.titles_visibility
)
182 title
= "Playlist content";
184 title
+= boost::lexical_cast
<std::string
>(Content
.size());
186 if (Content
.size() == 1)
190 title
.resize(Content
.getWidth());
192 Content
.setTitle(title
);
197 if (isActiveWindow(Content
) && Content
.reallyEmpty())
199 Content
.setHighlightColor(Config
.main_highlight_color
);
200 Playlists
.setHighlightColor(Config
.active_column_color
);
204 if (Playlists
.empty() && Content
.reallyEmpty())
206 Content
.Window::clear();
207 Content
.Window::display();
211 int PlaylistEditor::windowTimeout()
213 if (Content
.reallyEmpty())
214 return m_window_timeout
;
216 return Screen
<WindowType
>::windowTimeout();
219 bool PlaylistEditor::isContentFiltered()
221 if (Content
.isFiltered())
223 Statusbar::print("Function currently unavailable due to filtered playlist content");
229 ProxySongList
PlaylistEditor::contentProxyList()
231 return ProxySongList(Content
, [](NC::Menu
<MPD::Song
>::Item
&item
) {
232 return &item
.value();
236 void PlaylistEditor::AddToPlaylist(bool add_n_play
)
240 if (isActiveWindow(Playlists
) && !Playlists
.empty())
243 withUnfilteredMenu(Content
, [&]() {
244 success
= addSongsToPlaylist(Content
.beginV(), Content
.endV(), add_n_play
, -1);
246 Statusbar::printf("Playlist \"%1%\" loaded%2%",
247 Playlists
.current().value(), withErrors(success
)
250 else if (isActiveWindow(Content
) && !Content
.empty())
251 addSongToPlaylist(Content
.current().value(), add_n_play
);
254 w
->scroll(NC::Scroll::Down
);
257 void PlaylistEditor::enterPressed()
262 void PlaylistEditor::spacePressed()
264 if (Config
.space_selects
)
266 if (isActiveWindow(Playlists
))
268 if (!Playlists
.empty())
270 Playlists
.current().setSelected(!Playlists
.current().isSelected());
271 Playlists
.scroll(NC::Scroll::Down
);
274 else if (isActiveWindow(Content
))
276 if (!Content
.empty())
278 Content
.current().setSelected(!Content
.current().isSelected());
279 Content
.scroll(NC::Scroll::Down
);
284 AddToPlaylist(false);
287 void PlaylistEditor::mouseButtonPressed(MEVENT me
)
289 if (!Playlists
.empty() && Playlists
.hasCoords(me
.x
, me
.y
))
291 if (!isActiveWindow(Playlists
))
293 if (previousColumnAvailable())
298 if (size_t(me
.y
) < Playlists
.size() && (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
)))
300 Playlists
.Goto(me
.y
);
301 if (me
.bstate
& BUTTON3_PRESSED
)
303 size_t pos
= Playlists
.choice();
305 if (pos
< Playlists
.size()-1)
306 Playlists
.scroll(NC::Scroll::Up
);
310 Screen
<WindowType
>::mouseButtonPressed(me
);
313 else if (!Content
.empty() && Content
.hasCoords(me
.x
, me
.y
))
315 if (!isActiveWindow(Content
))
317 if (nextColumnAvailable())
322 if (size_t(me
.y
) < Content
.size() && (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
)))
325 if (me
.bstate
& BUTTON1_PRESSED
)
327 size_t pos
= Content
.choice();
329 if (pos
< Content
.size()-1)
330 Content
.scroll(NC::Scroll::Up
);
336 Screen
<WindowType
>::mouseButtonPressed(me
);
340 /***********************************************************************/
342 bool PlaylistEditor::allowsFiltering()
347 std::string
PlaylistEditor::currentFilter()
350 if (isActiveWindow(Playlists
))
351 filter
= RegexFilter
<std::string
>::currentFilter(Playlists
);
352 else if (isActiveWindow(Content
))
353 filter
= RegexFilter
<MPD::Song
>::currentFilter(Content
);
357 void PlaylistEditor::applyFilter(const std::string
&filter
)
361 if (isActiveWindow(Playlists
))
363 Playlists
.clearFilter();
364 Playlists
.clearFilterResults();
366 else if (isActiveWindow(Content
))
368 Content
.clearFilter();
369 Content
.clearFilterResults();
375 if (isActiveWindow(Playlists
))
378 auto rx
= RegexFilter
<std::string
>(
379 boost::regex(filter
, Config
.regex_type
), PlaylistEntryMatcher
);
380 Playlists
.filter(Playlists
.begin(), Playlists
.end(), rx
);
382 else if (isActiveWindow(Content
))
385 auto rx
= RegexFilter
<MPD::Song
>(
386 boost::regex(filter
, Config
.regex_type
), SongEntryMatcher
);
387 Content
.filter(Content
.begin(), Content
.end(), rx
);
390 catch (boost::bad_expression
&) { }
393 /***********************************************************************/
395 bool PlaylistEditor::allowsSearching()
400 bool PlaylistEditor::search(const std::string
&constraint
)
402 if (constraint
.empty())
404 if (isActiveWindow(Playlists
))
405 Playlists
.clearSearchResults();
406 else if (isActiveWindow(Content
))
407 Content
.clearSearchResults();
413 if (isActiveWindow(Playlists
))
415 auto rx
= RegexFilter
<std::string
>(
416 boost::regex(constraint
, Config
.regex_type
), PlaylistEntryMatcher
);
417 result
= Playlists
.search(Playlists
.begin(), Playlists
.end(), rx
);
419 else if (isActiveWindow(Content
))
421 auto rx
= RegexFilter
<MPD::Song
>(
422 boost::regex(constraint
, Config
.regex_type
), SongEntryMatcher
);
423 result
= Content
.search(Content
.begin(), Content
.end(), rx
);
427 catch (boost::bad_expression
&)
433 void PlaylistEditor::nextFound(bool wrap
)
435 if (isActiveWindow(Playlists
))
436 Playlists
.nextFound(wrap
);
437 else if (isActiveWindow(Content
))
438 Content
.nextFound(wrap
);
441 void PlaylistEditor::prevFound(bool wrap
)
443 if (isActiveWindow(Playlists
))
444 Playlists
.prevFound(wrap
);
445 else if (isActiveWindow(Content
))
446 Content
.prevFound(wrap
);
449 /***********************************************************************/
451 ProxySongList
PlaylistEditor::proxySongList()
453 auto ptr
= ProxySongList();
454 if (isActiveWindow(Content
))
455 ptr
= contentProxyList();
459 bool PlaylistEditor::allowsSelection()
464 void PlaylistEditor::reverseSelection()
466 if (isActiveWindow(Playlists
))
467 reverseSelectionHelper(Playlists
.begin(), Playlists
.end());
468 else if (isActiveWindow(Content
))
469 reverseSelectionHelper(Content
.begin(), Content
.end());
472 MPD::SongList
PlaylistEditor::getSelectedSongs()
474 MPD::SongList result
;
475 if (isActiveWindow(Playlists
))
477 bool any_selected
= false;
478 for (auto &e
: Playlists
)
483 Mpd
.GetPlaylistContent(e
.value(), vectorMoveInserter(result
));
486 // if no item is selected, add songs from right column
487 if (!any_selected
&& !Content
.empty())
489 withUnfilteredMenu(Content
, [this, &result
]() {
490 result
.insert(result
.end(), Content
.beginV(), Content
.endV());
494 else if (isActiveWindow(Content
))
496 for (auto &e
: Content
)
498 result
.push_back(e
.value());
499 // if no item is selected, add current one
500 if (result
.empty() && !Content
.empty())
501 result
.push_back(Content
.current().value());
506 /***********************************************************************/
508 bool PlaylistEditor::previousColumnAvailable()
510 if (isActiveWindow(Content
))
512 if (!Playlists
.reallyEmpty())
518 void PlaylistEditor::previousColumn()
520 if (isActiveWindow(Content
))
522 Content
.setHighlightColor(Config
.main_highlight_color
);
525 Playlists
.setHighlightColor(Config
.active_column_color
);
529 bool PlaylistEditor::nextColumnAvailable()
531 if (isActiveWindow(Playlists
))
533 if (!Content
.reallyEmpty())
539 void PlaylistEditor::nextColumn()
541 if (isActiveWindow(Playlists
))
543 Playlists
.setHighlightColor(Config
.main_highlight_color
);
546 Content
.setHighlightColor(Config
.active_column_color
);
550 /***********************************************************************/
552 void PlaylistEditor::updateTimer()
554 m_timer
= Global::Timer
;
557 void PlaylistEditor::Locate(const std::string
&name
)
560 for (size_t i
= 0; i
< Playlists
.size(); ++i
)
562 if (name
== Playlists
[i
].value())
564 Playlists
.highlight(i
);
574 std::string
SongToString(const MPD::Song
&s
)
577 switch (Config
.playlist_display_mode
)
579 case DisplayMode::Classic
:
580 result
= s
.toString(Config
.song_list_format_dollar_free
, Config
.tags_separator
);
582 case DisplayMode::Columns
:
583 result
= s
.toString(Config
.song_in_columns_to_string_format
, Config
.tags_separator
);
589 bool PlaylistEntryMatcher(const boost::regex
&rx
, const std::string
&playlist
)
591 return boost::regex_search(playlist
, rx
);
594 bool SongEntryMatcher(const boost::regex
&rx
, const MPD::Song
&s
)
596 return boost::regex_search(SongToString(s
), rx
);