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/lambda/bind.hpp>
23 #include <boost/date_time/posix_time/posix_time.hpp>
31 #include "playlist_editor.h"
32 #include "menu_impl.h"
35 #include "statusbar.h"
36 #include "tag_editor.h"
37 #include "helpers/song_iterator_maker.h"
38 #include "utility/functional.h"
39 #include "utility/comparators.h"
41 #include "screen_switcher.h"
43 using Global::MainHeight
;
44 using Global::MainStartY
;
46 namespace ph
= std::placeholders
;
48 PlaylistEditor
*myPlaylistEditor
;
52 size_t LeftColumnStartX
;
53 size_t LeftColumnWidth
;
54 size_t RightColumnStartX
;
55 size_t RightColumnWidth
;
57 std::string
SongToString(const MPD::Song
&s
);
58 bool PlaylistEntryMatcher(const Regex::Regex
&rx
, const MPD::Playlist
&playlist
);
59 bool SongEntryMatcher(const Regex::Regex
&rx
, const MPD::Song
&s
);
63 PlaylistEditor::PlaylistEditor()
64 : m_timer(boost::posix_time::from_time_t(0))
65 , m_window_timeout(Config
.data_fetching_delay
? 250 : BaseScreen::defaultWindowTimeout
)
66 , m_fetching_delay(boost::posix_time::milliseconds(Config
.data_fetching_delay
? 250 : -1))
68 LeftColumnWidth
= COLS
/3-1;
69 RightColumnStartX
= LeftColumnWidth
+1;
70 RightColumnWidth
= COLS
-LeftColumnWidth
-1;
72 Playlists
= NC::Menu
<MPD::Playlist
>(0, MainStartY
, LeftColumnWidth
, MainHeight
, Config
.titles_visibility
? "Playlists" : "", Config
.main_color
, NC::Border());
73 Playlists
.setHighlightColor(Config
.active_column_color
);
74 Playlists
.cyclicScrolling(Config
.use_cyclic_scrolling
);
75 Playlists
.centeredCursor(Config
.centered_cursor
);
76 Playlists
.setSelectedPrefix(Config
.selected_item_prefix
);
77 Playlists
.setSelectedSuffix(Config
.selected_item_suffix
);
78 Playlists
.setItemDisplayer([](NC::Menu
<MPD::Playlist
> &menu
) {
79 menu
<< Charset::utf8ToLocale(menu
.drawn()->value().path());
82 Content
= NC::Menu
<MPD::Song
>(RightColumnStartX
, MainStartY
, RightColumnWidth
, MainHeight
, Config
.titles_visibility
? "Content" : "", Config
.main_color
, NC::Border());
83 Content
.setHighlightColor(Config
.main_highlight_color
);
84 Content
.cyclicScrolling(Config
.use_cyclic_scrolling
);
85 Content
.centeredCursor(Config
.centered_cursor
);
86 Content
.setSelectedPrefix(Config
.selected_item_prefix
);
87 Content
.setSelectedSuffix(Config
.selected_item_suffix
);
88 switch (Config
.playlist_editor_display_mode
)
90 case DisplayMode::Classic
:
91 Content
.setItemDisplayer(std::bind(
92 Display::Songs
, ph::_1
, std::cref(Content
), std::cref(Config
.song_list_format
)
95 case DisplayMode::Columns
:
96 Content
.setItemDisplayer(std::bind(
97 Display::SongsInColumns
, ph::_1
, std::cref(Content
)
105 void PlaylistEditor::resize()
107 size_t x_offset
, width
;
108 getWindowResizeParams(x_offset
, width
);
110 LeftColumnStartX
= x_offset
;
111 LeftColumnWidth
= width
/3-1;
112 RightColumnStartX
= LeftColumnStartX
+LeftColumnWidth
+1;
113 RightColumnWidth
= width
-LeftColumnWidth
-1;
115 Playlists
.resize(LeftColumnWidth
, MainHeight
);
116 Content
.resize(RightColumnWidth
, MainHeight
);
118 Playlists
.moveTo(LeftColumnStartX
, MainStartY
);
119 Content
.moveTo(RightColumnStartX
, MainStartY
);
124 std::wstring
PlaylistEditor::title()
126 return L
"Playlist editor";
129 void PlaylistEditor::refresh()
132 drawSeparator(RightColumnStartX
-1);
136 void PlaylistEditor::switchTo()
138 SwitchTo::execute(this);
139 markSongsInPlaylist(Content
);
144 void PlaylistEditor::update()
147 ScopedUnfilteredMenu
<MPD::Playlist
> sunfilter_playlists(ReapplyFilter::No
, Playlists
);
148 if (Playlists
.empty() || m_playlists_update_requested
)
150 m_playlists_update_requested
= false;
151 sunfilter_playlists
.set(ReapplyFilter::Yes
, true);
155 for (MPD::PlaylistIterator it
= Mpd
.GetPlaylists(), end
; it
!= end
; ++it
, ++idx
)
157 if (idx
< Playlists
.size())
158 Playlists
[idx
].value() = std::move(*it
);
160 Playlists
.addItem(std::move(*it
));
163 catch (MPD::ServerError
&e
)
165 if (e
.code() == MPD_SERVER_ERROR_SYSTEM
) // no playlists directory
166 Statusbar::print(e
.what());
170 if (idx
< Playlists
.size())
171 Playlists
.resizeList(idx
);
172 std::sort(Playlists
.beginV(), Playlists
.endV(),
173 LocaleBasedSorting(std::locale(), Config
.ignore_leading_the
));
178 ScopedUnfilteredMenu
<MPD::Song
> sunfilter_content(ReapplyFilter::No
, Content
);
179 if (!Playlists
.empty()
180 && ((Content
.empty() && Global::Timer
- m_timer
> m_fetching_delay
)
181 || m_content_update_requested
))
183 m_content_update_requested
= false;
184 sunfilter_content
.set(ReapplyFilter::Yes
, true);
186 MPD::SongIterator s
= Mpd
.GetPlaylistContent(Playlists
.current()->value().path()), end
;
187 for (; s
!= end
; ++s
, ++idx
)
189 bool in_playlist
= myPlaylist
->checkForSong(*s
);
190 if (idx
< Content
.size())
192 Content
[idx
].setBold(in_playlist
);
193 Content
[idx
].value() = std::move(*s
);
197 auto properties
= NC::List::Properties::Selectable
;
199 properties
|= NC::List::Properties::Bold
;
200 Content
.addItem(std::move(*s
), properties
);
203 if (idx
< Content
.size())
204 Content
.resizeList(idx
);
206 if (Config
.titles_visibility
)
208 wtitle
= (boost::format("Content (%1% %2%)")
209 % boost::lexical_cast
<std::string
>(Content
.size())
210 % (Content
.size() == 1 ? "item" : "items")).str();
211 wtitle
.resize(Content
.getWidth());
213 Content
.setTitle(wtitle
);
214 Content
.refreshBorder();
219 int PlaylistEditor::windowTimeout()
221 ScopedUnfilteredMenu
<MPD::Song
> sunfilter_content(ReapplyFilter::No
, Content
);
223 return m_window_timeout
;
225 return Screen
<WindowType
>::windowTimeout();
228 void PlaylistEditor::mouseButtonPressed(MEVENT me
)
230 if (!Playlists
.empty() && Playlists
.hasCoords(me
.x
, me
.y
))
232 if (!isActiveWindow(Playlists
))
234 if (previousColumnAvailable())
239 if (size_t(me
.y
) < Playlists
.size() && (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
)))
241 Playlists
.Goto(me
.y
);
242 if (me
.bstate
& BUTTON3_PRESSED
)
243 addItemToPlaylist(false);
246 Screen
<WindowType
>::mouseButtonPressed(me
);
249 else if (!Content
.empty() && Content
.hasCoords(me
.x
, me
.y
))
251 if (!isActiveWindow(Content
))
253 if (nextColumnAvailable())
258 if (size_t(me
.y
) < Content
.size() && (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
)))
261 bool play
= me
.bstate
& BUTTON3_PRESSED
;
262 addItemToPlaylist(play
);
265 Screen
<WindowType
>::mouseButtonPressed(me
);
269 /***********************************************************************/
271 bool PlaylistEditor::allowsSearching()
276 const std::string
&PlaylistEditor::searchConstraint()
278 if (isActiveWindow(Playlists
))
279 return m_playlists_search_predicate
.constraint();
280 else if (isActiveWindow(Content
))
281 return m_content_search_predicate
.constraint();
282 throw std::runtime_error("no active window");
285 void PlaylistEditor::setSearchConstraint(const std::string
&constraint
)
287 if (isActiveWindow(Playlists
))
289 m_playlists_search_predicate
= Regex::Filter
<MPD::Playlist
>(
292 PlaylistEntryMatcher
);
294 else if (isActiveWindow(Content
))
296 m_content_search_predicate
= Regex::Filter
<MPD::Song
>(
303 void PlaylistEditor::clearSearchConstraint()
305 if (isActiveWindow(Playlists
))
306 m_playlists_search_predicate
.clear();
307 else if (isActiveWindow(Content
))
308 m_content_search_predicate
.clear();
311 bool PlaylistEditor::search(SearchDirection direction
, bool wrap
, bool skip_current
)
314 if (isActiveWindow(Playlists
))
315 result
= ::search(Playlists
, m_playlists_search_predicate
, direction
, wrap
, skip_current
);
316 else if (isActiveWindow(Content
))
317 result
= ::search(Content
, m_content_search_predicate
, direction
, wrap
, skip_current
);
321 std::string
PlaylistEditor::currentFilter()
324 if (isActiveWindow(Playlists
))
326 if (auto pred
= Playlists
.filterPredicate
<Regex::Filter
<MPD::Playlist
>>())
327 result
= pred
->constraint();
329 else if (isActiveWindow(Content
))
331 if (auto pred
= Content
.filterPredicate
<Regex::Filter
<MPD::Song
>>())
332 result
= pred
->constraint();
337 void PlaylistEditor::applyFilter(const std::string
&constraint
)
339 if (isActiveWindow(Playlists
))
341 if (!constraint
.empty())
343 Playlists
.applyFilter(Regex::Filter
<MPD::Playlist
>(
346 PlaylistEntryMatcher
));
349 Playlists
.clearFilter();
351 else if (isActiveWindow(Content
))
353 if (!constraint
.empty())
355 Content
.applyFilter(Regex::Filter
<MPD::Song
>(
361 Content
.clearFilter();
366 /***********************************************************************/
368 bool PlaylistEditor::itemAvailable()
370 if (isActiveWindow(Playlists
))
371 return !Playlists
.empty();
372 if (isActiveWindow(Content
))
373 return !Content
.empty();
377 bool PlaylistEditor::addItemToPlaylist(bool play
)
380 if (isActiveWindow(Playlists
))
382 ScopedUnfilteredMenu
<MPD::Song
> sunfilter_content(ReapplyFilter::No
, Content
);
383 result
= addSongsToPlaylist(Content
.beginV(), Content
.endV(), play
, -1);
384 Statusbar::printf("Playlist \"%1%\" loaded%2%",
385 Playlists
.current()->value().path(), withErrors(result
));
387 else if (isActiveWindow(Content
))
388 result
= addSongToPlaylist(Content
.current()->value(), play
);
392 std::vector
<MPD::Song
> PlaylistEditor::getSelectedSongs()
394 std::vector
<MPD::Song
> result
;
395 if (isActiveWindow(Playlists
))
397 bool any_selected
= false;
398 for (auto &e
: Playlists
)
404 std::make_move_iterator(Mpd
.GetPlaylistContent(e
.value().path())),
405 std::make_move_iterator(MPD::SongIterator()),
406 std::back_inserter(result
));
409 // if no item is selected, add songs from right column
410 ScopedUnfilteredMenu
<MPD::Song
> sunfilter_content(ReapplyFilter::No
, Content
);
411 if (!any_selected
&& !Playlists
.empty())
412 std::copy(Content
.beginV(), Content
.endV(), std::back_inserter(result
));
414 else if (isActiveWindow(Content
))
415 result
= Content
.getSelectedSongs();
419 /***********************************************************************/
421 bool PlaylistEditor::previousColumnAvailable()
423 if (isActiveWindow(Content
))
425 ScopedUnfilteredMenu
<MPD::Playlist
> sunfilter_playlists(ReapplyFilter::No
, Playlists
);
426 if (!Playlists
.empty())
432 void PlaylistEditor::previousColumn()
434 if (isActiveWindow(Content
))
436 Content
.setHighlightColor(Config
.main_highlight_color
);
439 Playlists
.setHighlightColor(Config
.active_column_color
);
443 bool PlaylistEditor::nextColumnAvailable()
445 if (isActiveWindow(Playlists
))
447 ScopedUnfilteredMenu
<MPD::Song
> sunfilter_content(ReapplyFilter::No
, Content
);
448 if (!Content
.empty())
454 void PlaylistEditor::nextColumn()
456 if (isActiveWindow(Playlists
))
458 Playlists
.setHighlightColor(Config
.main_highlight_color
);
461 Content
.setHighlightColor(Config
.active_column_color
);
465 /***********************************************************************/
467 void PlaylistEditor::updateTimer()
469 m_timer
= Global::Timer
;
472 void PlaylistEditor::locatePlaylist(const MPD::Playlist
&playlist
)
475 Playlists
.clearFilter();
476 auto first
= Playlists
.beginV(), last
= Playlists
.endV();
477 auto it
= std::find(first
, last
, playlist
);
480 Playlists
.highlight(it
- first
);
482 Content
.clearFilter();
489 std::string
SongToString(const MPD::Song
&s
)
492 switch (Config
.playlist_display_mode
)
494 case DisplayMode::Classic
:
495 result
= Format::stringify
<char>(Config
.song_list_format
, &s
);
497 case DisplayMode::Columns
:
498 result
= Format::stringify
<char>(Config
.song_columns_mode_format
, &s
);
504 bool PlaylistEntryMatcher(const Regex::Regex
&rx
, const MPD::Playlist
&playlist
)
506 return Regex::search(playlist
.path(), rx
);
509 bool SongEntryMatcher(const Regex::Regex
&rx
, const MPD::Song
&s
)
511 return Regex::search(SongToString(s
), rx
);