Fix InternetLyricsFetcher
[ncmpcpp.git] / src / actions.cpp
blob144d4f02a9375dc6e78e879d2d3f939def5ac04c
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/date_time/posix_time/posix_time.hpp>
25 #include <boost/filesystem/operations.hpp>
26 #include <boost/locale/conversion.hpp>
27 #include <boost/lexical_cast.hpp>
28 #include <algorithm>
29 #include <iostream>
31 #include "actions.h"
32 #include "charset.h"
33 #include "config.h"
34 #include "display.h"
35 #include "global.h"
36 #include "mpdpp.h"
37 #include "helpers.h"
38 #include "statusbar.h"
39 #include "utility/comparators.h"
40 #include "utility/conversion.h"
41 #include "utility/scoped_value.h"
43 #include "curses/menu_impl.h"
44 #include "bindings.h"
45 #include "screens/browser.h"
46 #include "screens/clock.h"
47 #include "screens/help.h"
48 #include "screens/media_library.h"
49 #include "screens/lastfm.h"
50 #include "screens/lyrics.h"
51 #include "screens/playlist.h"
52 #include "screens/playlist_editor.h"
53 #include "screens/sort_playlist.h"
54 #include "screens/search_engine.h"
55 #include "screens/sel_items_adder.h"
56 #include "screens/server_info.h"
57 #include "screens/song_info.h"
58 #include "screens/outputs.h"
59 #include "utility/readline.h"
60 #include "utility/string.h"
61 #include "utility/type_conversions.h"
62 #include "screens/tag_editor.h"
63 #include "screens/tiny_tag_editor.h"
64 #include "screens/visualizer.h"
65 #include "title.h"
66 #include "tags.h"
68 #ifdef HAVE_TAGLIB_H
69 # include "fileref.h"
70 # include "tag.h"
71 #endif // HAVE_TAGLIB_H
73 using Global::myScreen;
75 namespace ph = std::placeholders;
77 namespace {
79 std::vector<std::shared_ptr<Actions::BaseAction>> AvailableActions;
81 void populateActions();
83 bool scrollTagCanBeRun(NC::List *&list, const SongList *&songs);
84 void scrollTagUpRun(NC::List *list, const SongList *songs, MPD::Song::GetFunction get);
85 void scrollTagDownRun(NC::List *list, const SongList *songs, MPD::Song::GetFunction get);
87 void seek(SearchDirection sd);
88 void findItem(const SearchDirection direction);
89 void listsChangeFinisher();
91 template <typename Iterator>
92 bool findSelectedRangeAndPrintInfoIfNot(Iterator &first, Iterator &last)
94 bool success = findSelectedRange(first, last);
95 if (!success)
96 Statusbar::print("No range selected");
97 return success;
100 template <typename Iterator>
101 Iterator nextScreenTypeInSequence(Iterator first, Iterator last, ScreenType type)
103 auto it = std::find(first, last, type);
104 if (it == last)
105 return first;
106 else
108 ++it;
109 if (it == last)
110 return first;
111 else
112 return it;
118 namespace Actions {
120 bool OriginalStatusbarVisibility;
121 bool ExitMainLoop = false;
123 size_t HeaderHeight;
124 size_t FooterHeight;
125 size_t FooterStartY;
127 void validateScreenSize()
129 using Global::MainHeight;
131 if (COLS < 30 || MainHeight < 5)
133 NC::destroyScreen();
134 std::cout << "Screen is too small to handle ncmpcpp correctly\n";
135 exit(1);
139 void initializeScreens()
141 myHelp = new Help;
142 myPlaylist = new Playlist;
143 myBrowser = new Browser;
144 mySearcher = new SearchEngine;
145 myLibrary = new MediaLibrary;
146 myPlaylistEditor = new PlaylistEditor;
147 myLyrics = new Lyrics;
148 mySelectedItemsAdder = new SelectedItemsAdder;
149 mySongInfo = new SongInfo;
150 myServerInfo = new ServerInfo;
151 mySortPlaylistDialog = new SortPlaylistDialog;
152 myLastfm = new Lastfm;
154 # ifdef HAVE_TAGLIB_H
155 myTinyTagEditor = new TinyTagEditor;
156 myTagEditor = new TagEditor;
157 # endif // HAVE_TAGLIB_H
159 # ifdef ENABLE_VISUALIZER
160 myVisualizer = new Visualizer;
161 # endif // ENABLE_VISUALIZER
163 # ifdef ENABLE_OUTPUTS
164 myOutputs = new Outputs;
165 # endif // ENABLE_OUTPUTS
167 # ifdef ENABLE_CLOCK
168 myClock = new Clock;
169 # endif // ENABLE_CLOCK
173 void setResizeFlags()
175 myHelp->hasToBeResized = 1;
176 myPlaylist->hasToBeResized = 1;
177 myBrowser->hasToBeResized = 1;
178 mySearcher->hasToBeResized = 1;
179 myLibrary->hasToBeResized = 1;
180 myPlaylistEditor->hasToBeResized = 1;
181 myLyrics->hasToBeResized = 1;
182 mySelectedItemsAdder->hasToBeResized = 1;
183 mySongInfo->hasToBeResized = 1;
184 myServerInfo->hasToBeResized = 1;
185 mySortPlaylistDialog->hasToBeResized = 1;
186 myLastfm->hasToBeResized = 1;
188 # ifdef HAVE_TAGLIB_H
189 myTinyTagEditor->hasToBeResized = 1;
190 myTagEditor->hasToBeResized = 1;
191 # endif // HAVE_TAGLIB_H
193 # ifdef ENABLE_VISUALIZER
194 myVisualizer->hasToBeResized = 1;
195 # endif // ENABLE_VISUALIZER
197 # ifdef ENABLE_OUTPUTS
198 myOutputs->hasToBeResized = 1;
199 # endif // ENABLE_OUTPUTS
201 # ifdef ENABLE_CLOCK
202 myClock->hasToBeResized = 1;
203 # endif // ENABLE_CLOCK
206 void resizeScreen(bool reload_main_window)
208 using Global::MainHeight;
209 using Global::wHeader;
210 using Global::wFooter;
212 // update internal screen dimensions
213 if (reload_main_window)
215 rl_resize_terminal();
216 endwin();
217 refresh();
218 // Remove KEY_RESIZE from input queue, I'm not sure how these make it in.
219 getch();
222 MainHeight = LINES-(Config.design == Design::Alternative ? 7 : 4);
224 validateScreenSize();
226 if (!Config.header_visibility)
227 MainHeight += 2;
228 if (!Config.statusbar_visibility)
229 ++MainHeight;
231 setResizeFlags();
233 applyToVisibleWindows(&BaseScreen::resize);
235 if (Config.header_visibility || Config.design == Design::Alternative)
236 wHeader->resize(COLS, HeaderHeight);
238 FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1);
239 wFooter->moveTo(0, FooterStartY);
240 wFooter->resize(COLS, Config.statusbar_visibility ? 2 : 1);
242 applyToVisibleWindows(&BaseScreen::refresh);
244 Status::Changes::elapsedTime(false);
245 Status::Changes::playerState();
246 // Note: routines for drawing separator if alternative user
247 // interface is active and header is hidden are placed in
248 // NcmpcppStatusChanges.StatusFlags
249 Status::Changes::flags();
250 drawHeader();
251 wFooter->refresh();
252 refresh();
255 void setWindowsDimensions()
257 using Global::MainStartY;
258 using Global::MainHeight;
260 MainStartY = Config.design == Design::Alternative ? 5 : 2;
261 MainHeight = LINES-(Config.design == Design::Alternative ? 7 : 4);
263 if (!Config.header_visibility)
265 MainStartY -= 2;
266 MainHeight += 2;
268 if (!Config.statusbar_visibility)
269 ++MainHeight;
271 HeaderHeight = Config.design == Design::Alternative ? (Config.header_visibility ? 5 : 3) : 2;
272 FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1);
273 FooterHeight = Config.statusbar_visibility ? 2 : 1;
276 void confirmAction(const boost::format &description)
278 Statusbar::ScopedLock slock;
279 Statusbar::put() << description.str()
280 << " [" << NC::Format::Bold << 'y' << NC::Format::NoBold
281 << '/' << NC::Format::Bold << 'n' << NC::Format::NoBold
282 << "] ";
283 char answer = Statusbar::Helpers::promptReturnOneOf({'y', 'n'});
284 if (answer == 'n')
285 throw NC::PromptAborted(std::string(1, answer));
288 bool isMPDMusicDirSet()
290 if (Config.mpd_music_dir.empty())
292 Statusbar::print("Proper mpd_music_dir variable has to be set in configuration file");
293 return false;
295 return true;
298 BaseAction &get(Actions::Type at)
300 if (AvailableActions.empty())
301 populateActions();
302 return *AvailableActions.at(static_cast<size_t>(at));
305 std::shared_ptr<BaseAction> get_(Actions::Type at)
307 if (AvailableActions.empty())
308 populateActions();
309 return AvailableActions.at(static_cast<size_t>(at));
312 std::shared_ptr<BaseAction> get_(const std::string &name)
314 std::shared_ptr<BaseAction> result;
315 if (AvailableActions.empty())
316 populateActions();
317 for (const auto &action : AvailableActions)
319 if (action->name() == name)
321 result = action;
322 break;
325 return result;
328 UpdateEnvironment::UpdateEnvironment()
329 : BaseAction(Type::UpdateEnvironment, "update_environment")
330 , m_past(boost::posix_time::from_time_t(0))
333 void UpdateEnvironment::run(bool update_timer, bool refresh_window, bool mpd_sync)
335 using Global::Timer;
337 // update timer, status if necessary etc.
338 Status::trace(update_timer, true);
340 // show lyrics consumer notification if appropriate
341 if (auto message = myLyrics->tryTakeConsumerMessage())
342 Statusbar::print(*message);
344 // header stuff
345 if ((myScreen == myPlaylist || myScreen == myBrowser || myScreen == myLyrics)
346 && (Timer - m_past > boost::posix_time::milliseconds(500))
349 drawHeader();
350 m_past = Timer;
353 if (refresh_window)
354 myScreen->refreshWindow();
356 // We want to synchronize with MPD during execution of an action chain.
357 if (mpd_sync)
359 int flags = Mpd.noidle();
360 if (flags)
361 Status::update(flags);
365 void UpdateEnvironment::run()
367 run(true, true, true);
370 bool MouseEvent::canBeRun()
372 return Config.mouse_support;
375 void MouseEvent::run()
377 using Global::VolumeState;
378 using Global::wFooter;
380 m_old_mouse_event = m_mouse_event;
381 m_mouse_event = wFooter->getMouseEvent();
383 //Statusbar::printf("(%1%, %2%, %3%)", m_mouse_event.bstate, m_mouse_event.x, m_mouse_event.y);
385 if (m_mouse_event.bstate & BUTTON1_PRESSED
386 && m_mouse_event.y == LINES-(Config.statusbar_visibility ? 2 : 1)
387 ) // progressbar
389 if (Status::State::player() == MPD::psStop)
390 return;
391 Mpd.Seek(Status::State::currentSongPosition(),
392 Status::State::totalTime()*m_mouse_event.x/double(COLS));
394 else if (m_mouse_event.bstate & BUTTON1_PRESSED
395 && (Config.statusbar_visibility || Config.design == Design::Alternative)
396 && Status::State::player() != MPD::psStop
397 && m_mouse_event.y == (Config.design == Design::Alternative ? 1 : LINES-1)
398 && m_mouse_event.x < 9
399 ) // playing/paused
401 Mpd.Toggle();
403 else if ((m_mouse_event.bstate & BUTTON5_PRESSED || m_mouse_event.bstate & BUTTON4_PRESSED)
404 && (Config.header_visibility || Config.design == Design::Alternative)
405 && m_mouse_event.y == 0 && size_t(m_mouse_event.x) > COLS-VolumeState.length()
406 ) // volume
408 if (m_mouse_event.bstate & BUTTON5_PRESSED)
409 get(Type::VolumeDown).execute();
410 else
411 get(Type::VolumeUp).execute();
413 else if (m_mouse_event.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED | BUTTON4_PRESSED | BUTTON5_PRESSED))
414 myScreen->mouseButtonPressed(m_mouse_event);
417 void ScrollUp::run()
419 myScreen->scroll(NC::Scroll::Up);
420 listsChangeFinisher();
423 void ScrollDown::run()
425 myScreen->scroll(NC::Scroll::Down);
426 listsChangeFinisher();
429 bool ScrollUpArtist::canBeRun()
431 return scrollTagCanBeRun(m_list, m_songs);
434 void ScrollUpArtist::run()
436 scrollTagUpRun(m_list, m_songs, &MPD::Song::getArtist);
439 bool ScrollUpAlbum::canBeRun()
441 return scrollTagCanBeRun(m_list, m_songs);
444 void ScrollUpAlbum::run()
446 scrollTagUpRun(m_list, m_songs, &MPD::Song::getAlbum);
449 bool ScrollDownArtist::canBeRun()
451 return scrollTagCanBeRun(m_list, m_songs);
454 void ScrollDownArtist::run()
456 scrollTagDownRun(m_list, m_songs, &MPD::Song::getArtist);
459 bool ScrollDownAlbum::canBeRun()
461 return scrollTagCanBeRun(m_list, m_songs);
464 void ScrollDownAlbum::run()
466 scrollTagDownRun(m_list, m_songs, &MPD::Song::getAlbum);
469 void PageUp::run()
471 myScreen->scroll(NC::Scroll::PageUp);
472 listsChangeFinisher();
475 void PageDown::run()
477 myScreen->scroll(NC::Scroll::PageDown);
478 listsChangeFinisher();
481 void MoveHome::run()
483 myScreen->scroll(NC::Scroll::Home);
484 listsChangeFinisher();
487 void MoveEnd::run()
489 myScreen->scroll(NC::Scroll::End);
490 listsChangeFinisher();
493 void ToggleInterface::run()
495 switch (Config.design)
497 case Design::Classic:
498 Config.design = Design::Alternative;
499 Config.statusbar_visibility = false;
500 break;
501 case Design::Alternative:
502 Config.design = Design::Classic;
503 Config.statusbar_visibility = OriginalStatusbarVisibility;
504 break;
506 setWindowsDimensions();
507 resizeScreen(false);
508 // unlock progressbar
509 Progressbar::ScopedLock();
510 Status::Changes::mixer();
511 Status::Changes::elapsedTime(false);
512 Statusbar::printf("User interface: %1%", Config.design);
515 bool JumpToParentDirectory::canBeRun()
517 return (myScreen == myBrowser)
518 # ifdef HAVE_TAGLIB_H
519 || (myScreen->activeWindow() == myTagEditor->Dirs)
520 # endif // HAVE_TAGLIB_H
524 void JumpToParentDirectory::run()
526 if (myScreen == myBrowser)
528 if (!myBrowser->inRootDirectory())
530 myBrowser->main().reset();
531 myBrowser->enterDirectory();
534 # ifdef HAVE_TAGLIB_H
535 else if (myScreen == myTagEditor)
537 if (myTagEditor->CurrentDir() != "/")
539 myTagEditor->Dirs->reset();
540 myTagEditor->enterDirectory();
543 # endif // HAVE_TAGLIB_H
546 bool RunAction::canBeRun()
548 m_ha = dynamic_cast<HasActions *>(myScreen);
549 return m_ha != nullptr
550 && m_ha->actionRunnable();
553 void RunAction::run()
555 m_ha->runAction();
558 bool PreviousColumn::canBeRun()
560 m_hc = dynamic_cast<HasColumns *>(myScreen);
561 return m_hc != nullptr
562 && m_hc->previousColumnAvailable();
565 void PreviousColumn::run()
567 m_hc->previousColumn();
570 bool NextColumn::canBeRun()
572 m_hc = dynamic_cast<HasColumns *>(myScreen);
573 return m_hc != nullptr
574 && m_hc->nextColumnAvailable();
577 void NextColumn::run()
579 m_hc->nextColumn();
582 bool MasterScreen::canBeRun()
584 using Global::myLockedScreen;
585 using Global::myInactiveScreen;
587 return myLockedScreen
588 && myInactiveScreen
589 && myLockedScreen != myScreen
590 && myScreen->isMergable();
593 void MasterScreen::run()
595 using Global::myInactiveScreen;
596 using Global::myLockedScreen;
598 myInactiveScreen = myScreen;
599 myScreen = myLockedScreen;
600 drawHeader();
603 bool SlaveScreen::canBeRun()
605 using Global::myLockedScreen;
606 using Global::myInactiveScreen;
608 return myLockedScreen
609 && myInactiveScreen
610 && myLockedScreen == myScreen
611 && myScreen->isMergable();
614 void SlaveScreen::run()
616 using Global::myInactiveScreen;
617 using Global::myLockedScreen;
619 myScreen = myInactiveScreen;
620 myInactiveScreen = myLockedScreen;
621 drawHeader();
624 void VolumeUp::run()
626 Mpd.ChangeVolume(static_cast<int>(Config.volume_change_step));
629 void VolumeDown::run()
631 Mpd.ChangeVolume(-static_cast<int>(Config.volume_change_step));
634 bool AddItemToPlaylist::canBeRun()
636 m_hs = dynamic_cast<HasSongs *>(myScreen);
637 return m_hs != nullptr && m_hs->itemAvailable();
640 void AddItemToPlaylist::run()
642 bool success = m_hs->addItemToPlaylist(false);
643 if (success)
645 myScreen->scroll(NC::Scroll::Down);
646 listsChangeFinisher();
650 bool PlayItem::canBeRun()
652 m_hs = dynamic_cast<HasSongs *>(myScreen);
653 return m_hs != nullptr && m_hs->itemAvailable();
656 void PlayItem::run()
658 bool success = m_hs->addItemToPlaylist(true);
659 if (success)
660 listsChangeFinisher();
663 bool DeletePlaylistItems::canBeRun()
665 return (myScreen == myPlaylist && !myPlaylist->main().empty())
666 || (myScreen->isActiveWindow(myPlaylistEditor->Content) && !myPlaylistEditor->Content.empty());
669 void DeletePlaylistItems::run()
671 if (myScreen == myPlaylist)
673 Statusbar::print("Deleting items...");
674 auto delete_fun = std::bind(&MPD::Connection::Delete, ph::_1, ph::_2);
675 deleteSelectedSongs(myPlaylist->main(), delete_fun);
676 Statusbar::print("Item(s) deleted");
678 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
680 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
681 auto delete_fun = std::bind(&MPD::Connection::PlaylistDelete, ph::_1, playlist, ph::_2);
682 Statusbar::print("Deleting items...");
683 deleteSelectedSongs(myPlaylistEditor->Content, delete_fun);
684 Statusbar::print("Item(s) deleted");
688 bool DeleteBrowserItems::canBeRun()
690 auto check_if_deletion_allowed = []() {
691 if (Config.allow_for_physical_item_deletion)
692 return true;
693 else
695 Statusbar::print("Flag \"allow_for_physical_item_deletion\" needs to be enabled in configuration file");
696 return false;
699 return myScreen == myBrowser
700 && !myBrowser->main().empty()
701 && isMPDMusicDirSet()
702 && check_if_deletion_allowed();
705 void DeleteBrowserItems::run()
707 auto get_name = [](const MPD::Item &item) -> std::string {
708 std::string iname;
709 switch (item.type())
711 case MPD::Item::Type::Directory:
712 iname = getBasename(item.directory().path());
713 break;
714 case MPD::Item::Type::Song:
715 iname = item.song().getName();
716 break;
717 case MPD::Item::Type::Playlist:
718 iname = getBasename(item.playlist().path());
719 break;
721 return iname;
724 boost::format question;
725 if (hasSelected(myBrowser->main().begin(), myBrowser->main().end()))
726 question = boost::format("Delete selected items?");
727 else
729 const auto &item = myBrowser->main().current()->value();
730 // parent directories are not accepted (and they
731 // can't be selected, so in other cases it's fine).
732 if (myBrowser->isParentDirectory(item))
733 return;
734 const char msg[] = "Delete \"%1%\"?";
735 question = boost::format(msg) % wideShorten(
736 get_name(item), COLS-const_strlen(msg)-5
739 confirmAction(question);
741 auto items = getSelectedOrCurrent(
742 myBrowser->main().begin(),
743 myBrowser->main().end(),
744 myBrowser->main().current()
746 for (const auto &item : items)
748 myBrowser->remove(item->value());
749 const char msg[] = "Deleted %1% \"%2%\"";
750 Statusbar::printf(msg,
751 itemTypeToString(item->value().type()),
752 wideShorten(get_name(item->value()), COLS-const_strlen(msg))
756 if (!myBrowser->isLocal())
757 Mpd.UpdateDirectory(myBrowser->currentDirectory());
758 myBrowser->requestUpdate();
761 bool DeleteStoredPlaylist::canBeRun()
763 return myScreen->isActiveWindow(myPlaylistEditor->Playlists);
766 void DeleteStoredPlaylist::run()
768 if (myPlaylistEditor->Playlists.empty())
769 return;
770 boost::format question;
771 if (hasSelected(myPlaylistEditor->Playlists.begin(), myPlaylistEditor->Playlists.end()))
772 question = boost::format("Delete selected playlists?");
773 else
774 question = boost::format("Delete playlist \"%1%\"?")
775 % wideShorten(myPlaylistEditor->Playlists.current()->value().path(), COLS-question.size()-10);
776 confirmAction(question);
777 auto list = getSelectedOrCurrent(
778 myPlaylistEditor->Playlists.begin(),
779 myPlaylistEditor->Playlists.end(),
780 myPlaylistEditor->Playlists.current()
782 for (const auto &item : list)
783 Mpd.DeletePlaylist(item->value().path());
784 Statusbar::printf("%1% deleted", list.size() == 1 ? "Playlist" : "Playlists");
785 // force playlists update. this happens automatically, but only after call
786 // to Key::read, therefore when we call PlaylistEditor::Update, it won't
787 // yet see it, so let's point that it needs to update it.
788 myPlaylistEditor->requestPlaylistsUpdate();
791 void ReplaySong::run()
793 if (Status::State::player() != MPD::psStop)
794 Mpd.Seek(Status::State::currentSongPosition(), 0);
797 void PreviousSong::run()
799 Mpd.Prev();
802 void NextSong::run()
804 Mpd.Next();
807 void Pause::run()
809 Mpd.Toggle();
812 void SavePlaylist::run()
814 using Global::wFooter;
816 std::string playlist_name;
818 Statusbar::ScopedLock slock;
819 Statusbar::put() << "Save playlist as: ";
820 playlist_name = wFooter->prompt();
824 Mpd.SavePlaylist(playlist_name);
825 Statusbar::printf("Playlist saved as \"%1%\"", playlist_name);
827 catch (MPD::ServerError &e)
829 if (e.code() == MPD_SERVER_ERROR_EXIST)
831 confirmAction(
832 boost::format("Playlist \"%1%\" already exists, overwrite?") % playlist_name
834 Mpd.DeletePlaylist(playlist_name);
835 Mpd.SavePlaylist(playlist_name);
836 Statusbar::print("Playlist overwritten");
838 else
839 throw;
843 void Stop::run()
845 Mpd.Stop();
848 void ExecuteCommand::run()
850 using Global::wFooter;
852 std::string cmd_name;
854 Statusbar::ScopedLock slock;
855 NC::Window::ScopedPromptHook helper(*wFooter,
856 Statusbar::Helpers::TryExecuteImmediateCommand()
858 Statusbar::put() << NC::Format::Bold << ":" << NC::Format::NoBold;
859 cmd_name = wFooter->prompt();
862 auto cmd = Bindings.findCommand(cmd_name);
863 if (cmd)
865 Statusbar::printf(1, "Executing %1%...", cmd_name);
866 bool res = cmd->binding().execute();
867 Statusbar::printf("Execution of command \"%1%\" %2%.",
868 cmd_name, res ? "successful" : "unsuccessful"
871 else
872 Statusbar::printf("No command named \"%1%\"", cmd_name);
875 bool MoveSortOrderUp::canBeRun()
877 return myScreen == mySortPlaylistDialog;
880 void MoveSortOrderUp::run()
882 mySortPlaylistDialog->moveSortOrderUp();
885 bool MoveSortOrderDown::canBeRun()
887 return myScreen == mySortPlaylistDialog;
890 void MoveSortOrderDown::run()
892 mySortPlaylistDialog->moveSortOrderDown();
895 bool MoveSelectedItemsUp::canBeRun()
897 return ((myScreen == myPlaylist
898 && !myPlaylist->main().empty())
899 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
900 && !myPlaylistEditor->Content.empty()));
903 void MoveSelectedItemsUp::run()
905 const char *filteredMsg = "Moving items up is disabled in filtered playlist";
906 if (myScreen == myPlaylist)
908 if (myPlaylist->main().isFiltered())
909 Statusbar::print(filteredMsg);
910 else
911 moveSelectedItemsUp(
912 myPlaylist->main(),
913 std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
915 else if (myScreen == myPlaylistEditor)
917 if (myPlaylistEditor->Content.isFiltered())
918 Statusbar::print(filteredMsg);
919 else
921 auto playlist = myPlaylistEditor->Playlists.current()->value().path();
922 moveSelectedItemsUp(
923 myPlaylistEditor->Content,
924 std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3));
929 bool MoveSelectedItemsDown::canBeRun()
931 return ((myScreen == myPlaylist
932 && !myPlaylist->main().empty())
933 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
934 && !myPlaylistEditor->Content.empty()));
937 void MoveSelectedItemsDown::run()
939 const char *filteredMsg = "Moving items down is disabled in filtered playlist";
940 if (myScreen == myPlaylist)
942 if (myPlaylist->main().isFiltered())
943 Statusbar::print(filteredMsg);
944 else
945 moveSelectedItemsDown(
946 myPlaylist->main(),
947 std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
949 else if (myScreen == myPlaylistEditor)
951 if (myPlaylistEditor->Content.isFiltered())
952 Statusbar::print(filteredMsg);
953 else
955 auto playlist = myPlaylistEditor->Playlists.current()->value().path();
956 moveSelectedItemsDown(
957 myPlaylistEditor->Content,
958 std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3));
963 bool MoveSelectedItemsTo::canBeRun()
965 return myScreen == myPlaylist
966 || myScreen->isActiveWindow(myPlaylistEditor->Content);
969 void MoveSelectedItemsTo::run()
971 if (myScreen == myPlaylist)
973 if (!myPlaylist->main().empty())
974 moveSelectedItemsTo(myPlaylist->main(), std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
976 else
978 assert(!myPlaylistEditor->Playlists.empty());
979 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
980 auto move_fun = std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3);
981 moveSelectedItemsTo(myPlaylistEditor->Content, move_fun);
985 bool Add::canBeRun()
987 return myScreen != myPlaylistEditor
988 || !myPlaylistEditor->Playlists.empty();
991 void Add::run()
993 using Global::wFooter;
995 std::string path;
997 Statusbar::ScopedLock slock;
998 Statusbar::put() << (myScreen == myPlaylistEditor ? "Add to playlist: " : "Add: ");
999 path = wFooter->prompt();
1002 // confirm when one wants to add the whole database
1003 if (path.empty())
1004 confirmAction("Are you sure you want to add the whole database?");
1006 Statusbar::put() << "Adding...";
1007 wFooter->refresh();
1008 if (myScreen == myPlaylistEditor)
1009 Mpd.AddToPlaylist(myPlaylistEditor->Playlists.current()->value().path(), path);
1010 else
1014 Mpd.Add(path);
1016 catch (MPD::ServerError &err)
1018 // If a path is not a file or directory, assume it is a playlist.
1019 if (err.code() == MPD_SERVER_ERROR_NO_EXIST)
1020 Mpd.LoadPlaylist(path);
1021 else
1022 throw;
1027 bool SeekForward::canBeRun()
1029 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1032 void SeekForward::run()
1034 seek(SearchDirection::Forward);
1037 bool SeekBackward::canBeRun()
1039 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1042 void SeekBackward::run()
1044 seek(SearchDirection::Backward);
1047 bool ToggleDisplayMode::canBeRun()
1049 return myScreen == myPlaylist
1050 || myScreen == myBrowser
1051 || myScreen == mySearcher
1052 || myScreen->isActiveWindow(myPlaylistEditor->Content);
1055 void ToggleDisplayMode::run()
1057 if (myScreen == myPlaylist)
1059 switch (Config.playlist_display_mode)
1061 case DisplayMode::Classic:
1062 Config.playlist_display_mode = DisplayMode::Columns;
1063 myPlaylist->main().setItemDisplayer(std::bind(
1064 Display::SongsInColumns, ph::_1, std::cref(myPlaylist->main())
1066 if (Config.titles_visibility)
1067 myPlaylist->main().setTitle(Display::Columns(myPlaylist->main().getWidth()));
1068 else
1069 myPlaylist->main().setTitle("");
1070 break;
1071 case DisplayMode::Columns:
1072 Config.playlist_display_mode = DisplayMode::Classic;
1073 myPlaylist->main().setItemDisplayer(std::bind(
1074 Display::Songs, ph::_1, std::cref(myPlaylist->main()), std::cref(Config.song_list_format)
1076 myPlaylist->main().setTitle("");
1078 Statusbar::printf("Playlist display mode: %1%", Config.playlist_display_mode);
1080 else if (myScreen == myBrowser)
1082 switch (Config.browser_display_mode)
1084 case DisplayMode::Classic:
1085 Config.browser_display_mode = DisplayMode::Columns;
1086 if (Config.titles_visibility)
1087 myBrowser->main().setTitle(Display::Columns(myBrowser->main().getWidth()));
1088 else
1089 myBrowser->main().setTitle("");
1090 break;
1091 case DisplayMode::Columns:
1092 Config.browser_display_mode = DisplayMode::Classic;
1093 myBrowser->main().setTitle("");
1094 break;
1096 Statusbar::printf("Browser display mode: %1%", Config.browser_display_mode);
1098 else if (myScreen == mySearcher)
1100 switch (Config.search_engine_display_mode)
1102 case DisplayMode::Classic:
1103 Config.search_engine_display_mode = DisplayMode::Columns;
1104 break;
1105 case DisplayMode::Columns:
1106 Config.search_engine_display_mode = DisplayMode::Classic;
1107 break;
1109 Statusbar::printf("Search engine display mode: %1%", Config.search_engine_display_mode);
1110 if (mySearcher->main().size() > SearchEngine::StaticOptions)
1111 mySearcher->main().setTitle(
1112 Config.search_engine_display_mode == DisplayMode::Columns
1113 && Config.titles_visibility
1114 ? Display::Columns(mySearcher->main().getWidth())
1115 : ""
1118 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
1120 switch (Config.playlist_editor_display_mode)
1122 case DisplayMode::Classic:
1123 Config.playlist_editor_display_mode = DisplayMode::Columns;
1124 myPlaylistEditor->Content.setItemDisplayer(std::bind(
1125 Display::SongsInColumns, ph::_1, std::cref(myPlaylistEditor->Content)
1127 break;
1128 case DisplayMode::Columns:
1129 Config.playlist_editor_display_mode = DisplayMode::Classic;
1130 myPlaylistEditor->Content.setItemDisplayer(std::bind(
1131 Display::Songs, ph::_1, std::cref(myPlaylistEditor->Content), std::cref(Config.song_list_format)
1133 break;
1135 Statusbar::printf("Playlist editor display mode: %1%", Config.playlist_editor_display_mode);
1139 bool ToggleSeparatorsBetweenAlbums::canBeRun()
1141 return true;
1144 void ToggleSeparatorsBetweenAlbums::run()
1146 Config.playlist_separate_albums = !Config.playlist_separate_albums;
1147 Statusbar::printf("Separators between albums: %1%",
1148 Config.playlist_separate_albums ? "on" : "off"
1152 bool ToggleLyricsUpdateOnSongChange::canBeRun()
1154 return myScreen == myLyrics;
1157 void ToggleLyricsUpdateOnSongChange::run()
1159 Config.now_playing_lyrics = !Config.now_playing_lyrics;
1160 Statusbar::printf("Update lyrics if song changes: %1%",
1161 Config.now_playing_lyrics ? "on" : "off"
1165 void ToggleLyricsFetcher::run()
1167 myLyrics->toggleFetcher();
1170 void ToggleFetchingLyricsInBackground::run()
1172 Config.fetch_lyrics_in_background = !Config.fetch_lyrics_in_background;
1173 Statusbar::printf("Fetching lyrics for playing songs in background: %1%",
1174 Config.fetch_lyrics_in_background ? "on" : "off");
1177 void TogglePlayingSongCentering::run()
1179 Config.autocenter_mode = !Config.autocenter_mode;
1180 Statusbar::printf("Centering playing song: %1%",
1181 Config.autocenter_mode ? "on" : "off"
1183 if (Config.autocenter_mode)
1185 auto s = myPlaylist->nowPlayingSong();
1186 if (!s.empty())
1187 myPlaylist->locateSong(s);
1191 void UpdateDatabase::run()
1193 if (myScreen == myBrowser)
1194 Mpd.UpdateDirectory(myBrowser->currentDirectory());
1195 # ifdef HAVE_TAGLIB_H
1196 else if (myScreen == myTagEditor)
1197 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1198 # endif // HAVE_TAGLIB_H
1199 else
1200 Mpd.UpdateDirectory("/");
1203 bool JumpToPlayingSong::canBeRun()
1205 m_song = myPlaylist->nowPlayingSong();
1206 return !m_song.empty()
1207 && (myScreen == myPlaylist
1208 || myScreen == myPlaylistEditor
1209 || myScreen == myBrowser
1210 || myScreen == myLibrary);
1213 void JumpToPlayingSong::run()
1215 if (myScreen == myPlaylist)
1217 myPlaylist->locateSong(m_song);
1219 else if (myScreen == myPlaylistEditor)
1221 myPlaylistEditor->locateSong(m_song);
1223 else if (myScreen == myBrowser)
1225 myBrowser->locateSong(m_song);
1227 else if (myScreen == myLibrary)
1229 myLibrary->locateSong(m_song);
1233 void ToggleRepeat::run()
1235 Mpd.SetRepeat(!Status::State::repeat());
1238 bool Shuffle::canBeRun()
1240 if (myScreen != myPlaylist)
1241 return false;
1242 m_begin = myPlaylist->main().begin();
1243 m_end = myPlaylist->main().end();
1244 return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end);
1247 void Shuffle::run()
1249 if (Config.ask_before_shuffling_playlists)
1250 confirmAction("Do you really want to shuffle selected range?");
1251 auto begin = myPlaylist->main().begin();
1252 Mpd.ShuffleRange(m_begin-begin, m_end-begin);
1253 Statusbar::print("Range shuffled");
1256 void ToggleRandom::run()
1258 Mpd.SetRandom(!Status::State::random());
1261 bool StartSearching::canBeRun()
1263 return myScreen == mySearcher && !mySearcher->main()[0].isInactive();
1266 void StartSearching::run()
1268 mySearcher->main().highlight(SearchEngine::SearchButton);
1269 mySearcher->main().setHighlighting(0);
1270 mySearcher->main().refresh();
1271 mySearcher->main().setHighlighting(1);
1272 mySearcher->runAction();
1275 bool SaveTagChanges::canBeRun()
1277 # ifdef HAVE_TAGLIB_H
1278 return myScreen == myTinyTagEditor
1279 || myScreen->activeWindow() == myTagEditor->TagTypes;
1280 # else
1281 return false;
1282 # endif // HAVE_TAGLIB_H
1285 void SaveTagChanges::run()
1287 # ifdef HAVE_TAGLIB_H
1288 if (myScreen == myTinyTagEditor)
1290 myTinyTagEditor->main().highlight(myTinyTagEditor->main().size()-2); // Save
1291 myTinyTagEditor->runAction();
1293 else if (myScreen->activeWindow() == myTagEditor->TagTypes)
1295 myTagEditor->TagTypes->highlight(myTagEditor->TagTypes->size()-1); // Save
1296 myTagEditor->runAction();
1298 # endif // HAVE_TAGLIB_H
1301 void ToggleSingle::run()
1303 Mpd.SetSingle(!Status::State::single());
1306 void ToggleConsume::run()
1308 Mpd.SetConsume(!Status::State::consume());
1311 void ToggleCrossfade::run()
1313 Mpd.SetCrossfade(Status::State::crossfade() ? 0 : Config.crossfade_time);
1316 void SetCrossfade::run()
1318 using Global::wFooter;
1320 Statusbar::ScopedLock slock;
1321 Statusbar::put() << "Set crossfade to: ";
1322 auto crossfade = fromString<unsigned>(wFooter->prompt());
1323 lowerBoundCheck(crossfade, 0u);
1324 Config.crossfade_time = crossfade;
1325 Mpd.SetCrossfade(crossfade);
1328 void SetVolume::run()
1330 using Global::wFooter;
1332 unsigned volume;
1334 Statusbar::ScopedLock slock;
1335 Statusbar::put() << "Set volume to: ";
1336 volume = fromString<unsigned>(wFooter->prompt());
1337 boundsCheck(volume, 0u, 100u);
1338 Mpd.SetVolume(volume);
1340 Statusbar::printf("Volume set to %1%%%", volume);
1343 bool EnterDirectory::canBeRun()
1345 bool result = false;
1346 if (myScreen == myBrowser && !myBrowser->main().empty())
1348 result = myBrowser->main().current()->value().type()
1349 == MPD::Item::Type::Directory;
1351 #ifdef HAVE_TAGLIB_H
1352 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1353 result = true;
1354 #endif // HAVE_TAGLIB_H
1355 return result;
1358 void EnterDirectory::run()
1360 if (myScreen == myBrowser)
1361 myBrowser->enterDirectory();
1362 #ifdef HAVE_TAGLIB_H
1363 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1365 if (!myTagEditor->enterDirectory())
1366 Statusbar::print("No subdirectories found");
1368 #endif // HAVE_TAGLIB_H
1371 bool EditSong::canBeRun()
1373 # ifdef HAVE_TAGLIB_H
1374 m_song = currentSong(myScreen);
1375 return m_song != nullptr && isMPDMusicDirSet();
1376 # else
1377 return false;
1378 # endif // HAVE_TAGLIB_H
1381 void EditSong::run()
1383 # ifdef HAVE_TAGLIB_H
1384 myTinyTagEditor->SetEdited(*m_song);
1385 myTinyTagEditor->switchTo();
1386 # endif // HAVE_TAGLIB_H
1389 bool EditLibraryTag::canBeRun()
1391 # ifdef HAVE_TAGLIB_H
1392 return myScreen->isActiveWindow(myLibrary->Tags)
1393 && !myLibrary->Tags.empty()
1394 && isMPDMusicDirSet();
1395 # else
1396 return false;
1397 # endif // HAVE_TAGLIB_H
1400 void EditLibraryTag::run()
1402 # ifdef HAVE_TAGLIB_H
1403 using Global::wFooter;
1405 std::string new_tag;
1407 Statusbar::ScopedLock slock;
1408 Statusbar::put() << NC::Format::Bold << tagTypeToString(Config.media_lib_primary_tag) << NC::Format::NoBold << ": ";
1409 new_tag = wFooter->prompt(myLibrary->Tags.current()->value().tag());
1411 if (!new_tag.empty() && new_tag != myLibrary->Tags.current()->value().tag())
1413 Statusbar::print("Updating tags...");
1414 Mpd.StartSearch(true);
1415 Mpd.AddSearch(Config.media_lib_primary_tag, myLibrary->Tags.current()->value().tag());
1416 MPD::MutableSong::SetFunction set = tagTypeToSetFunction(Config.media_lib_primary_tag);
1417 assert(set);
1418 bool success = true;
1419 std::string dir_to_update;
1420 for (MPD::SongIterator s = Mpd.CommitSearchSongs(), end; s != end; ++s)
1422 MPD::MutableSong ms = std::move(*s);
1423 ms.setTags(set, new_tag);
1424 Statusbar::printf("Updating tags in \"%1%\"...", ms.getName());
1425 std::string path = Config.mpd_music_dir + ms.getURI();
1426 if (!Tags::write(ms))
1428 success = false;
1429 Statusbar::printf("Error while writing tags to \"%1%\": %2%",
1430 ms.getName(), strerror(errno));
1431 s.finish();
1432 break;
1434 if (dir_to_update.empty())
1435 dir_to_update = ms.getURI();
1436 else
1437 dir_to_update = getSharedDirectory(dir_to_update, ms.getURI());
1439 if (success)
1441 Mpd.UpdateDirectory(dir_to_update);
1442 Statusbar::print("Tags updated successfully");
1445 # endif // HAVE_TAGLIB_H
1448 bool EditLibraryAlbum::canBeRun()
1450 # ifdef HAVE_TAGLIB_H
1451 return myScreen->isActiveWindow(myLibrary->Albums)
1452 && !myLibrary->Albums.empty()
1453 && isMPDMusicDirSet();
1454 # else
1455 return false;
1456 # endif // HAVE_TAGLIB_H
1459 void EditLibraryAlbum::run()
1461 # ifdef HAVE_TAGLIB_H
1462 using Global::wFooter;
1463 // FIXME: merge this and EditLibraryTag. also, prompt on failure if user wants to continue
1464 std::string new_album;
1466 Statusbar::ScopedLock slock;
1467 Statusbar::put() << NC::Format::Bold << "Album: " << NC::Format::NoBold;
1468 new_album = wFooter->prompt(myLibrary->Albums.current()->value().entry().album());
1470 if (!new_album.empty() && new_album != myLibrary->Albums.current()->value().entry().album())
1472 bool success = 1;
1473 Statusbar::print("Updating tags...");
1474 for (size_t i = 0; i < myLibrary->Songs.size(); ++i)
1476 Statusbar::printf("Updating tags in \"%1%\"...", myLibrary->Songs[i].value().getName());
1477 std::string path = Config.mpd_music_dir + myLibrary->Songs[i].value().getURI();
1478 TagLib::FileRef f(path.c_str());
1479 if (f.isNull())
1481 const char msg[] = "Error while opening file \"%1%\"";
1482 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1483 success = 0;
1484 break;
1486 f.tag()->setAlbum(ToWString(new_album));
1487 if (!f.save())
1489 const char msg[] = "Error while writing tags in \"%1%\"";
1490 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1491 success = 0;
1492 break;
1495 if (success)
1497 Mpd.UpdateDirectory(getSharedDirectory(myLibrary->Songs.beginV(), myLibrary->Songs.endV()));
1498 Statusbar::print("Tags updated successfully");
1501 # endif // HAVE_TAGLIB_H
1504 bool EditDirectoryName::canBeRun()
1506 return ((myScreen == myBrowser
1507 && !myBrowser->main().empty()
1508 && myBrowser->main().current()->value().type() == MPD::Item::Type::Directory)
1509 # ifdef HAVE_TAGLIB_H
1510 || (myScreen->activeWindow() == myTagEditor->Dirs
1511 && !myTagEditor->Dirs->empty()
1512 && myTagEditor->Dirs->choice() > 0)
1513 # endif // HAVE_TAGLIB_H
1514 ) && isMPDMusicDirSet();
1517 void EditDirectoryName::run()
1519 using Global::wFooter;
1520 if (myScreen == myBrowser)
1522 std::string old_dir = myBrowser->main().current()->value().directory().path();
1523 std::string new_dir;
1525 Statusbar::ScopedLock slock;
1526 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1527 new_dir = wFooter->prompt(old_dir);
1529 if (!new_dir.empty() && new_dir != old_dir)
1531 std::string full_old_dir;
1532 if (!myBrowser->isLocal())
1533 full_old_dir += Config.mpd_music_dir;
1534 full_old_dir += old_dir;
1535 std::string full_new_dir;
1536 if (!myBrowser->isLocal())
1537 full_new_dir += Config.mpd_music_dir;
1538 full_new_dir += new_dir;
1539 boost::filesystem::rename(full_old_dir, full_new_dir);
1540 const char msg[] = "Directory renamed to \"%1%\"";
1541 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1542 if (!myBrowser->isLocal())
1543 Mpd.UpdateDirectory(getSharedDirectory(old_dir, new_dir));
1544 myBrowser->requestUpdate();
1547 # ifdef HAVE_TAGLIB_H
1548 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1550 std::string old_dir = myTagEditor->Dirs->current()->value().first, new_dir;
1552 Statusbar::ScopedLock slock;
1553 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1554 new_dir = wFooter->prompt(old_dir);
1556 if (!new_dir.empty() && new_dir != old_dir)
1558 std::string full_old_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + old_dir;
1559 std::string full_new_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + new_dir;
1560 if (rename(full_old_dir.c_str(), full_new_dir.c_str()) == 0)
1562 const char msg[] = "Directory renamed to \"%1%\"";
1563 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1564 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1566 else
1568 const char msg[] = "Couldn't rename \"%1%\": %2%";
1569 Statusbar::printf(msg, wideShorten(old_dir, COLS-const_strlen(msg)-25), strerror(errno));
1573 # endif // HAVE_TAGLIB_H
1576 bool EditPlaylistName::canBeRun()
1578 return (myScreen->isActiveWindow(myPlaylistEditor->Playlists)
1579 && !myPlaylistEditor->Playlists.empty())
1580 || (myScreen == myBrowser
1581 && !myBrowser->main().empty()
1582 && myBrowser->main().current()->value().type() == MPD::Item::Type::Playlist);
1585 void EditPlaylistName::run()
1587 using Global::wFooter;
1588 std::string old_name, new_name;
1589 if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
1590 old_name = myPlaylistEditor->Playlists.current()->value().path();
1591 else
1592 old_name = myBrowser->main().current()->value().playlist().path();
1594 Statusbar::ScopedLock slock;
1595 Statusbar::put() << NC::Format::Bold << "Playlist: " << NC::Format::NoBold;
1596 new_name = wFooter->prompt(old_name);
1598 if (!new_name.empty() && new_name != old_name)
1600 Mpd.Rename(old_name, new_name);
1601 const char msg[] = "Playlist renamed to \"%1%\"";
1602 Statusbar::printf(msg, wideShorten(new_name, COLS-const_strlen(msg)));
1606 bool EditLyrics::canBeRun()
1608 return myScreen == myLyrics;
1611 void EditLyrics::run()
1613 myLyrics->edit();
1616 bool JumpToBrowser::canBeRun()
1618 m_song = currentSong(myScreen);
1619 return m_song != nullptr;
1622 void JumpToBrowser::run()
1624 myBrowser->locateSong(*m_song);
1627 bool JumpToMediaLibrary::canBeRun()
1629 m_song = currentSong(myScreen);
1630 return m_song != nullptr;
1633 void JumpToMediaLibrary::run()
1635 myLibrary->locateSong(*m_song);
1638 bool JumpToPlaylistEditor::canBeRun()
1640 return myScreen == myBrowser
1641 && myBrowser->main().current()->value().type() == MPD::Item::Type::Playlist;
1644 void JumpToPlaylistEditor::run()
1646 myPlaylistEditor->locatePlaylist(myBrowser->main().current()->value().playlist());
1649 void ToggleScreenLock::run()
1651 using Global::wFooter;
1652 using Global::myLockedScreen;
1653 const char *msg_unlockable_screen = "Current screen can't be locked";
1654 if (myLockedScreen != nullptr)
1656 BaseScreen::unlock();
1657 Actions::setResizeFlags();
1658 myScreen->resize();
1659 Statusbar::print("Screen unlocked");
1661 else if (!myScreen->isLockable())
1663 Statusbar::print(msg_unlockable_screen);
1665 else
1667 unsigned part = Config.locked_screen_width_part*100;
1668 if (Config.ask_for_locked_screen_width_part)
1670 Statusbar::ScopedLock slock;
1671 Statusbar::put() << "% of the locked screen's width to be reserved (20-80): ";
1672 part = fromString<unsigned>(wFooter->prompt(boost::lexical_cast<std::string>(part)));
1674 boundsCheck(part, 20u, 80u);
1675 Config.locked_screen_width_part = part/100.0;
1676 if (myScreen->lock())
1677 Statusbar::printf("Screen locked (with %1%%% width)", part);
1678 else
1679 Statusbar::print(msg_unlockable_screen);
1683 bool JumpToTagEditor::canBeRun()
1685 # ifdef HAVE_TAGLIB_H
1686 m_song = currentSong(myScreen);
1687 return m_song != nullptr && isMPDMusicDirSet();
1688 # else
1689 return false;
1690 # endif // HAVE_TAGLIB_H
1693 void JumpToTagEditor::run()
1695 # ifdef HAVE_TAGLIB_H
1696 myTagEditor->LocateSong(*m_song);
1697 # endif // HAVE_TAGLIB_H
1700 bool JumpToPositionInSong::canBeRun()
1702 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1705 void JumpToPositionInSong::run()
1707 using Global::wFooter;
1709 const MPD::Song s = myPlaylist->nowPlayingSong();
1711 std::string spos;
1713 Statusbar::ScopedLock slock;
1714 Statusbar::put() << "Position to go (in %/m:ss/seconds(s)): ";
1715 spos = wFooter->prompt();
1718 boost::regex rx;
1719 boost::smatch what;
1720 if (boost::regex_match(spos, what, rx.assign("([0-9]+):([0-9]{2})"))) // mm:ss
1722 auto mins = fromString<unsigned>(what[1]);
1723 auto secs = fromString<unsigned>(what[2]);
1724 boundsCheck(secs, 0u, 60u);
1725 Mpd.Seek(s.getPosition(), mins * 60 + secs);
1727 else if (boost::regex_match(spos, what, rx.assign("([0-9]+)s"))) // position in seconds
1729 auto secs = fromString<unsigned>(what[1]);
1730 Mpd.Seek(s.getPosition(), secs);
1732 else if (boost::regex_match(spos, what, rx.assign("([0-9]+)[%]{0,1}"))) // position in %
1734 auto percent = fromString<unsigned>(what[1]);
1735 boundsCheck(percent, 0u, 100u);
1736 int secs = (percent * s.getDuration()) / 100.0;
1737 Mpd.Seek(s.getPosition(), secs);
1739 else
1740 Statusbar::print("Invalid format ([m]:[ss], [s]s, [%]%, [%] accepted)");
1743 bool SelectItem::canBeRun()
1745 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1746 return m_list != nullptr
1747 && !m_list->empty()
1748 && m_list->currentP()->isSelectable();
1751 void SelectItem::run()
1753 auto current = m_list->currentP();
1754 current->setSelected(!current->isSelected());
1757 bool SelectRange::canBeRun()
1759 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1760 if (m_list == nullptr)
1761 return false;
1762 m_begin = m_list->beginP();
1763 m_end = m_list->endP();
1764 return findRange(m_begin, m_end);
1767 void SelectRange::run()
1769 for (; m_begin != m_end; ++m_begin)
1770 m_begin->setSelected(true);
1771 Statusbar::print("Range selected");
1774 bool ReverseSelection::canBeRun()
1776 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1777 return m_list != nullptr;
1780 void ReverseSelection::run()
1782 for (auto &p : *m_list)
1783 p.setSelected(!p.isSelected());
1784 Statusbar::print("Selection reversed");
1787 bool RemoveSelection::canBeRun()
1789 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1790 return m_list != nullptr;
1793 void RemoveSelection::run()
1795 for (auto &p : *m_list)
1796 p.setSelected(false);
1797 Statusbar::print("Selection removed");
1800 bool SelectAlbum::canBeRun()
1802 auto *w = myScreen->activeWindow();
1803 if (m_list != static_cast<void *>(w))
1804 m_list = dynamic_cast<NC::List *>(w);
1805 if (m_songs != static_cast<void *>(w))
1806 m_songs = dynamic_cast<SongList *>(w);
1807 return m_list != nullptr && !m_list->empty()
1808 && m_songs != nullptr;
1811 void SelectAlbum::run()
1813 const auto front = m_songs->beginS(), current = m_songs->currentS(), end = m_songs->endS();
1814 if (current->song() == nullptr)
1815 return;
1816 auto get = &MPD::Song::getAlbum;
1817 const std::string tag = current->song()->getTags(get);
1818 // go up
1819 for (auto it = current; it != front;)
1821 --it;
1822 if (it->song() == nullptr || it->song()->getTags(get) != tag)
1823 break;
1824 it->properties().setSelected(true);
1826 // go down
1827 for (auto it = current;;)
1829 it->properties().setSelected(true);
1830 if (++it == end)
1831 break;
1832 if (it->song() == nullptr || it->song()->getTags(get) != tag)
1833 break;
1835 Statusbar::print("Album around cursor position selected");
1838 bool SelectFoundItems::canBeRun()
1840 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1841 if (m_list == nullptr || m_list->empty())
1842 return false;
1843 m_searchable = dynamic_cast<Searchable *>(myScreen);
1844 return m_searchable != nullptr && m_searchable->allowsSearching();
1847 void SelectFoundItems::run()
1849 auto current_pos = m_list->choice();
1850 myScreen->activeWindow()->scroll(NC::Scroll::Home);
1851 bool found = m_searchable->search(SearchDirection::Forward, false, false);
1852 if (found)
1854 Statusbar::print("Searching for items...");
1855 m_list->currentP()->setSelected(true);
1856 while (m_searchable->search(SearchDirection::Forward, false, true))
1857 m_list->currentP()->setSelected(true);
1858 Statusbar::print("Found items selected");
1860 m_list->highlight(current_pos);
1863 bool AddSelectedItems::canBeRun()
1865 return myScreen != mySelectedItemsAdder;
1868 void AddSelectedItems::run()
1870 mySelectedItemsAdder->switchTo();
1873 void CropMainPlaylist::run()
1875 auto &w = myPlaylist->main();
1876 // cropping doesn't make sense in this case
1877 if (w.size() <= 1)
1878 return;
1879 if (Config.ask_before_clearing_playlists)
1880 confirmAction("Do you really want to crop main playlist?");
1881 Statusbar::print("Cropping playlist...");
1882 selectCurrentIfNoneSelected(w);
1883 cropPlaylist(w, std::bind(&MPD::Connection::Delete, ph::_1, ph::_2));
1884 Statusbar::print("Playlist cropped");
1887 bool CropPlaylist::canBeRun()
1889 return myScreen == myPlaylistEditor;
1892 void CropPlaylist::run()
1894 auto &w = myPlaylistEditor->Content;
1895 // cropping doesn't make sense in this case
1896 if (w.size() <= 1)
1897 return;
1898 assert(!myPlaylistEditor->Playlists.empty());
1899 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
1900 if (Config.ask_before_clearing_playlists)
1901 confirmAction(boost::format("Do you really want to crop playlist \"%1%\"?") % playlist);
1902 selectCurrentIfNoneSelected(w);
1903 Statusbar::printf("Cropping playlist \"%1%\"...", playlist);
1904 cropPlaylist(w, std::bind(&MPD::Connection::PlaylistDelete, ph::_1, playlist, ph::_2));
1905 Statusbar::printf("Playlist \"%1%\" cropped", playlist);
1908 void ClearMainPlaylist::run()
1910 if (!myPlaylist->main().empty() && Config.ask_before_clearing_playlists)
1911 confirmAction("Do you really want to clear main playlist?");
1912 Mpd.ClearMainPlaylist();
1913 Statusbar::print("Playlist cleared");
1914 myPlaylist->main().reset();
1917 bool ClearPlaylist::canBeRun()
1919 return myScreen == myPlaylistEditor;
1922 void ClearPlaylist::run()
1924 if (myPlaylistEditor->Playlists.empty())
1925 return;
1926 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
1927 if (Config.ask_before_clearing_playlists)
1928 confirmAction(boost::format("Do you really want to clear playlist \"%1%\"?") % playlist);
1929 Mpd.ClearPlaylist(playlist);
1930 Statusbar::printf("Playlist \"%1%\" cleared", playlist);
1933 bool SortPlaylist::canBeRun()
1935 if (myScreen != myPlaylist)
1936 return false;
1937 auto first = myPlaylist->main().begin(), last = myPlaylist->main().end();
1938 return findSelectedRangeAndPrintInfoIfNot(first, last);
1941 void SortPlaylist::run()
1943 mySortPlaylistDialog->switchTo();
1946 bool ReversePlaylist::canBeRun()
1948 if (myScreen != myPlaylist)
1949 return false;
1950 m_begin = myPlaylist->main().begin();
1951 m_end = myPlaylist->main().end();
1952 return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end);
1955 void ReversePlaylist::run()
1957 Statusbar::print("Reversing range...");
1958 Mpd.StartCommandsList();
1959 for (--m_end; m_begin < m_end; ++m_begin, --m_end)
1960 Mpd.Swap(m_begin->value().getPosition(), m_end->value().getPosition());
1961 Mpd.CommitCommandsList();
1962 Statusbar::print("Range reversed");
1965 bool ApplyFilter::canBeRun()
1967 m_filterable = dynamic_cast<Filterable *>(myScreen);
1968 return m_filterable != nullptr
1969 && m_filterable->allowsFiltering();
1972 void ApplyFilter::run()
1974 using Global::wFooter;
1976 std::string filter = m_filterable->currentFilter();
1977 if (!filter.empty())
1979 m_filterable->applyFilter(filter);
1980 myScreen->refreshWindow();
1985 ScopedValue<bool> disabled_autocenter_mode(Config.autocenter_mode, false);
1986 Statusbar::ScopedLock slock;
1987 NC::Window::ScopedPromptHook helper(
1988 *wFooter,
1989 Statusbar::Helpers::ApplyFilterImmediately(m_filterable));
1990 Statusbar::put() << "Apply filter: ";
1991 filter = wFooter->prompt(filter);
1993 catch (NC::PromptAborted &)
1995 m_filterable->applyFilter(filter);
1996 throw;
1999 if (filter.empty())
2000 Statusbar::printf("Filtering disabled");
2001 else
2002 Statusbar::printf("Using filter \"%1%\"", filter);
2004 if (myScreen == myPlaylist)
2005 myPlaylist->reloadTotalLength();
2007 listsChangeFinisher();
2010 bool Find::canBeRun()
2012 return myScreen == myHelp
2013 || myScreen == myLyrics
2014 || myScreen == myLastfm;
2017 void Find::run()
2019 using Global::wFooter;
2021 std::string token;
2023 Statusbar::ScopedLock slock;
2024 Statusbar::put() << "Find: ";
2025 token = wFooter->prompt();
2028 Statusbar::print("Searching...");
2029 auto s = static_cast<Screen<NC::Scrollpad> *>(myScreen);
2030 s->main().removeProperties();
2031 if (token.empty() || s->main().setProperties(NC::Format::Reverse, token, NC::Format::NoReverse, Config.regex_type))
2032 Statusbar::print("Done");
2033 else
2034 Statusbar::print("No matching patterns found");
2035 s->main().flush();
2038 bool FindItemBackward::canBeRun()
2040 auto w = dynamic_cast<Searchable *>(myScreen);
2041 return w && w->allowsSearching();
2044 void FindItemForward::run()
2046 findItem(SearchDirection::Forward);
2047 listsChangeFinisher();
2050 bool FindItemForward::canBeRun()
2052 auto w = dynamic_cast<Searchable *>(myScreen);
2053 return w && w->allowsSearching();
2056 void FindItemBackward::run()
2058 findItem(SearchDirection::Backward);
2059 listsChangeFinisher();
2062 bool NextFoundItem::canBeRun()
2064 return dynamic_cast<Searchable *>(myScreen);
2067 void NextFoundItem::run()
2069 Searchable *w = dynamic_cast<Searchable *>(myScreen);
2070 assert(w != nullptr);
2071 w->search(SearchDirection::Forward, Config.wrapped_search, true);
2072 listsChangeFinisher();
2075 bool PreviousFoundItem::canBeRun()
2077 return dynamic_cast<Searchable *>(myScreen);
2080 void PreviousFoundItem::run()
2082 Searchable *w = dynamic_cast<Searchable *>(myScreen);
2083 assert(w != nullptr);
2084 w->search(SearchDirection::Backward, Config.wrapped_search, true);
2085 listsChangeFinisher();
2088 void ToggleFindMode::run()
2090 Config.wrapped_search = !Config.wrapped_search;
2091 Statusbar::printf("Search mode: %1%",
2092 Config.wrapped_search ? "Wrapped" : "Normal"
2096 void ToggleReplayGainMode::run()
2098 using Global::wFooter;
2100 char rgm = 0;
2102 Statusbar::ScopedLock slock;
2103 Statusbar::put() << "Replay gain mode? "
2104 << "[" << NC::Format::Bold << 'o' << NC::Format::NoBold << "ff"
2105 << "/" << NC::Format::Bold << 't' << NC::Format::NoBold << "rack"
2106 << "/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "lbum"
2107 << "] ";
2108 rgm = Statusbar::Helpers::promptReturnOneOf({'t', 'a', 'o'});
2110 switch (rgm)
2112 case 't':
2113 Mpd.SetReplayGainMode(MPD::rgmTrack);
2114 break;
2115 case 'a':
2116 Mpd.SetReplayGainMode(MPD::rgmAlbum);
2117 break;
2118 case 'o':
2119 Mpd.SetReplayGainMode(MPD::rgmOff);
2120 break;
2121 default: // impossible
2122 throw std::runtime_error(
2123 (boost::format("ToggleReplayGainMode: impossible case reached: %1%") % rgm).str()
2126 Statusbar::printf("Replay gain mode: %1%", Mpd.GetReplayGainMode());
2129 void ToggleAddMode::run()
2131 std::string mode_desc;
2132 switch (Config.space_add_mode)
2134 case SpaceAddMode::AddRemove:
2135 Config.space_add_mode = SpaceAddMode::AlwaysAdd;
2136 mode_desc = "always add an item to playlist";
2137 break;
2138 case SpaceAddMode::AlwaysAdd:
2139 Config.space_add_mode = SpaceAddMode::AddRemove;
2140 mode_desc = "add an item to playlist or remove if already added";
2141 break;
2143 Statusbar::printf("Add mode: %1%", mode_desc);
2146 void ToggleMouse::run()
2148 Config.mouse_support = !Config.mouse_support;
2149 if (Config.mouse_support)
2150 NC::Mouse::enable();
2151 else
2152 NC::Mouse::disable();
2153 Statusbar::printf("Mouse support %1%",
2154 Config.mouse_support ? "enabled" : "disabled"
2158 void ToggleBitrateVisibility::run()
2160 Config.display_bitrate = !Config.display_bitrate;
2161 Statusbar::printf("Bitrate visibility %1%",
2162 Config.display_bitrate ? "enabled" : "disabled"
2166 void AddRandomItems::run()
2168 using Global::wFooter;
2169 char rnd_type = 0;
2171 Statusbar::ScopedLock slock;
2172 Statusbar::put() << "Add random? "
2173 << "[" << NC::Format::Bold << 's' << NC::Format::NoBold << "ongs"
2174 << "/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtists"
2175 << "/" << "album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtists"
2176 << "/" << "al" << NC::Format::Bold << 'b' << NC::Format::NoBold << "ums"
2177 << "] ";
2178 rnd_type = Statusbar::Helpers::promptReturnOneOf({'s', 'a', 'A', 'b'});
2181 mpd_tag_type tag_type = MPD_TAG_ARTIST;
2182 std::string tag_type_str ;
2183 if (rnd_type != 's')
2185 tag_type = charToTagType(rnd_type);
2186 tag_type_str = boost::locale::to_lower(tagTypeToString(tag_type));
2188 else
2189 tag_type_str = "song";
2191 unsigned number;
2193 Statusbar::ScopedLock slock;
2194 Statusbar::put() << "Number of random " << tag_type_str << "s: ";
2195 number = fromString<unsigned>(wFooter->prompt());
2197 if (number > 0)
2199 bool success;
2200 if (rnd_type == 's')
2201 success = Mpd.AddRandomSongs(number, Global::RNG);
2202 else
2203 success = Mpd.AddRandomTag(tag_type, number, Global::RNG);
2204 if (success)
2205 Statusbar::printf("%1% random %2%%3% added to playlist", number, tag_type_str, number == 1 ? "" : "s");
2209 bool ToggleBrowserSortMode::canBeRun()
2211 return myScreen == myBrowser;
2214 void ToggleBrowserSortMode::run()
2216 switch (Config.browser_sort_mode)
2218 case SortMode::Name:
2219 Config.browser_sort_mode = SortMode::ModificationTime;
2220 Statusbar::print("Sort songs by: modification time");
2221 break;
2222 case SortMode::ModificationTime:
2223 Config.browser_sort_mode = SortMode::CustomFormat;
2224 Statusbar::print("Sort songs by: custom format");
2225 break;
2226 case SortMode::CustomFormat:
2227 Config.browser_sort_mode = SortMode::NoOp;
2228 Statusbar::print("Do not sort songs");
2229 break;
2230 case SortMode::NoOp:
2231 Config.browser_sort_mode = SortMode::Name;
2232 Statusbar::print("Sort songs by: name");
2234 if (Config.browser_sort_mode != SortMode::NoOp)
2236 size_t sort_offset = myBrowser->inRootDirectory() ? 0 : 1;
2237 std::sort(myBrowser->main().begin()+sort_offset, myBrowser->main().end(),
2238 LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode)
2243 bool ToggleLibraryTagType::canBeRun()
2245 return (myScreen->isActiveWindow(myLibrary->Tags))
2246 || (myLibrary->columns() == 2 && myScreen->isActiveWindow(myLibrary->Albums));
2249 void ToggleLibraryTagType::run()
2251 using Global::wFooter;
2253 char tag_type = 0;
2255 Statusbar::ScopedLock slock;
2256 Statusbar::put() << "Tag type? "
2257 << "[" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtist"
2258 << "/" << "album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtist"
2259 << "/" << NC::Format::Bold << 'y' << NC::Format::NoBold << "ear"
2260 << "/" << NC::Format::Bold << 'g' << NC::Format::NoBold << "enre"
2261 << "/" << NC::Format::Bold << 'c' << NC::Format::NoBold << "omposer"
2262 << "/" << NC::Format::Bold << 'p' << NC::Format::NoBold << "erformer"
2263 << "] ";
2264 tag_type = Statusbar::Helpers::promptReturnOneOf({'a', 'A', 'y', 'g', 'c', 'p'});
2266 mpd_tag_type new_tagitem = charToTagType(tag_type);
2267 if (new_tagitem != Config.media_lib_primary_tag)
2269 Config.media_lib_primary_tag = new_tagitem;
2270 std::string item_type = tagTypeToString(Config.media_lib_primary_tag);
2271 myLibrary->Tags.setTitle(Config.titles_visibility ? item_type + "s" : "");
2272 myLibrary->Tags.reset();
2273 item_type = boost::locale::to_lower(item_type);
2274 std::string and_mtime = Config.media_library_sort_by_mtime ?
2275 " and mtime" :
2277 if (myLibrary->columns() == 2)
2279 myLibrary->Songs.clear();
2280 myLibrary->Albums.reset();
2281 myLibrary->Albums.clear();
2282 myLibrary->Albums.setTitle(Config.titles_visibility ? "Albums (sorted by " + item_type + and_mtime + ")" : "");
2283 myLibrary->Albums.display();
2285 else
2287 myLibrary->Tags.clear();
2288 myLibrary->Tags.display();
2290 Statusbar::printf("Switched to the list of %1%s", item_type);
2294 bool ToggleMediaLibrarySortMode::canBeRun()
2296 return myScreen == myLibrary;
2299 void ToggleMediaLibrarySortMode::run()
2301 myLibrary->toggleSortMode();
2304 bool FetchLyricsInBackground::canBeRun()
2306 m_hs = dynamic_cast<HasSongs *>(myScreen);
2307 return m_hs != nullptr && m_hs->itemAvailable();
2310 void FetchLyricsInBackground::run()
2312 auto songs = m_hs->getSelectedSongs();
2313 for (const auto &s : songs)
2314 myLyrics->fetchInBackground(s, true);
2315 Statusbar::print("Selected songs queued for lyrics fetching");
2318 bool RefetchLyrics::canBeRun()
2320 return myScreen == myLyrics;
2323 void RefetchLyrics::run()
2325 myLyrics->refetchCurrent();
2328 bool SetSelectedItemsPriority::canBeRun()
2330 if (Mpd.Version() < 17)
2332 Statusbar::print("Priorities are supported in MPD >= 0.17.0");
2333 return false;
2335 return myScreen == myPlaylist && !myPlaylist->main().empty();
2338 void SetSelectedItemsPriority::run()
2340 using Global::wFooter;
2342 unsigned prio;
2344 Statusbar::ScopedLock slock;
2345 Statusbar::put() << "Set priority [0-255]: ";
2346 prio = fromString<unsigned>(wFooter->prompt());
2347 boundsCheck(prio, 0u, 255u);
2349 myPlaylist->setSelectedItemsPriority(prio);
2352 bool ToggleOutput::canBeRun()
2354 #ifdef ENABLE_OUTPUTS
2355 return myScreen == myOutputs;
2356 #else
2357 return false;
2358 #endif // ENABLE_OUTPUTS
2361 void ToggleOutput::run()
2363 #ifdef ENABLE_OUTPUTS
2364 myOutputs->toggleOutput();
2365 #endif // ENABLE_OUTPUTS
2368 bool ToggleVisualizationType::canBeRun()
2370 # ifdef ENABLE_VISUALIZER
2371 return myScreen == myVisualizer;
2372 # else
2373 return false;
2374 # endif // ENABLE_VISUALIZER
2377 void ToggleVisualizationType::run()
2379 # ifdef ENABLE_VISUALIZER
2380 myVisualizer->ToggleVisualizationType();
2381 # endif // ENABLE_VISUALIZER
2384 void ShowSongInfo::run()
2386 mySongInfo->switchTo();
2389 bool ShowArtistInfo::canBeRun()
2391 return myScreen == myLastfm
2392 || (myScreen->isActiveWindow(myLibrary->Tags)
2393 && !myLibrary->Tags.empty()
2394 && Config.media_lib_primary_tag == MPD_TAG_ARTIST)
2395 || currentSong(myScreen);
2398 void ShowArtistInfo::run()
2400 if (myScreen == myLastfm)
2402 myLastfm->switchTo();
2403 return;
2406 std::string artist;
2407 if (myScreen->isActiveWindow(myLibrary->Tags))
2409 assert(!myLibrary->Tags.empty());
2410 assert(Config.media_lib_primary_tag == MPD_TAG_ARTIST);
2411 artist = myLibrary->Tags.current()->value().tag();
2413 else
2415 auto s = currentSong(myScreen);
2416 assert(s);
2417 artist = s->getArtist();
2420 if (!artist.empty())
2422 myLastfm->queueJob(new LastFm::ArtistInfo(artist, Config.lastfm_preferred_language));
2423 if (!isVisible(myLastfm))
2424 myLastfm->switchTo();
2428 bool ShowLyrics::canBeRun()
2430 if (myScreen == myLyrics)
2432 m_song = nullptr;
2433 return true;
2435 else
2437 m_song = currentSong(myScreen);
2438 return m_song != nullptr;
2442 void ShowLyrics::run()
2444 if (m_song != nullptr)
2445 myLyrics->fetch(*m_song);
2446 if (myScreen == myLyrics || !isVisible(myLyrics))
2447 myLyrics->switchTo();
2450 void Quit::run()
2452 ExitMainLoop = true;
2455 void NextScreen::run()
2457 if (Config.screen_switcher_previous)
2459 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2460 tababble->switchToPreviousScreen();
2462 else if (!Config.screen_sequence.empty())
2464 auto screen = nextScreenTypeInSequence(
2465 Config.screen_sequence.begin(),
2466 Config.screen_sequence.end(),
2467 myScreen->type());
2468 toScreen(*screen)->switchTo();
2472 void PreviousScreen::run()
2474 if (Config.screen_switcher_previous)
2476 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2477 tababble->switchToPreviousScreen();
2479 else if (!Config.screen_sequence.empty())
2481 auto screen = nextScreenTypeInSequence(
2482 Config.screen_sequence.rbegin(),
2483 Config.screen_sequence.rend(),
2484 myScreen->type());
2485 toScreen(*screen)->switchTo();
2489 bool ShowHelp::canBeRun()
2491 return myScreen != myHelp
2492 # ifdef HAVE_TAGLIB_H
2493 && myScreen != myTinyTagEditor
2494 # endif // HAVE_TAGLIB_H
2498 void ShowHelp::run()
2500 myHelp->switchTo();
2503 bool ShowPlaylist::canBeRun()
2505 return myScreen != myPlaylist
2506 # ifdef HAVE_TAGLIB_H
2507 && myScreen != myTinyTagEditor
2508 # endif // HAVE_TAGLIB_H
2512 void ShowPlaylist::run()
2514 myPlaylist->switchTo();
2517 bool ShowBrowser::canBeRun()
2519 return myScreen != myBrowser
2520 # ifdef HAVE_TAGLIB_H
2521 && myScreen != myTinyTagEditor
2522 # endif // HAVE_TAGLIB_H
2526 void ShowBrowser::run()
2528 myBrowser->switchTo();
2531 bool ChangeBrowseMode::canBeRun()
2533 return myScreen == myBrowser;
2536 void ChangeBrowseMode::run()
2538 myBrowser->changeBrowseMode();
2541 bool ShowSearchEngine::canBeRun()
2543 return myScreen != mySearcher
2544 # ifdef HAVE_TAGLIB_H
2545 && myScreen != myTinyTagEditor
2546 # endif // HAVE_TAGLIB_H
2550 void ShowSearchEngine::run()
2552 mySearcher->switchTo();
2555 bool ResetSearchEngine::canBeRun()
2557 return myScreen == mySearcher;
2560 void ResetSearchEngine::run()
2562 mySearcher->reset();
2565 bool ShowMediaLibrary::canBeRun()
2567 return myScreen != myLibrary
2568 # ifdef HAVE_TAGLIB_H
2569 && myScreen != myTinyTagEditor
2570 # endif // HAVE_TAGLIB_H
2574 void ShowMediaLibrary::run()
2576 myLibrary->switchTo();
2579 bool ToggleMediaLibraryColumnsMode::canBeRun()
2581 return myScreen == myLibrary;
2584 void ToggleMediaLibraryColumnsMode::run()
2586 myLibrary->toggleColumnsMode();
2587 myLibrary->refresh();
2590 bool ShowPlaylistEditor::canBeRun()
2592 return myScreen != myPlaylistEditor
2593 # ifdef HAVE_TAGLIB_H
2594 && myScreen != myTinyTagEditor
2595 # endif // HAVE_TAGLIB_H
2599 void ShowPlaylistEditor::run()
2601 myPlaylistEditor->switchTo();
2604 bool ShowTagEditor::canBeRun()
2606 # ifdef HAVE_TAGLIB_H
2607 return myScreen != myTagEditor
2608 && myScreen != myTinyTagEditor;
2609 # else
2610 return false;
2611 # endif // HAVE_TAGLIB_H
2614 void ShowTagEditor::run()
2616 # ifdef HAVE_TAGLIB_H
2617 if (isMPDMusicDirSet())
2618 myTagEditor->switchTo();
2619 # endif // HAVE_TAGLIB_H
2622 bool ShowOutputs::canBeRun()
2624 # ifdef ENABLE_OUTPUTS
2625 return myScreen != myOutputs
2626 # ifdef HAVE_TAGLIB_H
2627 && myScreen != myTinyTagEditor
2628 # endif // HAVE_TAGLIB_H
2630 # else
2631 return false;
2632 # endif // ENABLE_OUTPUTS
2635 void ShowOutputs::run()
2637 # ifdef ENABLE_OUTPUTS
2638 myOutputs->switchTo();
2639 # endif // ENABLE_OUTPUTS
2642 bool ShowVisualizer::canBeRun()
2644 # ifdef ENABLE_VISUALIZER
2645 return myScreen != myVisualizer
2646 # ifdef HAVE_TAGLIB_H
2647 && myScreen != myTinyTagEditor
2648 # endif // HAVE_TAGLIB_H
2650 # else
2651 return false;
2652 # endif // ENABLE_VISUALIZER
2655 void ShowVisualizer::run()
2657 # ifdef ENABLE_VISUALIZER
2658 myVisualizer->switchTo();
2659 # endif // ENABLE_VISUALIZER
2662 bool ShowClock::canBeRun()
2664 # ifdef ENABLE_CLOCK
2665 return myScreen != myClock
2666 # ifdef HAVE_TAGLIB_H
2667 && myScreen != myTinyTagEditor
2668 # endif // HAVE_TAGLIB_H
2670 # else
2671 return false;
2672 # endif // ENABLE_CLOCK
2675 void ShowClock::run()
2677 # ifdef ENABLE_CLOCK
2678 myClock->switchTo();
2679 # endif // ENABLE_CLOCK
2682 #ifdef HAVE_TAGLIB_H
2683 bool ShowServerInfo::canBeRun()
2685 return myScreen != myTinyTagEditor;
2687 #endif // HAVE_TAGLIB_H
2689 void ShowServerInfo::run()
2691 myServerInfo->switchTo();
2696 namespace {
2698 void populateActions()
2700 AvailableActions.resize(static_cast<size_t>(Actions::Type::_numberOfActions));
2701 auto insert_action = [](Actions::BaseAction *a) {
2702 AvailableActions.at(static_cast<size_t>(a->type())).reset(a);
2704 insert_action(new Actions::Dummy());
2705 insert_action(new Actions::UpdateEnvironment());
2706 insert_action(new Actions::MouseEvent());
2707 insert_action(new Actions::ScrollUp());
2708 insert_action(new Actions::ScrollDown());
2709 insert_action(new Actions::ScrollUpArtist());
2710 insert_action(new Actions::ScrollUpAlbum());
2711 insert_action(new Actions::ScrollDownArtist());
2712 insert_action(new Actions::ScrollDownAlbum());
2713 insert_action(new Actions::PageUp());
2714 insert_action(new Actions::PageDown());
2715 insert_action(new Actions::MoveHome());
2716 insert_action(new Actions::MoveEnd());
2717 insert_action(new Actions::ToggleInterface());
2718 insert_action(new Actions::JumpToParentDirectory());
2719 insert_action(new Actions::RunAction());
2720 insert_action(new Actions::SelectItem());
2721 insert_action(new Actions::SelectRange());
2722 insert_action(new Actions::PreviousColumn());
2723 insert_action(new Actions::NextColumn());
2724 insert_action(new Actions::MasterScreen());
2725 insert_action(new Actions::SlaveScreen());
2726 insert_action(new Actions::VolumeUp());
2727 insert_action(new Actions::VolumeDown());
2728 insert_action(new Actions::AddItemToPlaylist());
2729 insert_action(new Actions::DeletePlaylistItems());
2730 insert_action(new Actions::DeleteStoredPlaylist());
2731 insert_action(new Actions::DeleteBrowserItems());
2732 insert_action(new Actions::ReplaySong());
2733 insert_action(new Actions::PreviousSong());
2734 insert_action(new Actions::NextSong());
2735 insert_action(new Actions::Pause());
2736 insert_action(new Actions::Stop());
2737 insert_action(new Actions::ExecuteCommand());
2738 insert_action(new Actions::SavePlaylist());
2739 insert_action(new Actions::MoveSortOrderUp());
2740 insert_action(new Actions::MoveSortOrderDown());
2741 insert_action(new Actions::MoveSelectedItemsUp());
2742 insert_action(new Actions::MoveSelectedItemsDown());
2743 insert_action(new Actions::MoveSelectedItemsTo());
2744 insert_action(new Actions::Add());
2745 insert_action(new Actions::PlayItem());
2746 insert_action(new Actions::SeekForward());
2747 insert_action(new Actions::SeekBackward());
2748 insert_action(new Actions::ToggleDisplayMode());
2749 insert_action(new Actions::ToggleSeparatorsBetweenAlbums());
2750 insert_action(new Actions::ToggleLyricsUpdateOnSongChange());
2751 insert_action(new Actions::ToggleLyricsFetcher());
2752 insert_action(new Actions::ToggleFetchingLyricsInBackground());
2753 insert_action(new Actions::TogglePlayingSongCentering());
2754 insert_action(new Actions::UpdateDatabase());
2755 insert_action(new Actions::JumpToPlayingSong());
2756 insert_action(new Actions::ToggleRepeat());
2757 insert_action(new Actions::Shuffle());
2758 insert_action(new Actions::ToggleRandom());
2759 insert_action(new Actions::StartSearching());
2760 insert_action(new Actions::SaveTagChanges());
2761 insert_action(new Actions::ToggleSingle());
2762 insert_action(new Actions::ToggleConsume());
2763 insert_action(new Actions::ToggleCrossfade());
2764 insert_action(new Actions::SetCrossfade());
2765 insert_action(new Actions::SetVolume());
2766 insert_action(new Actions::EnterDirectory());
2767 insert_action(new Actions::EditSong());
2768 insert_action(new Actions::EditLibraryTag());
2769 insert_action(new Actions::EditLibraryAlbum());
2770 insert_action(new Actions::EditDirectoryName());
2771 insert_action(new Actions::EditPlaylistName());
2772 insert_action(new Actions::EditLyrics());
2773 insert_action(new Actions::JumpToBrowser());
2774 insert_action(new Actions::JumpToMediaLibrary());
2775 insert_action(new Actions::JumpToPlaylistEditor());
2776 insert_action(new Actions::ToggleScreenLock());
2777 insert_action(new Actions::JumpToTagEditor());
2778 insert_action(new Actions::JumpToPositionInSong());
2779 insert_action(new Actions::ReverseSelection());
2780 insert_action(new Actions::RemoveSelection());
2781 insert_action(new Actions::SelectAlbum());
2782 insert_action(new Actions::SelectFoundItems());
2783 insert_action(new Actions::AddSelectedItems());
2784 insert_action(new Actions::CropMainPlaylist());
2785 insert_action(new Actions::CropPlaylist());
2786 insert_action(new Actions::ClearMainPlaylist());
2787 insert_action(new Actions::ClearPlaylist());
2788 insert_action(new Actions::SortPlaylist());
2789 insert_action(new Actions::ReversePlaylist());
2790 insert_action(new Actions::ApplyFilter());
2791 insert_action(new Actions::Find());
2792 insert_action(new Actions::FindItemForward());
2793 insert_action(new Actions::FindItemBackward());
2794 insert_action(new Actions::NextFoundItem());
2795 insert_action(new Actions::PreviousFoundItem());
2796 insert_action(new Actions::ToggleFindMode());
2797 insert_action(new Actions::ToggleReplayGainMode());
2798 insert_action(new Actions::ToggleAddMode());
2799 insert_action(new Actions::ToggleMouse());
2800 insert_action(new Actions::ToggleBitrateVisibility());
2801 insert_action(new Actions::AddRandomItems());
2802 insert_action(new Actions::ToggleBrowserSortMode());
2803 insert_action(new Actions::ToggleLibraryTagType());
2804 insert_action(new Actions::ToggleMediaLibrarySortMode());
2805 insert_action(new Actions::FetchLyricsInBackground());
2806 insert_action(new Actions::RefetchLyrics());
2807 insert_action(new Actions::SetSelectedItemsPriority());
2808 insert_action(new Actions::ToggleOutput());
2809 insert_action(new Actions::ToggleVisualizationType());
2810 insert_action(new Actions::ShowSongInfo());
2811 insert_action(new Actions::ShowArtistInfo());
2812 insert_action(new Actions::ShowLyrics());
2813 insert_action(new Actions::Quit());
2814 insert_action(new Actions::NextScreen());
2815 insert_action(new Actions::PreviousScreen());
2816 insert_action(new Actions::ShowHelp());
2817 insert_action(new Actions::ShowPlaylist());
2818 insert_action(new Actions::ShowBrowser());
2819 insert_action(new Actions::ChangeBrowseMode());
2820 insert_action(new Actions::ShowSearchEngine());
2821 insert_action(new Actions::ResetSearchEngine());
2822 insert_action(new Actions::ShowMediaLibrary());
2823 insert_action(new Actions::ToggleMediaLibraryColumnsMode());
2824 insert_action(new Actions::ShowPlaylistEditor());
2825 insert_action(new Actions::ShowTagEditor());
2826 insert_action(new Actions::ShowOutputs());
2827 insert_action(new Actions::ShowVisualizer());
2828 insert_action(new Actions::ShowClock());
2829 insert_action(new Actions::ShowServerInfo());
2830 for (size_t i = 0; i < AvailableActions.size(); ++i)
2832 if (AvailableActions[i] == nullptr)
2833 throw std::logic_error("undefined action at position "
2834 + boost::lexical_cast<std::string>(i));
2838 bool scrollTagCanBeRun(NC::List *&list, const SongList *&songs)
2840 auto w = myScreen->activeWindow();
2841 if (list != static_cast<void *>(w))
2842 list = dynamic_cast<NC::List *>(w);
2843 if (songs != static_cast<void *>(w))
2844 songs = dynamic_cast<SongList *>(w);
2845 return list != nullptr && !list->empty()
2846 && songs != nullptr;
2849 void scrollTagUpRun(NC::List *list, const SongList *songs, MPD::Song::GetFunction get)
2851 const auto front = songs->beginS();
2852 auto it = songs->currentS();
2853 if (it->song() != nullptr)
2855 const std::string tag = it->song()->getTags(get);
2856 while (it != front)
2858 --it;
2859 if (it->song() == nullptr || it->song()->getTags(get) != tag)
2860 break;
2862 list->highlight(it-front);
2866 void scrollTagDownRun(NC::List *list, const SongList *songs, MPD::Song::GetFunction get)
2868 const auto front = songs->beginS(), back = --songs->endS();
2869 auto it = songs->currentS();
2870 if (it->song() != nullptr)
2872 const std::string tag = it->song()->getTags(get);
2873 while (it != back)
2875 ++it;
2876 if (it->song() == nullptr || it->song()->getTags(get) != tag)
2877 break;
2879 list->highlight(it-front);
2883 void seek(SearchDirection sd)
2885 using Global::wHeader;
2886 using Global::wFooter;
2887 using Global::Timer;
2888 using Global::SeekingInProgress;
2890 if (!Status::State::totalTime())
2892 Statusbar::print("Unknown item length");
2893 return;
2896 Progressbar::ScopedLock progressbar_lock;
2897 Statusbar::ScopedLock statusbar_lock;
2899 unsigned songpos = Status::State::elapsedTime();
2900 auto t = Timer;
2902 NC::Window::ScopedTimeout stimeout{*wFooter, BaseScreen::defaultWindowTimeout};
2904 // Accept single action of a given type or action chain for which all actions
2905 // can be run and one of them is of the given type. This will still not work
2906 // in some contrived cases, but allows for more flexibility than accepting
2907 // single actions only.
2908 auto hasRunnableAction = [](BindingsConfiguration::BindingIteratorPair &bindings,
2909 Actions::Type type) {
2910 bool success = false;
2911 for (auto binding = bindings.first; binding != bindings.second; ++binding)
2913 auto &actions = binding->actions();
2914 for (const auto &action : actions)
2916 if (action->canBeRun())
2918 if (action->type() == type)
2919 success = true;
2921 else
2923 success = false;
2924 break;
2927 if (success)
2928 break;
2930 return success;
2933 SeekingInProgress = true;
2934 while (true)
2936 Status::trace();
2938 unsigned howmuch = Config.incremental_seeking
2939 ? (Timer-t).total_seconds()/2+Config.seek_time
2940 : Config.seek_time;
2942 NC::Key::Type input = readKey(*wFooter);
2944 switch (sd)
2946 case SearchDirection::Backward:
2947 if (songpos > 0)
2949 if (songpos < howmuch)
2950 songpos = 0;
2951 else
2952 songpos -= howmuch;
2954 break;
2955 case SearchDirection::Forward:
2956 if (songpos < Status::State::totalTime())
2957 songpos = std::min(songpos + howmuch, Status::State::totalTime());
2958 break;
2961 std::string tracklength;
2962 // FIXME: merge this with the code in status.cpp
2963 switch (Config.design)
2965 case Design::Classic:
2966 tracklength = " [";
2967 if (Config.display_remaining_time)
2969 tracklength += "-";
2970 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
2972 else
2973 tracklength += MPD::Song::ShowTime(songpos);
2974 tracklength += "/";
2975 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
2976 tracklength += "]";
2977 *wFooter << NC::XY(wFooter->getWidth()-tracklength.length(), 1)
2978 << Config.statusbar_time_color
2979 << tracklength
2980 << NC::FormattedColor::End(Config.statusbar_time_color);
2981 break;
2982 case Design::Alternative:
2983 if (Config.display_remaining_time)
2985 tracklength = "-";
2986 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
2988 else
2989 tracklength = MPD::Song::ShowTime(songpos);
2990 tracklength += "/";
2991 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
2992 *wHeader << NC::XY(0, 0)
2993 << Config.statusbar_time_color
2994 << tracklength
2995 << NC::FormattedColor::End(Config.statusbar_time_color)
2996 << " ";
2997 wHeader->refresh();
2998 break;
3000 Progressbar::draw(songpos, Status::State::totalTime());
3001 wFooter->refresh();
3003 auto k = Bindings.get(input);
3004 if (hasRunnableAction(k, Actions::Type::SeekBackward))
3005 sd = SearchDirection::Backward;
3006 else if (hasRunnableAction(k, Actions::Type::SeekForward))
3007 sd = SearchDirection::Forward;
3008 else
3009 break;
3011 SeekingInProgress = false;
3012 Mpd.Seek(Status::State::currentSongPosition(), songpos);
3015 void findItem(const SearchDirection direction)
3017 using Global::wFooter;
3019 Searchable *w = dynamic_cast<Searchable *>(myScreen);
3020 assert(w != nullptr);
3021 assert(w->allowsSearching());
3023 std::string constraint = w->searchConstraint();
3026 ScopedValue<bool> disabled_autocenter_mode(Config.autocenter_mode, false);
3027 Statusbar::ScopedLock slock;
3028 NC::Window::ScopedPromptHook prompt_hook(
3029 *wFooter,
3030 Statusbar::Helpers::FindImmediately(w, direction));
3031 Statusbar::put() << (boost::format("Find %1%: ") % direction).str();
3032 constraint = wFooter->prompt(constraint);
3034 catch (NC::PromptAborted &)
3036 w->setSearchConstraint(constraint);
3037 w->search(direction, Config.wrapped_search, false);
3038 throw;
3041 if (constraint.empty())
3043 Statusbar::printf("Constraint unset");
3044 w->clearSearchConstraint();
3046 else
3047 Statusbar::printf("Using constraint \"%1%\"", constraint);
3050 void listsChangeFinisher()
3052 if (myScreen == myLibrary
3053 || myScreen == myPlaylistEditor
3054 # ifdef HAVE_TAGLIB_H
3055 || myScreen == myTagEditor
3056 # endif // HAVE_TAGLIB_H
3059 if (myScreen->activeWindow() == &myLibrary->Tags)
3061 myLibrary->Albums.clear();
3062 myLibrary->Albums.refresh();
3063 myLibrary->Songs.clear();
3064 myLibrary->Songs.refresh();
3065 myLibrary->updateTimer();
3067 else if (myScreen->activeWindow() == &myLibrary->Albums)
3069 myLibrary->Songs.clear();
3070 myLibrary->Songs.refresh();
3071 myLibrary->updateTimer();
3073 else if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
3075 myPlaylistEditor->Content.clear();
3076 myPlaylistEditor->Content.refresh();
3077 myPlaylistEditor->updateTimer();
3079 # ifdef HAVE_TAGLIB_H
3080 else if (myScreen->activeWindow() == myTagEditor->Dirs)
3082 myTagEditor->Tags->clear();
3084 # endif // HAVE_TAGLIB_H