Make 'update_environment' action also update local mpd status
[ncmpcpp.git] / src / actions.cpp
blobc77466cc67ecbb3f45f012b7159726386ef6f908
1 /***************************************************************************
2 * Copyright (C) 2008-2016 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"
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();
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 auto answer = Statusbar::Helpers::promptReturnOneOf({"y", "n"});
284 if (answer == "n")
285 throw NC::PromptAborted(std::move(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 int volume = std::min(Status::State::volume()+Config.volume_change_step, 100u);
627 Mpd.SetVolume(volume);
630 void VolumeDown::run()
632 int volume = std::max(int(Status::State::volume()-Config.volume_change_step), 0);
633 Mpd.SetVolume(volume);
636 bool AddItemToPlaylist::canBeRun()
638 m_hs = dynamic_cast<HasSongs *>(myScreen);
639 return m_hs != nullptr && m_hs->itemAvailable();
642 void AddItemToPlaylist::run()
644 bool success = m_hs->addItemToPlaylist(false);
645 if (success)
647 myScreen->scroll(NC::Scroll::Down);
648 listsChangeFinisher();
652 bool PlayItem::canBeRun()
654 m_hs = dynamic_cast<HasSongs *>(myScreen);
655 return m_hs != nullptr && m_hs->itemAvailable();
658 void PlayItem::run()
660 bool success = m_hs->addItemToPlaylist(true);
661 if (success)
662 listsChangeFinisher();
665 bool DeletePlaylistItems::canBeRun()
667 return (myScreen == myPlaylist && !myPlaylist->main().empty())
668 || (myScreen->isActiveWindow(myPlaylistEditor->Content) && !myPlaylistEditor->Content.empty());
671 void DeletePlaylistItems::run()
673 if (myScreen == myPlaylist)
675 Statusbar::print("Deleting items...");
676 auto delete_fun = std::bind(&MPD::Connection::Delete, ph::_1, ph::_2);
677 deleteSelectedSongs(myPlaylist->main(), delete_fun);
678 Statusbar::print("Item(s) deleted");
680 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
682 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
683 auto delete_fun = std::bind(&MPD::Connection::PlaylistDelete, ph::_1, playlist, ph::_2);
684 Statusbar::print("Deleting items...");
685 deleteSelectedSongs(myPlaylistEditor->Content, delete_fun);
686 Statusbar::print("Item(s) deleted");
690 bool DeleteBrowserItems::canBeRun()
692 auto check_if_deletion_allowed = []() {
693 if (Config.allow_for_physical_item_deletion)
694 return true;
695 else
697 Statusbar::print("Flag \"allow_for_physical_item_deletion\" needs to be enabled in configuration file");
698 return false;
701 return myScreen == myBrowser
702 && !myBrowser->main().empty()
703 && isMPDMusicDirSet()
704 && check_if_deletion_allowed();
707 void DeleteBrowserItems::run()
709 auto get_name = [](const MPD::Item &item) -> std::string {
710 std::string iname;
711 switch (item.type())
713 case MPD::Item::Type::Directory:
714 iname = getBasename(item.directory().path());
715 break;
716 case MPD::Item::Type::Song:
717 iname = item.song().getName();
718 break;
719 case MPD::Item::Type::Playlist:
720 iname = getBasename(item.playlist().path());
721 break;
723 return iname;
726 boost::format question;
727 if (hasSelected(myBrowser->main().begin(), myBrowser->main().end()))
728 question = boost::format("Delete selected items?");
729 else
731 const auto &item = myBrowser->main().current()->value();
732 // parent directories are not accepted (and they
733 // can't be selected, so in other cases it's fine).
734 if (myBrowser->isParentDirectory(item))
735 return;
736 const char msg[] = "Delete \"%1%\"?";
737 question = boost::format(msg) % wideShorten(
738 get_name(item), COLS-const_strlen(msg)-5
741 confirmAction(question);
743 auto items = getSelectedOrCurrent(
744 myBrowser->main().begin(),
745 myBrowser->main().end(),
746 myBrowser->main().current()
748 for (const auto &item : items)
750 myBrowser->remove(item->value());
751 const char msg[] = "Deleted %1% \"%2%\"";
752 Statusbar::printf(msg,
753 itemTypeToString(item->value().type()),
754 wideShorten(get_name(item->value()), COLS-const_strlen(msg))
758 if (!myBrowser->isLocal())
759 Mpd.UpdateDirectory(myBrowser->currentDirectory());
760 myBrowser->requestUpdate();
763 bool DeleteStoredPlaylist::canBeRun()
765 return myScreen->isActiveWindow(myPlaylistEditor->Playlists);
768 void DeleteStoredPlaylist::run()
770 if (myPlaylistEditor->Playlists.empty())
771 return;
772 boost::format question;
773 if (hasSelected(myPlaylistEditor->Playlists.begin(), myPlaylistEditor->Playlists.end()))
774 question = boost::format("Delete selected playlists?");
775 else
776 question = boost::format("Delete playlist \"%1%\"?")
777 % wideShorten(myPlaylistEditor->Playlists.current()->value().path(), COLS-question.size()-10);
778 confirmAction(question);
779 auto list = getSelectedOrCurrent(
780 myPlaylistEditor->Playlists.begin(),
781 myPlaylistEditor->Playlists.end(),
782 myPlaylistEditor->Playlists.current()
784 for (const auto &item : list)
785 Mpd.DeletePlaylist(item->value().path());
786 Statusbar::printf("%1% deleted", list.size() == 1 ? "Playlist" : "Playlists");
787 // force playlists update. this happens automatically, but only after call
788 // to Key::read, therefore when we call PlaylistEditor::Update, it won't
789 // yet see it, so let's point that it needs to update it.
790 myPlaylistEditor->requestPlaylistsUpdate();
793 void ReplaySong::run()
795 if (Status::State::player() != MPD::psStop)
796 Mpd.Seek(Status::State::currentSongPosition(), 0);
799 void PreviousSong::run()
801 Mpd.Prev();
804 void NextSong::run()
806 Mpd.Next();
809 void Pause::run()
811 Mpd.Toggle();
814 void SavePlaylist::run()
816 using Global::wFooter;
818 std::string playlist_name;
820 Statusbar::ScopedLock slock;
821 Statusbar::put() << "Save playlist as: ";
822 playlist_name = wFooter->prompt();
826 Mpd.SavePlaylist(playlist_name);
827 Statusbar::printf("Playlist saved as \"%1%\"", playlist_name);
829 catch (MPD::ServerError &e)
831 if (e.code() == MPD_SERVER_ERROR_EXIST)
833 confirmAction(
834 boost::format("Playlist \"%1%\" already exists, overwrite?") % playlist_name
836 Mpd.DeletePlaylist(playlist_name);
837 Mpd.SavePlaylist(playlist_name);
838 Statusbar::print("Playlist overwritten");
840 else
841 throw;
845 void Stop::run()
847 Mpd.Stop();
850 void ExecuteCommand::run()
852 using Global::wFooter;
854 std::string cmd_name;
856 Statusbar::ScopedLock slock;
857 NC::Window::ScopedPromptHook helper(*wFooter,
858 Statusbar::Helpers::TryExecuteImmediateCommand()
860 Statusbar::put() << NC::Format::Bold << ":" << NC::Format::NoBold;
861 cmd_name = wFooter->prompt();
864 auto cmd = Bindings.findCommand(cmd_name);
865 if (cmd)
867 Statusbar::printf(1, "Executing %1%...", cmd_name);
868 bool res = cmd->binding().execute();
869 Statusbar::printf("Execution of command \"%1%\" %2%.",
870 cmd_name, res ? "successful" : "unsuccessful"
873 else
874 Statusbar::printf("No command named \"%1%\"", cmd_name);
877 bool MoveSortOrderUp::canBeRun()
879 return myScreen == mySortPlaylistDialog;
882 void MoveSortOrderUp::run()
884 mySortPlaylistDialog->moveSortOrderUp();
887 bool MoveSortOrderDown::canBeRun()
889 return myScreen == mySortPlaylistDialog;
892 void MoveSortOrderDown::run()
894 mySortPlaylistDialog->moveSortOrderDown();
897 bool MoveSelectedItemsUp::canBeRun()
899 return ((myScreen == myPlaylist
900 && !myPlaylist->main().empty())
901 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
902 && !myPlaylistEditor->Content.empty()));
905 void MoveSelectedItemsUp::run()
907 const char *filteredMsg = "Moving items up is disabled in filtered playlist";
908 if (myScreen == myPlaylist)
910 if (myPlaylist->main().isFiltered())
911 Statusbar::print(filteredMsg);
912 else
913 moveSelectedItemsUp(
914 myPlaylist->main(),
915 std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
917 else if (myScreen == myPlaylistEditor)
919 if (myPlaylistEditor->Content.isFiltered())
920 Statusbar::print(filteredMsg);
921 else
923 auto playlist = myPlaylistEditor->Playlists.current()->value().path();
924 moveSelectedItemsUp(
925 myPlaylistEditor->Content,
926 std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3));
931 bool MoveSelectedItemsDown::canBeRun()
933 return ((myScreen == myPlaylist
934 && !myPlaylist->main().empty())
935 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
936 && !myPlaylistEditor->Content.empty()));
939 void MoveSelectedItemsDown::run()
941 const char *filteredMsg = "Moving items down is disabled in filtered playlist";
942 if (myScreen == myPlaylist)
944 if (myPlaylist->main().isFiltered())
945 Statusbar::print(filteredMsg);
946 else
947 moveSelectedItemsDown(
948 myPlaylist->main(),
949 std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
951 else if (myScreen == myPlaylistEditor)
953 if (myPlaylistEditor->Content.isFiltered())
954 Statusbar::print(filteredMsg);
955 else
957 auto playlist = myPlaylistEditor->Playlists.current()->value().path();
958 moveSelectedItemsDown(
959 myPlaylistEditor->Content,
960 std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3));
965 bool MoveSelectedItemsTo::canBeRun()
967 return myScreen == myPlaylist
968 || myScreen->isActiveWindow(myPlaylistEditor->Content);
971 void MoveSelectedItemsTo::run()
973 if (myScreen == myPlaylist)
975 if (!myPlaylist->main().empty())
976 moveSelectedItemsTo(myPlaylist->main(), std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
978 else
980 assert(!myPlaylistEditor->Playlists.empty());
981 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
982 auto move_fun = std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3);
983 moveSelectedItemsTo(myPlaylistEditor->Content, move_fun);
987 bool Add::canBeRun()
989 return myScreen != myPlaylistEditor
990 || !myPlaylistEditor->Playlists.empty();
993 void Add::run()
995 using Global::wFooter;
997 std::string path;
999 Statusbar::ScopedLock slock;
1000 Statusbar::put() << (myScreen == myPlaylistEditor ? "Add to playlist: " : "Add: ");
1001 path = wFooter->prompt();
1004 // confirm when one wants to add the whole database
1005 if (path.empty())
1006 confirmAction("Are you sure you want to add the whole database?");
1008 Statusbar::put() << "Adding...";
1009 wFooter->refresh();
1010 if (myScreen == myPlaylistEditor)
1011 Mpd.AddToPlaylist(myPlaylistEditor->Playlists.current()->value().path(), path);
1012 else
1016 Mpd.Add(path);
1018 catch (MPD::ServerError &err)
1020 // If a path is not a file or directory, assume it is a playlist.
1021 if (err.code() == MPD_SERVER_ERROR_NO_EXIST)
1022 Mpd.LoadPlaylist(path);
1023 else
1024 throw;
1029 bool SeekForward::canBeRun()
1031 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1034 void SeekForward::run()
1036 seek();
1039 bool SeekBackward::canBeRun()
1041 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1044 void SeekBackward::run()
1046 seek();
1049 bool ToggleDisplayMode::canBeRun()
1051 return myScreen == myPlaylist
1052 || myScreen == myBrowser
1053 || myScreen == mySearcher
1054 || myScreen->isActiveWindow(myPlaylistEditor->Content);
1057 void ToggleDisplayMode::run()
1059 if (myScreen == myPlaylist)
1061 switch (Config.playlist_display_mode)
1063 case DisplayMode::Classic:
1064 Config.playlist_display_mode = DisplayMode::Columns;
1065 myPlaylist->main().setItemDisplayer(std::bind(
1066 Display::SongsInColumns, ph::_1, std::cref(myPlaylist->main())
1068 if (Config.titles_visibility)
1069 myPlaylist->main().setTitle(Display::Columns(myPlaylist->main().getWidth()));
1070 else
1071 myPlaylist->main().setTitle("");
1072 break;
1073 case DisplayMode::Columns:
1074 Config.playlist_display_mode = DisplayMode::Classic;
1075 myPlaylist->main().setItemDisplayer(std::bind(
1076 Display::Songs, ph::_1, std::cref(myPlaylist->main()), std::cref(Config.song_list_format)
1078 myPlaylist->main().setTitle("");
1080 Statusbar::printf("Playlist display mode: %1%", Config.playlist_display_mode);
1082 else if (myScreen == myBrowser)
1084 switch (Config.browser_display_mode)
1086 case DisplayMode::Classic:
1087 Config.browser_display_mode = DisplayMode::Columns;
1088 if (Config.titles_visibility)
1089 myBrowser->main().setTitle(Display::Columns(myBrowser->main().getWidth()));
1090 else
1091 myBrowser->main().setTitle("");
1092 break;
1093 case DisplayMode::Columns:
1094 Config.browser_display_mode = DisplayMode::Classic;
1095 myBrowser->main().setTitle("");
1096 break;
1098 Statusbar::printf("Browser display mode: %1%", Config.browser_display_mode);
1100 else if (myScreen == mySearcher)
1102 switch (Config.search_engine_display_mode)
1104 case DisplayMode::Classic:
1105 Config.search_engine_display_mode = DisplayMode::Columns;
1106 break;
1107 case DisplayMode::Columns:
1108 Config.search_engine_display_mode = DisplayMode::Classic;
1109 break;
1111 Statusbar::printf("Search engine display mode: %1%", Config.search_engine_display_mode);
1112 if (mySearcher->main().size() > SearchEngine::StaticOptions)
1113 mySearcher->main().setTitle(
1114 Config.search_engine_display_mode == DisplayMode::Columns
1115 && Config.titles_visibility
1116 ? Display::Columns(mySearcher->main().getWidth())
1117 : ""
1120 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
1122 switch (Config.playlist_editor_display_mode)
1124 case DisplayMode::Classic:
1125 Config.playlist_editor_display_mode = DisplayMode::Columns;
1126 myPlaylistEditor->Content.setItemDisplayer(std::bind(
1127 Display::SongsInColumns, ph::_1, std::cref(myPlaylistEditor->Content)
1129 break;
1130 case DisplayMode::Columns:
1131 Config.playlist_editor_display_mode = DisplayMode::Classic;
1132 myPlaylistEditor->Content.setItemDisplayer(std::bind(
1133 Display::Songs, ph::_1, std::cref(myPlaylistEditor->Content), std::cref(Config.song_list_format)
1135 break;
1137 Statusbar::printf("Playlist editor display mode: %1%", Config.playlist_editor_display_mode);
1141 bool ToggleSeparatorsBetweenAlbums::canBeRun()
1143 return true;
1146 void ToggleSeparatorsBetweenAlbums::run()
1148 Config.playlist_separate_albums = !Config.playlist_separate_albums;
1149 Statusbar::printf("Separators between albums: %1%",
1150 Config.playlist_separate_albums ? "on" : "off"
1154 bool ToggleLyricsUpdateOnSongChange::canBeRun()
1156 return myScreen == myLyrics;
1159 void ToggleLyricsUpdateOnSongChange::run()
1161 Config.now_playing_lyrics = !Config.now_playing_lyrics;
1162 Statusbar::printf("Update lyrics if song changes: %1%",
1163 Config.now_playing_lyrics ? "on" : "off"
1167 void ToggleLyricsFetcher::run()
1169 myLyrics->toggleFetcher();
1172 void ToggleFetchingLyricsInBackground::run()
1174 Config.fetch_lyrics_in_background = !Config.fetch_lyrics_in_background;
1175 Statusbar::printf("Fetching lyrics for playing songs in background: %1%",
1176 Config.fetch_lyrics_in_background ? "on" : "off");
1179 void TogglePlayingSongCentering::run()
1181 Config.autocenter_mode = !Config.autocenter_mode;
1182 Statusbar::printf("Centering playing song: %1%",
1183 Config.autocenter_mode ? "on" : "off"
1185 if (Config.autocenter_mode)
1187 auto s = myPlaylist->nowPlayingSong();
1188 if (!s.empty())
1189 myPlaylist->locateSong(s);
1193 void UpdateDatabase::run()
1195 if (myScreen == myBrowser)
1196 Mpd.UpdateDirectory(myBrowser->currentDirectory());
1197 # ifdef HAVE_TAGLIB_H
1198 else if (myScreen == myTagEditor)
1199 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1200 # endif // HAVE_TAGLIB_H
1201 else
1202 Mpd.UpdateDirectory("/");
1205 bool JumpToPlayingSong::canBeRun()
1207 return myScreen == myPlaylist
1208 || myScreen == myBrowser
1209 || myScreen == myLibrary;
1212 void JumpToPlayingSong::run()
1214 auto s = myPlaylist->nowPlayingSong();
1215 if (s.empty())
1216 return;
1217 if (myScreen == myPlaylist)
1219 myPlaylist->locateSong(s);
1221 else if (myScreen == myBrowser)
1223 myBrowser->locateSong(s);
1225 else if (myScreen == myLibrary)
1227 myLibrary->locateSong(s);
1231 void ToggleRepeat::run()
1233 Mpd.SetRepeat(!Status::State::repeat());
1236 bool Shuffle::canBeRun()
1238 if (myScreen != myPlaylist)
1239 return false;
1240 m_begin = myPlaylist->main().begin();
1241 m_end = myPlaylist->main().end();
1242 return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end);
1245 void Shuffle::run()
1247 if (Config.ask_before_shuffling_playlists)
1248 confirmAction("Do you really want to shuffle selected range?");
1249 auto begin = myPlaylist->main().begin();
1250 Mpd.ShuffleRange(m_begin-begin, m_end-begin);
1251 Statusbar::print("Range shuffled");
1254 void ToggleRandom::run()
1256 Mpd.SetRandom(!Status::State::random());
1259 bool StartSearching::canBeRun()
1261 return myScreen == mySearcher && !mySearcher->main()[0].isInactive();
1264 void StartSearching::run()
1266 mySearcher->main().highlight(SearchEngine::SearchButton);
1267 mySearcher->main().setHighlighting(0);
1268 mySearcher->main().refresh();
1269 mySearcher->main().setHighlighting(1);
1270 mySearcher->runAction();
1273 bool SaveTagChanges::canBeRun()
1275 # ifdef HAVE_TAGLIB_H
1276 return myScreen == myTinyTagEditor
1277 || myScreen->activeWindow() == myTagEditor->TagTypes;
1278 # else
1279 return false;
1280 # endif // HAVE_TAGLIB_H
1283 void SaveTagChanges::run()
1285 # ifdef HAVE_TAGLIB_H
1286 if (myScreen == myTinyTagEditor)
1288 myTinyTagEditor->main().highlight(myTinyTagEditor->main().size()-2); // Save
1289 myTinyTagEditor->runAction();
1291 else if (myScreen->activeWindow() == myTagEditor->TagTypes)
1293 myTagEditor->TagTypes->highlight(myTagEditor->TagTypes->size()-1); // Save
1294 myTagEditor->runAction();
1296 # endif // HAVE_TAGLIB_H
1299 void ToggleSingle::run()
1301 Mpd.SetSingle(!Status::State::single());
1304 void ToggleConsume::run()
1306 Mpd.SetConsume(!Status::State::consume());
1309 void ToggleCrossfade::run()
1311 Mpd.SetCrossfade(Status::State::crossfade() ? 0 : Config.crossfade_time);
1314 void SetCrossfade::run()
1316 using Global::wFooter;
1318 Statusbar::ScopedLock slock;
1319 Statusbar::put() << "Set crossfade to: ";
1320 auto crossfade = fromString<unsigned>(wFooter->prompt());
1321 lowerBoundCheck(crossfade, 0u);
1322 Config.crossfade_time = crossfade;
1323 Mpd.SetCrossfade(crossfade);
1326 void SetVolume::run()
1328 using Global::wFooter;
1330 unsigned volume;
1332 Statusbar::ScopedLock slock;
1333 Statusbar::put() << "Set volume to: ";
1334 volume = fromString<unsigned>(wFooter->prompt());
1335 boundsCheck(volume, 0u, 100u);
1336 Mpd.SetVolume(volume);
1338 Statusbar::printf("Volume set to %1%%%", volume);
1341 bool EnterDirectory::canBeRun()
1343 bool result = false;
1344 if (myScreen == myBrowser && !myBrowser->main().empty())
1346 result = myBrowser->main().current()->value().type()
1347 == MPD::Item::Type::Directory;
1349 #ifdef HAVE_TAGLIB_H
1350 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1351 result = true;
1352 #endif // HAVE_TAGLIB_H
1353 return result;
1356 void EnterDirectory::run()
1358 if (myScreen == myBrowser)
1359 myBrowser->enterDirectory();
1360 #ifdef HAVE_TAGLIB_H
1361 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1363 if (!myTagEditor->enterDirectory())
1364 Statusbar::print("No subdirectories found");
1366 #endif // HAVE_TAGLIB_H
1369 bool EditSong::canBeRun()
1371 # ifdef HAVE_TAGLIB_H
1372 m_song = currentSong(myScreen);
1373 return m_song != nullptr && isMPDMusicDirSet();
1374 # else
1375 return false;
1376 # endif // HAVE_TAGLIB_H
1379 void EditSong::run()
1381 # ifdef HAVE_TAGLIB_H
1382 myTinyTagEditor->SetEdited(*m_song);
1383 myTinyTagEditor->switchTo();
1384 # endif // HAVE_TAGLIB_H
1387 bool EditLibraryTag::canBeRun()
1389 # ifdef HAVE_TAGLIB_H
1390 return myScreen->isActiveWindow(myLibrary->Tags)
1391 && !myLibrary->Tags.empty()
1392 && isMPDMusicDirSet();
1393 # else
1394 return false;
1395 # endif // HAVE_TAGLIB_H
1398 void EditLibraryTag::run()
1400 # ifdef HAVE_TAGLIB_H
1401 using Global::wFooter;
1403 std::string new_tag;
1405 Statusbar::ScopedLock slock;
1406 Statusbar::put() << NC::Format::Bold << tagTypeToString(Config.media_lib_primary_tag) << NC::Format::NoBold << ": ";
1407 new_tag = wFooter->prompt(myLibrary->Tags.current()->value().tag());
1409 if (!new_tag.empty() && new_tag != myLibrary->Tags.current()->value().tag())
1411 Statusbar::print("Updating tags...");
1412 Mpd.StartSearch(true);
1413 Mpd.AddSearch(Config.media_lib_primary_tag, myLibrary->Tags.current()->value().tag());
1414 MPD::MutableSong::SetFunction set = tagTypeToSetFunction(Config.media_lib_primary_tag);
1415 assert(set);
1416 bool success = true;
1417 std::string dir_to_update;
1418 for (MPD::SongIterator s = Mpd.CommitSearchSongs(), end; s != end; ++s)
1420 MPD::MutableSong ms = std::move(*s);
1421 ms.setTags(set, new_tag);
1422 Statusbar::printf("Updating tags in \"%1%\"...", ms.getName());
1423 std::string path = Config.mpd_music_dir + ms.getURI();
1424 if (!Tags::write(ms))
1426 success = false;
1427 Statusbar::printf("Error while writing tags to \"%1%\": %2%",
1428 ms.getName(), strerror(errno));
1429 s.finish();
1430 break;
1432 if (dir_to_update.empty())
1433 dir_to_update = ms.getURI();
1434 else
1435 dir_to_update = getSharedDirectory(dir_to_update, ms.getURI());
1437 if (success)
1439 Mpd.UpdateDirectory(dir_to_update);
1440 Statusbar::print("Tags updated successfully");
1443 # endif // HAVE_TAGLIB_H
1446 bool EditLibraryAlbum::canBeRun()
1448 # ifdef HAVE_TAGLIB_H
1449 return myScreen->isActiveWindow(myLibrary->Albums)
1450 && !myLibrary->Albums.empty()
1451 && isMPDMusicDirSet();
1452 # else
1453 return false;
1454 # endif // HAVE_TAGLIB_H
1457 void EditLibraryAlbum::run()
1459 # ifdef HAVE_TAGLIB_H
1460 using Global::wFooter;
1461 // FIXME: merge this and EditLibraryTag. also, prompt on failure if user wants to continue
1462 std::string new_album;
1464 Statusbar::ScopedLock slock;
1465 Statusbar::put() << NC::Format::Bold << "Album: " << NC::Format::NoBold;
1466 new_album = wFooter->prompt(myLibrary->Albums.current()->value().entry().album());
1468 if (!new_album.empty() && new_album != myLibrary->Albums.current()->value().entry().album())
1470 bool success = 1;
1471 Statusbar::print("Updating tags...");
1472 for (size_t i = 0; i < myLibrary->Songs.size(); ++i)
1474 Statusbar::printf("Updating tags in \"%1%\"...", myLibrary->Songs[i].value().getName());
1475 std::string path = Config.mpd_music_dir + myLibrary->Songs[i].value().getURI();
1476 TagLib::FileRef f(path.c_str());
1477 if (f.isNull())
1479 const char msg[] = "Error while opening file \"%1%\"";
1480 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1481 success = 0;
1482 break;
1484 f.tag()->setAlbum(ToWString(new_album));
1485 if (!f.save())
1487 const char msg[] = "Error while writing tags in \"%1%\"";
1488 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1489 success = 0;
1490 break;
1493 if (success)
1495 Mpd.UpdateDirectory(getSharedDirectory(myLibrary->Songs.beginV(), myLibrary->Songs.endV()));
1496 Statusbar::print("Tags updated successfully");
1499 # endif // HAVE_TAGLIB_H
1502 bool EditDirectoryName::canBeRun()
1504 return ((myScreen == myBrowser
1505 && !myBrowser->main().empty()
1506 && myBrowser->main().current()->value().type() == MPD::Item::Type::Directory)
1507 # ifdef HAVE_TAGLIB_H
1508 || (myScreen->activeWindow() == myTagEditor->Dirs
1509 && !myTagEditor->Dirs->empty()
1510 && myTagEditor->Dirs->choice() > 0)
1511 # endif // HAVE_TAGLIB_H
1512 ) && isMPDMusicDirSet();
1515 void EditDirectoryName::run()
1517 using Global::wFooter;
1518 if (myScreen == myBrowser)
1520 std::string old_dir = myBrowser->main().current()->value().directory().path();
1521 std::string new_dir;
1523 Statusbar::ScopedLock slock;
1524 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1525 new_dir = wFooter->prompt(old_dir);
1527 if (!new_dir.empty() && new_dir != old_dir)
1529 std::string full_old_dir;
1530 if (!myBrowser->isLocal())
1531 full_old_dir += Config.mpd_music_dir;
1532 full_old_dir += old_dir;
1533 std::string full_new_dir;
1534 if (!myBrowser->isLocal())
1535 full_new_dir += Config.mpd_music_dir;
1536 full_new_dir += new_dir;
1537 boost::filesystem::rename(full_old_dir, full_new_dir);
1538 const char msg[] = "Directory renamed to \"%1%\"";
1539 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1540 if (!myBrowser->isLocal())
1541 Mpd.UpdateDirectory(getSharedDirectory(old_dir, new_dir));
1542 myBrowser->requestUpdate();
1545 # ifdef HAVE_TAGLIB_H
1546 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1548 std::string old_dir = myTagEditor->Dirs->current()->value().first, new_dir;
1550 Statusbar::ScopedLock slock;
1551 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1552 new_dir = wFooter->prompt(old_dir);
1554 if (!new_dir.empty() && new_dir != old_dir)
1556 std::string full_old_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + old_dir;
1557 std::string full_new_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + new_dir;
1558 if (rename(full_old_dir.c_str(), full_new_dir.c_str()) == 0)
1560 const char msg[] = "Directory renamed to \"%1%\"";
1561 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1562 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1564 else
1566 const char msg[] = "Couldn't rename \"%1%\": %2%";
1567 Statusbar::printf(msg, wideShorten(old_dir, COLS-const_strlen(msg)-25), strerror(errno));
1571 # endif // HAVE_TAGLIB_H
1574 bool EditPlaylistName::canBeRun()
1576 return (myScreen->isActiveWindow(myPlaylistEditor->Playlists)
1577 && !myPlaylistEditor->Playlists.empty())
1578 || (myScreen == myBrowser
1579 && !myBrowser->main().empty()
1580 && myBrowser->main().current()->value().type() == MPD::Item::Type::Playlist);
1583 void EditPlaylistName::run()
1585 using Global::wFooter;
1586 std::string old_name, new_name;
1587 if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
1588 old_name = myPlaylistEditor->Playlists.current()->value().path();
1589 else
1590 old_name = myBrowser->main().current()->value().playlist().path();
1592 Statusbar::ScopedLock slock;
1593 Statusbar::put() << NC::Format::Bold << "Playlist: " << NC::Format::NoBold;
1594 new_name = wFooter->prompt(old_name);
1596 if (!new_name.empty() && new_name != old_name)
1598 Mpd.Rename(old_name, new_name);
1599 const char msg[] = "Playlist renamed to \"%1%\"";
1600 Statusbar::printf(msg, wideShorten(new_name, COLS-const_strlen(msg)));
1604 bool EditLyrics::canBeRun()
1606 return myScreen == myLyrics;
1609 void EditLyrics::run()
1611 myLyrics->edit();
1614 bool JumpToBrowser::canBeRun()
1616 m_song = currentSong(myScreen);
1617 return m_song != nullptr;
1620 void JumpToBrowser::run()
1622 myBrowser->locateSong(*m_song);
1625 bool JumpToMediaLibrary::canBeRun()
1627 m_song = currentSong(myScreen);
1628 return m_song != nullptr;
1631 void JumpToMediaLibrary::run()
1633 myLibrary->locateSong(*m_song);
1636 bool JumpToPlaylistEditor::canBeRun()
1638 return myScreen == myBrowser
1639 && myBrowser->main().current()->value().type() == MPD::Item::Type::Playlist;
1642 void JumpToPlaylistEditor::run()
1644 myPlaylistEditor->locatePlaylist(myBrowser->main().current()->value().playlist());
1647 void ToggleScreenLock::run()
1649 using Global::wFooter;
1650 using Global::myLockedScreen;
1651 const char *msg_unlockable_screen = "Current screen can't be locked";
1652 if (myLockedScreen != nullptr)
1654 BaseScreen::unlock();
1655 Actions::setResizeFlags();
1656 myScreen->resize();
1657 Statusbar::print("Screen unlocked");
1659 else if (!myScreen->isLockable())
1661 Statusbar::print(msg_unlockable_screen);
1663 else
1665 unsigned part = Config.locked_screen_width_part*100;
1666 if (Config.ask_for_locked_screen_width_part)
1668 Statusbar::ScopedLock slock;
1669 Statusbar::put() << "% of the locked screen's width to be reserved (20-80): ";
1670 part = fromString<unsigned>(wFooter->prompt(boost::lexical_cast<std::string>(part)));
1672 boundsCheck(part, 20u, 80u);
1673 Config.locked_screen_width_part = part/100.0;
1674 if (myScreen->lock())
1675 Statusbar::printf("Screen locked (with %1%%% width)", part);
1676 else
1677 Statusbar::print(msg_unlockable_screen);
1681 bool JumpToTagEditor::canBeRun()
1683 # ifdef HAVE_TAGLIB_H
1684 m_song = currentSong(myScreen);
1685 return m_song != nullptr && isMPDMusicDirSet();
1686 # else
1687 return false;
1688 # endif // HAVE_TAGLIB_H
1691 void JumpToTagEditor::run()
1693 # ifdef HAVE_TAGLIB_H
1694 myTagEditor->LocateSong(*m_song);
1695 # endif // HAVE_TAGLIB_H
1698 bool JumpToPositionInSong::canBeRun()
1700 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1703 void JumpToPositionInSong::run()
1705 using Global::wFooter;
1707 const MPD::Song s = myPlaylist->nowPlayingSong();
1709 std::string spos;
1711 Statusbar::ScopedLock slock;
1712 Statusbar::put() << "Position to go (in %/m:ss/seconds(s)): ";
1713 spos = wFooter->prompt();
1716 boost::regex rx;
1717 boost::smatch what;
1718 if (boost::regex_match(spos, what, rx.assign("([0-9]+):([0-9]{2})"))) // mm:ss
1720 auto mins = fromString<unsigned>(what[1]);
1721 auto secs = fromString<unsigned>(what[2]);
1722 boundsCheck(secs, 0u, 60u);
1723 Mpd.Seek(s.getPosition(), mins * 60 + secs);
1725 else if (boost::regex_match(spos, what, rx.assign("([0-9]+)s"))) // position in seconds
1727 auto secs = fromString<unsigned>(what[1]);
1728 Mpd.Seek(s.getPosition(), secs);
1730 else if (boost::regex_match(spos, what, rx.assign("([0-9]+)[%]{0,1}"))) // position in %
1732 auto percent = fromString<unsigned>(what[1]);
1733 boundsCheck(percent, 0u, 100u);
1734 int secs = (percent * s.getDuration()) / 100.0;
1735 Mpd.Seek(s.getPosition(), secs);
1737 else
1738 Statusbar::print("Invalid format ([m]:[ss], [s]s, [%]%, [%] accepted)");
1741 bool SelectItem::canBeRun()
1743 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1744 return m_list != nullptr
1745 && !m_list->empty()
1746 && m_list->currentP()->isSelectable();
1749 void SelectItem::run()
1751 auto current = m_list->currentP();
1752 current->setSelected(!current->isSelected());
1755 bool SelectRange::canBeRun()
1757 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1758 if (m_list == nullptr)
1759 return false;
1760 m_begin = m_list->beginP();
1761 m_end = m_list->endP();
1762 return findRange(m_begin, m_end);
1765 void SelectRange::run()
1767 for (; m_begin != m_end; ++m_begin)
1768 m_begin->setSelected(true);
1769 Statusbar::print("Range selected");
1772 bool ReverseSelection::canBeRun()
1774 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1775 return m_list != nullptr;
1778 void ReverseSelection::run()
1780 for (auto &p : *m_list)
1781 p.setSelected(!p.isSelected());
1782 Statusbar::print("Selection reversed");
1785 bool RemoveSelection::canBeRun()
1787 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1788 return m_list != nullptr;
1791 void RemoveSelection::run()
1793 for (auto &p : *m_list)
1794 p.setSelected(false);
1795 Statusbar::print("Selection removed");
1798 bool SelectAlbum::canBeRun()
1800 auto *w = myScreen->activeWindow();
1801 if (m_list != static_cast<void *>(w))
1802 m_list = dynamic_cast<NC::List *>(w);
1803 if (m_songs != static_cast<void *>(w))
1804 m_songs = dynamic_cast<SongList *>(w);
1805 return m_list != nullptr && !m_list->empty()
1806 && m_songs != nullptr;
1809 void SelectAlbum::run()
1811 const auto front = m_songs->beginS(), current = m_songs->currentS(), end = m_songs->endS();
1812 if (current->song() == nullptr)
1813 return;
1814 auto get = &MPD::Song::getAlbum;
1815 const std::string tag = current->song()->getTags(get);
1816 // go up
1817 for (auto it = current; it != front;)
1819 --it;
1820 if (it->song() == nullptr || it->song()->getTags(get) != tag)
1821 break;
1822 it->properties().setSelected(true);
1824 // go down
1825 for (auto it = current;;)
1827 it->properties().setSelected(true);
1828 if (++it == end)
1829 break;
1830 if (it->song() == nullptr || it->song()->getTags(get) != tag)
1831 break;
1833 Statusbar::print("Album around cursor position selected");
1836 bool SelectFoundItems::canBeRun()
1838 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1839 if (m_list == nullptr || m_list->empty())
1840 return false;
1841 m_searchable = dynamic_cast<Searchable *>(myScreen);
1842 return m_searchable != nullptr && m_searchable->allowsSearching();
1845 void SelectFoundItems::run()
1847 auto current_pos = m_list->choice();
1848 myScreen->activeWindow()->scroll(NC::Scroll::Home);
1849 bool found = m_searchable->search(SearchDirection::Forward, false, false);
1850 if (found)
1852 Statusbar::print("Searching for items...");
1853 m_list->currentP()->setSelected(true);
1854 while (m_searchable->search(SearchDirection::Forward, false, true))
1855 m_list->currentP()->setSelected(true);
1856 Statusbar::print("Found items selected");
1858 m_list->highlight(current_pos);
1861 bool AddSelectedItems::canBeRun()
1863 return myScreen != mySelectedItemsAdder;
1866 void AddSelectedItems::run()
1868 mySelectedItemsAdder->switchTo();
1871 void CropMainPlaylist::run()
1873 auto &w = myPlaylist->main();
1874 // cropping doesn't make sense in this case
1875 if (w.size() <= 1)
1876 return;
1877 if (Config.ask_before_clearing_playlists)
1878 confirmAction("Do you really want to crop main playlist?");
1879 Statusbar::print("Cropping playlist...");
1880 selectCurrentIfNoneSelected(w);
1881 cropPlaylist(w, std::bind(&MPD::Connection::Delete, ph::_1, ph::_2));
1882 Statusbar::print("Playlist cropped");
1885 bool CropPlaylist::canBeRun()
1887 return myScreen == myPlaylistEditor;
1890 void CropPlaylist::run()
1892 auto &w = myPlaylistEditor->Content;
1893 // cropping doesn't make sense in this case
1894 if (w.size() <= 1)
1895 return;
1896 assert(!myPlaylistEditor->Playlists.empty());
1897 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
1898 if (Config.ask_before_clearing_playlists)
1899 confirmAction(boost::format("Do you really want to crop playlist \"%1%\"?") % playlist);
1900 selectCurrentIfNoneSelected(w);
1901 Statusbar::printf("Cropping playlist \"%1%\"...", playlist);
1902 cropPlaylist(w, std::bind(&MPD::Connection::PlaylistDelete, ph::_1, playlist, ph::_2));
1903 Statusbar::printf("Playlist \"%1%\" cropped", playlist);
1906 void ClearMainPlaylist::run()
1908 if (!myPlaylist->main().empty() && Config.ask_before_clearing_playlists)
1909 confirmAction("Do you really want to clear main playlist?");
1910 Mpd.ClearMainPlaylist();
1911 Statusbar::print("Playlist cleared");
1912 myPlaylist->main().reset();
1915 bool ClearPlaylist::canBeRun()
1917 return myScreen == myPlaylistEditor;
1920 void ClearPlaylist::run()
1922 if (myPlaylistEditor->Playlists.empty())
1923 return;
1924 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
1925 if (Config.ask_before_clearing_playlists)
1926 confirmAction(boost::format("Do you really want to clear playlist \"%1%\"?") % playlist);
1927 Mpd.ClearPlaylist(playlist);
1928 Statusbar::printf("Playlist \"%1%\" cleared", playlist);
1931 bool SortPlaylist::canBeRun()
1933 if (myScreen != myPlaylist)
1934 return false;
1935 auto first = myPlaylist->main().begin(), last = myPlaylist->main().end();
1936 return findSelectedRangeAndPrintInfoIfNot(first, last);
1939 void SortPlaylist::run()
1941 mySortPlaylistDialog->switchTo();
1944 bool ReversePlaylist::canBeRun()
1946 if (myScreen != myPlaylist)
1947 return false;
1948 m_begin = myPlaylist->main().begin();
1949 m_end = myPlaylist->main().end();
1950 return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end);
1953 void ReversePlaylist::run()
1955 Statusbar::print("Reversing range...");
1956 Mpd.StartCommandsList();
1957 for (--m_end; m_begin < m_end; ++m_begin, --m_end)
1958 Mpd.Swap(m_begin->value().getPosition(), m_end->value().getPosition());
1959 Mpd.CommitCommandsList();
1960 Statusbar::print("Range reversed");
1963 bool ApplyFilter::canBeRun()
1965 m_filterable = dynamic_cast<Filterable *>(myScreen);
1966 return m_filterable != nullptr
1967 && m_filterable->allowsFiltering();
1970 void ApplyFilter::run()
1972 using Global::wFooter;
1974 std::string filter = m_filterable->currentFilter();
1975 if (!filter.empty())
1977 m_filterable->applyFilter(filter);
1978 myScreen->refreshWindow();
1983 Statusbar::ScopedLock slock;
1984 NC::Window::ScopedPromptHook helper(
1985 *wFooter,
1986 Statusbar::Helpers::ApplyFilterImmediately(m_filterable));
1987 Statusbar::put() << "Apply filter: ";
1988 filter = wFooter->prompt(filter);
1990 catch (NC::PromptAborted &)
1992 m_filterable->applyFilter(filter);
1993 throw;
1996 if (filter.empty())
1997 Statusbar::printf("Filtering disabled");
1998 else
1999 Statusbar::printf("Using filter \"%1%\"", filter);
2001 if (myScreen == myPlaylist)
2002 myPlaylist->reloadTotalLength();
2004 listsChangeFinisher();
2007 bool Find::canBeRun()
2009 return myScreen == myHelp
2010 || myScreen == myLyrics
2011 || myScreen == myLastfm;
2014 void Find::run()
2016 using Global::wFooter;
2018 std::string token;
2020 Statusbar::ScopedLock slock;
2021 Statusbar::put() << "Find: ";
2022 token = wFooter->prompt();
2025 Statusbar::print("Searching...");
2026 auto s = static_cast<Screen<NC::Scrollpad> *>(myScreen);
2027 s->main().removeProperties();
2028 if (token.empty() || s->main().setProperties(NC::Format::Reverse, token, NC::Format::NoReverse, Config.regex_type))
2029 Statusbar::print("Done");
2030 else
2031 Statusbar::print("No matching patterns found");
2032 s->main().flush();
2035 bool FindItemBackward::canBeRun()
2037 auto w = dynamic_cast<Searchable *>(myScreen);
2038 return w && w->allowsSearching();
2041 void FindItemForward::run()
2043 findItem(SearchDirection::Forward);
2044 listsChangeFinisher();
2047 bool FindItemForward::canBeRun()
2049 auto w = dynamic_cast<Searchable *>(myScreen);
2050 return w && w->allowsSearching();
2053 void FindItemBackward::run()
2055 findItem(SearchDirection::Backward);
2056 listsChangeFinisher();
2059 bool NextFoundItem::canBeRun()
2061 return dynamic_cast<Searchable *>(myScreen);
2064 void NextFoundItem::run()
2066 Searchable *w = dynamic_cast<Searchable *>(myScreen);
2067 assert(w != nullptr);
2068 w->search(SearchDirection::Forward, Config.wrapped_search, true);
2069 listsChangeFinisher();
2072 bool PreviousFoundItem::canBeRun()
2074 return dynamic_cast<Searchable *>(myScreen);
2077 void PreviousFoundItem::run()
2079 Searchable *w = dynamic_cast<Searchable *>(myScreen);
2080 assert(w != nullptr);
2081 w->search(SearchDirection::Backward, Config.wrapped_search, true);
2082 listsChangeFinisher();
2085 void ToggleFindMode::run()
2087 Config.wrapped_search = !Config.wrapped_search;
2088 Statusbar::printf("Search mode: %1%",
2089 Config.wrapped_search ? "Wrapped" : "Normal"
2093 void ToggleReplayGainMode::run()
2095 using Global::wFooter;
2097 char rgm = 0;
2099 Statusbar::ScopedLock slock;
2100 Statusbar::put() << "Replay gain mode? "
2101 << "[" << NC::Format::Bold << 'o' << NC::Format::NoBold << "ff"
2102 << "/" << NC::Format::Bold << 't' << NC::Format::NoBold << "rack"
2103 << "/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "lbum"
2104 << "] ";
2105 rgm = Statusbar::Helpers::promptReturnOneOf({"t", "a", "o"})[0];
2107 switch (rgm)
2109 case 't':
2110 Mpd.SetReplayGainMode(MPD::rgmTrack);
2111 break;
2112 case 'a':
2113 Mpd.SetReplayGainMode(MPD::rgmAlbum);
2114 break;
2115 case 'o':
2116 Mpd.SetReplayGainMode(MPD::rgmOff);
2117 break;
2118 default: // impossible
2119 throw std::runtime_error(
2120 (boost::format("ToggleReplayGainMode: impossible case reached: %1%") % rgm).str()
2123 Statusbar::printf("Replay gain mode: %1%", Mpd.GetReplayGainMode());
2126 void ToggleAddMode::run()
2128 std::string mode_desc;
2129 switch (Config.space_add_mode)
2131 case SpaceAddMode::AddRemove:
2132 Config.space_add_mode = SpaceAddMode::AlwaysAdd;
2133 mode_desc = "always add an item to playlist";
2134 break;
2135 case SpaceAddMode::AlwaysAdd:
2136 Config.space_add_mode = SpaceAddMode::AddRemove;
2137 mode_desc = "add an item to playlist or remove if already added";
2138 break;
2140 Statusbar::printf("Add mode: %1%", mode_desc);
2143 void ToggleMouse::run()
2145 Config.mouse_support = !Config.mouse_support;
2146 if (Config.mouse_support)
2147 NC::Mouse::enable();
2148 else
2149 NC::Mouse::disable();
2150 Statusbar::printf("Mouse support %1%",
2151 Config.mouse_support ? "enabled" : "disabled"
2155 void ToggleBitrateVisibility::run()
2157 Config.display_bitrate = !Config.display_bitrate;
2158 Statusbar::printf("Bitrate visibility %1%",
2159 Config.display_bitrate ? "enabled" : "disabled"
2163 void AddRandomItems::run()
2165 using Global::wFooter;
2166 char rnd_type = 0;
2168 Statusbar::ScopedLock slock;
2169 Statusbar::put() << "Add random? "
2170 << "[" << NC::Format::Bold << 's' << NC::Format::NoBold << "ongs"
2171 << "/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtists"
2172 << "/" << "album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtists"
2173 << "/" << "al" << NC::Format::Bold << 'b' << NC::Format::NoBold << "ums"
2174 << "] ";
2175 rnd_type = Statusbar::Helpers::promptReturnOneOf({"s", "a", "A", "b"})[0];
2178 mpd_tag_type tag_type = MPD_TAG_ARTIST;
2179 std::string tag_type_str ;
2180 if (rnd_type != 's')
2182 tag_type = charToTagType(rnd_type);
2183 tag_type_str = boost::locale::to_lower(tagTypeToString(tag_type));
2185 else
2186 tag_type_str = "song";
2188 unsigned number;
2190 Statusbar::ScopedLock slock;
2191 Statusbar::put() << "Number of random " << tag_type_str << "s: ";
2192 number = fromString<unsigned>(wFooter->prompt());
2194 if (number > 0)
2196 bool success;
2197 if (rnd_type == 's')
2198 success = Mpd.AddRandomSongs(number, Global::RNG);
2199 else
2200 success = Mpd.AddRandomTag(tag_type, number, Global::RNG);
2201 if (success)
2202 Statusbar::printf("%1% random %2%%3% added to playlist", number, tag_type_str, number == 1 ? "" : "s");
2206 bool ToggleBrowserSortMode::canBeRun()
2208 return myScreen == myBrowser;
2211 void ToggleBrowserSortMode::run()
2213 switch (Config.browser_sort_mode)
2215 case SortMode::Name:
2216 Config.browser_sort_mode = SortMode::ModificationTime;
2217 Statusbar::print("Sort songs by: modification time");
2218 break;
2219 case SortMode::ModificationTime:
2220 Config.browser_sort_mode = SortMode::CustomFormat;
2221 Statusbar::print("Sort songs by: custom format");
2222 break;
2223 case SortMode::CustomFormat:
2224 Config.browser_sort_mode = SortMode::NoOp;
2225 Statusbar::print("Do not sort songs");
2226 break;
2227 case SortMode::NoOp:
2228 Config.browser_sort_mode = SortMode::Name;
2229 Statusbar::print("Sort songs by: name");
2231 if (Config.browser_sort_mode != SortMode::NoOp)
2233 size_t sort_offset = myBrowser->inRootDirectory() ? 0 : 1;
2234 std::sort(myBrowser->main().begin()+sort_offset, myBrowser->main().end(),
2235 LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode)
2240 bool ToggleLibraryTagType::canBeRun()
2242 return (myScreen->isActiveWindow(myLibrary->Tags))
2243 || (myLibrary->columns() == 2 && myScreen->isActiveWindow(myLibrary->Albums));
2246 void ToggleLibraryTagType::run()
2248 using Global::wFooter;
2250 char tag_type = 0;
2252 Statusbar::ScopedLock slock;
2253 Statusbar::put() << "Tag type? "
2254 << "[" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtist"
2255 << "/" << "album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtist"
2256 << "/" << NC::Format::Bold << 'y' << NC::Format::NoBold << "ear"
2257 << "/" << NC::Format::Bold << 'g' << NC::Format::NoBold << "enre"
2258 << "/" << NC::Format::Bold << 'c' << NC::Format::NoBold << "omposer"
2259 << "/" << NC::Format::Bold << 'p' << NC::Format::NoBold << "erformer"
2260 << "] ";
2261 tag_type = Statusbar::Helpers::promptReturnOneOf({"a", "A", "y", "g", "c", "p"})[0];
2263 mpd_tag_type new_tagitem = charToTagType(tag_type);
2264 if (new_tagitem != Config.media_lib_primary_tag)
2266 Config.media_lib_primary_tag = new_tagitem;
2267 std::string item_type = tagTypeToString(Config.media_lib_primary_tag);
2268 myLibrary->Tags.setTitle(Config.titles_visibility ? item_type + "s" : "");
2269 myLibrary->Tags.reset();
2270 item_type = boost::locale::to_lower(item_type);
2271 std::string and_mtime = Config.media_library_sort_by_mtime ?
2272 " and mtime" :
2274 if (myLibrary->columns() == 2)
2276 myLibrary->Songs.clear();
2277 myLibrary->Albums.reset();
2278 myLibrary->Albums.clear();
2279 myLibrary->Albums.setTitle(Config.titles_visibility ? "Albums (sorted by " + item_type + and_mtime + ")" : "");
2280 myLibrary->Albums.display();
2282 else
2284 myLibrary->Tags.clear();
2285 myLibrary->Tags.display();
2287 Statusbar::printf("Switched to the list of %1%s", item_type);
2291 bool ToggleMediaLibrarySortMode::canBeRun()
2293 return myScreen == myLibrary;
2296 void ToggleMediaLibrarySortMode::run()
2298 myLibrary->toggleSortMode();
2301 bool FetchLyricsInBackground::canBeRun()
2303 m_hs = dynamic_cast<HasSongs *>(myScreen);
2304 return m_hs != nullptr && m_hs->itemAvailable();
2307 void FetchLyricsInBackground::run()
2309 auto songs = m_hs->getSelectedSongs();
2310 for (const auto &s : songs)
2311 myLyrics->fetchInBackground(s, true);
2312 Statusbar::print("Selected songs queued for lyrics fetching");
2315 bool RefetchLyrics::canBeRun()
2317 return myScreen == myLyrics;
2320 void RefetchLyrics::run()
2322 myLyrics->refetchCurrent();
2325 bool SetSelectedItemsPriority::canBeRun()
2327 if (Mpd.Version() < 17)
2329 Statusbar::print("Priorities are supported in MPD >= 0.17.0");
2330 return false;
2332 return myScreen == myPlaylist && !myPlaylist->main().empty();
2335 void SetSelectedItemsPriority::run()
2337 using Global::wFooter;
2339 unsigned prio;
2341 Statusbar::ScopedLock slock;
2342 Statusbar::put() << "Set priority [0-255]: ";
2343 prio = fromString<unsigned>(wFooter->prompt());
2344 boundsCheck(prio, 0u, 255u);
2346 myPlaylist->setSelectedItemsPriority(prio);
2349 bool ToggleOutput::canBeRun()
2351 #ifdef ENABLE_OUTPUTS
2352 return myScreen == myOutputs;
2353 #else
2354 return false;
2355 #endif // ENABLE_OUTPUTS
2358 void ToggleOutput::run()
2360 #ifdef ENABLE_OUTPUTS
2361 myOutputs->toggleOutput();
2362 #endif // ENABLE_OUTPUTS
2365 bool ToggleVisualizationType::canBeRun()
2367 # ifdef ENABLE_VISUALIZER
2368 return myScreen == myVisualizer;
2369 # else
2370 return false;
2371 # endif // ENABLE_VISUALIZER
2374 void ToggleVisualizationType::run()
2376 # ifdef ENABLE_VISUALIZER
2377 myVisualizer->ToggleVisualizationType();
2378 # endif // ENABLE_VISUALIZER
2381 void ShowSongInfo::run()
2383 mySongInfo->switchTo();
2386 bool ShowArtistInfo::canBeRun()
2388 return myScreen == myLastfm
2389 || (myScreen->isActiveWindow(myLibrary->Tags)
2390 && !myLibrary->Tags.empty()
2391 && Config.media_lib_primary_tag == MPD_TAG_ARTIST)
2392 || currentSong(myScreen);
2395 void ShowArtistInfo::run()
2397 if (myScreen == myLastfm)
2399 myLastfm->switchTo();
2400 return;
2403 std::string artist;
2404 if (myScreen->isActiveWindow(myLibrary->Tags))
2406 assert(!myLibrary->Tags.empty());
2407 assert(Config.media_lib_primary_tag == MPD_TAG_ARTIST);
2408 artist = myLibrary->Tags.current()->value().tag();
2410 else
2412 auto s = currentSong(myScreen);
2413 assert(s);
2414 artist = s->getArtist();
2417 if (!artist.empty())
2419 myLastfm->queueJob(new LastFm::ArtistInfo(artist, Config.lastfm_preferred_language));
2420 if (!isVisible(myLastfm))
2421 myLastfm->switchTo();
2425 bool ShowLyrics::canBeRun()
2427 if (myScreen == myLyrics)
2429 m_song = nullptr;
2430 return true;
2432 else
2434 m_song = currentSong(myScreen);
2435 return m_song != nullptr;
2439 void ShowLyrics::run()
2441 if (m_song != nullptr)
2442 myLyrics->fetch(*m_song);
2443 if (myScreen == myLyrics || !isVisible(myLyrics))
2444 myLyrics->switchTo();
2447 void Quit::run()
2449 ExitMainLoop = true;
2452 void NextScreen::run()
2454 if (Config.screen_switcher_previous)
2456 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2457 tababble->switchToPreviousScreen();
2459 else if (!Config.screen_sequence.empty())
2461 auto screen = nextScreenTypeInSequence(
2462 Config.screen_sequence.begin(),
2463 Config.screen_sequence.end(),
2464 myScreen->type());
2465 toScreen(*screen)->switchTo();
2469 void PreviousScreen::run()
2471 if (Config.screen_switcher_previous)
2473 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2474 tababble->switchToPreviousScreen();
2476 else if (!Config.screen_sequence.empty())
2478 auto screen = nextScreenTypeInSequence(
2479 Config.screen_sequence.rbegin(),
2480 Config.screen_sequence.rend(),
2481 myScreen->type());
2482 toScreen(*screen)->switchTo();
2486 bool ShowHelp::canBeRun()
2488 return myScreen != myHelp
2489 # ifdef HAVE_TAGLIB_H
2490 && myScreen != myTinyTagEditor
2491 # endif // HAVE_TAGLIB_H
2495 void ShowHelp::run()
2497 myHelp->switchTo();
2500 bool ShowPlaylist::canBeRun()
2502 return myScreen != myPlaylist
2503 # ifdef HAVE_TAGLIB_H
2504 && myScreen != myTinyTagEditor
2505 # endif // HAVE_TAGLIB_H
2509 void ShowPlaylist::run()
2511 myPlaylist->switchTo();
2514 bool ShowBrowser::canBeRun()
2516 return myScreen != myBrowser
2517 # ifdef HAVE_TAGLIB_H
2518 && myScreen != myTinyTagEditor
2519 # endif // HAVE_TAGLIB_H
2523 void ShowBrowser::run()
2525 myBrowser->switchTo();
2528 bool ChangeBrowseMode::canBeRun()
2530 return myScreen == myBrowser;
2533 void ChangeBrowseMode::run()
2535 myBrowser->changeBrowseMode();
2538 bool ShowSearchEngine::canBeRun()
2540 return myScreen != mySearcher
2541 # ifdef HAVE_TAGLIB_H
2542 && myScreen != myTinyTagEditor
2543 # endif // HAVE_TAGLIB_H
2547 void ShowSearchEngine::run()
2549 mySearcher->switchTo();
2552 bool ResetSearchEngine::canBeRun()
2554 return myScreen == mySearcher;
2557 void ResetSearchEngine::run()
2559 mySearcher->reset();
2562 bool ShowMediaLibrary::canBeRun()
2564 return myScreen != myLibrary
2565 # ifdef HAVE_TAGLIB_H
2566 && myScreen != myTinyTagEditor
2567 # endif // HAVE_TAGLIB_H
2571 void ShowMediaLibrary::run()
2573 myLibrary->switchTo();
2576 bool ToggleMediaLibraryColumnsMode::canBeRun()
2578 return myScreen == myLibrary;
2581 void ToggleMediaLibraryColumnsMode::run()
2583 myLibrary->toggleColumnsMode();
2584 myLibrary->refresh();
2587 bool ShowPlaylistEditor::canBeRun()
2589 return myScreen != myPlaylistEditor
2590 # ifdef HAVE_TAGLIB_H
2591 && myScreen != myTinyTagEditor
2592 # endif // HAVE_TAGLIB_H
2596 void ShowPlaylistEditor::run()
2598 myPlaylistEditor->switchTo();
2601 bool ShowTagEditor::canBeRun()
2603 # ifdef HAVE_TAGLIB_H
2604 return myScreen != myTagEditor
2605 && myScreen != myTinyTagEditor;
2606 # else
2607 return false;
2608 # endif // HAVE_TAGLIB_H
2611 void ShowTagEditor::run()
2613 # ifdef HAVE_TAGLIB_H
2614 if (isMPDMusicDirSet())
2615 myTagEditor->switchTo();
2616 # endif // HAVE_TAGLIB_H
2619 bool ShowOutputs::canBeRun()
2621 # ifdef ENABLE_OUTPUTS
2622 return myScreen != myOutputs
2623 # ifdef HAVE_TAGLIB_H
2624 && myScreen != myTinyTagEditor
2625 # endif // HAVE_TAGLIB_H
2627 # else
2628 return false;
2629 # endif // ENABLE_OUTPUTS
2632 void ShowOutputs::run()
2634 # ifdef ENABLE_OUTPUTS
2635 myOutputs->switchTo();
2636 # endif // ENABLE_OUTPUTS
2639 bool ShowVisualizer::canBeRun()
2641 # ifdef ENABLE_VISUALIZER
2642 return myScreen != myVisualizer
2643 # ifdef HAVE_TAGLIB_H
2644 && myScreen != myTinyTagEditor
2645 # endif // HAVE_TAGLIB_H
2647 # else
2648 return false;
2649 # endif // ENABLE_VISUALIZER
2652 void ShowVisualizer::run()
2654 # ifdef ENABLE_VISUALIZER
2655 myVisualizer->switchTo();
2656 # endif // ENABLE_VISUALIZER
2659 bool ShowClock::canBeRun()
2661 # ifdef ENABLE_CLOCK
2662 return myScreen != myClock
2663 # ifdef HAVE_TAGLIB_H
2664 && myScreen != myTinyTagEditor
2665 # endif // HAVE_TAGLIB_H
2667 # else
2668 return false;
2669 # endif // ENABLE_CLOCK
2672 void ShowClock::run()
2674 # ifdef ENABLE_CLOCK
2675 myClock->switchTo();
2676 # endif // ENABLE_CLOCK
2679 #ifdef HAVE_TAGLIB_H
2680 bool ShowServerInfo::canBeRun()
2682 return myScreen != myTinyTagEditor;
2684 #endif // HAVE_TAGLIB_H
2686 void ShowServerInfo::run()
2688 myServerInfo->switchTo();
2693 namespace {
2695 void populateActions()
2697 AvailableActions.resize(static_cast<size_t>(Actions::Type::_numberOfActions));
2698 auto insert_action = [](Actions::BaseAction *a) {
2699 AvailableActions.at(static_cast<size_t>(a->type())).reset(a);
2701 insert_action(new Actions::Dummy());
2702 insert_action(new Actions::UpdateEnvironment());
2703 insert_action(new Actions::MouseEvent());
2704 insert_action(new Actions::ScrollUp());
2705 insert_action(new Actions::ScrollDown());
2706 insert_action(new Actions::ScrollUpArtist());
2707 insert_action(new Actions::ScrollUpAlbum());
2708 insert_action(new Actions::ScrollDownArtist());
2709 insert_action(new Actions::ScrollDownAlbum());
2710 insert_action(new Actions::PageUp());
2711 insert_action(new Actions::PageDown());
2712 insert_action(new Actions::MoveHome());
2713 insert_action(new Actions::MoveEnd());
2714 insert_action(new Actions::ToggleInterface());
2715 insert_action(new Actions::JumpToParentDirectory());
2716 insert_action(new Actions::RunAction());
2717 insert_action(new Actions::SelectItem());
2718 insert_action(new Actions::SelectRange());
2719 insert_action(new Actions::PreviousColumn());
2720 insert_action(new Actions::NextColumn());
2721 insert_action(new Actions::MasterScreen());
2722 insert_action(new Actions::SlaveScreen());
2723 insert_action(new Actions::VolumeUp());
2724 insert_action(new Actions::VolumeDown());
2725 insert_action(new Actions::AddItemToPlaylist());
2726 insert_action(new Actions::DeletePlaylistItems());
2727 insert_action(new Actions::DeleteStoredPlaylist());
2728 insert_action(new Actions::DeleteBrowserItems());
2729 insert_action(new Actions::ReplaySong());
2730 insert_action(new Actions::PreviousSong());
2731 insert_action(new Actions::NextSong());
2732 insert_action(new Actions::Pause());
2733 insert_action(new Actions::Stop());
2734 insert_action(new Actions::ExecuteCommand());
2735 insert_action(new Actions::SavePlaylist());
2736 insert_action(new Actions::MoveSortOrderUp());
2737 insert_action(new Actions::MoveSortOrderDown());
2738 insert_action(new Actions::MoveSelectedItemsUp());
2739 insert_action(new Actions::MoveSelectedItemsDown());
2740 insert_action(new Actions::MoveSelectedItemsTo());
2741 insert_action(new Actions::Add());
2742 insert_action(new Actions::PlayItem());
2743 insert_action(new Actions::SeekForward());
2744 insert_action(new Actions::SeekBackward());
2745 insert_action(new Actions::ToggleDisplayMode());
2746 insert_action(new Actions::ToggleSeparatorsBetweenAlbums());
2747 insert_action(new Actions::ToggleLyricsUpdateOnSongChange());
2748 insert_action(new Actions::ToggleLyricsFetcher());
2749 insert_action(new Actions::ToggleFetchingLyricsInBackground());
2750 insert_action(new Actions::TogglePlayingSongCentering());
2751 insert_action(new Actions::UpdateDatabase());
2752 insert_action(new Actions::JumpToPlayingSong());
2753 insert_action(new Actions::ToggleRepeat());
2754 insert_action(new Actions::Shuffle());
2755 insert_action(new Actions::ToggleRandom());
2756 insert_action(new Actions::StartSearching());
2757 insert_action(new Actions::SaveTagChanges());
2758 insert_action(new Actions::ToggleSingle());
2759 insert_action(new Actions::ToggleConsume());
2760 insert_action(new Actions::ToggleCrossfade());
2761 insert_action(new Actions::SetCrossfade());
2762 insert_action(new Actions::SetVolume());
2763 insert_action(new Actions::EnterDirectory());
2764 insert_action(new Actions::EditSong());
2765 insert_action(new Actions::EditLibraryTag());
2766 insert_action(new Actions::EditLibraryAlbum());
2767 insert_action(new Actions::EditDirectoryName());
2768 insert_action(new Actions::EditPlaylistName());
2769 insert_action(new Actions::EditLyrics());
2770 insert_action(new Actions::JumpToBrowser());
2771 insert_action(new Actions::JumpToMediaLibrary());
2772 insert_action(new Actions::JumpToPlaylistEditor());
2773 insert_action(new Actions::ToggleScreenLock());
2774 insert_action(new Actions::JumpToTagEditor());
2775 insert_action(new Actions::JumpToPositionInSong());
2776 insert_action(new Actions::ReverseSelection());
2777 insert_action(new Actions::RemoveSelection());
2778 insert_action(new Actions::SelectAlbum());
2779 insert_action(new Actions::SelectFoundItems());
2780 insert_action(new Actions::AddSelectedItems());
2781 insert_action(new Actions::CropMainPlaylist());
2782 insert_action(new Actions::CropPlaylist());
2783 insert_action(new Actions::ClearMainPlaylist());
2784 insert_action(new Actions::ClearPlaylist());
2785 insert_action(new Actions::SortPlaylist());
2786 insert_action(new Actions::ReversePlaylist());
2787 insert_action(new Actions::ApplyFilter());
2788 insert_action(new Actions::Find());
2789 insert_action(new Actions::FindItemForward());
2790 insert_action(new Actions::FindItemBackward());
2791 insert_action(new Actions::NextFoundItem());
2792 insert_action(new Actions::PreviousFoundItem());
2793 insert_action(new Actions::ToggleFindMode());
2794 insert_action(new Actions::ToggleReplayGainMode());
2795 insert_action(new Actions::ToggleAddMode());
2796 insert_action(new Actions::ToggleMouse());
2797 insert_action(new Actions::ToggleBitrateVisibility());
2798 insert_action(new Actions::AddRandomItems());
2799 insert_action(new Actions::ToggleBrowserSortMode());
2800 insert_action(new Actions::ToggleLibraryTagType());
2801 insert_action(new Actions::ToggleMediaLibrarySortMode());
2802 insert_action(new Actions::FetchLyricsInBackground());
2803 insert_action(new Actions::RefetchLyrics());
2804 insert_action(new Actions::SetSelectedItemsPriority());
2805 insert_action(new Actions::ToggleOutput());
2806 insert_action(new Actions::ToggleVisualizationType());
2807 insert_action(new Actions::ShowSongInfo());
2808 insert_action(new Actions::ShowArtistInfo());
2809 insert_action(new Actions::ShowLyrics());
2810 insert_action(new Actions::Quit());
2811 insert_action(new Actions::NextScreen());
2812 insert_action(new Actions::PreviousScreen());
2813 insert_action(new Actions::ShowHelp());
2814 insert_action(new Actions::ShowPlaylist());
2815 insert_action(new Actions::ShowBrowser());
2816 insert_action(new Actions::ChangeBrowseMode());
2817 insert_action(new Actions::ShowSearchEngine());
2818 insert_action(new Actions::ResetSearchEngine());
2819 insert_action(new Actions::ShowMediaLibrary());
2820 insert_action(new Actions::ToggleMediaLibraryColumnsMode());
2821 insert_action(new Actions::ShowPlaylistEditor());
2822 insert_action(new Actions::ShowTagEditor());
2823 insert_action(new Actions::ShowOutputs());
2824 insert_action(new Actions::ShowVisualizer());
2825 insert_action(new Actions::ShowClock());
2826 insert_action(new Actions::ShowServerInfo());
2827 for (size_t i = 0; i < AvailableActions.size(); ++i)
2829 if (AvailableActions[i] == nullptr)
2830 throw std::logic_error("undefined action at position "
2831 + boost::lexical_cast<std::string>(i));
2835 bool scrollTagCanBeRun(NC::List *&list, const SongList *&songs)
2837 auto w = myScreen->activeWindow();
2838 if (list != static_cast<void *>(w))
2839 list = dynamic_cast<NC::List *>(w);
2840 if (songs != static_cast<void *>(w))
2841 songs = dynamic_cast<SongList *>(w);
2842 return list != nullptr && !list->empty()
2843 && songs != nullptr;
2846 void scrollTagUpRun(NC::List *list, const SongList *songs, MPD::Song::GetFunction get)
2848 const auto front = songs->beginS();
2849 auto it = songs->currentS();
2850 if (it->song() != nullptr)
2852 const std::string tag = it->song()->getTags(get);
2853 while (it != front)
2855 --it;
2856 if (it->song() == nullptr || it->song()->getTags(get) != tag)
2857 break;
2859 list->highlight(it-front);
2863 void scrollTagDownRun(NC::List *list, const SongList *songs, MPD::Song::GetFunction get)
2865 const auto front = songs->beginS(), back = --songs->endS();
2866 auto it = songs->currentS();
2867 if (it->song() != nullptr)
2869 const std::string tag = it->song()->getTags(get);
2870 while (it != back)
2872 ++it;
2873 if (it->song() == nullptr || it->song()->getTags(get) != tag)
2874 break;
2876 list->highlight(it-front);
2880 void seek()
2882 using Global::wHeader;
2883 using Global::wFooter;
2884 using Global::Timer;
2885 using Global::SeekingInProgress;
2887 if (!Status::State::totalTime())
2889 Statusbar::print("Unknown item length");
2890 return;
2893 Progressbar::ScopedLock progressbar_lock;
2894 Statusbar::ScopedLock statusbar_lock;
2896 unsigned songpos = Status::State::elapsedTime();
2897 auto t = Timer;
2899 int old_timeout = wFooter->getTimeout();
2900 wFooter->setTimeout(BaseScreen::defaultWindowTimeout);
2902 // Accept single action of a given type or action chain for which all actions
2903 // can be run and one of them is of the given type. This will still not work
2904 // in some contrived cases, but allows for more flexibility than accepting
2905 // single actions only.
2906 auto hasRunnableAction = [](BindingsConfiguration::BindingIteratorPair &bindings,
2907 Actions::Type type) {
2908 bool success = false;
2909 for (auto binding = bindings.first; binding != bindings.second; ++binding)
2911 auto &actions = binding->actions();
2912 for (const auto &action : actions)
2914 if (action->canBeRun())
2916 if (action->type() == type)
2917 success = true;
2919 else
2921 success = false;
2922 break;
2925 if (success)
2926 break;
2928 return success;
2931 SeekingInProgress = true;
2932 while (true)
2934 Status::trace();
2936 unsigned howmuch = Config.incremental_seeking
2937 ? (Timer-t).total_seconds()/2+Config.seek_time
2938 : Config.seek_time;
2940 NC::Key::Type input = readKey(*wFooter);
2942 auto k = Bindings.get(input);
2943 if (hasRunnableAction(k, Actions::Type::SeekForward))
2945 if (songpos < Status::State::totalTime())
2946 songpos = std::min(songpos + howmuch, Status::State::totalTime());
2948 else if (hasRunnableAction(k, Actions::Type::SeekBackward))
2950 if (songpos > 0)
2952 if (songpos < howmuch)
2953 songpos = 0;
2954 else
2955 songpos -= howmuch;
2958 else
2959 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 SeekingInProgress = false;
3004 Mpd.Seek(Status::State::currentSongPosition(), songpos);
3006 wFooter->setTimeout(old_timeout);
3009 void findItem(const SearchDirection direction)
3011 using Global::wFooter;
3013 Searchable *w = dynamic_cast<Searchable *>(myScreen);
3014 assert(w != nullptr);
3015 assert(w->allowsSearching());
3017 std::string constraint = w->searchConstraint();
3020 Statusbar::ScopedLock slock;
3021 NC::Window::ScopedPromptHook prompt_hook(
3022 *wFooter,
3023 Statusbar::Helpers::FindImmediately(w, direction));
3024 Statusbar::put() << (boost::format("Find %1%: ") % direction).str();
3025 constraint = wFooter->prompt(constraint);
3027 catch (NC::PromptAborted &)
3029 w->setSearchConstraint(constraint);
3030 w->search(direction, Config.wrapped_search, false);
3031 throw;
3034 if (constraint.empty())
3036 Statusbar::printf("Constraint unset");
3037 w->clearSearchConstraint();
3039 else
3040 Statusbar::printf("Using constraint \"%1%\"", constraint);
3043 void listsChangeFinisher()
3045 if (myScreen == myLibrary
3046 || myScreen == myPlaylistEditor
3047 # ifdef HAVE_TAGLIB_H
3048 || myScreen == myTagEditor
3049 # endif // HAVE_TAGLIB_H
3052 if (myScreen->activeWindow() == &myLibrary->Tags)
3054 myLibrary->Albums.clear();
3055 myLibrary->Albums.refresh();
3056 myLibrary->Songs.clear();
3057 myLibrary->Songs.refresh();
3058 myLibrary->updateTimer();
3060 else if (myScreen->activeWindow() == &myLibrary->Albums)
3062 myLibrary->Songs.clear();
3063 myLibrary->Songs.refresh();
3064 myLibrary->updateTimer();
3066 else if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
3068 myPlaylistEditor->Content.clear();
3069 myPlaylistEditor->Content.refresh();
3070 myPlaylistEditor->updateTimer();
3072 # ifdef HAVE_TAGLIB_H
3073 else if (myScreen->activeWindow() == myTagEditor->Dirs)
3075 myTagEditor->Tags->clear();
3077 # endif // HAVE_TAGLIB_H