actions: make JumpToPlayingSong work also if player is stopped
[ncmpcpp.git] / src / actions.cpp
blob9769d5fccb7b31194f128dc1e6c706ecbcd12a76
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 <cassert>
22 #include <cerrno>
23 #include <cstring>
24 #include <boost/array.hpp>
25 #include <boost/bind.hpp>
26 #include <boost/locale/conversion.hpp>
27 #include <boost/lexical_cast.hpp>
28 #include <algorithm>
29 #include <iostream>
30 #include <readline/readline.h>
32 #include "actions.h"
33 #include "charset.h"
34 #include "config.h"
35 #include "display.h"
36 #include "global.h"
37 #include "mpdpp.h"
38 #include "helpers.h"
39 #include "statusbar.h"
40 #include "utility/comparators.h"
41 #include "utility/conversion.h"
43 #include "bindings.h"
44 #include "browser.h"
45 #include "clock.h"
46 #include "help.h"
47 #include "media_library.h"
48 #include "lastfm.h"
49 #include "lyrics.h"
50 #include "playlist.h"
51 #include "playlist_editor.h"
52 #include "sort_playlist.h"
53 #include "search_engine.h"
54 #include "sel_items_adder.h"
55 #include "server_info.h"
56 #include "song_info.h"
57 #include "outputs.h"
58 #include "utility/string.h"
59 #include "utility/type_conversions.h"
60 #include "tag_editor.h"
61 #include "tiny_tag_editor.h"
62 #include "visualizer.h"
63 #include "title.h"
64 #include "tags.h"
66 #ifdef HAVE_TAGLIB_H
67 # include "fileref.h"
68 # include "tag.h"
69 #endif // HAVE_TAGLIB_H
71 using Global::myScreen;
73 namespace {//
75 enum class Find { Forward, Backward };
77 boost::array<
78 Actions::BaseAction *, static_cast<size_t>(Actions::Type::_numberOfActions)
79 > AvailableActions;
81 void populateActions();
83 void seek();
84 void findItem(const Find direction);
85 void listsChangeFinisher();
89 namespace Actions {//
91 bool OriginalStatusbarVisibility;
92 bool ExitMainLoop = false;
94 size_t HeaderHeight;
95 size_t FooterHeight;
96 size_t FooterStartY;
98 void validateScreenSize()
100 using Global::MainHeight;
102 if (COLS < 30 || MainHeight < 5)
104 NC::destroyScreen();
105 std::cout << "Screen is too small to handle ncmpcpp correctly\n";
106 exit(1);
110 void initializeScreens()
112 myHelp = new Help;
113 myPlaylist = new Playlist;
114 myBrowser = new Browser;
115 mySearcher = new SearchEngine;
116 myLibrary = new MediaLibrary;
117 myPlaylistEditor = new PlaylistEditor;
118 myLyrics = new Lyrics;
119 mySelectedItemsAdder = new SelectedItemsAdder;
120 mySongInfo = new SongInfo;
121 myServerInfo = new ServerInfo;
122 mySortPlaylistDialog = new SortPlaylistDialog;
124 # ifdef HAVE_CURL_CURL_H
125 myLastfm = new Lastfm;
126 # endif // HAVE_CURL_CURL_H
128 # ifdef HAVE_TAGLIB_H
129 myTinyTagEditor = new TinyTagEditor;
130 myTagEditor = new TagEditor;
131 # endif // HAVE_TAGLIB_H
133 # ifdef ENABLE_VISUALIZER
134 myVisualizer = new Visualizer;
135 # endif // ENABLE_VISUALIZER
137 # ifdef ENABLE_OUTPUTS
138 myOutputs = new Outputs;
139 # endif // ENABLE_OUTPUTS
141 # ifdef ENABLE_CLOCK
142 myClock = new Clock;
143 # endif // ENABLE_CLOCK
147 void setResizeFlags()
149 myHelp->hasToBeResized = 1;
150 myPlaylist->hasToBeResized = 1;
151 myBrowser->hasToBeResized = 1;
152 mySearcher->hasToBeResized = 1;
153 myLibrary->hasToBeResized = 1;
154 myPlaylistEditor->hasToBeResized = 1;
155 myLyrics->hasToBeResized = 1;
156 mySelectedItemsAdder->hasToBeResized = 1;
157 mySongInfo->hasToBeResized = 1;
158 myServerInfo->hasToBeResized = 1;
159 mySortPlaylistDialog->hasToBeResized = 1;
161 # ifdef HAVE_CURL_CURL_H
162 myLastfm->hasToBeResized = 1;
163 # endif // HAVE_CURL_CURL_H
165 # ifdef HAVE_TAGLIB_H
166 myTinyTagEditor->hasToBeResized = 1;
167 myTagEditor->hasToBeResized = 1;
168 # endif // HAVE_TAGLIB_H
170 # ifdef ENABLE_VISUALIZER
171 myVisualizer->hasToBeResized = 1;
172 # endif // ENABLE_VISUALIZER
174 # ifdef ENABLE_OUTPUTS
175 myOutputs->hasToBeResized = 1;
176 # endif // ENABLE_OUTPUTS
178 # ifdef ENABLE_CLOCK
179 myClock->hasToBeResized = 1;
180 # endif // ENABLE_CLOCK
183 void resizeScreen(bool reload_main_window)
185 using Global::MainHeight;
186 using Global::wHeader;
187 using Global::wFooter;
189 # if defined(USE_PDCURSES)
190 resize_term(0, 0);
191 # else
192 // update internal screen dimensions
193 if (reload_main_window)
195 rl_resize_terminal();
196 endwin();
197 refresh();
199 # endif
201 MainHeight = LINES-(Config.design == Design::Alternative ? 7 : 4);
203 validateScreenSize();
205 if (!Config.header_visibility)
206 MainHeight += 2;
207 if (!Config.statusbar_visibility)
208 ++MainHeight;
210 setResizeFlags();
212 applyToVisibleWindows(&BaseScreen::resize);
214 if (Config.header_visibility || Config.design == Design::Alternative)
215 wHeader->resize(COLS, HeaderHeight);
217 FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1);
218 wFooter->moveTo(0, FooterStartY);
219 wFooter->resize(COLS, Config.statusbar_visibility ? 2 : 1);
221 applyToVisibleWindows(&BaseScreen::refresh);
223 Status::Changes::elapsedTime(false);
224 Status::Changes::playerState();
225 // Note: routines for drawing separator if alternative user
226 // interface is active and header is hidden are placed in
227 // NcmpcppStatusChanges.StatusFlags
228 Status::Changes::flags();
229 drawHeader();
230 wFooter->refresh();
231 refresh();
234 void setWindowsDimensions()
236 using Global::MainStartY;
237 using Global::MainHeight;
239 MainStartY = Config.design == Design::Alternative ? 5 : 2;
240 MainHeight = LINES-(Config.design == Design::Alternative ? 7 : 4);
242 if (!Config.header_visibility)
244 MainStartY -= 2;
245 MainHeight += 2;
247 if (!Config.statusbar_visibility)
248 ++MainHeight;
250 HeaderHeight = Config.design == Design::Alternative ? (Config.header_visibility ? 5 : 3) : 1;
251 FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1);
252 FooterHeight = Config.statusbar_visibility ? 2 : 1;
255 bool askYesNoQuestion(const boost::format &fmt, void (*callback)())
257 using Global::wFooter;
259 Statusbar::lock();
260 Statusbar::put() << fmt.str() << " [" << NC::Format::Bold << 'y' << NC::Format::NoBold << '/' << NC::Format::Bold << 'n' << NC::Format::NoBold << "]";
261 wFooter->refresh();
262 int answer = 0;
265 if (callback)
266 callback();
267 answer = wFooter->readKey();
269 while (answer != 'y' && answer != 'n');
270 Statusbar::unlock();
271 return answer == 'y';
274 bool isMPDMusicDirSet()
276 if (Config.mpd_music_dir.empty())
278 Statusbar::print("Proper mpd_music_dir variable has to be set in configuration file");
279 return false;
281 return true;
284 BaseAction &get(Actions::Type at)
286 if (AvailableActions[1] == nullptr)
287 populateActions();
288 BaseAction *action = AvailableActions[static_cast<size_t>(at)];
289 // action should be always present if action type in queried
290 assert(action != nullptr);
291 return *action;
294 BaseAction *get(const std::string &name)
296 BaseAction *result = 0;
297 if (AvailableActions[1] == nullptr)
298 populateActions();
299 for (auto it = AvailableActions.begin(); it != AvailableActions.end(); ++it)
301 if (*it != nullptr && (*it)->name() == name)
303 result = *it;
304 break;
307 return result;
310 bool MouseEvent::canBeRun() const
312 return Config.mouse_support;
315 void MouseEvent::run()
317 using Global::VolumeState;
319 m_old_mouse_event = m_mouse_event;
320 getmouse(&m_mouse_event);
321 // workaround shitty ncurses behavior introduced in >=5.8, when we mysteriously get
322 // a few times after ncmpcpp startup 2^27 code instead of BUTTON{1,3}_RELEASED. since that
323 // 2^27 thing shows constantly instead of BUTTON2_PRESSED, it was redefined to be recognized
324 // as BUTTON2_PRESSED. but clearly we don't want to trigger behavior bound to BUTTON2
325 // after BUTTON{1,3} was pressed. so, here is the workaround: if last event was BUTTON{1,3}_PRESSED,
326 // we MUST get BUTTON{1,3}_RELEASED afterwards. if we get BUTTON2_PRESSED, erroneus behavior
327 // is about to occur and we need to prevent that.
328 if (m_old_mouse_event.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED) && m_mouse_event.bstate & BUTTON2_PRESSED)
329 return;
330 if (m_mouse_event.bstate & BUTTON1_PRESSED
331 && m_mouse_event.y == LINES-(Config.statusbar_visibility ? 2 : 1)
332 ) // progressbar
334 if (Status::State::player() == MPD::psStop)
335 return;
336 Mpd.Seek(Status::State::currentSongPosition(),
337 Status::State::totalTime()*m_mouse_event.x/double(COLS));
339 else if (m_mouse_event.bstate & BUTTON1_PRESSED
340 && (Config.statusbar_visibility || Config.design == Design::Alternative)
341 && Status::State::player() != MPD::psStop
342 && m_mouse_event.y == (Config.design == Design::Alternative ? 1 : LINES-1)
343 && m_mouse_event.x < 9
344 ) // playing/paused
346 Mpd.Toggle();
348 else if ((m_mouse_event.bstate & BUTTON2_PRESSED || m_mouse_event.bstate & BUTTON4_PRESSED)
349 && (Config.header_visibility || Config.design == Design::Alternative)
350 && m_mouse_event.y == 0 && size_t(m_mouse_event.x) > COLS-VolumeState.length()
351 ) // volume
353 if (m_mouse_event.bstate & BUTTON2_PRESSED)
354 get(Type::VolumeDown).execute();
355 else
356 get(Type::VolumeUp).execute();
358 else if (m_mouse_event.bstate & (BUTTON1_PRESSED | BUTTON2_PRESSED | BUTTON3_PRESSED | BUTTON4_PRESSED))
359 myScreen->mouseButtonPressed(m_mouse_event);
362 void ScrollUp::run()
364 myScreen->scroll(NC::Scroll::Up);
365 listsChangeFinisher();
368 void ScrollDown::run()
370 myScreen->scroll(NC::Scroll::Down);
371 listsChangeFinisher();
374 bool ScrollUpArtist::canBeRun() const
376 return proxySongList(myScreen);
379 void ScrollUpArtist::run()
381 auto pl = proxySongList(myScreen);
382 assert(pl);
383 size_t pos = pl.choice();
384 if (MPD::Song *s = pl.getSong(pos))
386 std::string artist = s->getArtist();
387 while (pos > 0)
389 s = pl.getSong(--pos);
390 if (!s || s->getArtist() != artist)
391 break;
393 pl.highlight(pos);
397 bool ScrollUpAlbum::canBeRun() const
399 return proxySongList(myScreen);
402 void ScrollUpAlbum::run()
404 auto pl = proxySongList(myScreen);
405 assert(pl);
406 size_t pos = pl.choice();
407 if (MPD::Song *s = pl.getSong(pos))
409 std::string album = s->getAlbum();
410 while (pos > 0)
412 s = pl.getSong(--pos);
413 if (!s || s->getAlbum() != album)
414 break;
416 pl.highlight(pos);
420 bool ScrollDownArtist::canBeRun() const
422 return proxySongList(myScreen);
425 void ScrollDownArtist::run()
427 auto pl = proxySongList(myScreen);
428 assert(pl);
429 size_t pos = pl.choice();
430 if (MPD::Song *s = pl.getSong(pos))
432 std::string artist = s->getArtist();
433 while (pos < pl.size() - 1)
435 s = pl.getSong(++pos);
436 if (!s || s->getArtist() != artist)
437 break;
439 pl.highlight(pos);
443 bool ScrollDownAlbum::canBeRun() const
445 return proxySongList(myScreen);
448 void ScrollDownAlbum::run()
450 auto pl = proxySongList(myScreen);
451 assert(pl);
452 size_t pos = pl.choice();
453 if (MPD::Song *s = pl.getSong(pos))
455 std::string album = s->getAlbum();
456 while (pos < pl.size() - 1)
458 s = pl.getSong(++pos);
459 if (!s || s->getAlbum() != album)
460 break;
462 pl.highlight(pos);
466 void PageUp::run()
468 myScreen->scroll(NC::Scroll::PageUp);
469 listsChangeFinisher();
472 void PageDown::run()
474 myScreen->scroll(NC::Scroll::PageDown);
475 listsChangeFinisher();
478 void MoveHome::run()
480 myScreen->scroll(NC::Scroll::Home);
481 listsChangeFinisher();
484 void MoveEnd::run()
486 myScreen->scroll(NC::Scroll::End);
487 listsChangeFinisher();
490 void ToggleInterface::run()
492 switch (Config.design)
494 case Design::Classic:
495 Config.design = Design::Alternative;
496 Config.statusbar_visibility = false;
497 break;
498 case Design::Alternative:
499 Config.design = Design::Classic;
500 Config.statusbar_visibility = OriginalStatusbarVisibility;
501 break;
503 setWindowsDimensions();
504 Progressbar::unlock();
505 Statusbar::unlock();
506 resizeScreen(false);
507 Status::Changes::mixer();
508 Status::Changes::elapsedTime(false);
509 Statusbar::printf("User interface: %1%", Config.design);
512 bool JumpToParentDirectory::canBeRun() const
514 return (myScreen == myBrowser)
515 # ifdef HAVE_TAGLIB_H
516 || (myScreen->activeWindow() == myTagEditor->Dirs)
517 # endif // HAVE_TAGLIB_H
521 void JumpToParentDirectory::run()
523 if (myScreen == myBrowser)
525 if (myBrowser->CurrentDir() != "/")
527 myBrowser->main().reset();
528 myBrowser->enterPressed();
531 # ifdef HAVE_TAGLIB_H
532 else if (myScreen == myTagEditor)
534 if (myTagEditor->CurrentDir() != "/")
536 myTagEditor->Dirs->reset();
537 myTagEditor->enterPressed();
540 # endif // HAVE_TAGLIB_H
543 void PressEnter::run()
545 myScreen->enterPressed();
548 void PressSpace::run()
550 myScreen->spacePressed();
553 bool PreviousColumn::canBeRun() const
555 auto hc = hasColumns(myScreen);
556 return hc && hc->previousColumnAvailable();
559 void PreviousColumn::run()
561 hasColumns(myScreen)->previousColumn();
564 bool NextColumn::canBeRun() const
566 auto hc = hasColumns(myScreen);
567 return hc && hc->nextColumnAvailable();
570 void NextColumn::run()
572 hasColumns(myScreen)->nextColumn();
575 bool MasterScreen::canBeRun() const
577 using Global::myLockedScreen;
578 using Global::myInactiveScreen;
580 return myLockedScreen
581 && myInactiveScreen
582 && myLockedScreen != myScreen
583 && myScreen->isMergable();
586 void MasterScreen::run()
588 using Global::myInactiveScreen;
589 using Global::myLockedScreen;
591 myInactiveScreen = myScreen;
592 myScreen = myLockedScreen;
593 drawHeader();
596 bool SlaveScreen::canBeRun() const
598 using Global::myLockedScreen;
599 using Global::myInactiveScreen;
601 return myLockedScreen
602 && myInactiveScreen
603 && myLockedScreen == myScreen
604 && myScreen->isMergable();
607 void SlaveScreen::run()
609 using Global::myInactiveScreen;
610 using Global::myLockedScreen;
612 myScreen = myInactiveScreen;
613 myInactiveScreen = myLockedScreen;
614 drawHeader();
617 void VolumeUp::run()
619 int volume = std::min(Status::State::volume()+Config.volume_change_step, 100u);
620 Mpd.SetVolume(volume);
623 void VolumeDown::run()
625 int volume = std::max(int(Status::State::volume()-Config.volume_change_step), 0);
626 Mpd.SetVolume(volume);
629 bool DeletePlaylistItems::canBeRun() const
631 return (myScreen == myPlaylist && !myPlaylist->main().empty())
632 || (myScreen->isActiveWindow(myPlaylistEditor->Content) && !myPlaylistEditor->Content.empty());
635 void DeletePlaylistItems::run()
637 if (myScreen == myPlaylist)
639 Statusbar::print("Deleting items...");
640 auto delete_fun = boost::bind(&MPD::Connection::Delete, _1, _2);
641 deleteSelectedSongs(myPlaylist->main(), delete_fun);
642 Statusbar::print("Item(s) deleted");
644 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
646 std::string playlist = myPlaylistEditor->Playlists.current().value();
647 auto delete_fun = boost::bind(&MPD::Connection::PlaylistDelete, _1, playlist, _2);
648 Statusbar::print("Deleting items...");
649 deleteSelectedSongs(myPlaylistEditor->Content, delete_fun);
650 Statusbar::print("Item(s) deleted");
654 bool DeleteBrowserItems::canBeRun() const
656 auto check_if_deletion_allowed = []() {
657 if (Config.allow_for_physical_item_deletion)
658 return true;
659 else
661 Statusbar::print("Flag \"allow_for_physical_item_deletion\" needs to be enabled in configuration file");
662 return false;
665 return myScreen == myBrowser
666 && !myBrowser->main().empty()
667 && isMPDMusicDirSet()
668 && check_if_deletion_allowed();
671 void DeleteBrowserItems::run()
673 boost::format question;
674 if (hasSelected(myBrowser->main().begin(), myBrowser->main().end()))
675 question = boost::format("Delete selected items?");
676 else
678 MPD::Item &item = myBrowser->main().current().value();
679 std::string iname = item.type == MPD::itSong ? item.song->getName() : item.name;
680 question = boost::format("Delete %1% \"%2%\"?")
681 % itemTypeToString(item.type) % wideShorten(iname, COLS-question.size()-10);
683 bool yes = askYesNoQuestion(question, Status::trace);
684 if (yes)
686 bool success = true;
687 auto list = getSelectedOrCurrent(myBrowser->main().begin(), myBrowser->main().end(), myBrowser->main().currentI());
688 for (auto it = list.begin(); it != list.end(); ++it)
690 const MPD::Item &i = (*it)->value();
691 std::string iname = i.type == MPD::itSong ? i.song->getName() : i.name;
692 std::string errmsg;
693 if (myBrowser->deleteItem(i, errmsg))
695 const char msg[] = "\"%1%\" deleted";
696 Statusbar::printf(msg, wideShorten(iname, COLS-const_strlen(msg)));
698 else
700 Statusbar::print(errmsg);
701 success = false;
702 break;
705 if (success)
707 if (myBrowser->isLocal())
708 myBrowser->GetDirectory(myBrowser->CurrentDir());
709 else
710 Mpd.UpdateDirectory(myBrowser->CurrentDir());
713 else
714 Statusbar::print("Aborted");
717 bool DeleteStoredPlaylist::canBeRun() const
719 return myScreen->isActiveWindow(myPlaylistEditor->Playlists)
720 && myPlaylistEditor->Playlists.empty();
723 void DeleteStoredPlaylist::run()
725 boost::format question;
726 if (hasSelected(myPlaylistEditor->Playlists.begin(), myPlaylistEditor->Playlists.end()))
727 question = boost::format("Delete selected playlists?");
728 else
729 question = boost::format("Delete playlist \"%1%\"?")
730 % wideShorten(myPlaylistEditor->Playlists.current().value(), COLS-question.size()-10);
731 bool yes = askYesNoQuestion(question, Status::trace);
732 if (yes)
734 auto list = getSelectedOrCurrent(myPlaylistEditor->Playlists.begin(), myPlaylistEditor->Playlists.end(), myPlaylistEditor->Playlists.currentI());
735 Mpd.StartCommandsList();
736 for (auto it = list.begin(); it != list.end(); ++it)
737 Mpd.DeletePlaylist((*it)->value());
738 Mpd.CommitCommandsList();
739 Statusbar::printf("%1% deleted", list.size() == 1 ? "Playlist" : "Playlists");
741 else
742 Statusbar::print("Aborted");
745 void ReplaySong::run()
747 if (Status::State::player() != MPD::psStop)
748 Mpd.Seek(Status::State::currentSongPosition(), 0);
751 void PreviousSong::run()
753 Mpd.Prev();
756 void NextSong::run()
758 Mpd.Next();
761 void Pause::run()
763 Mpd.Toggle();
766 void SavePlaylist::run()
768 using Global::wFooter;
770 Statusbar::lock();
771 Statusbar::put() << "Save playlist as: ";
772 std::string playlist_name = wFooter->getString();
773 Statusbar::unlock();
774 if (playlist_name.find("/") != std::string::npos)
776 Statusbar::print("Playlist name must not contain slashes");
777 return;
779 if (!playlist_name.empty())
781 if (myPlaylist->main().isFiltered())
783 Mpd.StartCommandsList();
784 for (size_t i = 0; i < myPlaylist->main().size(); ++i)
785 Mpd.AddToPlaylist(playlist_name, myPlaylist->main()[i].value());
786 Mpd.CommitCommandsList();
787 Statusbar::printf("Filtered items added to playlist \"%1%\"", playlist_name);
789 else
793 Mpd.SavePlaylist(playlist_name);
794 Statusbar::printf("Playlist saved as \"%1%\"", playlist_name);
796 catch (MPD::ServerError &e)
798 if (e.code() == MPD_SERVER_ERROR_EXIST)
800 bool yes = askYesNoQuestion(
801 boost::format("Playlist \"%1%\" already exists, overwrite?") % playlist_name,
802 Status::trace
804 if (yes)
806 Mpd.DeletePlaylist(playlist_name);
807 Mpd.SavePlaylist(playlist_name);
808 Statusbar::print("Playlist overwritten");
810 else
811 Statusbar::print("Aborted");
812 if (myScreen == myPlaylist)
813 myPlaylist->EnableHighlighting();
815 else
816 throw e;
820 if (!myBrowser->isLocal()
821 && myBrowser->CurrentDir() == "/"
822 && !myBrowser->main().empty())
823 myBrowser->GetDirectory(myBrowser->CurrentDir());
826 void Stop::run()
828 Mpd.Stop();
831 void ExecuteCommand::run()
833 using Global::wFooter;
834 Statusbar::lock();
835 Statusbar::put() << NC::Format::Bold << ":" << NC::Format::NoBold;
836 wFooter->setGetStringHelper(Statusbar::Helpers::TryExecuteImmediateCommand());
837 std::string cmd_name = wFooter->getString();
838 wFooter->setGetStringHelper(Statusbar::Helpers::getString);
839 Statusbar::unlock();
840 if (cmd_name.empty())
841 return;
842 auto cmd = Bindings.findCommand(cmd_name);
843 if (cmd)
845 Statusbar::printf(1, "Executing %1%...", cmd_name);
846 bool res = cmd->binding().execute();
847 Statusbar::printf("Execution of command \"%1%\" %2%.",
848 cmd_name, res ? "successful" : "unsuccessful"
851 else
852 Statusbar::printf("No command named \"%1%\"", cmd_name);
855 bool MoveSortOrderUp::canBeRun() const
857 return myScreen == mySortPlaylistDialog;
860 void MoveSortOrderUp::run()
862 mySortPlaylistDialog->moveSortOrderUp();
865 bool MoveSortOrderDown::canBeRun() const
867 return myScreen == mySortPlaylistDialog;
870 void MoveSortOrderDown::run()
872 mySortPlaylistDialog->moveSortOrderDown();
875 bool MoveSelectedItemsUp::canBeRun() const
877 return ((myScreen == myPlaylist
878 && !myPlaylist->main().empty()
879 && !myPlaylist->isFiltered())
880 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
881 && !myPlaylistEditor->Content.empty()
882 && !myPlaylistEditor->isContentFiltered()));
885 void MoveSelectedItemsUp::run()
887 if (myScreen == myPlaylist)
889 moveSelectedItemsUp(myPlaylist->main(), boost::bind(&MPD::Connection::Move, _1, _2, _3));
891 else if (myScreen == myPlaylistEditor)
893 assert(!myPlaylistEditor->Playlists.empty());
894 std::string playlist = myPlaylistEditor->Playlists.current().value();
895 auto move_fun = boost::bind(&MPD::Connection::PlaylistMove, _1, playlist, _2, _3);
896 moveSelectedItemsUp(myPlaylistEditor->Content, move_fun);
900 bool MoveSelectedItemsDown::canBeRun() const
902 return ((myScreen == myPlaylist
903 && !myPlaylist->main().empty()
904 && !myPlaylist->isFiltered())
905 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
906 && !myPlaylistEditor->Content.empty()
907 && !myPlaylistEditor->isContentFiltered()));
910 void MoveSelectedItemsDown::run()
912 if (myScreen == myPlaylist)
914 moveSelectedItemsDown(myPlaylist->main(), boost::bind(&MPD::Connection::Move, _1, _2, _3));
916 else if (myScreen == myPlaylistEditor)
918 assert(!myPlaylistEditor->Playlists.empty());
919 std::string playlist = myPlaylistEditor->Playlists.current().value();
920 auto move_fun = boost::bind(&MPD::Connection::PlaylistMove, _1, playlist, _2, _3);
921 moveSelectedItemsDown(myPlaylistEditor->Content, move_fun);
925 bool MoveSelectedItemsTo::canBeRun() const
927 return myScreen == myPlaylist
928 || myScreen->isActiveWindow(myPlaylistEditor->Content);
931 void MoveSelectedItemsTo::run()
933 if (myScreen == myPlaylist)
934 moveSelectedItemsTo(myPlaylist->main(), boost::bind(&MPD::Connection::Move, _1, _2, _3));
935 else
937 assert(!myPlaylistEditor->Playlists.empty());
938 std::string playlist = myPlaylistEditor->Playlists.current().value();
939 auto move_fun = boost::bind(&MPD::Connection::PlaylistMove, _1, playlist, _2, _3);
940 moveSelectedItemsTo(myPlaylistEditor->Content, move_fun);
944 bool Add::canBeRun() const
946 return myScreen != myPlaylistEditor
947 || !myPlaylistEditor->Playlists.empty();
950 void Add::run()
952 using Global::wFooter;
954 Statusbar::lock();
955 Statusbar::put() << (myScreen == myPlaylistEditor ? "Add to playlist: " : "Add: ");
956 std::string path = wFooter->getString();
957 Statusbar::unlock();
958 if (!path.empty())
960 Statusbar::put() << "Adding...";
961 wFooter->refresh();
962 if (myScreen == myPlaylistEditor)
963 Mpd.AddToPlaylist(myPlaylistEditor->Playlists.current().value(), path);
964 else
966 const char lastfm_url[] = "lastfm://";
967 if (path.compare(0, const_strlen(lastfm_url), lastfm_url) == 0
968 || path.find(".asx", path.length()-4) != std::string::npos
969 || path.find(".cue", path.length()-4) != std::string::npos
970 || path.find(".m3u", path.length()-4) != std::string::npos
971 || path.find(".pls", path.length()-4) != std::string::npos
972 || path.find(".xspf", path.length()-5) != std::string::npos)
973 Mpd.LoadPlaylist(path);
974 else
975 Mpd.Add(path);
980 bool SeekForward::canBeRun() const
982 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
985 void SeekForward::run()
987 seek();
990 bool SeekBackward::canBeRun() const
992 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
995 void SeekBackward::run()
997 seek();
1000 bool ToggleDisplayMode::canBeRun() const
1002 return myScreen == myPlaylist
1003 || myScreen == myBrowser
1004 || myScreen == mySearcher
1005 || myScreen->isActiveWindow(myPlaylistEditor->Content);
1008 void ToggleDisplayMode::run()
1010 if (myScreen == myPlaylist)
1012 switch (Config.playlist_display_mode)
1014 case DisplayMode::Classic:
1015 Config.playlist_display_mode = DisplayMode::Columns;
1016 myPlaylist->main().setItemDisplayer(boost::bind(
1017 Display::SongsInColumns, _1, myPlaylist->proxySongList()
1019 if (Config.titles_visibility)
1020 myPlaylist->main().setTitle(Display::Columns(myPlaylist->main().getWidth()));
1021 else
1022 myPlaylist->main().setTitle("");
1023 break;
1024 case DisplayMode::Columns:
1025 Config.playlist_display_mode = DisplayMode::Classic;
1026 myPlaylist->main().setItemDisplayer(boost::bind(
1027 Display::Songs, _1, myPlaylist->proxySongList(), Config.song_list_format
1029 myPlaylist->main().setTitle("");
1031 Statusbar::printf("Playlist display mode: %1%", Config.playlist_display_mode);
1033 else if (myScreen == myBrowser)
1035 switch (Config.browser_display_mode)
1037 case DisplayMode::Classic:
1038 Config.browser_display_mode = DisplayMode::Columns;
1039 if (Config.titles_visibility)
1040 myBrowser->main().setTitle(Display::Columns(myBrowser->main().getWidth()));
1041 else
1042 myBrowser->main().setTitle("");
1043 break;
1044 case DisplayMode::Columns:
1045 Config.browser_display_mode = DisplayMode::Classic;
1046 myBrowser->main().setTitle("");
1047 break;
1049 Statusbar::printf("Browser display mode: %1%", Config.browser_display_mode);
1051 else if (myScreen == mySearcher)
1053 switch (Config.search_engine_display_mode)
1055 case DisplayMode::Classic:
1056 Config.search_engine_display_mode = DisplayMode::Columns;
1057 break;
1058 case DisplayMode::Columns:
1059 Config.search_engine_display_mode = DisplayMode::Classic;
1060 break;
1062 Statusbar::printf("Search engine display mode: %1%", Config.search_engine_display_mode);
1063 if (mySearcher->main().size() > SearchEngine::StaticOptions)
1064 mySearcher->main().setTitle(
1065 Config.search_engine_display_mode == DisplayMode::Columns
1066 && Config.titles_visibility
1067 ? Display::Columns(mySearcher->main().getWidth())
1068 : ""
1071 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
1073 switch (Config.playlist_editor_display_mode)
1075 case DisplayMode::Classic:
1076 Config.playlist_editor_display_mode = DisplayMode::Columns;
1077 myPlaylistEditor->Content.setItemDisplayer(boost::bind(
1078 Display::SongsInColumns, _1, myPlaylistEditor->contentProxyList()
1080 break;
1081 case DisplayMode::Columns:
1082 Config.playlist_editor_display_mode = DisplayMode::Classic;
1083 myPlaylistEditor->Content.setItemDisplayer(boost::bind(
1084 Display::Songs, _1, myPlaylistEditor->contentProxyList(), Config.song_list_format
1086 break;
1088 Statusbar::printf("Playlist editor display mode: %1%", Config.playlist_editor_display_mode);
1092 bool ToggleSeparatorsBetweenAlbums::canBeRun() const
1094 return true;
1097 void ToggleSeparatorsBetweenAlbums::run()
1099 Config.playlist_separate_albums = !Config.playlist_separate_albums;
1100 Statusbar::printf("Separators between albums: %1%",
1101 Config.playlist_separate_albums ? "on" : "off"
1105 #ifndef HAVE_CURL_CURL_H
1106 bool ToggleLyricsFetcher::canBeRun() const
1108 return false;
1110 #endif // NOT HAVE_CURL_CURL_H
1112 void ToggleLyricsFetcher::run()
1114 # ifdef HAVE_CURL_CURL_H
1115 myLyrics->ToggleFetcher();
1116 # endif // HAVE_CURL_CURL_H
1119 #ifndef HAVE_CURL_CURL_H
1120 bool ToggleFetchingLyricsInBackground::canBeRun() const
1122 return false;
1124 #endif // NOT HAVE_CURL_CURL_H
1126 void ToggleFetchingLyricsInBackground::run()
1128 # ifdef HAVE_CURL_CURL_H
1129 Config.fetch_lyrics_in_background = !Config.fetch_lyrics_in_background;
1130 Statusbar::printf("Fetching lyrics for playing songs in background: %1%",
1131 Config.fetch_lyrics_in_background ? "on" : "off"
1133 # endif // HAVE_CURL_CURL_H
1136 void TogglePlayingSongCentering::run()
1138 Config.autocenter_mode = !Config.autocenter_mode;
1139 Statusbar::printf("Centering playing song: %1%",
1140 Config.autocenter_mode ? "on" : "off"
1142 if (Config.autocenter_mode
1143 && Status::State::player() != MPD::psUnknown
1144 && !myPlaylist->main().isFiltered())
1145 myPlaylist->main().highlight(Status::State::currentSongPosition());
1148 void UpdateDatabase::run()
1150 if (myScreen == myBrowser)
1151 Mpd.UpdateDirectory(myBrowser->CurrentDir());
1152 # ifdef HAVE_TAGLIB_H
1153 else if (myScreen == myTagEditor)
1154 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1155 # endif // HAVE_TAGLIB_H
1156 else
1157 Mpd.UpdateDirectory("/");
1160 bool JumpToPlayingSong::canBeRun() const
1162 return ((myScreen == myPlaylist && !myPlaylist->isFiltered())
1163 || myScreen == myBrowser
1164 || myScreen == myLibrary)
1165 && Status::State::player() != MPD::psUnknown;
1168 void JumpToPlayingSong::run()
1170 if (myScreen == myPlaylist)
1171 myPlaylist->main().highlight(Status::State::currentSongPosition());
1172 else if (myScreen == myBrowser)
1174 myBrowser->LocateSong(myPlaylist->nowPlayingSong());
1175 drawHeader();
1177 else if (myScreen == myLibrary)
1179 myLibrary->LocateSong(myPlaylist->nowPlayingSong());
1183 void ToggleRepeat::run()
1185 Mpd.SetRepeat(!Status::State::repeat());
1188 void Shuffle::run()
1190 Mpd.Shuffle();
1193 void ToggleRandom::run()
1195 Mpd.SetRandom(!Status::State::random());
1198 bool StartSearching::canBeRun() const
1200 return myScreen == mySearcher && !mySearcher->main()[0].isInactive();
1203 void StartSearching::run()
1205 mySearcher->main().highlight(SearchEngine::SearchButton);
1206 mySearcher->main().setHighlighting(0);
1207 mySearcher->main().refresh();
1208 mySearcher->main().setHighlighting(1);
1209 mySearcher->enterPressed();
1212 bool SaveTagChanges::canBeRun() const
1214 # ifdef HAVE_TAGLIB_H
1215 return myScreen == myTinyTagEditor
1216 || myScreen->activeWindow() == myTagEditor->TagTypes;
1217 # else
1218 return false;
1219 # endif // HAVE_TAGLIB_H
1222 void SaveTagChanges::run()
1224 # ifdef HAVE_TAGLIB_H
1225 if (myScreen == myTinyTagEditor)
1227 myTinyTagEditor->main().highlight(myTinyTagEditor->main().size()-2); // Save
1228 myTinyTagEditor->enterPressed();
1230 else if (myScreen->activeWindow() == myTagEditor->TagTypes)
1232 myTagEditor->TagTypes->highlight(myTagEditor->TagTypes->size()-1); // Save
1233 myTagEditor->enterPressed();
1235 # endif // HAVE_TAGLIB_H
1238 void ToggleSingle::run()
1240 Mpd.SetSingle(!Status::State::single());
1243 void ToggleConsume::run()
1245 Mpd.SetConsume(!Status::State::consume());
1248 void ToggleCrossfade::run()
1250 Mpd.SetCrossfade(Status::State::crossfade() ? 0 : Config.crossfade_time);
1253 void SetCrossfade::run()
1255 using Global::wFooter;
1257 Statusbar::lock();
1258 Statusbar::put() << "Set crossfade to: ";
1259 std::string crossfade = wFooter->getString();
1260 Statusbar::unlock();
1261 int cf = fromString<unsigned>(crossfade);
1262 lowerBoundCheck(cf, 1);
1263 Config.crossfade_time = cf;
1264 Mpd.SetCrossfade(cf);
1267 void SetVolume::run()
1269 using Global::wFooter;
1271 Statusbar::lock();
1272 Statusbar::put() << "Set volume to: ";
1273 std::string strvolume = wFooter->getString();
1274 Statusbar::unlock();
1275 int volume = fromString<unsigned>(strvolume);
1276 boundsCheck(volume, 0, 100);
1277 Mpd.SetVolume(volume);
1278 Statusbar::printf("Volume set to %1%%%", volume);
1281 bool EditSong::canBeRun() const
1283 # ifdef HAVE_TAGLIB_H
1284 return currentSong(myScreen)
1285 && isMPDMusicDirSet();
1286 # else
1287 return false;
1288 # endif // HAVE_TAGLIB_H
1291 void EditSong::run()
1293 # ifdef HAVE_TAGLIB_H
1294 auto s = currentSong(myScreen);
1295 myTinyTagEditor->SetEdited(*s);
1296 myTinyTagEditor->switchTo();
1297 # endif // HAVE_TAGLIB_H
1300 bool EditLibraryTag::canBeRun() const
1302 # ifdef HAVE_TAGLIB_H
1303 return myScreen->isActiveWindow(myLibrary->Tags)
1304 && !myLibrary->Tags.empty()
1305 && isMPDMusicDirSet();
1306 # else
1307 return false;
1308 # endif // HAVE_TAGLIB_H
1311 void EditLibraryTag::run()
1313 # ifdef HAVE_TAGLIB_H
1314 using Global::wFooter;
1316 Statusbar::lock();
1317 Statusbar::put() << NC::Format::Bold << tagTypeToString(Config.media_lib_primary_tag) << NC::Format::NoBold << ": ";
1318 std::string new_tag = wFooter->getString(myLibrary->Tags.current().value().tag());
1319 Statusbar::unlock();
1320 if (!new_tag.empty() && new_tag != myLibrary->Tags.current().value().tag())
1322 Statusbar::print("Updating tags...");
1323 Mpd.StartSearch(1);
1324 Mpd.AddSearch(Config.media_lib_primary_tag, myLibrary->Tags.current().value().tag());
1325 MPD::MutableSong::SetFunction set = tagTypeToSetFunction(Config.media_lib_primary_tag);
1326 assert(set);
1327 bool success = true;
1328 std::string dir_to_update;
1329 Mpd.CommitSearchSongs([set, &dir_to_update, &new_tag, &success](MPD::Song s) {
1330 if (!success)
1331 return;
1332 MPD::MutableSong ms = s;
1333 ms.setTags(set, new_tag, Config.tags_separator);
1334 Statusbar::printf("Updating tags in \"%1%\"...", ms.getName());
1335 std::string path = Config.mpd_music_dir + ms.getURI();
1336 if (!Tags::write(ms))
1338 const char msg[] = "Error while updating tags in \"%1%\"";
1339 Statusbar::printf(msg, wideShorten(ms.getURI(), COLS-const_strlen(msg)));
1340 success = false;
1342 if (dir_to_update.empty())
1343 dir_to_update = s.getURI();
1344 else
1345 dir_to_update = getSharedDirectory(dir_to_update, s.getURI());
1347 if (success)
1349 Mpd.UpdateDirectory(dir_to_update);
1350 Statusbar::print("Tags updated successfully");
1353 # endif // HAVE_TAGLIB_H
1356 bool EditLibraryAlbum::canBeRun() const
1358 # ifdef HAVE_TAGLIB_H
1359 return myScreen->isActiveWindow(myLibrary->Albums)
1360 && !myLibrary->Albums.empty()
1361 && isMPDMusicDirSet();
1362 # else
1363 return false;
1364 # endif // HAVE_TAGLIB_H
1367 void EditLibraryAlbum::run()
1369 # ifdef HAVE_TAGLIB_H
1370 using Global::wFooter;
1372 Statusbar::lock();
1373 Statusbar::put() << NC::Format::Bold << "Album: " << NC::Format::NoBold;
1374 std::string new_album = wFooter->getString(myLibrary->Albums.current().value().entry().album());
1375 Statusbar::unlock();
1376 if (!new_album.empty() && new_album != myLibrary->Albums.current().value().entry().album())
1378 bool success = 1;
1379 Statusbar::print("Updating tags...");
1380 for (size_t i = 0; i < myLibrary->Songs.size(); ++i)
1382 Statusbar::printf("Updating tags in \"%1%\"...", myLibrary->Songs[i].value().getName());
1383 std::string path = Config.mpd_music_dir + myLibrary->Songs[i].value().getURI();
1384 TagLib::FileRef f(path.c_str());
1385 if (f.isNull())
1387 const char msg[] = "Error while opening file \"%1%\"";
1388 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1389 success = 0;
1390 break;
1392 f.tag()->setAlbum(ToWString(new_album));
1393 if (!f.save())
1395 const char msg[] = "Error while writing tags in \"%1%\"";
1396 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1397 success = 0;
1398 break;
1401 if (success)
1403 Mpd.UpdateDirectory(getSharedDirectory(myLibrary->Songs.beginV(), myLibrary->Songs.endV()));
1404 Statusbar::print("Tags updated successfully");
1407 # endif // HAVE_TAGLIB_H
1410 bool EditDirectoryName::canBeRun() const
1412 return ((myScreen == myBrowser
1413 && !myBrowser->main().empty()
1414 && myBrowser->main().current().value().type == MPD::itDirectory)
1415 # ifdef HAVE_TAGLIB_H
1416 || (myScreen->activeWindow() == myTagEditor->Dirs
1417 && !myTagEditor->Dirs->empty()
1418 && myTagEditor->Dirs->choice() > 0)
1419 # endif // HAVE_TAGLIB_H
1420 ) && isMPDMusicDirSet();
1423 void EditDirectoryName::run()
1425 using Global::wFooter;
1427 if (myScreen == myBrowser)
1429 std::string old_dir = myBrowser->main().current().value().name;
1430 Statusbar::lock();
1431 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1432 std::string new_dir = wFooter->getString(old_dir);
1433 Statusbar::unlock();
1434 if (!new_dir.empty() && new_dir != old_dir)
1436 std::string full_old_dir;
1437 if (!myBrowser->isLocal())
1438 full_old_dir += Config.mpd_music_dir;
1439 full_old_dir += old_dir;
1440 std::string full_new_dir;
1441 if (!myBrowser->isLocal())
1442 full_new_dir += Config.mpd_music_dir;
1443 full_new_dir += new_dir;
1444 int rename_result = rename(full_old_dir.c_str(), full_new_dir.c_str());
1445 if (rename_result == 0)
1447 const char msg[] = "Directory renamed to \"%1%\"";
1448 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1449 if (!myBrowser->isLocal())
1450 Mpd.UpdateDirectory(getSharedDirectory(old_dir, new_dir));
1451 myBrowser->GetDirectory(myBrowser->CurrentDir());
1453 else
1455 const char msg[] = "Couldn't rename \"%1%\": %s";
1456 Statusbar::printf(msg, wideShorten(old_dir, COLS-const_strlen(msg)-25), strerror(errno));
1460 # ifdef HAVE_TAGLIB_H
1461 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1463 std::string old_dir = myTagEditor->Dirs->current().value().first;
1464 Statusbar::lock();
1465 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1466 std::string new_dir = wFooter->getString(old_dir);
1467 Statusbar::unlock();
1468 if (!new_dir.empty() && new_dir != old_dir)
1470 std::string full_old_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + old_dir;
1471 std::string full_new_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + new_dir;
1472 if (rename(full_old_dir.c_str(), full_new_dir.c_str()) == 0)
1474 const char msg[] = "Directory renamed to \"%1%\"";
1475 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1476 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1478 else
1480 const char msg[] = "Couldn't rename \"%1%\": %2%";
1481 Statusbar::printf(msg, wideShorten(old_dir, COLS-const_strlen(msg)-25), strerror(errno));
1485 # endif // HAVE_TAGLIB_H
1488 bool EditPlaylistName::canBeRun() const
1490 return (myScreen->isActiveWindow(myPlaylistEditor->Playlists)
1491 && !myPlaylistEditor->Playlists.empty())
1492 || (myScreen == myBrowser
1493 && !myBrowser->main().empty()
1494 && myBrowser->main().current().value().type == MPD::itPlaylist);
1497 void EditPlaylistName::run()
1499 using Global::wFooter;
1501 std::string old_name;
1502 if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
1503 old_name = myPlaylistEditor->Playlists.current().value();
1504 else
1505 old_name = myBrowser->main().current().value().name;
1506 Statusbar::lock();
1507 Statusbar::put() << NC::Format::Bold << "Playlist: " << NC::Format::NoBold;
1508 std::string new_name = wFooter->getString(old_name);
1509 Statusbar::unlock();
1510 if (!new_name.empty() && new_name != old_name)
1512 Mpd.Rename(old_name, new_name);
1513 const char msg[] = "Playlist renamed to \"%1%\"";
1514 Statusbar::printf(msg, wideShorten(new_name, COLS-const_strlen(msg)));
1515 if (!myBrowser->isLocal())
1516 myBrowser->GetDirectory("/");
1520 bool EditLyrics::canBeRun() const
1522 return myScreen == myLyrics;
1525 void EditLyrics::run()
1527 myLyrics->Edit();
1530 bool JumpToBrowser::canBeRun() const
1532 return currentSong(myScreen);
1535 void JumpToBrowser::run()
1537 auto s = currentSong(myScreen);
1538 myBrowser->LocateSong(*s);
1541 bool JumpToMediaLibrary::canBeRun() const
1543 return currentSong(myScreen);
1546 void JumpToMediaLibrary::run()
1548 auto s = currentSong(myScreen);
1549 myLibrary->LocateSong(*s);
1552 bool JumpToPlaylistEditor::canBeRun() const
1554 return myScreen == myBrowser
1555 && myBrowser->main().current().value().type == MPD::itPlaylist;
1558 void JumpToPlaylistEditor::run()
1560 myPlaylistEditor->Locate(myBrowser->main().current().value().name);
1563 void ToggleScreenLock::run()
1565 using Global::wFooter;
1566 using Global::myLockedScreen;
1568 if (myLockedScreen != 0)
1570 BaseScreen::unlock();
1571 Actions::setResizeFlags();
1572 myScreen->resize();
1573 Statusbar::print("Screen unlocked");
1575 else
1577 int part = Config.locked_screen_width_part*100;
1578 if (Config.ask_for_locked_screen_width_part)
1580 Statusbar::lock();
1581 Statusbar::put() << "% of the locked screen's width to be reserved (20-80): ";
1582 std::string strpart = wFooter->getString(boost::lexical_cast<std::string>(part));
1583 Statusbar::unlock();
1584 part = fromString<unsigned>(strpart);
1586 boundsCheck(part, 20, 80);
1587 Config.locked_screen_width_part = part/100.0;
1588 if (myScreen->lock())
1589 Statusbar::printf("Screen locked (with %1%%% width)", part);
1590 else
1591 Statusbar::print("Current screen can't be locked");
1595 bool JumpToTagEditor::canBeRun() const
1597 # ifdef HAVE_TAGLIB_H
1598 return currentSong(myScreen)
1599 && isMPDMusicDirSet();
1600 # else
1601 return false;
1602 # endif // HAVE_TAGLIB_H
1605 void JumpToTagEditor::run()
1607 # ifdef HAVE_TAGLIB_H
1608 auto s = currentSong(myScreen);
1609 myTagEditor->LocateSong(*s);
1610 # endif // HAVE_TAGLIB_H
1613 bool JumpToPositionInSong::canBeRun() const
1615 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1618 void JumpToPositionInSong::run()
1620 using Global::wFooter;
1622 const MPD::Song s = myPlaylist->nowPlayingSong();
1624 Statusbar::lock();
1625 Statusbar::put() << "Position to go (in %/m:ss/seconds(s)): ";
1626 std::string strpos = wFooter->getString();
1627 Statusbar::unlock();
1629 boost::regex rx;
1630 boost::smatch what;
1632 if (boost::regex_match(strpos, what, rx.assign("([0-9]+):([0-9]{2})"))) // mm:ss
1634 int mins = fromString<int>(what[1]);
1635 int secs = fromString<int>(what[2]);
1636 boundsCheck(secs, 0, 60);
1637 Mpd.Seek(s.getPosition(), mins * 60 + secs);
1639 else if (boost::regex_match(strpos, what, rx.assign("([0-9]+)s"))) // position in seconds
1641 int secs = fromString<int>(what[1]);
1642 Mpd.Seek(s.getPosition(), secs);
1644 else if (boost::regex_match(strpos, what, rx.assign("([0-9]+)[%]{0,1}"))) // position in %
1646 int percent = fromString<int>(what[1]);
1647 boundsCheck(percent, 0, 100);
1648 int secs = (percent * s.getDuration()) / 100.0;
1649 Mpd.Seek(s.getPosition(), secs);
1651 else
1652 Statusbar::print("Invalid format ([m]:[ss], [s]s, [%]%, [%] accepted)");
1655 bool ReverseSelection::canBeRun() const
1657 auto w = hasSongs(myScreen);
1658 return w && w->allowsSelection();
1661 void ReverseSelection::run()
1663 auto w = hasSongs(myScreen);
1664 w->reverseSelection();
1665 Statusbar::print("Selection reversed");
1668 bool RemoveSelection::canBeRun() const
1670 return proxySongList(myScreen);
1673 void RemoveSelection::run()
1675 auto pl = proxySongList(myScreen);
1676 for (size_t i = 0; i < pl.size(); ++i)
1677 pl.setSelected(i, false);
1678 Statusbar::print("Selection removed");
1681 bool SelectAlbum::canBeRun() const
1683 auto w = hasSongs(myScreen);
1684 return w && w->allowsSelection() && w->proxySongList();
1687 void SelectAlbum::run()
1689 auto pl = proxySongList(myScreen);
1690 size_t pos = pl.choice();
1691 if (MPD::Song *s = pl.getSong(pos))
1693 std::string album = s->getAlbum();
1694 // select song under cursor
1695 pl.setSelected(pos, true);
1696 // go up
1697 while (pos > 0)
1699 s = pl.getSong(--pos);
1700 if (!s || s->getAlbum() != album)
1701 break;
1702 else
1703 pl.setSelected(pos, true);
1705 // go down
1706 pos = pl.choice();
1707 while (pos < pl.size() - 1)
1709 s = pl.getSong(++pos);
1710 if (!s || s->getAlbum() != album)
1711 break;
1712 else
1713 pl.setSelected(pos, true);
1715 Statusbar::print("Album around cursor position selected");
1719 bool AddSelectedItems::canBeRun() const
1721 return myScreen != mySelectedItemsAdder;
1724 void AddSelectedItems::run()
1726 mySelectedItemsAdder->switchTo();
1729 void CropMainPlaylist::run()
1731 auto &w = myPlaylist->main();
1732 // cropping doesn't make sense in this case
1733 if (w.size() <= 1)
1734 return;
1735 bool yes = true;
1736 if (Config.ask_before_clearing_playlists)
1737 yes = askYesNoQuestion("Do you really want to crop main playlist?", Status::trace);
1738 if (yes)
1740 Statusbar::print("Cropping playlist...");
1741 selectCurrentIfNoneSelected(w);
1742 cropPlaylist(w, boost::bind(&MPD::Connection::Delete, _1, _2));
1743 Statusbar::print("Playlist cropped");
1747 bool CropPlaylist::canBeRun() const
1749 return myScreen == myPlaylistEditor;
1752 void CropPlaylist::run()
1754 auto &w = myPlaylistEditor->Content;
1755 // cropping doesn't make sense in this case
1756 if (w.size() <= 1)
1757 return;
1758 assert(!myPlaylistEditor->Playlists.empty());
1759 std::string playlist = myPlaylistEditor->Playlists.current().value();
1760 bool yes = true;
1761 if (Config.ask_before_clearing_playlists)
1762 yes = askYesNoQuestion(
1763 boost::format("Do you really want to crop playlist \"%1%\"?") % playlist,
1764 Status::trace
1766 if (yes)
1768 selectCurrentIfNoneSelected(w);
1769 Statusbar::printf("Cropping playlist \"%1%\"...", playlist);
1770 cropPlaylist(w, boost::bind(&MPD::Connection::PlaylistDelete, _1, playlist, _2));
1771 Statusbar::printf("Playlist \"%1%\" cropped", playlist);
1775 void ClearMainPlaylist::run()
1777 bool yes = true;
1778 if (Config.ask_before_clearing_playlists)
1779 yes = askYesNoQuestion("Do you really want to clear main playlist?", Status::trace);
1780 if (yes)
1782 auto delete_fun = boost::bind(&MPD::Connection::Delete, _1, _2);
1783 auto clear_fun = boost::bind(&MPD::Connection::ClearMainPlaylist, _1);
1784 Statusbar::printf("Deleting items...");
1785 clearPlaylist(myPlaylist->main(), delete_fun, clear_fun);
1786 Statusbar::printf("Items deleted");
1787 myPlaylist->main().reset();
1791 bool ClearPlaylist::canBeRun() const
1793 return myScreen == myPlaylistEditor;
1796 void ClearPlaylist::run()
1798 assert(!myPlaylistEditor->Playlists.empty());
1799 std::string playlist = myPlaylistEditor->Playlists.current().value();
1800 bool yes = true;
1801 if (Config.ask_before_clearing_playlists)
1802 yes = askYesNoQuestion(
1803 boost::format("Do you really want to clear playlist \"%1%\"?") % playlist,
1804 Status::trace
1806 if (yes)
1808 auto delete_fun = boost::bind(&MPD::Connection::PlaylistDelete, _1, playlist, _2);
1809 auto clear_fun = boost::bind(&MPD::Connection::ClearPlaylist, _1, playlist);
1810 Statusbar::printf("Deleting items from \"%1%\"...", playlist);
1811 clearPlaylist(myPlaylistEditor->Content, delete_fun, clear_fun);
1812 Statusbar::printf("Items deleted from \"%1%\"", playlist);
1816 bool SortPlaylist::canBeRun() const
1818 return myScreen == myPlaylist;
1821 void SortPlaylist::run()
1823 mySortPlaylistDialog->switchTo();
1826 bool ReversePlaylist::canBeRun() const
1828 return myScreen == myPlaylist;
1831 void ReversePlaylist::run()
1833 myPlaylist->Reverse();
1836 bool ApplyFilter::canBeRun() const
1838 auto w = dynamic_cast<Filterable *>(myScreen);
1839 return w && w->allowsFiltering();
1842 void ApplyFilter::run()
1844 using Global::wFooter;
1846 Filterable *f = dynamic_cast<Filterable *>(myScreen);
1847 std::string filter = f->currentFilter();
1848 // if filter is already here, apply it
1849 if (!filter.empty())
1851 f->applyFilter(filter);
1852 myScreen->refreshWindow();
1855 Statusbar::lock();
1856 Statusbar::put() << NC::Format::Bold << "Apply filter: " << NC::Format::NoBold;
1857 wFooter->setGetStringHelper(Statusbar::Helpers::ApplyFilterImmediately(f, filter));
1858 wFooter->getString(filter);
1859 wFooter->setGetStringHelper(Statusbar::Helpers::getString);
1860 Statusbar::unlock();
1862 filter = f->currentFilter();
1863 if (filter.empty())
1865 myPlaylist->main().clearFilterResults();
1866 Statusbar::printf("Filtering disabled");
1868 else
1870 // apply filter here so even if old one wasn't modified
1871 // (and callback wasn't invoked), it still gets applied.
1872 f->applyFilter(filter);
1873 Statusbar::printf("Using filter \"%1%\"", filter);
1876 if (myScreen == myPlaylist)
1878 myPlaylist->EnableHighlighting();
1879 myPlaylist->reloadTotalLength();
1880 drawHeader();
1882 listsChangeFinisher();
1885 bool Find::canBeRun() const
1887 return myScreen == myHelp
1888 || myScreen == myLyrics
1889 # ifdef HAVE_CURL_CURL_H
1890 || myScreen == myLastfm
1891 # endif // HAVE_CURL_CURL_H
1895 void Find::run()
1897 using Global::wFooter;
1899 Statusbar::lock();
1900 Statusbar::put() << "Find: ";
1901 std::string findme = wFooter->getString();
1902 Statusbar::unlock();
1904 Statusbar::print("Searching...");
1905 auto s = static_cast<Screen<NC::Scrollpad> *>(myScreen);
1906 s->main().removeProperties();
1907 if (findme.empty() || s->main().setProperties(NC::Format::Reverse, findme, NC::Format::NoReverse))
1908 Statusbar::print("Done");
1909 else
1910 Statusbar::print("No matching patterns found");
1911 s->main().flush();
1914 bool FindItemBackward::canBeRun() const
1916 auto w = dynamic_cast<Searchable *>(myScreen);
1917 return w && w->allowsSearching();
1920 void FindItemForward::run()
1922 findItem(::Find::Forward);
1923 listsChangeFinisher();
1926 bool FindItemForward::canBeRun() const
1928 auto w = dynamic_cast<Searchable *>(myScreen);
1929 return w && w->allowsSearching();
1932 void FindItemBackward::run()
1934 findItem(::Find::Backward);
1935 listsChangeFinisher();
1938 bool NextFoundItem::canBeRun() const
1940 return dynamic_cast<Searchable *>(myScreen);
1943 void NextFoundItem::run()
1945 Searchable *w = dynamic_cast<Searchable *>(myScreen);
1946 w->nextFound(Config.wrapped_search);
1947 listsChangeFinisher();
1950 bool PreviousFoundItem::canBeRun() const
1952 return dynamic_cast<Searchable *>(myScreen);
1955 void PreviousFoundItem::run()
1957 Searchable *w = dynamic_cast<Searchable *>(myScreen);
1958 w->prevFound(Config.wrapped_search);
1959 listsChangeFinisher();
1962 void ToggleFindMode::run()
1964 Config.wrapped_search = !Config.wrapped_search;
1965 Statusbar::printf("Search mode: %1%",
1966 Config.wrapped_search ? "Wrapped" : "Normal"
1970 void ToggleReplayGainMode::run()
1972 using Global::wFooter;
1974 Statusbar::lock();
1975 Statusbar::put() << "Replay gain mode? [" << NC::Format::Bold << 'o' << NC::Format::NoBold << "ff/" << NC::Format::Bold << 't' << NC::Format::NoBold << "rack/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "lbum]";
1976 wFooter->refresh();
1977 int answer = 0;
1980 Status::trace();
1981 answer = wFooter->readKey();
1983 while (answer != 'o' && answer != 't' && answer != 'a');
1984 Statusbar::unlock();
1985 Mpd.SetReplayGainMode(answer == 't' ? MPD::rgmTrack : (answer == 'a' ? MPD::rgmAlbum : MPD::rgmOff));
1986 Statusbar::printf("Replay gain mode: %1%", Mpd.GetReplayGainMode());
1989 void ToggleSpaceMode::run()
1991 Config.space_selects = !Config.space_selects;
1992 Statusbar::printf("Space mode: %1% item", Config.space_selects ? "select" : "add");
1995 void ToggleAddMode::run()
1997 std::string mode_desc;
1998 switch (Config.space_add_mode)
2000 case SpaceAddMode::AddRemove:
2001 Config.space_add_mode = SpaceAddMode::AlwaysAdd;
2002 mode_desc = "always add an item to playlist";
2003 break;
2004 case SpaceAddMode::AlwaysAdd:
2005 Config.space_add_mode = SpaceAddMode::AddRemove;
2006 mode_desc = "add an item to playlist or remove if already added";
2007 break;
2009 Statusbar::printf("Add mode: %1%", mode_desc);
2012 void ToggleMouse::run()
2014 Config.mouse_support = !Config.mouse_support;
2015 mousemask(Config.mouse_support ? ALL_MOUSE_EVENTS : 0, 0);
2016 Statusbar::printf("Mouse support %1%",
2017 Config.mouse_support ? "enabled" : "disabled"
2021 void ToggleBitrateVisibility::run()
2023 Config.display_bitrate = !Config.display_bitrate;
2024 Statusbar::printf("Bitrate visibility %1%",
2025 Config.display_bitrate ? "enabled" : "disabled"
2029 void AddRandomItems::run()
2031 using Global::wFooter;
2033 Statusbar::lock();
2034 Statusbar::put() << "Add random? [" << NC::Format::Bold << 's' << NC::Format::NoBold << "ongs/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtists/al" << NC::Format::Bold << 'b' << NC::Format::NoBold << "ums] ";
2035 wFooter->refresh();
2036 int answer = 0;
2039 Status::trace();
2040 answer = wFooter->readKey();
2042 while (answer != 's' && answer != 'a' && answer != 'b');
2043 Statusbar::unlock();
2045 mpd_tag_type tag_type = MPD_TAG_ARTIST;
2046 std::string tag_type_str ;
2047 if (answer != 's')
2049 tag_type = charToTagType(answer);
2050 tag_type_str = boost::locale::to_lower(tagTypeToString(tag_type));
2052 else
2053 tag_type_str = "song";
2055 Statusbar::lock();
2056 Statusbar::put() << "Number of random " << tag_type_str << "s: ";
2057 std::string strnum = wFooter->getString();
2058 Statusbar::unlock();
2059 size_t number = fromString<size_t>(strnum);
2060 if (number && (answer == 's' ? Mpd.AddRandomSongs(number) : Mpd.AddRandomTag(tag_type, number)))
2062 Statusbar::printf("%1% random %2%%3% added to playlist",
2063 number, tag_type_str, number == 1 ? "" : "s"
2068 bool ToggleBrowserSortMode::canBeRun() const
2070 return myScreen == myBrowser;
2073 void ToggleBrowserSortMode::run()
2075 switch (Config.browser_sort_mode)
2077 case SortMode::Name:
2078 Config.browser_sort_mode = SortMode::ModificationTime;
2079 Statusbar::print("Sort songs by: modification time");
2080 break;
2081 case SortMode::ModificationTime:
2082 Config.browser_sort_mode = SortMode::CustomFormat;
2083 Statusbar::print("Sort songs by: custom format");
2084 break;
2085 case SortMode::CustomFormat:
2086 Config.browser_sort_mode = SortMode::NoOp;
2087 Statusbar::print("Do not sort songs");
2088 break;
2089 case SortMode::NoOp:
2090 Config.browser_sort_mode = SortMode::Name;
2091 Statusbar::print("Sort songs by: name");
2093 withUnfilteredMenuReapplyFilter(myBrowser->main(), [] {
2094 if (Config.browser_sort_mode != SortMode::NoOp)
2095 std::sort(myBrowser->main().begin()+(myBrowser->CurrentDir() != "/"), myBrowser->main().end(),
2096 LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode)
2101 bool ToggleLibraryTagType::canBeRun() const
2103 return (myScreen->isActiveWindow(myLibrary->Tags))
2104 || (myLibrary->Columns() == 2 && myScreen->isActiveWindow(myLibrary->Albums));
2107 void ToggleLibraryTagType::run()
2109 using Global::wFooter;
2111 Statusbar::lock();
2112 Statusbar::put() << "Tag type? [" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtist/album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtist/" << NC::Format::Bold << 'y' << NC::Format::NoBold << "ear/" << NC::Format::Bold << 'g' << NC::Format::NoBold << "enre/" << NC::Format::Bold << 'c' << NC::Format::NoBold << "omposer/" << NC::Format::Bold << 'p' << NC::Format::NoBold << "erformer] ";
2113 wFooter->refresh();
2114 int answer = 0;
2117 Status::trace();
2118 answer = wFooter->readKey();
2120 while (answer != 'a' && answer != 'A' && answer != 'y' && answer != 'g' && answer != 'c' && answer != 'p');
2121 Statusbar::unlock();
2122 mpd_tag_type new_tagitem = charToTagType(answer);
2123 if (new_tagitem != Config.media_lib_primary_tag)
2125 Config.media_lib_primary_tag = new_tagitem;
2126 std::string item_type = tagTypeToString(Config.media_lib_primary_tag);
2127 myLibrary->Tags.setTitle(Config.titles_visibility ? item_type + "s" : "");
2128 myLibrary->Tags.reset();
2129 item_type = boost::locale::to_lower(item_type);
2130 std::string and_mtime = Config.media_library_sort_by_mtime ?
2131 " and mtime" :
2133 if (myLibrary->Columns() == 2)
2135 myLibrary->Songs.clear();
2136 myLibrary->Albums.reset();
2137 myLibrary->Albums.clear();
2138 myLibrary->Albums.setTitle(Config.titles_visibility ? "Albums (sorted by " + item_type + and_mtime + ")" : "");
2139 myLibrary->Albums.display();
2141 else
2143 myLibrary->Tags.clear();
2144 myLibrary->Tags.display();
2146 Statusbar::printf("Switched to the list of %1%s", item_type);
2150 bool ToggleMediaLibrarySortMode::canBeRun() const
2152 return myScreen == myLibrary;
2155 void ToggleMediaLibrarySortMode::run()
2157 myLibrary->toggleSortMode();
2160 bool RefetchLyrics::canBeRun() const
2162 # ifdef HAVE_CURL_CURL_H
2163 return myScreen == myLyrics;
2164 # else
2165 return false;
2166 # endif // HAVE_CURL_CURL_H
2169 void RefetchLyrics::run()
2171 # ifdef HAVE_CURL_CURL_H
2172 myLyrics->Refetch();
2173 # endif // HAVE_CURL_CURL_H
2176 bool SetSelectedItemsPriority::canBeRun() const
2178 if (Mpd.Version() < 17)
2180 Statusbar::print("Priorities are supported in MPD >= 0.17.0");
2181 return false;
2183 return myScreen == myPlaylist && !myPlaylist->main().empty();
2186 void SetSelectedItemsPriority::run()
2188 using Global::wFooter;
2190 Statusbar::lock();
2191 Statusbar::put() << "Set priority [0-255]: ";
2192 std::string strprio = wFooter->getString();
2193 Statusbar::unlock();
2194 unsigned prio = fromString<unsigned>(strprio);
2195 boundsCheck(prio, 0u, 255u);
2196 myPlaylist->SetSelectedItemsPriority(prio);
2199 bool SetVisualizerSampleMultiplier::canBeRun() const
2201 # ifdef ENABLE_VISUALIZER
2202 return myScreen == myVisualizer;
2203 # else
2204 return false;
2205 # endif // ENABLE_VISUALIZER
2208 void SetVisualizerSampleMultiplier::run()
2210 # ifdef ENABLE_VISUALIZER
2211 using Global::wFooter;
2213 Statusbar::lock();
2214 Statusbar::put() << "Set visualizer sample multiplier: ";
2215 std::string smultiplier = wFooter->getString();
2216 Statusbar::unlock();
2218 double multiplier = fromString<double>(smultiplier);
2219 lowerBoundCheck(multiplier, 0.0);
2220 Config.visualizer_sample_multiplier = multiplier;
2221 # endif // ENABLE_VISUALIZER
2224 bool FilterPlaylistOnPriorities::canBeRun() const
2226 return myScreen == myPlaylist;
2229 void FilterPlaylistOnPriorities::run()
2231 using Global::wFooter;
2233 Statusbar::lock();
2234 Statusbar::put() << "Show songs with priority higher than: ";
2235 std::string strprio = wFooter->getString();
2236 Statusbar::unlock();
2237 unsigned prio = fromString<unsigned>(strprio);
2238 boundsCheck(prio, 0u, 255u);
2239 myPlaylist->main().filter(myPlaylist->main().begin(), myPlaylist->main().end(),
2240 [prio](const NC::Menu<MPD::Song>::Item &s) {
2241 return s.value().getPrio() > prio;
2243 Statusbar::printf("Playlist filtered (songs with priority higher than %1%)", prio);
2246 void ShowSongInfo::run()
2248 mySongInfo->switchTo();
2251 bool ShowArtistInfo::canBeRun() const
2253 #ifdef HAVE_CURL_CURL_H
2254 return myScreen == myLastfm
2255 || (myScreen->isActiveWindow(myLibrary->Tags)
2256 && !myLibrary->Tags.empty()
2257 && Config.media_lib_primary_tag == MPD_TAG_ARTIST)
2258 || currentSong(myScreen);
2259 # else
2260 return false;
2261 # endif // NOT HAVE_CURL_CURL_H
2264 void ShowArtistInfo::run()
2266 # ifdef HAVE_CURL_CURL_H
2267 if (myScreen == myLastfm)
2269 myLastfm->switchTo();
2270 return;
2273 std::string artist;
2274 if (myScreen->isActiveWindow(myLibrary->Tags))
2276 assert(!myLibrary->Tags.empty());
2277 assert(Config.media_lib_primary_tag == MPD_TAG_ARTIST);
2278 artist = myLibrary->Tags.current().value().tag();
2280 else
2282 auto s = currentSong(myScreen);
2283 assert(s);
2284 artist = s->getArtist();
2287 if (!artist.empty())
2289 myLastfm->queueJob(LastFm::ArtistInfo(artist, Config.lastfm_preferred_language));
2290 myLastfm->switchTo();
2292 # endif // HAVE_CURL_CURL_H
2295 void ShowLyrics::run()
2297 myLyrics->switchTo();
2300 void Quit::run()
2302 ExitMainLoop = true;
2305 void NextScreen::run()
2307 if (Config.screen_switcher_previous)
2309 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2310 tababble->switchToPreviousScreen();
2312 else if (!Config.screen_sequence.empty())
2314 const auto &seq = Config.screen_sequence;
2315 auto screen_type = std::find(seq.begin(), seq.end(), myScreen->type());
2316 if (++screen_type == seq.end())
2317 toScreen(seq.front())->switchTo();
2318 else
2319 toScreen(*screen_type)->switchTo();
2323 void PreviousScreen::run()
2325 if (Config.screen_switcher_previous)
2327 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2328 tababble->switchToPreviousScreen();
2330 else if (!Config.screen_sequence.empty())
2332 const auto &seq = Config.screen_sequence;
2333 auto screen_type = std::find(seq.begin(), seq.end(), myScreen->type());
2334 if (screen_type == seq.begin())
2335 toScreen(seq.back())->switchTo();
2336 else
2337 toScreen(*--screen_type)->switchTo();
2341 bool ShowHelp::canBeRun() const
2343 return myScreen != myHelp
2344 # ifdef HAVE_TAGLIB_H
2345 && myScreen != myTinyTagEditor
2346 # endif // HAVE_TAGLIB_H
2350 void ShowHelp::run()
2352 myHelp->switchTo();
2355 bool ShowPlaylist::canBeRun() const
2357 return myScreen != myPlaylist
2358 # ifdef HAVE_TAGLIB_H
2359 && myScreen != myTinyTagEditor
2360 # endif // HAVE_TAGLIB_H
2364 void ShowPlaylist::run()
2366 myPlaylist->switchTo();
2369 bool ShowBrowser::canBeRun() const
2371 return myScreen != myBrowser
2372 # ifdef HAVE_TAGLIB_H
2373 && myScreen != myTinyTagEditor
2374 # endif // HAVE_TAGLIB_H
2378 void ShowBrowser::run()
2380 myBrowser->switchTo();
2383 bool ChangeBrowseMode::canBeRun() const
2385 return myScreen == myBrowser;
2388 void ChangeBrowseMode::run()
2390 myBrowser->ChangeBrowseMode();
2393 bool ShowSearchEngine::canBeRun() const
2395 return myScreen != mySearcher
2396 # ifdef HAVE_TAGLIB_H
2397 && myScreen != myTinyTagEditor
2398 # endif // HAVE_TAGLIB_H
2402 void ShowSearchEngine::run()
2404 mySearcher->switchTo();
2407 bool ResetSearchEngine::canBeRun() const
2409 return myScreen == mySearcher;
2412 void ResetSearchEngine::run()
2414 mySearcher->reset();
2417 bool ShowMediaLibrary::canBeRun() const
2419 return myScreen != myLibrary
2420 # ifdef HAVE_TAGLIB_H
2421 && myScreen != myTinyTagEditor
2422 # endif // HAVE_TAGLIB_H
2426 void ShowMediaLibrary::run()
2428 myLibrary->switchTo();
2431 bool ToggleMediaLibraryColumnsMode::canBeRun() const
2433 return myScreen == myLibrary;
2436 void ToggleMediaLibraryColumnsMode::run()
2438 myLibrary->toggleColumnsMode();
2439 myLibrary->refresh();
2442 bool ShowPlaylistEditor::canBeRun() const
2444 return myScreen != myPlaylistEditor
2445 # ifdef HAVE_TAGLIB_H
2446 && myScreen != myTinyTagEditor
2447 # endif // HAVE_TAGLIB_H
2451 void ShowPlaylistEditor::run()
2453 myPlaylistEditor->switchTo();
2456 bool ShowTagEditor::canBeRun() const
2458 # ifdef HAVE_TAGLIB_H
2459 return myScreen != myTagEditor
2460 && myScreen != myTinyTagEditor;
2461 # else
2462 return false;
2463 # endif // HAVE_TAGLIB_H
2466 void ShowTagEditor::run()
2468 # ifdef HAVE_TAGLIB_H
2469 if (isMPDMusicDirSet())
2470 myTagEditor->switchTo();
2471 # endif // HAVE_TAGLIB_H
2474 bool ShowOutputs::canBeRun() const
2476 # ifdef ENABLE_OUTPUTS
2477 return myScreen != myOutputs
2478 # ifdef HAVE_TAGLIB_H
2479 && myScreen != myTinyTagEditor
2480 # endif // HAVE_TAGLIB_H
2482 # else
2483 return false;
2484 # endif // ENABLE_OUTPUTS
2487 void ShowOutputs::run()
2489 # ifdef ENABLE_OUTPUTS
2490 myOutputs->switchTo();
2491 # endif // ENABLE_OUTPUTS
2494 bool ShowVisualizer::canBeRun() const
2496 # ifdef ENABLE_VISUALIZER
2497 return myScreen != myVisualizer
2498 # ifdef HAVE_TAGLIB_H
2499 && myScreen != myTinyTagEditor
2500 # endif // HAVE_TAGLIB_H
2502 # else
2503 return false;
2504 # endif // ENABLE_VISUALIZER
2507 void ShowVisualizer::run()
2509 # ifdef ENABLE_VISUALIZER
2510 myVisualizer->switchTo();
2511 # endif // ENABLE_VISUALIZER
2514 bool ShowClock::canBeRun() const
2516 # ifdef ENABLE_CLOCK
2517 return myScreen != myClock
2518 # ifdef HAVE_TAGLIB_H
2519 && myScreen != myTinyTagEditor
2520 # endif // HAVE_TAGLIB_H
2522 # else
2523 return false;
2524 # endif // ENABLE_CLOCK
2527 void ShowClock::run()
2529 # ifdef ENABLE_CLOCK
2530 myClock->switchTo();
2531 # endif // ENABLE_CLOCK
2534 #ifdef HAVE_TAGLIB_H
2535 bool ShowServerInfo::canBeRun() const
2537 return myScreen != myTinyTagEditor;
2539 #endif // HAVE_TAGLIB_H
2541 void ShowServerInfo::run()
2543 myServerInfo->switchTo();
2548 namespace {//
2550 void populateActions()
2552 auto insert_action = [](Actions::BaseAction *a) {
2553 AvailableActions[static_cast<size_t>(a->type())] = a;
2555 insert_action(new Actions::Dummy());
2556 insert_action(new Actions::MouseEvent());
2557 insert_action(new Actions::ScrollUp());
2558 insert_action(new Actions::ScrollDown());
2559 insert_action(new Actions::ScrollUpArtist());
2560 insert_action(new Actions::ScrollUpAlbum());
2561 insert_action(new Actions::ScrollDownArtist());
2562 insert_action(new Actions::ScrollDownAlbum());
2563 insert_action(new Actions::PageUp());
2564 insert_action(new Actions::PageDown());
2565 insert_action(new Actions::MoveHome());
2566 insert_action(new Actions::MoveEnd());
2567 insert_action(new Actions::ToggleInterface());
2568 insert_action(new Actions::JumpToParentDirectory());
2569 insert_action(new Actions::PressEnter());
2570 insert_action(new Actions::PressSpace());
2571 insert_action(new Actions::PreviousColumn());
2572 insert_action(new Actions::NextColumn());
2573 insert_action(new Actions::MasterScreen());
2574 insert_action(new Actions::SlaveScreen());
2575 insert_action(new Actions::VolumeUp());
2576 insert_action(new Actions::VolumeDown());
2577 insert_action(new Actions::DeletePlaylistItems());
2578 insert_action(new Actions::DeleteStoredPlaylist());
2579 insert_action(new Actions::DeleteBrowserItems());
2580 insert_action(new Actions::ReplaySong());
2581 insert_action(new Actions::PreviousSong());
2582 insert_action(new Actions::NextSong());
2583 insert_action(new Actions::Pause());
2584 insert_action(new Actions::Stop());
2585 insert_action(new Actions::ExecuteCommand());
2586 insert_action(new Actions::SavePlaylist());
2587 insert_action(new Actions::MoveSortOrderUp());
2588 insert_action(new Actions::MoveSortOrderDown());
2589 insert_action(new Actions::MoveSelectedItemsUp());
2590 insert_action(new Actions::MoveSelectedItemsDown());
2591 insert_action(new Actions::MoveSelectedItemsTo());
2592 insert_action(new Actions::Add());
2593 insert_action(new Actions::SeekForward());
2594 insert_action(new Actions::SeekBackward());
2595 insert_action(new Actions::ToggleDisplayMode());
2596 insert_action(new Actions::ToggleSeparatorsBetweenAlbums());
2597 insert_action(new Actions::ToggleLyricsFetcher());
2598 insert_action(new Actions::ToggleFetchingLyricsInBackground());
2599 insert_action(new Actions::TogglePlayingSongCentering());
2600 insert_action(new Actions::UpdateDatabase());
2601 insert_action(new Actions::JumpToPlayingSong());
2602 insert_action(new Actions::ToggleRepeat());
2603 insert_action(new Actions::Shuffle());
2604 insert_action(new Actions::ToggleRandom());
2605 insert_action(new Actions::StartSearching());
2606 insert_action(new Actions::SaveTagChanges());
2607 insert_action(new Actions::ToggleSingle());
2608 insert_action(new Actions::ToggleConsume());
2609 insert_action(new Actions::ToggleCrossfade());
2610 insert_action(new Actions::SetCrossfade());
2611 insert_action(new Actions::SetVolume());
2612 insert_action(new Actions::EditSong());
2613 insert_action(new Actions::EditLibraryTag());
2614 insert_action(new Actions::EditLibraryAlbum());
2615 insert_action(new Actions::EditDirectoryName());
2616 insert_action(new Actions::EditPlaylistName());
2617 insert_action(new Actions::EditLyrics());
2618 insert_action(new Actions::JumpToBrowser());
2619 insert_action(new Actions::JumpToMediaLibrary());
2620 insert_action(new Actions::JumpToPlaylistEditor());
2621 insert_action(new Actions::ToggleScreenLock());
2622 insert_action(new Actions::JumpToTagEditor());
2623 insert_action(new Actions::JumpToPositionInSong());
2624 insert_action(new Actions::ReverseSelection());
2625 insert_action(new Actions::RemoveSelection());
2626 insert_action(new Actions::SelectAlbum());
2627 insert_action(new Actions::AddSelectedItems());
2628 insert_action(new Actions::CropMainPlaylist());
2629 insert_action(new Actions::CropPlaylist());
2630 insert_action(new Actions::ClearMainPlaylist());
2631 insert_action(new Actions::ClearPlaylist());
2632 insert_action(new Actions::SortPlaylist());
2633 insert_action(new Actions::ReversePlaylist());
2634 insert_action(new Actions::ApplyFilter());
2635 insert_action(new Actions::Find());
2636 insert_action(new Actions::FindItemForward());
2637 insert_action(new Actions::FindItemBackward());
2638 insert_action(new Actions::NextFoundItem());
2639 insert_action(new Actions::PreviousFoundItem());
2640 insert_action(new Actions::ToggleFindMode());
2641 insert_action(new Actions::ToggleReplayGainMode());
2642 insert_action(new Actions::ToggleSpaceMode());
2643 insert_action(new Actions::ToggleAddMode());
2644 insert_action(new Actions::ToggleMouse());
2645 insert_action(new Actions::ToggleBitrateVisibility());
2646 insert_action(new Actions::AddRandomItems());
2647 insert_action(new Actions::ToggleBrowserSortMode());
2648 insert_action(new Actions::ToggleLibraryTagType());
2649 insert_action(new Actions::ToggleMediaLibrarySortMode());
2650 insert_action(new Actions::RefetchLyrics());
2651 insert_action(new Actions::SetSelectedItemsPriority());
2652 insert_action(new Actions::SetVisualizerSampleMultiplier());
2653 insert_action(new Actions::FilterPlaylistOnPriorities());
2654 insert_action(new Actions::ShowSongInfo());
2655 insert_action(new Actions::ShowArtistInfo());
2656 insert_action(new Actions::ShowLyrics());
2657 insert_action(new Actions::Quit());
2658 insert_action(new Actions::NextScreen());
2659 insert_action(new Actions::PreviousScreen());
2660 insert_action(new Actions::ShowHelp());
2661 insert_action(new Actions::ShowPlaylist());
2662 insert_action(new Actions::ShowBrowser());
2663 insert_action(new Actions::ChangeBrowseMode());
2664 insert_action(new Actions::ShowSearchEngine());
2665 insert_action(new Actions::ResetSearchEngine());
2666 insert_action(new Actions::ShowMediaLibrary());
2667 insert_action(new Actions::ToggleMediaLibraryColumnsMode());
2668 insert_action(new Actions::ShowPlaylistEditor());
2669 insert_action(new Actions::ShowTagEditor());
2670 insert_action(new Actions::ShowOutputs());
2671 insert_action(new Actions::ShowVisualizer());
2672 insert_action(new Actions::ShowClock());
2673 insert_action(new Actions::ShowServerInfo());
2676 void seek()
2678 using Global::wHeader;
2679 using Global::wFooter;
2680 using Global::Timer;
2681 using Global::SeekingInProgress;
2683 if (!Status::State::totalTime())
2685 Statusbar::print("Unknown item length");
2686 return;
2689 Progressbar::lock();
2690 Statusbar::lock();
2692 unsigned songpos = Status::State::elapsedTime();
2693 auto t = Timer;
2695 int old_timeout = wFooter->getTimeout();
2696 wFooter->setTimeout(500);
2698 auto seekForward = &Actions::get(Actions::Type::SeekForward);
2699 auto seekBackward = &Actions::get(Actions::Type::SeekBackward);
2701 SeekingInProgress = true;
2702 while (true)
2704 Status::trace();
2706 unsigned howmuch = Config.incremental_seeking
2707 ? (Timer-t).seconds()/2+Config.seek_time
2708 : Config.seek_time;
2710 Key input = Key::read(*wFooter);
2711 auto k = Bindings.get(input);
2712 if (k.first == k.second || !k.first->isSingle()) // no single action?
2713 break;
2714 auto a = k.first->action();
2715 if (a == seekForward)
2717 if (songpos < Status::State::totalTime())
2718 songpos = std::min(songpos + howmuch, Status::State::totalTime());
2720 else if (a == seekBackward)
2722 if (songpos > 0)
2724 if (songpos < howmuch)
2725 songpos = 0;
2726 else
2727 songpos -= howmuch;
2730 else
2731 break;
2733 *wFooter << NC::Format::Bold;
2734 std::string tracklength;
2735 switch (Config.design)
2737 case Design::Classic:
2738 tracklength = " [";
2739 if (Config.display_remaining_time)
2741 tracklength += "-";
2742 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
2744 else
2745 tracklength += MPD::Song::ShowTime(songpos);
2746 tracklength += "/";
2747 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
2748 tracklength += "]";
2749 *wFooter << NC::XY(wFooter->getWidth()-tracklength.length(), 1) << tracklength;
2750 break;
2751 case Design::Alternative:
2752 if (Config.display_remaining_time)
2754 tracklength = "-";
2755 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
2757 else
2758 tracklength = MPD::Song::ShowTime(songpos);
2759 tracklength += "/";
2760 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
2761 *wHeader << NC::XY(0, 0) << tracklength << " ";
2762 wHeader->refresh();
2763 break;
2765 *wFooter << NC::Format::NoBold;
2766 Progressbar::draw(songpos, Status::State::totalTime());
2767 wFooter->refresh();
2769 SeekingInProgress = false;
2770 Mpd.Seek(Status::State::currentSongPosition(), songpos);
2772 wFooter->setTimeout(old_timeout);
2774 Progressbar::unlock();
2775 Statusbar::unlock();
2778 void findItem(const Find direction)
2780 using Global::wFooter;
2782 Searchable *w = dynamic_cast<Searchable *>(myScreen);
2783 assert(w);
2784 assert(w->allowsSearching());
2786 Statusbar::lock();
2787 Statusbar::put() << "Find " << (direction == Find::Forward ? "forward" : "backward") << ": ";
2788 std::string findme = wFooter->getString();
2789 Statusbar::unlock();
2791 if (!findme.empty())
2792 Statusbar::print("Searching...");
2794 bool success = w->search(findme);
2796 if (findme.empty())
2797 return;
2799 if (success)
2800 Statusbar::print("Searching finished");
2801 else
2802 Statusbar::printf("Unable to find \"%1%\"", findme);
2804 if (direction == ::Find::Forward)
2805 w->nextFound(Config.wrapped_search);
2806 else
2807 w->prevFound(Config.wrapped_search);
2809 if (myScreen == myPlaylist)
2810 myPlaylist->EnableHighlighting();
2813 void listsChangeFinisher()
2815 if (myScreen == myLibrary
2816 || myScreen == myPlaylistEditor
2817 # ifdef HAVE_TAGLIB_H
2818 || myScreen == myTagEditor
2819 # endif // HAVE_TAGLIB_H
2822 if (myScreen->activeWindow() == &myLibrary->Tags)
2824 myLibrary->Albums.clear();
2825 myLibrary->Albums.refresh();
2826 myLibrary->Songs.clear();
2827 myLibrary->Songs.refresh();
2828 myLibrary->updateTimer();
2830 else if (myScreen->activeWindow() == &myLibrary->Albums)
2832 myLibrary->Songs.clear();
2833 myLibrary->Songs.refresh();
2834 myLibrary->updateTimer();
2836 else if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
2838 myPlaylistEditor->Content.clear();
2839 myPlaylistEditor->Content.refresh();
2840 myPlaylistEditor->updateTimer();
2842 # ifdef HAVE_TAGLIB_H
2843 else if (myScreen->activeWindow() == myTagEditor->Dirs)
2845 myTagEditor->Tags->clear();
2847 # endif // HAVE_TAGLIB_H