Config: reformat all descriptions to fill 80 columns
[ncmpcpp.git] / src / actions.cpp
blob87cca2c3e890685b192ab04280dff181d753047d
1 /***************************************************************************
2 * Copyright (C) 2008-2017 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/date_time/posix_time/posix_time.hpp>
26 #include <boost/filesystem/operations.hpp>
27 #include <boost/locale/conversion.hpp>
28 #include <boost/lexical_cast.hpp>
29 #include <algorithm>
30 #include <iostream>
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"
42 #include "utility/scoped_value.h"
44 #include "curses/menu_impl.h"
45 #include "bindings.h"
46 #include "screens/browser.h"
47 #include "screens/clock.h"
48 #include "screens/help.h"
49 #include "screens/media_library.h"
50 #include "screens/lastfm.h"
51 #include "screens/lyrics.h"
52 #include "screens/playlist.h"
53 #include "screens/playlist_editor.h"
54 #include "screens/sort_playlist.h"
55 #include "screens/search_engine.h"
56 #include "screens/sel_items_adder.h"
57 #include "screens/server_info.h"
58 #include "screens/song_info.h"
59 #include "screens/outputs.h"
60 #include "utility/readline.h"
61 #include "utility/string.h"
62 #include "utility/type_conversions.h"
63 #include "screens/tag_editor.h"
64 #include "screens/tiny_tag_editor.h"
65 #include "screens/visualizer.h"
66 #include "title.h"
67 #include "tags.h"
69 #ifdef HAVE_TAGLIB_H
70 # include "fileref.h"
71 # include "tag.h"
72 #endif // HAVE_TAGLIB_H
74 using Global::myScreen;
76 namespace ph = std::placeholders;
78 namespace {
80 std::vector<std::shared_ptr<Actions::BaseAction>> AvailableActions;
82 void populateActions();
84 bool scrollTagCanBeRun(NC::List *&list, const SongList *&songs);
85 void scrollTagUpRun(NC::List *list, const SongList *songs, MPD::Song::GetFunction get);
86 void scrollTagDownRun(NC::List *list, const SongList *songs, MPD::Song::GetFunction get);
88 void seek(SearchDirection sd);
89 void findItem(const SearchDirection direction);
90 void listsChangeFinisher();
92 template <typename Iterator>
93 bool findSelectedRangeAndPrintInfoIfNot(Iterator &first, Iterator &last)
95 bool success = findSelectedRange(first, last);
96 if (!success)
97 Statusbar::print("No range selected");
98 return success;
101 template <typename Iterator>
102 Iterator nextScreenTypeInSequence(Iterator first, Iterator last, ScreenType type)
104 auto it = std::find(first, last, type);
105 if (it == last)
106 return first;
107 else
109 ++it;
110 if (it == last)
111 return first;
112 else
113 return it;
119 namespace Actions {
121 bool OriginalStatusbarVisibility;
122 bool ExitMainLoop = false;
124 size_t HeaderHeight;
125 size_t FooterHeight;
126 size_t FooterStartY;
128 void validateScreenSize()
130 using Global::MainHeight;
132 if (COLS < 30 || MainHeight < 5)
134 NC::destroyScreen();
135 std::cout << "Screen is too small to handle ncmpcpp correctly\n";
136 exit(1);
140 void initializeScreens()
142 myHelp = new Help;
143 myPlaylist = new Playlist;
144 myBrowser = new Browser;
145 mySearcher = new SearchEngine;
146 myLibrary = new MediaLibrary;
147 myPlaylistEditor = new PlaylistEditor;
148 myLyrics = new Lyrics;
149 mySelectedItemsAdder = new SelectedItemsAdder;
150 mySongInfo = new SongInfo;
151 myServerInfo = new ServerInfo;
152 mySortPlaylistDialog = new SortPlaylistDialog;
153 myLastfm = new Lastfm;
155 # ifdef HAVE_TAGLIB_H
156 myTinyTagEditor = new TinyTagEditor;
157 myTagEditor = new TagEditor;
158 # endif // HAVE_TAGLIB_H
160 # ifdef ENABLE_VISUALIZER
161 myVisualizer = new Visualizer;
162 # endif // ENABLE_VISUALIZER
164 # ifdef ENABLE_OUTPUTS
165 myOutputs = new Outputs;
166 # endif // ENABLE_OUTPUTS
168 # ifdef ENABLE_CLOCK
169 myClock = new Clock;
170 # endif // ENABLE_CLOCK
174 void setResizeFlags()
176 myHelp->hasToBeResized = 1;
177 myPlaylist->hasToBeResized = 1;
178 myBrowser->hasToBeResized = 1;
179 mySearcher->hasToBeResized = 1;
180 myLibrary->hasToBeResized = 1;
181 myPlaylistEditor->hasToBeResized = 1;
182 myLyrics->hasToBeResized = 1;
183 mySelectedItemsAdder->hasToBeResized = 1;
184 mySongInfo->hasToBeResized = 1;
185 myServerInfo->hasToBeResized = 1;
186 mySortPlaylistDialog->hasToBeResized = 1;
187 myLastfm->hasToBeResized = 1;
189 # ifdef HAVE_TAGLIB_H
190 myTinyTagEditor->hasToBeResized = 1;
191 myTagEditor->hasToBeResized = 1;
192 # endif // HAVE_TAGLIB_H
194 # ifdef ENABLE_VISUALIZER
195 myVisualizer->hasToBeResized = 1;
196 # endif // ENABLE_VISUALIZER
198 # ifdef ENABLE_OUTPUTS
199 myOutputs->hasToBeResized = 1;
200 # endif // ENABLE_OUTPUTS
202 # ifdef ENABLE_CLOCK
203 myClock->hasToBeResized = 1;
204 # endif // ENABLE_CLOCK
207 void resizeScreen(bool reload_main_window)
209 using Global::MainHeight;
210 using Global::wHeader;
211 using Global::wFooter;
213 // update internal screen dimensions
214 if (reload_main_window)
216 rl_resize_terminal();
217 endwin();
218 refresh();
219 // Remove KEY_RESIZE from input queue, I'm not sure how these make it in.
220 getch();
223 MainHeight = LINES-(Config.design == Design::Alternative ? 7 : 4);
225 validateScreenSize();
227 if (!Config.header_visibility)
228 MainHeight += 2;
229 if (!Config.statusbar_visibility)
230 ++MainHeight;
232 setResizeFlags();
234 applyToVisibleWindows(&BaseScreen::resize);
236 if (Config.header_visibility || Config.design == Design::Alternative)
237 wHeader->resize(COLS, HeaderHeight);
239 FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1);
240 wFooter->moveTo(0, FooterStartY);
241 wFooter->resize(COLS, Config.statusbar_visibility ? 2 : 1);
243 applyToVisibleWindows(&BaseScreen::refresh);
245 Status::Changes::elapsedTime(false);
246 Status::Changes::playerState();
247 // Note: routines for drawing separator if alternative user
248 // interface is active and header is hidden are placed in
249 // NcmpcppStatusChanges.StatusFlags
250 Status::Changes::flags();
251 drawHeader();
252 wFooter->refresh();
253 refresh();
256 void setWindowsDimensions()
258 using Global::MainStartY;
259 using Global::MainHeight;
261 MainStartY = Config.design == Design::Alternative ? 5 : 2;
262 MainHeight = LINES-(Config.design == Design::Alternative ? 7 : 4);
264 if (!Config.header_visibility)
266 MainStartY -= 2;
267 MainHeight += 2;
269 if (!Config.statusbar_visibility)
270 ++MainHeight;
272 HeaderHeight = Config.design == Design::Alternative ? (Config.header_visibility ? 5 : 3) : 2;
273 FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1);
274 FooterHeight = Config.statusbar_visibility ? 2 : 1;
277 void confirmAction(const boost::format &description)
279 Statusbar::ScopedLock slock;
280 Statusbar::put() << description.str()
281 << " [" << NC::Format::Bold << 'y' << NC::Format::NoBold
282 << '/' << NC::Format::Bold << 'n' << NC::Format::NoBold
283 << "] ";
284 char answer = Statusbar::Helpers::promptReturnOneOf({'y', 'n'});
285 if (answer == 'n')
286 throw NC::PromptAborted(std::string(1, answer));
289 bool isMPDMusicDirSet()
291 if (Config.mpd_music_dir.empty())
293 Statusbar::print("Proper mpd_music_dir variable has to be set in configuration file");
294 return false;
296 return true;
299 BaseAction &get(Actions::Type at)
301 if (AvailableActions.empty())
302 populateActions();
303 return *AvailableActions.at(static_cast<size_t>(at));
306 std::shared_ptr<BaseAction> get_(Actions::Type at)
308 if (AvailableActions.empty())
309 populateActions();
310 return AvailableActions.at(static_cast<size_t>(at));
313 std::shared_ptr<BaseAction> get_(const std::string &name)
315 std::shared_ptr<BaseAction> result;
316 if (AvailableActions.empty())
317 populateActions();
318 for (const auto &action : AvailableActions)
320 if (action->name() == name)
322 result = action;
323 break;
326 return result;
329 UpdateEnvironment::UpdateEnvironment()
330 : BaseAction(Type::UpdateEnvironment, "update_environment")
331 , m_past(boost::posix_time::from_time_t(0))
334 void UpdateEnvironment::run(bool update_timer, bool refresh_window, bool mpd_sync)
336 using Global::Timer;
338 // update timer, status if necessary etc.
339 Status::trace(update_timer, true);
341 // show lyrics consumer notification if appropriate
342 if (auto message = myLyrics->tryTakeConsumerMessage())
343 Statusbar::print(*message);
345 // header stuff
346 if ((myScreen == myPlaylist || myScreen == myBrowser || myScreen == myLyrics)
347 && (Timer - m_past > boost::posix_time::milliseconds(500))
350 drawHeader();
351 m_past = Timer;
354 if (refresh_window)
355 myScreen->refreshWindow();
357 // We want to synchronize with MPD during execution of an action chain.
358 if (mpd_sync)
360 int flags = Mpd.noidle();
361 if (flags)
362 Status::update(flags);
366 void UpdateEnvironment::run()
368 run(true, true, true);
371 bool MouseEvent::canBeRun()
373 return Config.mouse_support;
376 void MouseEvent::run()
378 using Global::VolumeState;
379 using Global::wFooter;
381 m_old_mouse_event = m_mouse_event;
382 m_mouse_event = wFooter->getMouseEvent();
384 //Statusbar::printf("(%1%, %2%, %3%)", m_mouse_event.bstate, m_mouse_event.x, m_mouse_event.y);
386 if (m_mouse_event.bstate & BUTTON1_PRESSED
387 && m_mouse_event.y == LINES-(Config.statusbar_visibility ? 2 : 1)
388 ) // progressbar
390 if (Status::State::player() == MPD::psStop)
391 return;
392 Mpd.Seek(Status::State::currentSongPosition(),
393 Status::State::totalTime()*m_mouse_event.x/double(COLS));
395 else if (m_mouse_event.bstate & BUTTON1_PRESSED
396 && (Config.statusbar_visibility || Config.design == Design::Alternative)
397 && Status::State::player() != MPD::psStop
398 && m_mouse_event.y == (Config.design == Design::Alternative ? 1 : LINES-1)
399 && m_mouse_event.x < 9
400 ) // playing/paused
402 Mpd.Toggle();
404 else if ((m_mouse_event.bstate & BUTTON5_PRESSED || m_mouse_event.bstate & BUTTON4_PRESSED)
405 && (Config.header_visibility || Config.design == Design::Alternative)
406 && m_mouse_event.y == 0 && size_t(m_mouse_event.x) > COLS-VolumeState.length()
407 ) // volume
409 if (m_mouse_event.bstate & BUTTON5_PRESSED)
410 get(Type::VolumeDown).execute();
411 else
412 get(Type::VolumeUp).execute();
414 else if (m_mouse_event.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED | BUTTON4_PRESSED | BUTTON5_PRESSED))
415 myScreen->mouseButtonPressed(m_mouse_event);
418 void ScrollUp::run()
420 myScreen->scroll(NC::Scroll::Up);
421 listsChangeFinisher();
424 void ScrollDown::run()
426 myScreen->scroll(NC::Scroll::Down);
427 listsChangeFinisher();
430 bool ScrollUpArtist::canBeRun()
432 return scrollTagCanBeRun(m_list, m_songs);
435 void ScrollUpArtist::run()
437 scrollTagUpRun(m_list, m_songs, &MPD::Song::getArtist);
440 bool ScrollUpAlbum::canBeRun()
442 return scrollTagCanBeRun(m_list, m_songs);
445 void ScrollUpAlbum::run()
447 scrollTagUpRun(m_list, m_songs, &MPD::Song::getAlbum);
450 bool ScrollDownArtist::canBeRun()
452 return scrollTagCanBeRun(m_list, m_songs);
455 void ScrollDownArtist::run()
457 scrollTagDownRun(m_list, m_songs, &MPD::Song::getArtist);
460 bool ScrollDownAlbum::canBeRun()
462 return scrollTagCanBeRun(m_list, m_songs);
465 void ScrollDownAlbum::run()
467 scrollTagDownRun(m_list, m_songs, &MPD::Song::getAlbum);
470 void PageUp::run()
472 myScreen->scroll(NC::Scroll::PageUp);
473 listsChangeFinisher();
476 void PageDown::run()
478 myScreen->scroll(NC::Scroll::PageDown);
479 listsChangeFinisher();
482 void MoveHome::run()
484 myScreen->scroll(NC::Scroll::Home);
485 listsChangeFinisher();
488 void MoveEnd::run()
490 myScreen->scroll(NC::Scroll::End);
491 listsChangeFinisher();
494 void ToggleInterface::run()
496 switch (Config.design)
498 case Design::Classic:
499 Config.design = Design::Alternative;
500 Config.statusbar_visibility = false;
501 break;
502 case Design::Alternative:
503 Config.design = Design::Classic;
504 Config.statusbar_visibility = OriginalStatusbarVisibility;
505 break;
507 setWindowsDimensions();
508 resizeScreen(false);
509 // unlock progressbar
510 Progressbar::ScopedLock();
511 Status::Changes::mixer();
512 Status::Changes::elapsedTime(false);
513 Statusbar::printf("User interface: %1%", Config.design);
516 bool JumpToParentDirectory::canBeRun()
518 return (myScreen == myBrowser)
519 # ifdef HAVE_TAGLIB_H
520 || (myScreen->activeWindow() == myTagEditor->Dirs)
521 # endif // HAVE_TAGLIB_H
525 void JumpToParentDirectory::run()
527 if (myScreen == myBrowser)
529 if (!myBrowser->inRootDirectory())
531 myBrowser->main().reset();
532 myBrowser->enterDirectory();
535 # ifdef HAVE_TAGLIB_H
536 else if (myScreen == myTagEditor)
538 if (myTagEditor->CurrentDir() != "/")
540 myTagEditor->Dirs->reset();
541 myTagEditor->enterDirectory();
544 # endif // HAVE_TAGLIB_H
547 bool RunAction::canBeRun()
549 m_ha = dynamic_cast<HasActions *>(myScreen);
550 return m_ha != nullptr
551 && m_ha->actionRunnable();
554 void RunAction::run()
556 m_ha->runAction();
559 bool PreviousColumn::canBeRun()
561 m_hc = dynamic_cast<HasColumns *>(myScreen);
562 return m_hc != nullptr
563 && m_hc->previousColumnAvailable();
566 void PreviousColumn::run()
568 m_hc->previousColumn();
571 bool NextColumn::canBeRun()
573 m_hc = dynamic_cast<HasColumns *>(myScreen);
574 return m_hc != nullptr
575 && m_hc->nextColumnAvailable();
578 void NextColumn::run()
580 m_hc->nextColumn();
583 bool MasterScreen::canBeRun()
585 using Global::myLockedScreen;
586 using Global::myInactiveScreen;
588 return myLockedScreen
589 && myInactiveScreen
590 && myLockedScreen != myScreen
591 && myScreen->isMergable();
594 void MasterScreen::run()
596 using Global::myInactiveScreen;
597 using Global::myLockedScreen;
599 myInactiveScreen = myScreen;
600 myScreen = myLockedScreen;
601 drawHeader();
604 bool SlaveScreen::canBeRun()
606 using Global::myLockedScreen;
607 using Global::myInactiveScreen;
609 return myLockedScreen
610 && myInactiveScreen
611 && myLockedScreen == myScreen
612 && myScreen->isMergable();
615 void SlaveScreen::run()
617 using Global::myInactiveScreen;
618 using Global::myLockedScreen;
620 myScreen = myInactiveScreen;
621 myInactiveScreen = myLockedScreen;
622 drawHeader();
625 void VolumeUp::run()
627 Mpd.ChangeVolume(static_cast<int>(Config.volume_change_step));
630 void VolumeDown::run()
632 Mpd.ChangeVolume(-static_cast<int>(Config.volume_change_step));
635 bool AddItemToPlaylist::canBeRun()
637 m_hs = dynamic_cast<HasSongs *>(myScreen);
638 return m_hs != nullptr && m_hs->itemAvailable();
641 void AddItemToPlaylist::run()
643 bool success = m_hs->addItemToPlaylist(false);
644 if (success)
646 myScreen->scroll(NC::Scroll::Down);
647 listsChangeFinisher();
651 bool PlayItem::canBeRun()
653 m_hs = dynamic_cast<HasSongs *>(myScreen);
654 return m_hs != nullptr && m_hs->itemAvailable();
657 void PlayItem::run()
659 bool success = m_hs->addItemToPlaylist(true);
660 if (success)
661 listsChangeFinisher();
664 bool DeletePlaylistItems::canBeRun()
666 return (myScreen == myPlaylist && !myPlaylist->main().empty())
667 || (myScreen->isActiveWindow(myPlaylistEditor->Content) && !myPlaylistEditor->Content.empty());
670 void DeletePlaylistItems::run()
672 if (myScreen == myPlaylist)
674 Statusbar::print("Deleting items...");
675 auto delete_fun = std::bind(&MPD::Connection::Delete, ph::_1, ph::_2);
676 deleteSelectedSongs(myPlaylist->main(), delete_fun);
677 Statusbar::print("Item(s) deleted");
679 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
681 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
682 auto delete_fun = std::bind(&MPD::Connection::PlaylistDelete, ph::_1, playlist, ph::_2);
683 Statusbar::print("Deleting items...");
684 deleteSelectedSongs(myPlaylistEditor->Content, delete_fun);
685 Statusbar::print("Item(s) deleted");
689 bool DeleteBrowserItems::canBeRun()
691 auto check_if_deletion_allowed = []() {
692 if (Config.allow_for_physical_item_deletion)
693 return true;
694 else
696 Statusbar::print("Flag \"allow_for_physical_item_deletion\" needs to be enabled in configuration file");
697 return false;
700 return myScreen == myBrowser
701 && !myBrowser->main().empty()
702 && isMPDMusicDirSet()
703 && check_if_deletion_allowed();
706 void DeleteBrowserItems::run()
708 auto get_name = [](const MPD::Item &item) -> std::string {
709 std::string iname;
710 switch (item.type())
712 case MPD::Item::Type::Directory:
713 iname = getBasename(item.directory().path());
714 break;
715 case MPD::Item::Type::Song:
716 iname = item.song().getName();
717 break;
718 case MPD::Item::Type::Playlist:
719 iname = getBasename(item.playlist().path());
720 break;
722 return iname;
725 boost::format question;
726 if (hasSelected(myBrowser->main().begin(), myBrowser->main().end()))
727 question = boost::format("Delete selected items?");
728 else
730 const auto &item = myBrowser->main().current()->value();
731 // parent directories are not accepted (and they
732 // can't be selected, so in other cases it's fine).
733 if (myBrowser->isParentDirectory(item))
734 return;
735 const char msg[] = "Delete \"%1%\"?";
736 question = boost::format(msg) % wideShorten(
737 get_name(item), COLS-const_strlen(msg)-5
740 confirmAction(question);
742 auto items = getSelectedOrCurrent(
743 myBrowser->main().begin(),
744 myBrowser->main().end(),
745 myBrowser->main().current()
747 for (const auto &item : items)
749 myBrowser->remove(item->value());
750 const char msg[] = "Deleted %1% \"%2%\"";
751 Statusbar::printf(msg,
752 itemTypeToString(item->value().type()),
753 wideShorten(get_name(item->value()), COLS-const_strlen(msg))
757 if (!myBrowser->isLocal())
758 Mpd.UpdateDirectory(myBrowser->currentDirectory());
759 myBrowser->requestUpdate();
762 bool DeleteStoredPlaylist::canBeRun()
764 return myScreen->isActiveWindow(myPlaylistEditor->Playlists);
767 void DeleteStoredPlaylist::run()
769 if (myPlaylistEditor->Playlists.empty())
770 return;
771 boost::format question;
772 if (hasSelected(myPlaylistEditor->Playlists.begin(), myPlaylistEditor->Playlists.end()))
773 question = boost::format("Delete selected playlists?");
774 else
775 question = boost::format("Delete playlist \"%1%\"?")
776 % wideShorten(myPlaylistEditor->Playlists.current()->value().path(), COLS-question.size()-10);
777 confirmAction(question);
778 auto list = getSelectedOrCurrent(
779 myPlaylistEditor->Playlists.begin(),
780 myPlaylistEditor->Playlists.end(),
781 myPlaylistEditor->Playlists.current()
783 for (const auto &item : list)
784 Mpd.DeletePlaylist(item->value().path());
785 Statusbar::printf("%1% deleted", list.size() == 1 ? "Playlist" : "Playlists");
786 // force playlists update. this happens automatically, but only after call
787 // to Key::read, therefore when we call PlaylistEditor::Update, it won't
788 // yet see it, so let's point that it needs to update it.
789 myPlaylistEditor->requestPlaylistsUpdate();
792 void ReplaySong::run()
794 if (Status::State::player() != MPD::psStop)
795 Mpd.Seek(Status::State::currentSongPosition(), 0);
798 void PreviousSong::run()
800 Mpd.Prev();
803 void NextSong::run()
805 Mpd.Next();
808 void Pause::run()
810 Mpd.Toggle();
813 void SavePlaylist::run()
815 using Global::wFooter;
817 std::string playlist_name;
819 Statusbar::ScopedLock slock;
820 Statusbar::put() << "Save playlist as: ";
821 playlist_name = wFooter->prompt();
825 Mpd.SavePlaylist(playlist_name);
826 Statusbar::printf("Playlist saved as \"%1%\"", playlist_name);
828 catch (MPD::ServerError &e)
830 if (e.code() == MPD_SERVER_ERROR_EXIST)
832 confirmAction(
833 boost::format("Playlist \"%1%\" already exists, overwrite?") % playlist_name
835 Mpd.DeletePlaylist(playlist_name);
836 Mpd.SavePlaylist(playlist_name);
837 Statusbar::print("Playlist overwritten");
839 else
840 throw;
844 void Stop::run()
846 Mpd.Stop();
849 void ExecuteCommand::run()
851 using Global::wFooter;
853 std::string cmd_name;
855 Statusbar::ScopedLock slock;
856 NC::Window::ScopedPromptHook helper(*wFooter,
857 Statusbar::Helpers::TryExecuteImmediateCommand()
859 Statusbar::put() << NC::Format::Bold << ":" << NC::Format::NoBold;
860 cmd_name = wFooter->prompt();
863 auto cmd = Bindings.findCommand(cmd_name);
864 if (cmd)
866 Statusbar::printf(1, "Executing %1%...", cmd_name);
867 bool res = cmd->binding().execute();
868 Statusbar::printf("Execution of command \"%1%\" %2%.",
869 cmd_name, res ? "successful" : "unsuccessful"
872 else
873 Statusbar::printf("No command named \"%1%\"", cmd_name);
876 bool MoveSortOrderUp::canBeRun()
878 return myScreen == mySortPlaylistDialog;
881 void MoveSortOrderUp::run()
883 mySortPlaylistDialog->moveSortOrderUp();
886 bool MoveSortOrderDown::canBeRun()
888 return myScreen == mySortPlaylistDialog;
891 void MoveSortOrderDown::run()
893 mySortPlaylistDialog->moveSortOrderDown();
896 bool MoveSelectedItemsUp::canBeRun()
898 return ((myScreen == myPlaylist
899 && !myPlaylist->main().empty())
900 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
901 && !myPlaylistEditor->Content.empty()));
904 void MoveSelectedItemsUp::run()
906 const char *filteredMsg = "Moving items up is disabled in filtered playlist";
907 if (myScreen == myPlaylist)
909 if (myPlaylist->main().isFiltered())
910 Statusbar::print(filteredMsg);
911 else
912 moveSelectedItemsUp(
913 myPlaylist->main(),
914 std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
916 else if (myScreen == myPlaylistEditor)
918 if (myPlaylistEditor->Content.isFiltered())
919 Statusbar::print(filteredMsg);
920 else
922 auto playlist = myPlaylistEditor->Playlists.current()->value().path();
923 moveSelectedItemsUp(
924 myPlaylistEditor->Content,
925 std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3));
930 bool MoveSelectedItemsDown::canBeRun()
932 return ((myScreen == myPlaylist
933 && !myPlaylist->main().empty())
934 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
935 && !myPlaylistEditor->Content.empty()));
938 void MoveSelectedItemsDown::run()
940 const char *filteredMsg = "Moving items down is disabled in filtered playlist";
941 if (myScreen == myPlaylist)
943 if (myPlaylist->main().isFiltered())
944 Statusbar::print(filteredMsg);
945 else
946 moveSelectedItemsDown(
947 myPlaylist->main(),
948 std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
950 else if (myScreen == myPlaylistEditor)
952 if (myPlaylistEditor->Content.isFiltered())
953 Statusbar::print(filteredMsg);
954 else
956 auto playlist = myPlaylistEditor->Playlists.current()->value().path();
957 moveSelectedItemsDown(
958 myPlaylistEditor->Content,
959 std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3));
964 bool MoveSelectedItemsTo::canBeRun()
966 return myScreen == myPlaylist
967 || myScreen->isActiveWindow(myPlaylistEditor->Content);
970 void MoveSelectedItemsTo::run()
972 if (myScreen == myPlaylist)
974 if (!myPlaylist->main().empty())
975 moveSelectedItemsTo(myPlaylist->main(), std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
977 else
979 assert(!myPlaylistEditor->Playlists.empty());
980 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
981 auto move_fun = std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3);
982 moveSelectedItemsTo(myPlaylistEditor->Content, move_fun);
986 bool Add::canBeRun()
988 return myScreen != myPlaylistEditor
989 || !myPlaylistEditor->Playlists.empty();
992 void Add::run()
994 using Global::wFooter;
996 std::string path;
998 Statusbar::ScopedLock slock;
999 Statusbar::put() << (myScreen == myPlaylistEditor ? "Add to playlist: " : "Add: ");
1000 path = wFooter->prompt();
1003 // confirm when one wants to add the whole database
1004 if (path.empty())
1005 confirmAction("Are you sure you want to add the whole database?");
1007 Statusbar::put() << "Adding...";
1008 wFooter->refresh();
1009 if (myScreen == myPlaylistEditor)
1010 Mpd.AddToPlaylist(myPlaylistEditor->Playlists.current()->value().path(), path);
1011 else
1015 Mpd.Add(path);
1017 catch (MPD::ServerError &err)
1019 // If a path is not a file or directory, assume it is a playlist.
1020 if (err.code() == MPD_SERVER_ERROR_NO_EXIST)
1021 Mpd.LoadPlaylist(path);
1022 else
1023 throw;
1028 bool SeekForward::canBeRun()
1030 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1033 void SeekForward::run()
1035 seek(SearchDirection::Forward);
1038 bool SeekBackward::canBeRun()
1040 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1043 void SeekBackward::run()
1045 seek(SearchDirection::Backward);
1048 bool ToggleDisplayMode::canBeRun()
1050 return myScreen == myPlaylist
1051 || myScreen == myBrowser
1052 || myScreen == mySearcher
1053 || myScreen->isActiveWindow(myPlaylistEditor->Content);
1056 void ToggleDisplayMode::run()
1058 if (myScreen == myPlaylist)
1060 switch (Config.playlist_display_mode)
1062 case DisplayMode::Classic:
1063 Config.playlist_display_mode = DisplayMode::Columns;
1064 myPlaylist->main().setItemDisplayer(std::bind(
1065 Display::SongsInColumns, ph::_1, std::cref(myPlaylist->main())
1067 if (Config.titles_visibility)
1068 myPlaylist->main().setTitle(Display::Columns(myPlaylist->main().getWidth()));
1069 else
1070 myPlaylist->main().setTitle("");
1071 break;
1072 case DisplayMode::Columns:
1073 Config.playlist_display_mode = DisplayMode::Classic;
1074 myPlaylist->main().setItemDisplayer(std::bind(
1075 Display::Songs, ph::_1, std::cref(myPlaylist->main()), std::cref(Config.song_list_format)
1077 myPlaylist->main().setTitle("");
1079 Statusbar::printf("Playlist display mode: %1%", Config.playlist_display_mode);
1081 else if (myScreen == myBrowser)
1083 switch (Config.browser_display_mode)
1085 case DisplayMode::Classic:
1086 Config.browser_display_mode = DisplayMode::Columns;
1087 if (Config.titles_visibility)
1088 myBrowser->main().setTitle(Display::Columns(myBrowser->main().getWidth()));
1089 else
1090 myBrowser->main().setTitle("");
1091 break;
1092 case DisplayMode::Columns:
1093 Config.browser_display_mode = DisplayMode::Classic;
1094 myBrowser->main().setTitle("");
1095 break;
1097 Statusbar::printf("Browser display mode: %1%", Config.browser_display_mode);
1099 else if (myScreen == mySearcher)
1101 switch (Config.search_engine_display_mode)
1103 case DisplayMode::Classic:
1104 Config.search_engine_display_mode = DisplayMode::Columns;
1105 break;
1106 case DisplayMode::Columns:
1107 Config.search_engine_display_mode = DisplayMode::Classic;
1108 break;
1110 Statusbar::printf("Search engine display mode: %1%", Config.search_engine_display_mode);
1111 if (mySearcher->main().size() > SearchEngine::StaticOptions)
1112 mySearcher->main().setTitle(
1113 Config.search_engine_display_mode == DisplayMode::Columns
1114 && Config.titles_visibility
1115 ? Display::Columns(mySearcher->main().getWidth())
1116 : ""
1119 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
1121 switch (Config.playlist_editor_display_mode)
1123 case DisplayMode::Classic:
1124 Config.playlist_editor_display_mode = DisplayMode::Columns;
1125 myPlaylistEditor->Content.setItemDisplayer(std::bind(
1126 Display::SongsInColumns, ph::_1, std::cref(myPlaylistEditor->Content)
1128 break;
1129 case DisplayMode::Columns:
1130 Config.playlist_editor_display_mode = DisplayMode::Classic;
1131 myPlaylistEditor->Content.setItemDisplayer(std::bind(
1132 Display::Songs, ph::_1, std::cref(myPlaylistEditor->Content), std::cref(Config.song_list_format)
1134 break;
1136 Statusbar::printf("Playlist editor display mode: %1%", Config.playlist_editor_display_mode);
1140 bool ToggleSeparatorsBetweenAlbums::canBeRun()
1142 return true;
1145 void ToggleSeparatorsBetweenAlbums::run()
1147 Config.playlist_separate_albums = !Config.playlist_separate_albums;
1148 Statusbar::printf("Separators between albums: %1%",
1149 Config.playlist_separate_albums ? "on" : "off"
1153 bool ToggleLyricsUpdateOnSongChange::canBeRun()
1155 return myScreen == myLyrics;
1158 void ToggleLyricsUpdateOnSongChange::run()
1160 Config.now_playing_lyrics = !Config.now_playing_lyrics;
1161 Statusbar::printf("Update lyrics if song changes: %1%",
1162 Config.now_playing_lyrics ? "on" : "off"
1166 void ToggleLyricsFetcher::run()
1168 myLyrics->toggleFetcher();
1171 void ToggleFetchingLyricsInBackground::run()
1173 Config.fetch_lyrics_in_background = !Config.fetch_lyrics_in_background;
1174 Statusbar::printf("Fetching lyrics for playing songs in background: %1%",
1175 Config.fetch_lyrics_in_background ? "on" : "off");
1178 void TogglePlayingSongCentering::run()
1180 Config.autocenter_mode = !Config.autocenter_mode;
1181 Statusbar::printf("Centering playing song: %1%",
1182 Config.autocenter_mode ? "on" : "off"
1184 if (Config.autocenter_mode)
1186 auto s = myPlaylist->nowPlayingSong();
1187 if (!s.empty())
1188 myPlaylist->locateSong(s);
1192 void UpdateDatabase::run()
1194 if (myScreen == myBrowser)
1195 Mpd.UpdateDirectory(myBrowser->currentDirectory());
1196 # ifdef HAVE_TAGLIB_H
1197 else if (myScreen == myTagEditor)
1198 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1199 # endif // HAVE_TAGLIB_H
1200 else
1201 Mpd.UpdateDirectory("/");
1204 bool JumpToPlayingSong::canBeRun()
1206 m_song = myPlaylist->nowPlayingSong();
1207 return !m_song.empty()
1208 && (myScreen == myPlaylist
1209 || myScreen == myPlaylistEditor
1210 || myScreen == myBrowser
1211 || myScreen == myLibrary);
1214 void JumpToPlayingSong::run()
1216 if (myScreen == myPlaylist)
1218 myPlaylist->locateSong(m_song);
1220 else if (myScreen == myPlaylistEditor)
1222 myPlaylistEditor->locateSong(m_song);
1224 else if (myScreen == myBrowser)
1226 myBrowser->locateSong(m_song);
1228 else if (myScreen == myLibrary)
1230 myLibrary->locateSong(m_song);
1234 void ToggleRepeat::run()
1236 Mpd.SetRepeat(!Status::State::repeat());
1239 bool Shuffle::canBeRun()
1241 if (myScreen != myPlaylist)
1242 return false;
1243 m_begin = myPlaylist->main().begin();
1244 m_end = myPlaylist->main().end();
1245 return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end);
1248 void Shuffle::run()
1250 if (Config.ask_before_shuffling_playlists)
1251 confirmAction("Do you really want to shuffle selected range?");
1252 auto begin = myPlaylist->main().begin();
1253 Mpd.ShuffleRange(m_begin-begin, m_end-begin);
1254 Statusbar::print("Range shuffled");
1257 void ToggleRandom::run()
1259 Mpd.SetRandom(!Status::State::random());
1262 bool StartSearching::canBeRun()
1264 return myScreen == mySearcher && !mySearcher->main()[0].isInactive();
1267 void StartSearching::run()
1269 mySearcher->main().highlight(SearchEngine::SearchButton);
1270 mySearcher->main().setHighlighting(0);
1271 mySearcher->main().refresh();
1272 mySearcher->main().setHighlighting(1);
1273 mySearcher->runAction();
1276 bool SaveTagChanges::canBeRun()
1278 # ifdef HAVE_TAGLIB_H
1279 return myScreen == myTinyTagEditor
1280 || myScreen->activeWindow() == myTagEditor->TagTypes;
1281 # else
1282 return false;
1283 # endif // HAVE_TAGLIB_H
1286 void SaveTagChanges::run()
1288 # ifdef HAVE_TAGLIB_H
1289 if (myScreen == myTinyTagEditor)
1291 myTinyTagEditor->main().highlight(myTinyTagEditor->main().size()-2); // Save
1292 myTinyTagEditor->runAction();
1294 else if (myScreen->activeWindow() == myTagEditor->TagTypes)
1296 myTagEditor->TagTypes->highlight(myTagEditor->TagTypes->size()-1); // Save
1297 myTagEditor->runAction();
1299 # endif // HAVE_TAGLIB_H
1302 void ToggleSingle::run()
1304 Mpd.SetSingle(!Status::State::single());
1307 void ToggleConsume::run()
1309 Mpd.SetConsume(!Status::State::consume());
1312 void ToggleCrossfade::run()
1314 Mpd.SetCrossfade(Status::State::crossfade() ? 0 : Config.crossfade_time);
1317 void SetCrossfade::run()
1319 using Global::wFooter;
1321 Statusbar::ScopedLock slock;
1322 Statusbar::put() << "Set crossfade to: ";
1323 auto crossfade = fromString<unsigned>(wFooter->prompt());
1324 lowerBoundCheck(crossfade, 0u);
1325 Config.crossfade_time = crossfade;
1326 Mpd.SetCrossfade(crossfade);
1329 void SetVolume::run()
1331 using Global::wFooter;
1333 unsigned volume;
1335 Statusbar::ScopedLock slock;
1336 Statusbar::put() << "Set volume to: ";
1337 volume = fromString<unsigned>(wFooter->prompt());
1338 boundsCheck(volume, 0u, 100u);
1339 Mpd.SetVolume(volume);
1341 Statusbar::printf("Volume set to %1%%%", volume);
1344 bool EnterDirectory::canBeRun()
1346 bool result = false;
1347 if (myScreen == myBrowser && !myBrowser->main().empty())
1349 result = myBrowser->main().current()->value().type()
1350 == MPD::Item::Type::Directory;
1352 #ifdef HAVE_TAGLIB_H
1353 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1354 result = true;
1355 #endif // HAVE_TAGLIB_H
1356 return result;
1359 void EnterDirectory::run()
1361 if (myScreen == myBrowser)
1362 myBrowser->enterDirectory();
1363 #ifdef HAVE_TAGLIB_H
1364 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1366 if (!myTagEditor->enterDirectory())
1367 Statusbar::print("No subdirectories found");
1369 #endif // HAVE_TAGLIB_H
1372 bool EditSong::canBeRun()
1374 # ifdef HAVE_TAGLIB_H
1375 m_song = currentSong(myScreen);
1376 return m_song != nullptr && isMPDMusicDirSet();
1377 # else
1378 return false;
1379 # endif // HAVE_TAGLIB_H
1382 void EditSong::run()
1384 # ifdef HAVE_TAGLIB_H
1385 myTinyTagEditor->SetEdited(*m_song);
1386 myTinyTagEditor->switchTo();
1387 # endif // HAVE_TAGLIB_H
1390 bool EditLibraryTag::canBeRun()
1392 # ifdef HAVE_TAGLIB_H
1393 return myScreen->isActiveWindow(myLibrary->Tags)
1394 && !myLibrary->Tags.empty()
1395 && isMPDMusicDirSet();
1396 # else
1397 return false;
1398 # endif // HAVE_TAGLIB_H
1401 void EditLibraryTag::run()
1403 # ifdef HAVE_TAGLIB_H
1404 using Global::wFooter;
1406 std::string new_tag;
1408 Statusbar::ScopedLock slock;
1409 Statusbar::put() << NC::Format::Bold << tagTypeToString(Config.media_lib_primary_tag) << NC::Format::NoBold << ": ";
1410 new_tag = wFooter->prompt(myLibrary->Tags.current()->value().tag());
1412 if (!new_tag.empty() && new_tag != myLibrary->Tags.current()->value().tag())
1414 Statusbar::print("Updating tags...");
1415 Mpd.StartSearch(true);
1416 Mpd.AddSearch(Config.media_lib_primary_tag, myLibrary->Tags.current()->value().tag());
1417 MPD::MutableSong::SetFunction set = tagTypeToSetFunction(Config.media_lib_primary_tag);
1418 assert(set);
1419 bool success = true;
1420 std::string dir_to_update;
1421 for (MPD::SongIterator s = Mpd.CommitSearchSongs(), end; s != end; ++s)
1423 MPD::MutableSong ms = std::move(*s);
1424 ms.setTags(set, new_tag);
1425 Statusbar::printf("Updating tags in \"%1%\"...", ms.getName());
1426 std::string path = Config.mpd_music_dir + ms.getURI();
1427 if (!Tags::write(ms))
1429 success = false;
1430 Statusbar::printf("Error while writing tags to \"%1%\": %2%",
1431 ms.getName(), strerror(errno));
1432 s.finish();
1433 break;
1435 if (dir_to_update.empty())
1436 dir_to_update = ms.getURI();
1437 else
1438 dir_to_update = getSharedDirectory(dir_to_update, ms.getURI());
1440 if (success)
1442 Mpd.UpdateDirectory(dir_to_update);
1443 Statusbar::print("Tags updated successfully");
1446 # endif // HAVE_TAGLIB_H
1449 bool EditLibraryAlbum::canBeRun()
1451 # ifdef HAVE_TAGLIB_H
1452 return myScreen->isActiveWindow(myLibrary->Albums)
1453 && !myLibrary->Albums.empty()
1454 && isMPDMusicDirSet();
1455 # else
1456 return false;
1457 # endif // HAVE_TAGLIB_H
1460 void EditLibraryAlbum::run()
1462 # ifdef HAVE_TAGLIB_H
1463 using Global::wFooter;
1464 // FIXME: merge this and EditLibraryTag. also, prompt on failure if user wants to continue
1465 std::string new_album;
1467 Statusbar::ScopedLock slock;
1468 Statusbar::put() << NC::Format::Bold << "Album: " << NC::Format::NoBold;
1469 new_album = wFooter->prompt(myLibrary->Albums.current()->value().entry().album());
1471 if (!new_album.empty() && new_album != myLibrary->Albums.current()->value().entry().album())
1473 bool success = 1;
1474 Statusbar::print("Updating tags...");
1475 for (size_t i = 0; i < myLibrary->Songs.size(); ++i)
1477 Statusbar::printf("Updating tags in \"%1%\"...", myLibrary->Songs[i].value().getName());
1478 std::string path = Config.mpd_music_dir + myLibrary->Songs[i].value().getURI();
1479 TagLib::FileRef f(path.c_str());
1480 if (f.isNull())
1482 const char msg[] = "Error while opening file \"%1%\"";
1483 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1484 success = 0;
1485 break;
1487 f.tag()->setAlbum(ToWString(new_album));
1488 if (!f.save())
1490 const char msg[] = "Error while writing tags in \"%1%\"";
1491 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1492 success = 0;
1493 break;
1496 if (success)
1498 Mpd.UpdateDirectory(getSharedDirectory(myLibrary->Songs.beginV(), myLibrary->Songs.endV()));
1499 Statusbar::print("Tags updated successfully");
1502 # endif // HAVE_TAGLIB_H
1505 bool EditDirectoryName::canBeRun()
1507 return ((myScreen == myBrowser
1508 && !myBrowser->main().empty()
1509 && myBrowser->main().current()->value().type() == MPD::Item::Type::Directory)
1510 # ifdef HAVE_TAGLIB_H
1511 || (myScreen->activeWindow() == myTagEditor->Dirs
1512 && !myTagEditor->Dirs->empty()
1513 && myTagEditor->Dirs->choice() > 0)
1514 # endif // HAVE_TAGLIB_H
1515 ) && isMPDMusicDirSet();
1518 void EditDirectoryName::run()
1520 using Global::wFooter;
1521 if (myScreen == myBrowser)
1523 std::string old_dir = myBrowser->main().current()->value().directory().path();
1524 std::string new_dir;
1526 Statusbar::ScopedLock slock;
1527 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1528 new_dir = wFooter->prompt(old_dir);
1530 if (!new_dir.empty() && new_dir != old_dir)
1532 std::string full_old_dir;
1533 if (!myBrowser->isLocal())
1534 full_old_dir += Config.mpd_music_dir;
1535 full_old_dir += old_dir;
1536 std::string full_new_dir;
1537 if (!myBrowser->isLocal())
1538 full_new_dir += Config.mpd_music_dir;
1539 full_new_dir += new_dir;
1540 boost::filesystem::rename(full_old_dir, full_new_dir);
1541 const char msg[] = "Directory renamed to \"%1%\"";
1542 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1543 if (!myBrowser->isLocal())
1544 Mpd.UpdateDirectory(getSharedDirectory(old_dir, new_dir));
1545 myBrowser->requestUpdate();
1548 # ifdef HAVE_TAGLIB_H
1549 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1551 std::string old_dir = myTagEditor->Dirs->current()->value().first, new_dir;
1553 Statusbar::ScopedLock slock;
1554 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1555 new_dir = wFooter->prompt(old_dir);
1557 if (!new_dir.empty() && new_dir != old_dir)
1559 std::string full_old_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + old_dir;
1560 std::string full_new_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + new_dir;
1561 if (rename(full_old_dir.c_str(), full_new_dir.c_str()) == 0)
1563 const char msg[] = "Directory renamed to \"%1%\"";
1564 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1565 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1567 else
1569 const char msg[] = "Couldn't rename \"%1%\": %2%";
1570 Statusbar::printf(msg, wideShorten(old_dir, COLS-const_strlen(msg)-25), strerror(errno));
1574 # endif // HAVE_TAGLIB_H
1577 bool EditPlaylistName::canBeRun()
1579 return (myScreen->isActiveWindow(myPlaylistEditor->Playlists)
1580 && !myPlaylistEditor->Playlists.empty())
1581 || (myScreen == myBrowser
1582 && !myBrowser->main().empty()
1583 && myBrowser->main().current()->value().type() == MPD::Item::Type::Playlist);
1586 void EditPlaylistName::run()
1588 using Global::wFooter;
1589 std::string old_name, new_name;
1590 if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
1591 old_name = myPlaylistEditor->Playlists.current()->value().path();
1592 else
1593 old_name = myBrowser->main().current()->value().playlist().path();
1595 Statusbar::ScopedLock slock;
1596 Statusbar::put() << NC::Format::Bold << "Playlist: " << NC::Format::NoBold;
1597 new_name = wFooter->prompt(old_name);
1599 if (!new_name.empty() && new_name != old_name)
1601 Mpd.Rename(old_name, new_name);
1602 const char msg[] = "Playlist renamed to \"%1%\"";
1603 Statusbar::printf(msg, wideShorten(new_name, COLS-const_strlen(msg)));
1607 bool EditLyrics::canBeRun()
1609 return myScreen == myLyrics;
1612 void EditLyrics::run()
1614 myLyrics->edit();
1617 bool JumpToBrowser::canBeRun()
1619 m_song = currentSong(myScreen);
1620 return m_song != nullptr;
1623 void JumpToBrowser::run()
1625 myBrowser->locateSong(*m_song);
1628 bool JumpToMediaLibrary::canBeRun()
1630 m_song = currentSong(myScreen);
1631 return m_song != nullptr;
1634 void JumpToMediaLibrary::run()
1636 myLibrary->locateSong(*m_song);
1639 bool JumpToPlaylistEditor::canBeRun()
1641 return myScreen == myBrowser
1642 && myBrowser->main().current()->value().type() == MPD::Item::Type::Playlist;
1645 void JumpToPlaylistEditor::run()
1647 myPlaylistEditor->locatePlaylist(myBrowser->main().current()->value().playlist());
1650 void ToggleScreenLock::run()
1652 using Global::wFooter;
1653 using Global::myLockedScreen;
1654 const char *msg_unlockable_screen = "Current screen can't be locked";
1655 if (myLockedScreen != nullptr)
1657 BaseScreen::unlock();
1658 Actions::setResizeFlags();
1659 myScreen->resize();
1660 Statusbar::print("Screen unlocked");
1662 else if (!myScreen->isLockable())
1664 Statusbar::print(msg_unlockable_screen);
1666 else
1668 unsigned part = Config.locked_screen_width_part*100;
1669 if (Config.ask_for_locked_screen_width_part)
1671 Statusbar::ScopedLock slock;
1672 Statusbar::put() << "% of the locked screen's width to be reserved (20-80): ";
1673 part = fromString<unsigned>(wFooter->prompt(boost::lexical_cast<std::string>(part)));
1675 boundsCheck(part, 20u, 80u);
1676 Config.locked_screen_width_part = part/100.0;
1677 if (myScreen->lock())
1678 Statusbar::printf("Screen locked (with %1%%% width)", part);
1679 else
1680 Statusbar::print(msg_unlockable_screen);
1684 bool JumpToTagEditor::canBeRun()
1686 # ifdef HAVE_TAGLIB_H
1687 m_song = currentSong(myScreen);
1688 return m_song != nullptr && isMPDMusicDirSet();
1689 # else
1690 return false;
1691 # endif // HAVE_TAGLIB_H
1694 void JumpToTagEditor::run()
1696 # ifdef HAVE_TAGLIB_H
1697 myTagEditor->LocateSong(*m_song);
1698 # endif // HAVE_TAGLIB_H
1701 bool JumpToPositionInSong::canBeRun()
1703 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1706 void JumpToPositionInSong::run()
1708 using Global::wFooter;
1710 const MPD::Song s = myPlaylist->nowPlayingSong();
1712 std::string spos;
1714 Statusbar::ScopedLock slock;
1715 Statusbar::put() << "Position to go (in %/m:ss/seconds(s)): ";
1716 spos = wFooter->prompt();
1719 boost::regex rx;
1720 boost::smatch what;
1721 if (boost::regex_match(spos, what, rx.assign("([0-9]+):([0-9]{2})"))) // mm:ss
1723 auto mins = fromString<unsigned>(what[1]);
1724 auto secs = fromString<unsigned>(what[2]);
1725 boundsCheck(secs, 0u, 60u);
1726 Mpd.Seek(s.getPosition(), mins * 60 + secs);
1728 else if (boost::regex_match(spos, what, rx.assign("([0-9]+)s"))) // position in seconds
1730 auto secs = fromString<unsigned>(what[1]);
1731 Mpd.Seek(s.getPosition(), secs);
1733 else if (boost::regex_match(spos, what, rx.assign("([0-9]+)[%]{0,1}"))) // position in %
1735 auto percent = fromString<unsigned>(what[1]);
1736 boundsCheck(percent, 0u, 100u);
1737 int secs = (percent * s.getDuration()) / 100.0;
1738 Mpd.Seek(s.getPosition(), secs);
1740 else
1741 Statusbar::print("Invalid format ([m]:[ss], [s]s, [%]%, [%] accepted)");
1744 bool SelectItem::canBeRun()
1746 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1747 return m_list != nullptr
1748 && !m_list->empty()
1749 && m_list->currentP()->isSelectable();
1752 void SelectItem::run()
1754 auto current = m_list->currentP();
1755 current->setSelected(!current->isSelected());
1758 bool SelectRange::canBeRun()
1760 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1761 if (m_list == nullptr)
1762 return false;
1763 m_begin = m_list->beginP();
1764 m_end = m_list->endP();
1765 return findRange(m_begin, m_end);
1768 void SelectRange::run()
1770 for (; m_begin != m_end; ++m_begin)
1771 m_begin->setSelected(true);
1772 Statusbar::print("Range selected");
1775 bool ReverseSelection::canBeRun()
1777 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1778 return m_list != nullptr;
1781 void ReverseSelection::run()
1783 for (auto &p : *m_list)
1784 p.setSelected(!p.isSelected());
1785 Statusbar::print("Selection reversed");
1788 bool RemoveSelection::canBeRun()
1790 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1791 return m_list != nullptr;
1794 void RemoveSelection::run()
1796 for (auto &p : *m_list)
1797 p.setSelected(false);
1798 Statusbar::print("Selection removed");
1801 bool SelectAlbum::canBeRun()
1803 auto *w = myScreen->activeWindow();
1804 if (m_list != static_cast<void *>(w))
1805 m_list = dynamic_cast<NC::List *>(w);
1806 if (m_songs != static_cast<void *>(w))
1807 m_songs = dynamic_cast<SongList *>(w);
1808 return m_list != nullptr && !m_list->empty()
1809 && m_songs != nullptr;
1812 void SelectAlbum::run()
1814 const auto front = m_songs->beginS(), current = m_songs->currentS(), end = m_songs->endS();
1815 if (current->song() == nullptr)
1816 return;
1817 auto get = &MPD::Song::getAlbum;
1818 const std::string tag = current->song()->getTags(get);
1819 // go up
1820 for (auto it = current; it != front;)
1822 --it;
1823 if (it->song() == nullptr || it->song()->getTags(get) != tag)
1824 break;
1825 it->properties().setSelected(true);
1827 // go down
1828 for (auto it = current;;)
1830 it->properties().setSelected(true);
1831 if (++it == end)
1832 break;
1833 if (it->song() == nullptr || it->song()->getTags(get) != tag)
1834 break;
1836 Statusbar::print("Album around cursor position selected");
1839 bool SelectFoundItems::canBeRun()
1841 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1842 if (m_list == nullptr || m_list->empty())
1843 return false;
1844 m_searchable = dynamic_cast<Searchable *>(myScreen);
1845 return m_searchable != nullptr && m_searchable->allowsSearching();
1848 void SelectFoundItems::run()
1850 auto current_pos = m_list->choice();
1851 myScreen->activeWindow()->scroll(NC::Scroll::Home);
1852 bool found = m_searchable->search(SearchDirection::Forward, false, false);
1853 if (found)
1855 Statusbar::print("Searching for items...");
1856 m_list->currentP()->setSelected(true);
1857 while (m_searchable->search(SearchDirection::Forward, false, true))
1858 m_list->currentP()->setSelected(true);
1859 Statusbar::print("Found items selected");
1861 m_list->highlight(current_pos);
1864 bool AddSelectedItems::canBeRun()
1866 return myScreen != mySelectedItemsAdder;
1869 void AddSelectedItems::run()
1871 mySelectedItemsAdder->switchTo();
1874 void CropMainPlaylist::run()
1876 auto &w = myPlaylist->main();
1877 // cropping doesn't make sense in this case
1878 if (w.size() <= 1)
1879 return;
1880 if (Config.ask_before_clearing_playlists)
1881 confirmAction("Do you really want to crop main playlist?");
1882 Statusbar::print("Cropping playlist...");
1883 selectCurrentIfNoneSelected(w);
1884 cropPlaylist(w, std::bind(&MPD::Connection::Delete, ph::_1, ph::_2));
1885 Statusbar::print("Playlist cropped");
1888 bool CropPlaylist::canBeRun()
1890 return myScreen == myPlaylistEditor;
1893 void CropPlaylist::run()
1895 auto &w = myPlaylistEditor->Content;
1896 // cropping doesn't make sense in this case
1897 if (w.size() <= 1)
1898 return;
1899 assert(!myPlaylistEditor->Playlists.empty());
1900 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
1901 if (Config.ask_before_clearing_playlists)
1902 confirmAction(boost::format("Do you really want to crop playlist \"%1%\"?") % playlist);
1903 selectCurrentIfNoneSelected(w);
1904 Statusbar::printf("Cropping playlist \"%1%\"...", playlist);
1905 cropPlaylist(w, std::bind(&MPD::Connection::PlaylistDelete, ph::_1, playlist, ph::_2));
1906 Statusbar::printf("Playlist \"%1%\" cropped", playlist);
1909 void ClearMainPlaylist::run()
1911 if (!myPlaylist->main().empty() && Config.ask_before_clearing_playlists)
1912 confirmAction("Do you really want to clear main playlist?");
1913 Mpd.ClearMainPlaylist();
1914 Statusbar::print("Playlist cleared");
1915 myPlaylist->main().reset();
1918 bool ClearPlaylist::canBeRun()
1920 return myScreen == myPlaylistEditor;
1923 void ClearPlaylist::run()
1925 if (myPlaylistEditor->Playlists.empty())
1926 return;
1927 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
1928 if (Config.ask_before_clearing_playlists)
1929 confirmAction(boost::format("Do you really want to clear playlist \"%1%\"?") % playlist);
1930 Mpd.ClearPlaylist(playlist);
1931 Statusbar::printf("Playlist \"%1%\" cleared", playlist);
1934 bool SortPlaylist::canBeRun()
1936 if (myScreen != myPlaylist)
1937 return false;
1938 auto first = myPlaylist->main().begin(), last = myPlaylist->main().end();
1939 return findSelectedRangeAndPrintInfoIfNot(first, last);
1942 void SortPlaylist::run()
1944 mySortPlaylistDialog->switchTo();
1947 bool ReversePlaylist::canBeRun()
1949 if (myScreen != myPlaylist)
1950 return false;
1951 m_begin = myPlaylist->main().begin();
1952 m_end = myPlaylist->main().end();
1953 return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end);
1956 void ReversePlaylist::run()
1958 Statusbar::print("Reversing range...");
1959 Mpd.StartCommandsList();
1960 for (--m_end; m_begin < m_end; ++m_begin, --m_end)
1961 Mpd.Swap(m_begin->value().getPosition(), m_end->value().getPosition());
1962 Mpd.CommitCommandsList();
1963 Statusbar::print("Range reversed");
1966 bool ApplyFilter::canBeRun()
1968 m_filterable = dynamic_cast<Filterable *>(myScreen);
1969 return m_filterable != nullptr
1970 && m_filterable->allowsFiltering();
1973 void ApplyFilter::run()
1975 using Global::wFooter;
1977 std::string filter = m_filterable->currentFilter();
1978 if (!filter.empty())
1980 m_filterable->applyFilter(filter);
1981 myScreen->refreshWindow();
1986 ScopedValue<bool> disabled_autocenter_mode(Config.autocenter_mode, false);
1987 Statusbar::ScopedLock slock;
1988 NC::Window::ScopedPromptHook helper(
1989 *wFooter,
1990 Statusbar::Helpers::ApplyFilterImmediately(m_filterable));
1991 Statusbar::put() << "Apply filter: ";
1992 filter = wFooter->prompt(filter);
1994 catch (NC::PromptAborted &)
1996 m_filterable->applyFilter(filter);
1997 throw;
2000 if (filter.empty())
2001 Statusbar::printf("Filtering disabled");
2002 else
2003 Statusbar::printf("Using filter \"%1%\"", filter);
2005 if (myScreen == myPlaylist)
2006 myPlaylist->reloadTotalLength();
2008 listsChangeFinisher();
2011 bool Find::canBeRun()
2013 return myScreen == myHelp
2014 || myScreen == myLyrics
2015 || myScreen == myLastfm;
2018 void Find::run()
2020 using Global::wFooter;
2022 std::string token;
2024 Statusbar::ScopedLock slock;
2025 Statusbar::put() << "Find: ";
2026 token = wFooter->prompt();
2029 Statusbar::print("Searching...");
2030 auto s = static_cast<Screen<NC::Scrollpad> *>(myScreen);
2031 s->main().removeProperties();
2032 if (token.empty() || s->main().setProperties(NC::Format::Reverse, token, NC::Format::NoReverse, Config.regex_type))
2033 Statusbar::print("Done");
2034 else
2035 Statusbar::print("No matching patterns found");
2036 s->main().flush();
2039 bool FindItemBackward::canBeRun()
2041 auto w = dynamic_cast<Searchable *>(myScreen);
2042 return w && w->allowsSearching();
2045 void FindItemForward::run()
2047 findItem(SearchDirection::Forward);
2048 listsChangeFinisher();
2051 bool FindItemForward::canBeRun()
2053 auto w = dynamic_cast<Searchable *>(myScreen);
2054 return w && w->allowsSearching();
2057 void FindItemBackward::run()
2059 findItem(SearchDirection::Backward);
2060 listsChangeFinisher();
2063 bool NextFoundItem::canBeRun()
2065 return dynamic_cast<Searchable *>(myScreen);
2068 void NextFoundItem::run()
2070 Searchable *w = dynamic_cast<Searchable *>(myScreen);
2071 assert(w != nullptr);
2072 w->search(SearchDirection::Forward, Config.wrapped_search, true);
2073 listsChangeFinisher();
2076 bool PreviousFoundItem::canBeRun()
2078 return dynamic_cast<Searchable *>(myScreen);
2081 void PreviousFoundItem::run()
2083 Searchable *w = dynamic_cast<Searchable *>(myScreen);
2084 assert(w != nullptr);
2085 w->search(SearchDirection::Backward, Config.wrapped_search, true);
2086 listsChangeFinisher();
2089 void ToggleFindMode::run()
2091 Config.wrapped_search = !Config.wrapped_search;
2092 Statusbar::printf("Search mode: %1%",
2093 Config.wrapped_search ? "Wrapped" : "Normal"
2097 void ToggleReplayGainMode::run()
2099 using Global::wFooter;
2101 char rgm = 0;
2103 Statusbar::ScopedLock slock;
2104 Statusbar::put() << "Replay gain mode? "
2105 << "[" << NC::Format::Bold << 'o' << NC::Format::NoBold << "ff"
2106 << "/" << NC::Format::Bold << 't' << NC::Format::NoBold << "rack"
2107 << "/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "lbum"
2108 << "] ";
2109 rgm = Statusbar::Helpers::promptReturnOneOf({'t', 'a', 'o'});
2111 switch (rgm)
2113 case 't':
2114 Mpd.SetReplayGainMode(MPD::rgmTrack);
2115 break;
2116 case 'a':
2117 Mpd.SetReplayGainMode(MPD::rgmAlbum);
2118 break;
2119 case 'o':
2120 Mpd.SetReplayGainMode(MPD::rgmOff);
2121 break;
2122 default: // impossible
2123 throw std::runtime_error(
2124 (boost::format("ToggleReplayGainMode: impossible case reached: %1%") % rgm).str()
2127 Statusbar::printf("Replay gain mode: %1%", Mpd.GetReplayGainMode());
2130 void ToggleAddMode::run()
2132 std::string mode_desc;
2133 switch (Config.space_add_mode)
2135 case SpaceAddMode::AddRemove:
2136 Config.space_add_mode = SpaceAddMode::AlwaysAdd;
2137 mode_desc = "always add an item to playlist";
2138 break;
2139 case SpaceAddMode::AlwaysAdd:
2140 Config.space_add_mode = SpaceAddMode::AddRemove;
2141 mode_desc = "add an item to playlist or remove if already added";
2142 break;
2144 Statusbar::printf("Add mode: %1%", mode_desc);
2147 void ToggleMouse::run()
2149 Config.mouse_support = !Config.mouse_support;
2150 if (Config.mouse_support)
2151 NC::Mouse::enable();
2152 else
2153 NC::Mouse::disable();
2154 Statusbar::printf("Mouse support %1%",
2155 Config.mouse_support ? "enabled" : "disabled"
2159 void ToggleBitrateVisibility::run()
2161 Config.display_bitrate = !Config.display_bitrate;
2162 Statusbar::printf("Bitrate visibility %1%",
2163 Config.display_bitrate ? "enabled" : "disabled"
2167 void AddRandomItems::run()
2169 using Global::wFooter;
2170 char rnd_type = 0;
2172 Statusbar::ScopedLock slock;
2173 Statusbar::put() << "Add random? "
2174 << "[" << NC::Format::Bold << 's' << NC::Format::NoBold << "ongs"
2175 << "/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtists"
2176 << "/" << "album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtists"
2177 << "/" << "al" << NC::Format::Bold << 'b' << NC::Format::NoBold << "ums"
2178 << "] ";
2179 rnd_type = Statusbar::Helpers::promptReturnOneOf({'s', 'a', 'A', 'b'});
2182 mpd_tag_type tag_type = MPD_TAG_ARTIST;
2183 std::string tag_type_str ;
2184 if (rnd_type != 's')
2186 tag_type = charToTagType(rnd_type);
2187 tag_type_str = boost::locale::to_lower(tagTypeToString(tag_type));
2189 else
2190 tag_type_str = "song";
2192 unsigned number;
2194 Statusbar::ScopedLock slock;
2195 Statusbar::put() << "Number of random " << tag_type_str << "s: ";
2196 number = fromString<unsigned>(wFooter->prompt());
2198 if (number > 0)
2200 bool success;
2201 if (rnd_type == 's')
2202 success = Mpd.AddRandomSongs(number, Global::RNG);
2203 else
2204 success = Mpd.AddRandomTag(tag_type, number, Global::RNG);
2205 if (success)
2206 Statusbar::printf("%1% random %2%%3% added to playlist", number, tag_type_str, number == 1 ? "" : "s");
2210 bool ToggleBrowserSortMode::canBeRun()
2212 return myScreen == myBrowser;
2215 void ToggleBrowserSortMode::run()
2217 switch (Config.browser_sort_mode)
2219 case SortMode::Name:
2220 Config.browser_sort_mode = SortMode::ModificationTime;
2221 Statusbar::print("Sort songs by: modification time");
2222 break;
2223 case SortMode::ModificationTime:
2224 Config.browser_sort_mode = SortMode::CustomFormat;
2225 Statusbar::print("Sort songs by: custom format");
2226 break;
2227 case SortMode::CustomFormat:
2228 Config.browser_sort_mode = SortMode::NoOp;
2229 Statusbar::print("Do not sort songs");
2230 break;
2231 case SortMode::NoOp:
2232 Config.browser_sort_mode = SortMode::Name;
2233 Statusbar::print("Sort songs by: name");
2235 if (Config.browser_sort_mode != SortMode::NoOp)
2237 size_t sort_offset = myBrowser->inRootDirectory() ? 0 : 1;
2238 std::sort(myBrowser->main().begin()+sort_offset, myBrowser->main().end(),
2239 LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode)
2244 bool ToggleLibraryTagType::canBeRun()
2246 return (myScreen->isActiveWindow(myLibrary->Tags))
2247 || (myLibrary->columns() == 2 && myScreen->isActiveWindow(myLibrary->Albums));
2250 void ToggleLibraryTagType::run()
2252 using Global::wFooter;
2254 char tag_type = 0;
2256 Statusbar::ScopedLock slock;
2257 Statusbar::put() << "Tag type? "
2258 << "[" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtist"
2259 << "/" << "album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtist"
2260 << "/" << NC::Format::Bold << 'y' << NC::Format::NoBold << "ear"
2261 << "/" << NC::Format::Bold << 'g' << NC::Format::NoBold << "enre"
2262 << "/" << NC::Format::Bold << 'c' << NC::Format::NoBold << "omposer"
2263 << "/" << NC::Format::Bold << 'p' << NC::Format::NoBold << "erformer"
2264 << "] ";
2265 tag_type = Statusbar::Helpers::promptReturnOneOf({'a', 'A', 'y', 'g', 'c', 'p'});
2267 mpd_tag_type new_tagitem = charToTagType(tag_type);
2268 if (new_tagitem != Config.media_lib_primary_tag)
2270 Config.media_lib_primary_tag = new_tagitem;
2271 std::string item_type = tagTypeToString(Config.media_lib_primary_tag);
2272 myLibrary->Tags.setTitle(Config.titles_visibility ? item_type + "s" : "");
2273 myLibrary->Tags.reset();
2274 item_type = boost::locale::to_lower(item_type);
2275 std::string and_mtime = Config.media_library_sort_by_mtime ?
2276 " and mtime" :
2278 if (myLibrary->columns() == 2)
2280 myLibrary->Songs.clear();
2281 myLibrary->Albums.reset();
2282 myLibrary->Albums.clear();
2283 myLibrary->Albums.setTitle(Config.titles_visibility ? "Albums (sorted by " + item_type + and_mtime + ")" : "");
2284 myLibrary->Albums.display();
2286 else
2288 myLibrary->Tags.clear();
2289 myLibrary->Tags.display();
2291 Statusbar::printf("Switched to the list of %1%s", item_type);
2295 bool ToggleMediaLibrarySortMode::canBeRun()
2297 return myScreen == myLibrary;
2300 void ToggleMediaLibrarySortMode::run()
2302 myLibrary->toggleSortMode();
2305 bool FetchLyricsInBackground::canBeRun()
2307 m_hs = dynamic_cast<HasSongs *>(myScreen);
2308 return m_hs != nullptr && m_hs->itemAvailable();
2311 void FetchLyricsInBackground::run()
2313 auto songs = m_hs->getSelectedSongs();
2314 for (const auto &s : songs)
2315 myLyrics->fetchInBackground(s, true);
2316 Statusbar::print("Selected songs queued for lyrics fetching");
2319 bool RefetchLyrics::canBeRun()
2321 return myScreen == myLyrics;
2324 void RefetchLyrics::run()
2326 myLyrics->refetchCurrent();
2329 bool SetSelectedItemsPriority::canBeRun()
2331 if (Mpd.Version() < 17)
2333 Statusbar::print("Priorities are supported in MPD >= 0.17.0");
2334 return false;
2336 return myScreen == myPlaylist && !myPlaylist->main().empty();
2339 void SetSelectedItemsPriority::run()
2341 using Global::wFooter;
2343 unsigned prio;
2345 Statusbar::ScopedLock slock;
2346 Statusbar::put() << "Set priority [0-255]: ";
2347 prio = fromString<unsigned>(wFooter->prompt());
2348 boundsCheck(prio, 0u, 255u);
2350 myPlaylist->setSelectedItemsPriority(prio);
2353 bool ToggleOutput::canBeRun()
2355 #ifdef ENABLE_OUTPUTS
2356 return myScreen == myOutputs;
2357 #else
2358 return false;
2359 #endif // ENABLE_OUTPUTS
2362 void ToggleOutput::run()
2364 #ifdef ENABLE_OUTPUTS
2365 myOutputs->toggleOutput();
2366 #endif // ENABLE_OUTPUTS
2369 bool ToggleVisualizationType::canBeRun()
2371 # ifdef ENABLE_VISUALIZER
2372 return myScreen == myVisualizer;
2373 # else
2374 return false;
2375 # endif // ENABLE_VISUALIZER
2378 void ToggleVisualizationType::run()
2380 # ifdef ENABLE_VISUALIZER
2381 myVisualizer->ToggleVisualizationType();
2382 # endif // ENABLE_VISUALIZER
2385 void ShowSongInfo::run()
2387 mySongInfo->switchTo();
2390 bool ShowArtistInfo::canBeRun()
2392 return myScreen == myLastfm
2393 || (myScreen->isActiveWindow(myLibrary->Tags)
2394 && !myLibrary->Tags.empty()
2395 && Config.media_lib_primary_tag == MPD_TAG_ARTIST)
2396 || currentSong(myScreen);
2399 void ShowArtistInfo::run()
2401 if (myScreen == myLastfm)
2403 myLastfm->switchTo();
2404 return;
2407 std::string artist;
2408 if (myScreen->isActiveWindow(myLibrary->Tags))
2410 assert(!myLibrary->Tags.empty());
2411 assert(Config.media_lib_primary_tag == MPD_TAG_ARTIST);
2412 artist = myLibrary->Tags.current()->value().tag();
2414 else
2416 auto s = currentSong(myScreen);
2417 assert(s);
2418 artist = s->getArtist();
2421 if (!artist.empty())
2423 myLastfm->queueJob(new LastFm::ArtistInfo(artist, Config.lastfm_preferred_language));
2424 if (!isVisible(myLastfm))
2425 myLastfm->switchTo();
2429 bool ShowLyrics::canBeRun()
2431 if (myScreen == myLyrics)
2433 m_song = nullptr;
2434 return true;
2436 else
2438 m_song = currentSong(myScreen);
2439 return m_song != nullptr;
2443 void ShowLyrics::run()
2445 if (m_song != nullptr)
2446 myLyrics->fetch(*m_song);
2447 if (myScreen == myLyrics || !isVisible(myLyrics))
2448 myLyrics->switchTo();
2451 void Quit::run()
2453 ExitMainLoop = true;
2456 void NextScreen::run()
2458 if (Config.screen_switcher_previous)
2460 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2461 tababble->switchToPreviousScreen();
2463 else if (!Config.screen_sequence.empty())
2465 auto screen = nextScreenTypeInSequence(
2466 Config.screen_sequence.begin(),
2467 Config.screen_sequence.end(),
2468 myScreen->type());
2469 toScreen(*screen)->switchTo();
2473 void PreviousScreen::run()
2475 if (Config.screen_switcher_previous)
2477 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2478 tababble->switchToPreviousScreen();
2480 else if (!Config.screen_sequence.empty())
2482 auto screen = nextScreenTypeInSequence(
2483 Config.screen_sequence.rbegin(),
2484 Config.screen_sequence.rend(),
2485 myScreen->type());
2486 toScreen(*screen)->switchTo();
2490 bool ShowHelp::canBeRun()
2492 return myScreen != myHelp
2493 # ifdef HAVE_TAGLIB_H
2494 && myScreen != myTinyTagEditor
2495 # endif // HAVE_TAGLIB_H
2499 void ShowHelp::run()
2501 myHelp->switchTo();
2504 bool ShowPlaylist::canBeRun()
2506 return myScreen != myPlaylist
2507 # ifdef HAVE_TAGLIB_H
2508 && myScreen != myTinyTagEditor
2509 # endif // HAVE_TAGLIB_H
2513 void ShowPlaylist::run()
2515 myPlaylist->switchTo();
2518 bool ShowBrowser::canBeRun()
2520 return myScreen != myBrowser
2521 # ifdef HAVE_TAGLIB_H
2522 && myScreen != myTinyTagEditor
2523 # endif // HAVE_TAGLIB_H
2527 void ShowBrowser::run()
2529 myBrowser->switchTo();
2532 bool ChangeBrowseMode::canBeRun()
2534 return myScreen == myBrowser;
2537 void ChangeBrowseMode::run()
2539 myBrowser->changeBrowseMode();
2542 bool ShowSearchEngine::canBeRun()
2544 return myScreen != mySearcher
2545 # ifdef HAVE_TAGLIB_H
2546 && myScreen != myTinyTagEditor
2547 # endif // HAVE_TAGLIB_H
2551 void ShowSearchEngine::run()
2553 mySearcher->switchTo();
2556 bool ResetSearchEngine::canBeRun()
2558 return myScreen == mySearcher;
2561 void ResetSearchEngine::run()
2563 mySearcher->reset();
2566 bool ShowMediaLibrary::canBeRun()
2568 return myScreen != myLibrary
2569 # ifdef HAVE_TAGLIB_H
2570 && myScreen != myTinyTagEditor
2571 # endif // HAVE_TAGLIB_H
2575 void ShowMediaLibrary::run()
2577 myLibrary->switchTo();
2580 bool ToggleMediaLibraryColumnsMode::canBeRun()
2582 return myScreen == myLibrary;
2585 void ToggleMediaLibraryColumnsMode::run()
2587 myLibrary->toggleColumnsMode();
2588 myLibrary->refresh();
2591 bool ShowPlaylistEditor::canBeRun()
2593 return myScreen != myPlaylistEditor
2594 # ifdef HAVE_TAGLIB_H
2595 && myScreen != myTinyTagEditor
2596 # endif // HAVE_TAGLIB_H
2600 void ShowPlaylistEditor::run()
2602 myPlaylistEditor->switchTo();
2605 bool ShowTagEditor::canBeRun()
2607 # ifdef HAVE_TAGLIB_H
2608 return myScreen != myTagEditor
2609 && myScreen != myTinyTagEditor;
2610 # else
2611 return false;
2612 # endif // HAVE_TAGLIB_H
2615 void ShowTagEditor::run()
2617 # ifdef HAVE_TAGLIB_H
2618 if (isMPDMusicDirSet())
2619 myTagEditor->switchTo();
2620 # endif // HAVE_TAGLIB_H
2623 bool ShowOutputs::canBeRun()
2625 # ifdef ENABLE_OUTPUTS
2626 return myScreen != myOutputs
2627 # ifdef HAVE_TAGLIB_H
2628 && myScreen != myTinyTagEditor
2629 # endif // HAVE_TAGLIB_H
2631 # else
2632 return false;
2633 # endif // ENABLE_OUTPUTS
2636 void ShowOutputs::run()
2638 # ifdef ENABLE_OUTPUTS
2639 myOutputs->switchTo();
2640 # endif // ENABLE_OUTPUTS
2643 bool ShowVisualizer::canBeRun()
2645 # ifdef ENABLE_VISUALIZER
2646 return myScreen != myVisualizer
2647 # ifdef HAVE_TAGLIB_H
2648 && myScreen != myTinyTagEditor
2649 # endif // HAVE_TAGLIB_H
2651 # else
2652 return false;
2653 # endif // ENABLE_VISUALIZER
2656 void ShowVisualizer::run()
2658 # ifdef ENABLE_VISUALIZER
2659 myVisualizer->switchTo();
2660 # endif // ENABLE_VISUALIZER
2663 bool ShowClock::canBeRun()
2665 # ifdef ENABLE_CLOCK
2666 return myScreen != myClock
2667 # ifdef HAVE_TAGLIB_H
2668 && myScreen != myTinyTagEditor
2669 # endif // HAVE_TAGLIB_H
2671 # else
2672 return false;
2673 # endif // ENABLE_CLOCK
2676 void ShowClock::run()
2678 # ifdef ENABLE_CLOCK
2679 myClock->switchTo();
2680 # endif // ENABLE_CLOCK
2683 #ifdef HAVE_TAGLIB_H
2684 bool ShowServerInfo::canBeRun()
2686 return myScreen != myTinyTagEditor;
2688 #endif // HAVE_TAGLIB_H
2690 void ShowServerInfo::run()
2692 myServerInfo->switchTo();
2697 namespace {
2699 void populateActions()
2701 AvailableActions.resize(static_cast<size_t>(Actions::Type::_numberOfActions));
2702 auto insert_action = [](Actions::BaseAction *a) {
2703 AvailableActions.at(static_cast<size_t>(a->type())).reset(a);
2705 insert_action(new Actions::Dummy());
2706 insert_action(new Actions::UpdateEnvironment());
2707 insert_action(new Actions::MouseEvent());
2708 insert_action(new Actions::ScrollUp());
2709 insert_action(new Actions::ScrollDown());
2710 insert_action(new Actions::ScrollUpArtist());
2711 insert_action(new Actions::ScrollUpAlbum());
2712 insert_action(new Actions::ScrollDownArtist());
2713 insert_action(new Actions::ScrollDownAlbum());
2714 insert_action(new Actions::PageUp());
2715 insert_action(new Actions::PageDown());
2716 insert_action(new Actions::MoveHome());
2717 insert_action(new Actions::MoveEnd());
2718 insert_action(new Actions::ToggleInterface());
2719 insert_action(new Actions::JumpToParentDirectory());
2720 insert_action(new Actions::RunAction());
2721 insert_action(new Actions::SelectItem());
2722 insert_action(new Actions::SelectRange());
2723 insert_action(new Actions::PreviousColumn());
2724 insert_action(new Actions::NextColumn());
2725 insert_action(new Actions::MasterScreen());
2726 insert_action(new Actions::SlaveScreen());
2727 insert_action(new Actions::VolumeUp());
2728 insert_action(new Actions::VolumeDown());
2729 insert_action(new Actions::AddItemToPlaylist());
2730 insert_action(new Actions::DeletePlaylistItems());
2731 insert_action(new Actions::DeleteStoredPlaylist());
2732 insert_action(new Actions::DeleteBrowserItems());
2733 insert_action(new Actions::ReplaySong());
2734 insert_action(new Actions::PreviousSong());
2735 insert_action(new Actions::NextSong());
2736 insert_action(new Actions::Pause());
2737 insert_action(new Actions::Stop());
2738 insert_action(new Actions::ExecuteCommand());
2739 insert_action(new Actions::SavePlaylist());
2740 insert_action(new Actions::MoveSortOrderUp());
2741 insert_action(new Actions::MoveSortOrderDown());
2742 insert_action(new Actions::MoveSelectedItemsUp());
2743 insert_action(new Actions::MoveSelectedItemsDown());
2744 insert_action(new Actions::MoveSelectedItemsTo());
2745 insert_action(new Actions::Add());
2746 insert_action(new Actions::PlayItem());
2747 insert_action(new Actions::SeekForward());
2748 insert_action(new Actions::SeekBackward());
2749 insert_action(new Actions::ToggleDisplayMode());
2750 insert_action(new Actions::ToggleSeparatorsBetweenAlbums());
2751 insert_action(new Actions::ToggleLyricsUpdateOnSongChange());
2752 insert_action(new Actions::ToggleLyricsFetcher());
2753 insert_action(new Actions::ToggleFetchingLyricsInBackground());
2754 insert_action(new Actions::TogglePlayingSongCentering());
2755 insert_action(new Actions::UpdateDatabase());
2756 insert_action(new Actions::JumpToPlayingSong());
2757 insert_action(new Actions::ToggleRepeat());
2758 insert_action(new Actions::Shuffle());
2759 insert_action(new Actions::ToggleRandom());
2760 insert_action(new Actions::StartSearching());
2761 insert_action(new Actions::SaveTagChanges());
2762 insert_action(new Actions::ToggleSingle());
2763 insert_action(new Actions::ToggleConsume());
2764 insert_action(new Actions::ToggleCrossfade());
2765 insert_action(new Actions::SetCrossfade());
2766 insert_action(new Actions::SetVolume());
2767 insert_action(new Actions::EnterDirectory());
2768 insert_action(new Actions::EditSong());
2769 insert_action(new Actions::EditLibraryTag());
2770 insert_action(new Actions::EditLibraryAlbum());
2771 insert_action(new Actions::EditDirectoryName());
2772 insert_action(new Actions::EditPlaylistName());
2773 insert_action(new Actions::EditLyrics());
2774 insert_action(new Actions::JumpToBrowser());
2775 insert_action(new Actions::JumpToMediaLibrary());
2776 insert_action(new Actions::JumpToPlaylistEditor());
2777 insert_action(new Actions::ToggleScreenLock());
2778 insert_action(new Actions::JumpToTagEditor());
2779 insert_action(new Actions::JumpToPositionInSong());
2780 insert_action(new Actions::ReverseSelection());
2781 insert_action(new Actions::RemoveSelection());
2782 insert_action(new Actions::SelectAlbum());
2783 insert_action(new Actions::SelectFoundItems());
2784 insert_action(new Actions::AddSelectedItems());
2785 insert_action(new Actions::CropMainPlaylist());
2786 insert_action(new Actions::CropPlaylist());
2787 insert_action(new Actions::ClearMainPlaylist());
2788 insert_action(new Actions::ClearPlaylist());
2789 insert_action(new Actions::SortPlaylist());
2790 insert_action(new Actions::ReversePlaylist());
2791 insert_action(new Actions::ApplyFilter());
2792 insert_action(new Actions::Find());
2793 insert_action(new Actions::FindItemForward());
2794 insert_action(new Actions::FindItemBackward());
2795 insert_action(new Actions::NextFoundItem());
2796 insert_action(new Actions::PreviousFoundItem());
2797 insert_action(new Actions::ToggleFindMode());
2798 insert_action(new Actions::ToggleReplayGainMode());
2799 insert_action(new Actions::ToggleAddMode());
2800 insert_action(new Actions::ToggleMouse());
2801 insert_action(new Actions::ToggleBitrateVisibility());
2802 insert_action(new Actions::AddRandomItems());
2803 insert_action(new Actions::ToggleBrowserSortMode());
2804 insert_action(new Actions::ToggleLibraryTagType());
2805 insert_action(new Actions::ToggleMediaLibrarySortMode());
2806 insert_action(new Actions::FetchLyricsInBackground());
2807 insert_action(new Actions::RefetchLyrics());
2808 insert_action(new Actions::SetSelectedItemsPriority());
2809 insert_action(new Actions::ToggleOutput());
2810 insert_action(new Actions::ToggleVisualizationType());
2811 insert_action(new Actions::ShowSongInfo());
2812 insert_action(new Actions::ShowArtistInfo());
2813 insert_action(new Actions::ShowLyrics());
2814 insert_action(new Actions::Quit());
2815 insert_action(new Actions::NextScreen());
2816 insert_action(new Actions::PreviousScreen());
2817 insert_action(new Actions::ShowHelp());
2818 insert_action(new Actions::ShowPlaylist());
2819 insert_action(new Actions::ShowBrowser());
2820 insert_action(new Actions::ChangeBrowseMode());
2821 insert_action(new Actions::ShowSearchEngine());
2822 insert_action(new Actions::ResetSearchEngine());
2823 insert_action(new Actions::ShowMediaLibrary());
2824 insert_action(new Actions::ToggleMediaLibraryColumnsMode());
2825 insert_action(new Actions::ShowPlaylistEditor());
2826 insert_action(new Actions::ShowTagEditor());
2827 insert_action(new Actions::ShowOutputs());
2828 insert_action(new Actions::ShowVisualizer());
2829 insert_action(new Actions::ShowClock());
2830 insert_action(new Actions::ShowServerInfo());
2831 for (size_t i = 0; i < AvailableActions.size(); ++i)
2833 if (AvailableActions[i] == nullptr)
2834 throw std::logic_error("undefined action at position "
2835 + boost::lexical_cast<std::string>(i));
2839 bool scrollTagCanBeRun(NC::List *&list, const SongList *&songs)
2841 auto w = myScreen->activeWindow();
2842 if (list != static_cast<void *>(w))
2843 list = dynamic_cast<NC::List *>(w);
2844 if (songs != static_cast<void *>(w))
2845 songs = dynamic_cast<SongList *>(w);
2846 return list != nullptr && !list->empty()
2847 && songs != nullptr;
2850 void scrollTagUpRun(NC::List *list, const SongList *songs, MPD::Song::GetFunction get)
2852 const auto front = songs->beginS();
2853 auto it = songs->currentS();
2854 if (it->song() != nullptr)
2856 const std::string tag = it->song()->getTags(get);
2857 while (it != front)
2859 --it;
2860 if (it->song() == nullptr || it->song()->getTags(get) != tag)
2861 break;
2863 list->highlight(it-front);
2867 void scrollTagDownRun(NC::List *list, const SongList *songs, MPD::Song::GetFunction get)
2869 const auto front = songs->beginS(), back = --songs->endS();
2870 auto it = songs->currentS();
2871 if (it->song() != nullptr)
2873 const std::string tag = it->song()->getTags(get);
2874 while (it != back)
2876 ++it;
2877 if (it->song() == nullptr || it->song()->getTags(get) != tag)
2878 break;
2880 list->highlight(it-front);
2884 void seek(SearchDirection sd)
2886 using Global::wHeader;
2887 using Global::wFooter;
2888 using Global::Timer;
2889 using Global::SeekingInProgress;
2891 if (!Status::State::totalTime())
2893 Statusbar::print("Unknown item length");
2894 return;
2897 Progressbar::ScopedLock progressbar_lock;
2898 Statusbar::ScopedLock statusbar_lock;
2900 unsigned songpos = Status::State::elapsedTime();
2901 auto t = Timer;
2903 NC::Window::ScopedTimeout stimeout{*wFooter, BaseScreen::defaultWindowTimeout};
2905 // Accept single action of a given type or action chain for which all actions
2906 // can be run and one of them is of the given type. This will still not work
2907 // in some contrived cases, but allows for more flexibility than accepting
2908 // single actions only.
2909 auto hasRunnableAction = [](BindingsConfiguration::BindingIteratorPair &bindings,
2910 Actions::Type type) {
2911 bool success = false;
2912 for (auto binding = bindings.first; binding != bindings.second; ++binding)
2914 auto &actions = binding->actions();
2915 for (const auto &action : actions)
2917 if (action->canBeRun())
2919 if (action->type() == type)
2920 success = true;
2922 else
2924 success = false;
2925 break;
2928 if (success)
2929 break;
2931 return success;
2934 SeekingInProgress = true;
2935 while (true)
2937 Status::trace();
2939 unsigned howmuch = Config.incremental_seeking
2940 ? (Timer-t).total_seconds()/2+Config.seek_time
2941 : Config.seek_time;
2943 NC::Key::Type input = readKey(*wFooter);
2945 switch (sd)
2947 case SearchDirection::Backward:
2948 if (songpos > 0)
2950 if (songpos < howmuch)
2951 songpos = 0;
2952 else
2953 songpos -= howmuch;
2955 break;
2956 case SearchDirection::Forward:
2957 if (songpos < Status::State::totalTime())
2958 songpos = std::min(songpos + howmuch, Status::State::totalTime());
2959 break;
2962 std::string tracklength;
2963 // FIXME: merge this with the code in status.cpp
2964 switch (Config.design)
2966 case Design::Classic:
2967 tracklength = " [";
2968 if (Config.display_remaining_time)
2970 tracklength += "-";
2971 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
2973 else
2974 tracklength += MPD::Song::ShowTime(songpos);
2975 tracklength += "/";
2976 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
2977 tracklength += "]";
2978 *wFooter << NC::XY(wFooter->getWidth()-tracklength.length(), 1)
2979 << Config.statusbar_time_color
2980 << tracklength
2981 << NC::FormattedColor::End(Config.statusbar_time_color);
2982 break;
2983 case Design::Alternative:
2984 if (Config.display_remaining_time)
2986 tracklength = "-";
2987 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
2989 else
2990 tracklength = MPD::Song::ShowTime(songpos);
2991 tracklength += "/";
2992 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
2993 *wHeader << NC::XY(0, 0)
2994 << Config.statusbar_time_color
2995 << tracklength
2996 << NC::FormattedColor::End(Config.statusbar_time_color)
2997 << " ";
2998 wHeader->refresh();
2999 break;
3001 Progressbar::draw(songpos, Status::State::totalTime());
3002 wFooter->refresh();
3004 auto k = Bindings.get(input);
3005 if (hasRunnableAction(k, Actions::Type::SeekBackward))
3006 sd = SearchDirection::Backward;
3007 else if (hasRunnableAction(k, Actions::Type::SeekForward))
3008 sd = SearchDirection::Forward;
3009 else
3010 break;
3012 SeekingInProgress = false;
3013 Mpd.Seek(Status::State::currentSongPosition(), songpos);
3016 void findItem(const SearchDirection direction)
3018 using Global::wFooter;
3020 Searchable *w = dynamic_cast<Searchable *>(myScreen);
3021 assert(w != nullptr);
3022 assert(w->allowsSearching());
3024 std::string constraint = w->searchConstraint();
3027 ScopedValue<bool> disabled_autocenter_mode(Config.autocenter_mode, false);
3028 Statusbar::ScopedLock slock;
3029 NC::Window::ScopedPromptHook prompt_hook(
3030 *wFooter,
3031 Statusbar::Helpers::FindImmediately(w, direction));
3032 Statusbar::put() << (boost::format("Find %1%: ") % direction).str();
3033 constraint = wFooter->prompt(constraint);
3035 catch (NC::PromptAborted &)
3037 w->setSearchConstraint(constraint);
3038 w->search(direction, Config.wrapped_search, false);
3039 throw;
3042 if (constraint.empty())
3044 Statusbar::printf("Constraint unset");
3045 w->clearSearchConstraint();
3047 else
3048 Statusbar::printf("Using constraint \"%1%\"", constraint);
3051 void listsChangeFinisher()
3053 if (myScreen == myLibrary
3054 || myScreen == myPlaylistEditor
3055 # ifdef HAVE_TAGLIB_H
3056 || myScreen == myTagEditor
3057 # endif // HAVE_TAGLIB_H
3060 if (myScreen->activeWindow() == &myLibrary->Tags)
3062 myLibrary->Albums.clear();
3063 myLibrary->Albums.refresh();
3064 myLibrary->Songs.clear();
3065 myLibrary->Songs.refresh();
3066 myLibrary->updateTimer();
3068 else if (myScreen->activeWindow() == &myLibrary->Albums)
3070 myLibrary->Songs.clear();
3071 myLibrary->Songs.refresh();
3072 myLibrary->updateTimer();
3074 else if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
3076 myPlaylistEditor->Content.clear();
3077 myPlaylistEditor->Content.refresh();
3078 myPlaylistEditor->updateTimer();
3080 # ifdef HAVE_TAGLIB_H
3081 else if (myScreen->activeWindow() == myTagEditor->Dirs)
3083 myTagEditor->Tags->clear();
3085 # endif // HAVE_TAGLIB_H