format: shorten date by simple truncation
[ncmpcpp.git] / src / playlist_editor.cpp
blob6012b704820e28bda8a65c1bdf90f33e078e465b
1 /***************************************************************************
2 * Copyright (C) 2008-2014 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
4 * *
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. *
9 * *
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. *
14 * *
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 ***************************************************************************/
21 #include <algorithm>
22 #include <boost/lambda/bind.hpp>
23 #include <boost/date_time/posix_time/posix_time.hpp>
24 #include <cassert>
26 #include "charset.h"
27 #include "display.h"
28 #include "global.h"
29 #include "helpers.h"
30 #include "playlist.h"
31 #include "playlist_editor.h"
32 #include "menu_impl.h"
33 #include "mpdpp.h"
34 #include "status.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"
40 #include "title.h"
41 #include "screen_switcher.h"
43 using Global::MainHeight;
44 using Global::MainStartY;
46 namespace ph = std::placeholders;
48 PlaylistEditor *myPlaylistEditor;
50 namespace {
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());
80 });
82 Content = NC::Menu<MPD::Song>(RightColumnStartX, MainStartY, RightColumnWidth, MainHeight, Config.titles_visibility ? "Playlist 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)
93 ));
94 break;
95 case DisplayMode::Columns:
96 Content.setItemDisplayer(std::bind(
97 Display::SongsInColumns, ph::_1, std::cref(Content)
98 ));
99 break;
102 w = &Playlists;
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);
121 hasToBeResized = 0;
124 std::wstring PlaylistEditor::title()
126 return L"Playlist editor";
129 void PlaylistEditor::refresh()
131 Playlists.display();
132 drawSeparator(RightColumnStartX-1);
133 Content.display();
136 void PlaylistEditor::switchTo()
138 SwitchTo::execute(this);
139 markSongsInPlaylist(Content);
140 drawHeader();
141 refresh();
144 void PlaylistEditor::update()
146 if (Playlists.empty() || m_playlists_update_requested)
148 m_playlists_update_requested = false;
149 size_t idx = 0;
150 for (MPD::PlaylistIterator it = Mpd.GetPlaylists(), end; it != end; ++it, ++idx)
152 if (idx < Playlists.size())
153 Playlists[idx].value() = std::move(*it);
154 else
155 Playlists.addItem(std::move(*it));
157 if (idx < Playlists.size())
158 Playlists.resizeList(idx);
159 std::sort(Playlists.beginV(), Playlists.endV(),
160 LocaleBasedSorting(std::locale(), Config.ignore_leading_the));
161 Playlists.refresh();
164 if ((Content.empty() && Global::Timer - m_timer > m_fetching_delay)
165 || m_content_update_requested)
167 m_content_update_requested = false;
168 if (Playlists.empty())
169 Content.clear();
170 else
172 size_t idx = 0;
173 MPD::SongIterator s = Mpd.GetPlaylistContent(Playlists.current()->value().path()), end;
174 for (; s != end; ++s, ++idx)
176 bool in_playlist = myPlaylist->checkForSong(*s);
177 if (idx < Content.size())
179 Content[idx].setBold(in_playlist);
180 Content[idx].value() = std::move(*s);
182 else
184 auto properties = NC::List::Properties::Selectable;
185 if (in_playlist)
186 properties |= NC::List::Properties::Bold;
187 Content.addItem(std::move(*s), properties);
190 if (idx < Content.size())
191 Content.resizeList(idx);
192 std::string wtitle;
193 if (Config.titles_visibility)
195 wtitle = (boost::format("Playlist content (%1%) %2%")
196 % boost::lexical_cast<std::string>(Content.size())
197 % (Content.size() == 1 ? "item" : "items")
198 ).str();
199 wtitle.resize(Content.getWidth());
201 Content.setTitle(wtitle);
203 Content.display();
206 if (isActiveWindow(Content) && Content.empty())
208 Content.setHighlightColor(Config.main_highlight_color);
209 Playlists.setHighlightColor(Config.active_column_color);
210 w = &Playlists;
213 if (Playlists.empty() && Content.empty())
215 Content.Window::clear();
216 Content.Window::display();
220 int PlaylistEditor::windowTimeout()
222 if (Content.empty())
223 return m_window_timeout;
224 else
225 return Screen<WindowType>::windowTimeout();
228 void PlaylistEditor::enterPressed()
230 addItemToPlaylist(true);
233 void PlaylistEditor::mouseButtonPressed(MEVENT me)
235 if (!Playlists.empty() && Playlists.hasCoords(me.x, me.y))
237 if (!isActiveWindow(Playlists))
239 if (previousColumnAvailable())
240 previousColumn();
241 else
242 return;
244 if (size_t(me.y) < Playlists.size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
246 Playlists.Goto(me.y);
247 if (me.bstate & BUTTON3_PRESSED)
248 addItemToPlaylist();
250 else
251 Screen<WindowType>::mouseButtonPressed(me);
252 Content.clear();
254 else if (!Content.empty() && Content.hasCoords(me.x, me.y))
256 if (!isActiveWindow(Content))
258 if (nextColumnAvailable())
259 nextColumn();
260 else
261 return;
263 if (size_t(me.y) < Content.size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
265 Content.Goto(me.y);
266 if (me.bstate & BUTTON1_PRESSED)
267 addItemToPlaylist();
268 else
269 enterPressed();
271 else
272 Screen<WindowType>::mouseButtonPressed(me);
276 /***********************************************************************/
278 bool PlaylistEditor::allowsSearching()
280 return true;
283 void PlaylistEditor::setSearchConstraint(const std::string &constraint)
285 if (isActiveWindow(Playlists))
287 m_playlists_search_predicate = Regex::Filter<MPD::Playlist>(
288 Regex::make(constraint, Config.regex_type),
289 PlaylistEntryMatcher
292 else if (isActiveWindow(Content))
294 m_content_search_predicate = Regex::Filter<MPD::Song>(
295 Regex::make(constraint, Config.regex_type),
296 SongEntryMatcher
301 void PlaylistEditor::clearConstraint()
303 if (isActiveWindow(Playlists))
304 m_playlists_search_predicate.clear();
305 else if (isActiveWindow(Content))
306 m_content_search_predicate.clear();
309 bool PlaylistEditor::find(SearchDirection direction, bool wrap, bool skip_current)
311 bool result = false;
312 if (isActiveWindow(Playlists))
313 result = search(Playlists, m_playlists_search_predicate, direction, wrap, skip_current);
314 else if (isActiveWindow(Content))
315 result = search(Content, m_content_search_predicate, direction, wrap, skip_current);
316 return result;
319 /***********************************************************************/
321 bool PlaylistEditor::addItemToPlaylist()
323 return addItemToPlaylist(false);
326 std::vector<MPD::Song> PlaylistEditor::getSelectedSongs()
328 std::vector<MPD::Song> result;
329 if (isActiveWindow(Playlists))
331 bool any_selected = false;
332 for (auto &e : Playlists)
334 if (e.isSelected())
336 any_selected = true;
337 std::copy(
338 std::make_move_iterator(Mpd.GetPlaylistContent(e.value().path())),
339 std::make_move_iterator(MPD::SongIterator()),
340 std::back_inserter(result)
344 // if no item is selected, add songs from right column
345 if (!any_selected && !Playlists.empty())
347 std::copy(
348 std::make_move_iterator(Mpd.GetPlaylistContent(Playlists.current()->value().path())),
349 std::make_move_iterator(MPD::SongIterator()),
350 std::back_inserter(result)
354 else if (isActiveWindow(Content))
355 result = Content.getSelectedSongs();
356 return result;
359 /***********************************************************************/
361 bool PlaylistEditor::previousColumnAvailable()
363 if (isActiveWindow(Content))
365 if (!Playlists.empty())
366 return true;
368 return false;
371 void PlaylistEditor::previousColumn()
373 if (isActiveWindow(Content))
375 Content.setHighlightColor(Config.main_highlight_color);
376 w->refresh();
377 w = &Playlists;
378 Playlists.setHighlightColor(Config.active_column_color);
382 bool PlaylistEditor::nextColumnAvailable()
384 if (isActiveWindow(Playlists))
386 if (!Content.empty())
387 return true;
389 return false;
392 void PlaylistEditor::nextColumn()
394 if (isActiveWindow(Playlists))
396 Playlists.setHighlightColor(Config.main_highlight_color);
397 w->refresh();
398 w = &Content;
399 Content.setHighlightColor(Config.active_column_color);
403 /***********************************************************************/
405 void PlaylistEditor::updateTimer()
407 m_timer = Global::Timer;
410 void PlaylistEditor::Locate(const MPD::Playlist &playlist)
412 update();
413 auto begin = Playlists.beginV(), end = Playlists.endV();
414 auto it = std::find(begin, end, playlist);
415 if (it != end)
417 Playlists.highlight(it-begin);
418 Content.clear();
419 switchTo();
423 bool PlaylistEditor::addItemToPlaylist(bool play)
425 bool result = false;
426 if (isActiveWindow(Playlists) && !Playlists.empty())
428 std::vector<MPD::Song> list(
429 std::make_move_iterator(Mpd.GetPlaylistContent(Playlists.current()->value().path())),
430 std::make_move_iterator(MPD::SongIterator())
432 result = addSongsToPlaylist(list.begin(), list.end(), play, -1);
433 Statusbar::printf("Playlist \"%1%\" loaded%2%",
434 Playlists.current()->value().path(), withErrors(result)
437 else if (isActiveWindow(Content) && !Content.empty())
438 result = addSongToPlaylist(Content.current()->value(), play);
439 return result;
442 namespace {
444 std::string SongToString(const MPD::Song &s)
446 std::string result;
447 switch (Config.playlist_display_mode)
449 case DisplayMode::Classic:
450 result = Format::stringify<char>(Config.song_list_format, &s);
451 break;
452 case DisplayMode::Columns:
453 result = Format::stringify<char>(Config.song_columns_mode_format, &s);
454 break;
456 return result;
459 bool PlaylistEntryMatcher(const Regex::Regex &rx, const MPD::Playlist &playlist)
461 return Regex::search(playlist.path(), rx);
464 bool SongEntryMatcher(const Regex::Regex &rx, const MPD::Song &s)
466 return Regex::search(SongToString(s), rx);