Add support for fetching lyrics in background for selected songs
[ncmpcpp.git] / src / actions.cpp
blob5ee283f7afee5d0387145035d4e271f6ab021dab
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 "bindings.h"
44 #include "browser.h"
45 #include "clock.h"
46 #include "help.h"
47 #include "media_library.h"
48 #include "menu_impl.h"
49 #include "lastfm.h"
50 #include "lyrics.h"
51 #include "playlist.h"
52 #include "playlist_editor.h"
53 #include "sort_playlist.h"
54 #include "search_engine.h"
55 #include "sel_items_adder.h"
56 #include "server_info.h"
57 #include "song_info.h"
58 #include "outputs.h"
59 #include "utility/readline.h"
60 #include "utility/string.h"
61 #include "utility/type_conversions.h"
62 #include "tag_editor.h"
63 #include "tiny_tag_editor.h"
64 #include "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 boost::array<
80 Actions::BaseAction *, static_cast<size_t>(Actions::Type::_numberOfActions)
81 > AvailableActions;
83 void populateActions();
85 bool scrollTagCanBeRun(NC::List *&list, SongList *&songs);
86 void scrollTagUpRun(NC::List *list, SongList *songs, MPD::Song::GetFunction get);
87 void scrollTagDownRun(NC::List *list, SongList *songs, MPD::Song::GetFunction get);
89 void seek();
90 void findItem(const SearchDirection direction);
91 void listsChangeFinisher();
93 template <typename Iterator>
94 bool findSelectedRangeAndPrintInfoIfNot(Iterator &first, Iterator &last)
96 bool success = findSelectedRange(first, last);
97 if (!success)
98 Statusbar::print("No range selected");
99 return success;
104 namespace Actions {
106 bool OriginalStatusbarVisibility;
107 bool ExitMainLoop = false;
109 size_t HeaderHeight;
110 size_t FooterHeight;
111 size_t FooterStartY;
113 void validateScreenSize()
115 using Global::MainHeight;
117 if (COLS < 30 || MainHeight < 5)
119 NC::destroyScreen();
120 std::cout << "Screen is too small to handle ncmpcpp correctly\n";
121 exit(1);
125 void initializeScreens()
127 myHelp = new Help;
128 myPlaylist = new Playlist;
129 myBrowser = new Browser;
130 mySearcher = new SearchEngine;
131 myLibrary = new MediaLibrary;
132 myPlaylistEditor = new PlaylistEditor;
133 myLyrics = new Lyrics;
134 mySelectedItemsAdder = new SelectedItemsAdder;
135 mySongInfo = new SongInfo;
136 myServerInfo = new ServerInfo;
137 mySortPlaylistDialog = new SortPlaylistDialog;
138 myLastfm = new Lastfm;
140 # ifdef HAVE_TAGLIB_H
141 myTinyTagEditor = new TinyTagEditor;
142 myTagEditor = new TagEditor;
143 # endif // HAVE_TAGLIB_H
145 # ifdef ENABLE_VISUALIZER
146 myVisualizer = new Visualizer;
147 # endif // ENABLE_VISUALIZER
149 # ifdef ENABLE_OUTPUTS
150 myOutputs = new Outputs;
151 # endif // ENABLE_OUTPUTS
153 # ifdef ENABLE_CLOCK
154 myClock = new Clock;
155 # endif // ENABLE_CLOCK
159 void setResizeFlags()
161 myHelp->hasToBeResized = 1;
162 myPlaylist->hasToBeResized = 1;
163 myBrowser->hasToBeResized = 1;
164 mySearcher->hasToBeResized = 1;
165 myLibrary->hasToBeResized = 1;
166 myPlaylistEditor->hasToBeResized = 1;
167 myLyrics->hasToBeResized = 1;
168 mySelectedItemsAdder->hasToBeResized = 1;
169 mySongInfo->hasToBeResized = 1;
170 myServerInfo->hasToBeResized = 1;
171 mySortPlaylistDialog->hasToBeResized = 1;
172 myLastfm->hasToBeResized = 1;
174 # ifdef HAVE_TAGLIB_H
175 myTinyTagEditor->hasToBeResized = 1;
176 myTagEditor->hasToBeResized = 1;
177 # endif // HAVE_TAGLIB_H
179 # ifdef ENABLE_VISUALIZER
180 myVisualizer->hasToBeResized = 1;
181 # endif // ENABLE_VISUALIZER
183 # ifdef ENABLE_OUTPUTS
184 myOutputs->hasToBeResized = 1;
185 # endif // ENABLE_OUTPUTS
187 # ifdef ENABLE_CLOCK
188 myClock->hasToBeResized = 1;
189 # endif // ENABLE_CLOCK
192 void resizeScreen(bool reload_main_window)
194 using Global::MainHeight;
195 using Global::wHeader;
196 using Global::wFooter;
198 // update internal screen dimensions
199 if (reload_main_window)
201 rl_resize_terminal();
202 endwin();
203 refresh();
206 MainHeight = LINES-(Config.design == Design::Alternative ? 7 : 4);
208 validateScreenSize();
210 if (!Config.header_visibility)
211 MainHeight += 2;
212 if (!Config.statusbar_visibility)
213 ++MainHeight;
215 setResizeFlags();
217 applyToVisibleWindows(&BaseScreen::resize);
219 if (Config.header_visibility || Config.design == Design::Alternative)
220 wHeader->resize(COLS, HeaderHeight);
222 FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1);
223 wFooter->moveTo(0, FooterStartY);
224 wFooter->resize(COLS, Config.statusbar_visibility ? 2 : 1);
226 applyToVisibleWindows(&BaseScreen::refresh);
228 Status::Changes::elapsedTime(false);
229 Status::Changes::playerState();
230 // Note: routines for drawing separator if alternative user
231 // interface is active and header is hidden are placed in
232 // NcmpcppStatusChanges.StatusFlags
233 Status::Changes::flags();
234 drawHeader();
235 wFooter->refresh();
236 refresh();
239 void setWindowsDimensions()
241 using Global::MainStartY;
242 using Global::MainHeight;
244 MainStartY = Config.design == Design::Alternative ? 5 : 2;
245 MainHeight = LINES-(Config.design == Design::Alternative ? 7 : 4);
247 if (!Config.header_visibility)
249 MainStartY -= 2;
250 MainHeight += 2;
252 if (!Config.statusbar_visibility)
253 ++MainHeight;
255 HeaderHeight = Config.design == Design::Alternative ? (Config.header_visibility ? 5 : 3) : 2;
256 FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1);
257 FooterHeight = Config.statusbar_visibility ? 2 : 1;
260 void confirmAction(const boost::format &description)
262 Statusbar::ScopedLock slock;
263 Statusbar::put() << description.str()
264 << " [" << NC::Format::Bold << 'y' << NC::Format::NoBold
265 << '/' << NC::Format::Bold << 'n' << NC::Format::NoBold
266 << "] ";
267 auto answer = Statusbar::Helpers::promptReturnOneOf({"y", "n"});
268 if (answer == "n")
269 throw NC::PromptAborted(std::move(answer));
272 bool isMPDMusicDirSet()
274 if (Config.mpd_music_dir.empty())
276 Statusbar::print("Proper mpd_music_dir variable has to be set in configuration file");
277 return false;
279 return true;
282 BaseAction &get(Actions::Type at)
284 if (AvailableActions[1] == nullptr)
285 populateActions();
286 BaseAction *action = AvailableActions[static_cast<size_t>(at)];
287 // action should be always present if action type in queried
288 assert(action != nullptr);
289 return *action;
292 BaseAction *get(const std::string &name)
294 BaseAction *result = 0;
295 if (AvailableActions[1] == nullptr)
296 populateActions();
297 for (auto it = AvailableActions.begin(); it != AvailableActions.end(); ++it)
299 if (*it != nullptr && (*it)->name() == name)
301 result = *it;
302 break;
305 return result;
308 UpdateEnvironment::UpdateEnvironment()
309 : BaseAction(Type::UpdateEnvironment, "update_environment")
310 , m_past(boost::posix_time::from_time_t(0))
313 void UpdateEnvironment::run(bool update_timer, bool refresh_window)
315 using Global::Timer;
317 // update timer, status if necessary etc.
318 Status::trace(update_timer, true);
320 // show lyrics consumer notification if appropriate
321 if (auto message = myLyrics->tryTakeConsumerMessage())
322 Statusbar::print(*message);
324 // header stuff
325 if ((myScreen == myPlaylist || myScreen == myBrowser || myScreen == myLyrics)
326 && (Timer - m_past > boost::posix_time::milliseconds(500))
329 drawHeader();
330 m_past = Timer;
333 if (refresh_window)
334 myScreen->refreshWindow();
337 void UpdateEnvironment::run()
339 run(true, true);
342 bool MouseEvent::canBeRun()
344 return Config.mouse_support;
347 void MouseEvent::run()
349 using Global::VolumeState;
350 using Global::wFooter;
352 m_old_mouse_event = m_mouse_event;
353 m_mouse_event = wFooter->getMouseEvent();
355 //Statusbar::printf("(%1%, %2%, %3%)", m_mouse_event.bstate, m_mouse_event.x, m_mouse_event.y);
357 if (m_mouse_event.bstate & BUTTON1_PRESSED
358 && m_mouse_event.y == LINES-(Config.statusbar_visibility ? 2 : 1)
359 ) // progressbar
361 if (Status::State::player() == MPD::psStop)
362 return;
363 Mpd.Seek(Status::State::currentSongPosition(),
364 Status::State::totalTime()*m_mouse_event.x/double(COLS));
366 else if (m_mouse_event.bstate & BUTTON1_PRESSED
367 && (Config.statusbar_visibility || Config.design == Design::Alternative)
368 && Status::State::player() != MPD::psStop
369 && m_mouse_event.y == (Config.design == Design::Alternative ? 1 : LINES-1)
370 && m_mouse_event.x < 9
371 ) // playing/paused
373 Mpd.Toggle();
375 else if ((m_mouse_event.bstate & BUTTON5_PRESSED || m_mouse_event.bstate & BUTTON4_PRESSED)
376 && (Config.header_visibility || Config.design == Design::Alternative)
377 && m_mouse_event.y == 0 && size_t(m_mouse_event.x) > COLS-VolumeState.length()
378 ) // volume
380 if (m_mouse_event.bstate & BUTTON5_PRESSED)
381 get(Type::VolumeDown).execute();
382 else
383 get(Type::VolumeUp).execute();
385 else if (m_mouse_event.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED | BUTTON4_PRESSED | BUTTON5_PRESSED))
386 myScreen->mouseButtonPressed(m_mouse_event);
389 void ScrollUp::run()
391 myScreen->scroll(NC::Scroll::Up);
392 listsChangeFinisher();
395 void ScrollDown::run()
397 myScreen->scroll(NC::Scroll::Down);
398 listsChangeFinisher();
401 bool ScrollUpArtist::canBeRun()
403 return scrollTagCanBeRun(m_list, m_songs);
406 void ScrollUpArtist::run()
408 scrollTagUpRun(m_list, m_songs, &MPD::Song::getArtist);
411 bool ScrollUpAlbum::canBeRun()
413 return scrollTagCanBeRun(m_list, m_songs);
416 void ScrollUpAlbum::run()
418 scrollTagUpRun(m_list, m_songs, &MPD::Song::getAlbum);
421 bool ScrollDownArtist::canBeRun()
423 return scrollTagCanBeRun(m_list, m_songs);
426 void ScrollDownArtist::run()
428 scrollTagDownRun(m_list, m_songs, &MPD::Song::getArtist);
431 bool ScrollDownAlbum::canBeRun()
433 return scrollTagCanBeRun(m_list, m_songs);
436 void ScrollDownAlbum::run()
438 scrollTagDownRun(m_list, m_songs, &MPD::Song::getAlbum);
441 void PageUp::run()
443 myScreen->scroll(NC::Scroll::PageUp);
444 listsChangeFinisher();
447 void PageDown::run()
449 myScreen->scroll(NC::Scroll::PageDown);
450 listsChangeFinisher();
453 void MoveHome::run()
455 myScreen->scroll(NC::Scroll::Home);
456 listsChangeFinisher();
459 void MoveEnd::run()
461 myScreen->scroll(NC::Scroll::End);
462 listsChangeFinisher();
465 void ToggleInterface::run()
467 switch (Config.design)
469 case Design::Classic:
470 Config.design = Design::Alternative;
471 Config.statusbar_visibility = false;
472 break;
473 case Design::Alternative:
474 Config.design = Design::Classic;
475 Config.statusbar_visibility = OriginalStatusbarVisibility;
476 break;
478 setWindowsDimensions();
479 resizeScreen(false);
480 // unlock progressbar
481 Progressbar::ScopedLock();
482 Status::Changes::mixer();
483 Status::Changes::elapsedTime(false);
484 Statusbar::printf("User interface: %1%", Config.design);
487 bool JumpToParentDirectory::canBeRun()
489 return (myScreen == myBrowser)
490 # ifdef HAVE_TAGLIB_H
491 || (myScreen->activeWindow() == myTagEditor->Dirs)
492 # endif // HAVE_TAGLIB_H
496 void JumpToParentDirectory::run()
498 if (myScreen == myBrowser)
500 if (!myBrowser->inRootDirectory())
502 myBrowser->main().reset();
503 myBrowser->enterDirectory();
506 # ifdef HAVE_TAGLIB_H
507 else if (myScreen == myTagEditor)
509 if (myTagEditor->CurrentDir() != "/")
511 myTagEditor->Dirs->reset();
512 myTagEditor->enterDirectory();
515 # endif // HAVE_TAGLIB_H
518 bool RunAction::canBeRun()
520 m_ha = dynamic_cast<HasActions *>(myScreen);
521 return m_ha != nullptr
522 && m_ha->actionRunnable();
525 void RunAction::run()
527 m_ha->runAction();
530 bool PreviousColumn::canBeRun()
532 m_hc = dynamic_cast<HasColumns *>(myScreen);
533 return m_hc != nullptr
534 && m_hc->previousColumnAvailable();
537 void PreviousColumn::run()
539 m_hc->previousColumn();
542 bool NextColumn::canBeRun()
544 m_hc = dynamic_cast<HasColumns *>(myScreen);
545 return m_hc != nullptr
546 && m_hc->nextColumnAvailable();
549 void NextColumn::run()
551 m_hc->nextColumn();
554 bool MasterScreen::canBeRun()
556 using Global::myLockedScreen;
557 using Global::myInactiveScreen;
559 return myLockedScreen
560 && myInactiveScreen
561 && myLockedScreen != myScreen
562 && myScreen->isMergable();
565 void MasterScreen::run()
567 using Global::myInactiveScreen;
568 using Global::myLockedScreen;
570 myInactiveScreen = myScreen;
571 myScreen = myLockedScreen;
572 drawHeader();
575 bool SlaveScreen::canBeRun()
577 using Global::myLockedScreen;
578 using Global::myInactiveScreen;
580 return myLockedScreen
581 && myInactiveScreen
582 && myLockedScreen == myScreen
583 && myScreen->isMergable();
586 void SlaveScreen::run()
588 using Global::myInactiveScreen;
589 using Global::myLockedScreen;
591 myScreen = myInactiveScreen;
592 myInactiveScreen = myLockedScreen;
593 drawHeader();
596 void VolumeUp::run()
598 int volume = std::min(Status::State::volume()+Config.volume_change_step, 100u);
599 Mpd.SetVolume(volume);
602 void VolumeDown::run()
604 int volume = std::max(int(Status::State::volume()-Config.volume_change_step), 0);
605 Mpd.SetVolume(volume);
608 bool AddItemToPlaylist::canBeRun()
610 m_hs = dynamic_cast<HasSongs *>(myScreen);
611 return m_hs != nullptr && m_hs->itemAvailable();
614 void AddItemToPlaylist::run()
616 bool success = m_hs->addItemToPlaylist(false);
617 if (success)
619 myScreen->scroll(NC::Scroll::Down);
620 listsChangeFinisher();
624 bool PlayItem::canBeRun()
626 m_hs = dynamic_cast<HasSongs *>(myScreen);
627 return m_hs != nullptr && m_hs->itemAvailable();
630 void PlayItem::run()
632 bool success = m_hs->addItemToPlaylist(true);
633 if (success)
634 listsChangeFinisher();
637 bool DeletePlaylistItems::canBeRun()
639 return (myScreen == myPlaylist && !myPlaylist->main().empty())
640 || (myScreen->isActiveWindow(myPlaylistEditor->Content) && !myPlaylistEditor->Content.empty());
643 void DeletePlaylistItems::run()
645 if (myScreen == myPlaylist)
647 Statusbar::print("Deleting items...");
648 auto delete_fun = std::bind(&MPD::Connection::Delete, ph::_1, ph::_2);
649 deleteSelectedSongs(myPlaylist->main(), delete_fun);
650 Statusbar::print("Item(s) deleted");
652 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
654 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
655 auto delete_fun = std::bind(&MPD::Connection::PlaylistDelete, ph::_1, playlist, ph::_2);
656 Statusbar::print("Deleting items...");
657 deleteSelectedSongs(myPlaylistEditor->Content, delete_fun);
658 Statusbar::print("Item(s) deleted");
662 bool DeleteBrowserItems::canBeRun()
664 auto check_if_deletion_allowed = []() {
665 if (Config.allow_for_physical_item_deletion)
666 return true;
667 else
669 Statusbar::print("Flag \"allow_for_physical_item_deletion\" needs to be enabled in configuration file");
670 return false;
673 return myScreen == myBrowser
674 && !myBrowser->main().empty()
675 && isMPDMusicDirSet()
676 && check_if_deletion_allowed();
679 void DeleteBrowserItems::run()
681 auto get_name = [](const MPD::Item &item) -> std::string {
682 std::string iname;
683 switch (item.type())
685 case MPD::Item::Type::Directory:
686 iname = getBasename(item.directory().path());
687 break;
688 case MPD::Item::Type::Song:
689 iname = item.song().getName();
690 break;
691 case MPD::Item::Type::Playlist:
692 iname = getBasename(item.playlist().path());
693 break;
695 return iname;
698 boost::format question;
699 if (hasSelected(myBrowser->main().begin(), myBrowser->main().end()))
700 question = boost::format("Delete selected items?");
701 else
703 const auto &item = myBrowser->main().current()->value();
704 // parent directories are not accepted (and they
705 // can't be selected, so in other cases it's fine).
706 if (myBrowser->isParentDirectory(item))
707 return;
708 const char msg[] = "Delete \"%1%\"?";
709 question = boost::format(msg) % wideShorten(
710 get_name(item), COLS-const_strlen(msg)-5
713 confirmAction(question);
715 auto items = getSelectedOrCurrent(
716 myBrowser->main().begin(),
717 myBrowser->main().end(),
718 myBrowser->main().current()
720 for (const auto &item : items)
722 myBrowser->remove(item->value());
723 const char msg[] = "Deleted %1% \"%2%\"";
724 Statusbar::printf(msg,
725 itemTypeToString(item->value().type()),
726 wideShorten(get_name(item->value()), COLS-const_strlen(msg))
730 if (!myBrowser->isLocal())
731 Mpd.UpdateDirectory(myBrowser->currentDirectory());
732 myBrowser->requestUpdate();
735 bool DeleteStoredPlaylist::canBeRun()
737 return myScreen->isActiveWindow(myPlaylistEditor->Playlists);
740 void DeleteStoredPlaylist::run()
742 if (myPlaylistEditor->Playlists.empty())
743 return;
744 boost::format question;
745 if (hasSelected(myPlaylistEditor->Playlists.begin(), myPlaylistEditor->Playlists.end()))
746 question = boost::format("Delete selected playlists?");
747 else
748 question = boost::format("Delete playlist \"%1%\"?")
749 % wideShorten(myPlaylistEditor->Playlists.current()->value().path(), COLS-question.size()-10);
750 confirmAction(question);
751 auto list = getSelectedOrCurrent(
752 myPlaylistEditor->Playlists.begin(),
753 myPlaylistEditor->Playlists.end(),
754 myPlaylistEditor->Playlists.current()
756 for (const auto &item : list)
757 Mpd.DeletePlaylist(item->value().path());
758 Statusbar::printf("%1% deleted", list.size() == 1 ? "Playlist" : "Playlists");
759 // force playlists update. this happens automatically, but only after call
760 // to Key::read, therefore when we call PlaylistEditor::Update, it won't
761 // yet see it, so let's point that it needs to update it.
762 myPlaylistEditor->requestPlaylistsUpdate();
765 void ReplaySong::run()
767 if (Status::State::player() != MPD::psStop)
768 Mpd.Seek(Status::State::currentSongPosition(), 0);
771 void PreviousSong::run()
773 Mpd.Prev();
776 void NextSong::run()
778 Mpd.Next();
781 void Pause::run()
783 Mpd.Toggle();
786 void SavePlaylist::run()
788 using Global::wFooter;
790 std::string playlist_name;
792 Statusbar::ScopedLock slock;
793 Statusbar::put() << "Save playlist as: ";
794 playlist_name = wFooter->prompt();
798 Mpd.SavePlaylist(playlist_name);
799 Statusbar::printf("Playlist saved as \"%1%\"", playlist_name);
801 catch (MPD::ServerError &e)
803 if (e.code() == MPD_SERVER_ERROR_EXIST)
805 confirmAction(
806 boost::format("Playlist \"%1%\" already exists, overwrite?") % playlist_name
808 Mpd.DeletePlaylist(playlist_name);
809 Mpd.SavePlaylist(playlist_name);
810 Statusbar::print("Playlist overwritten");
812 else
813 throw;
817 void Stop::run()
819 Mpd.Stop();
822 void ExecuteCommand::run()
824 using Global::wFooter;
826 std::string cmd_name;
828 Statusbar::ScopedLock slock;
829 NC::Window::ScopedPromptHook helper(*wFooter,
830 Statusbar::Helpers::TryExecuteImmediateCommand()
832 Statusbar::put() << NC::Format::Bold << ":" << NC::Format::NoBold;
833 cmd_name = wFooter->prompt();
836 auto cmd = Bindings.findCommand(cmd_name);
837 if (cmd)
839 Statusbar::printf(1, "Executing %1%...", cmd_name);
840 bool res = cmd->binding().execute();
841 Statusbar::printf("Execution of command \"%1%\" %2%.",
842 cmd_name, res ? "successful" : "unsuccessful"
845 else
846 Statusbar::printf("No command named \"%1%\"", cmd_name);
849 bool MoveSortOrderUp::canBeRun()
851 return myScreen == mySortPlaylistDialog;
854 void MoveSortOrderUp::run()
856 mySortPlaylistDialog->moveSortOrderUp();
859 bool MoveSortOrderDown::canBeRun()
861 return myScreen == mySortPlaylistDialog;
864 void MoveSortOrderDown::run()
866 mySortPlaylistDialog->moveSortOrderDown();
869 bool MoveSelectedItemsUp::canBeRun()
871 return ((myScreen == myPlaylist
872 && !myPlaylist->main().empty())
873 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
874 && !myPlaylistEditor->Content.empty()));
877 void MoveSelectedItemsUp::run()
879 const char *filteredMsg = "Moving items up is disabled in filtered playlist";
880 if (myScreen == myPlaylist)
882 if (myPlaylist->main().isFiltered())
883 Statusbar::print(filteredMsg);
884 else
885 moveSelectedItemsUp(
886 myPlaylist->main(),
887 std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
889 else if (myScreen == myPlaylistEditor)
891 if (myPlaylistEditor->Content.isFiltered())
892 Statusbar::print(filteredMsg);
893 else
895 auto playlist = myPlaylistEditor->Playlists.current()->value().path();
896 moveSelectedItemsUp(
897 myPlaylistEditor->Content,
898 std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3));
903 bool MoveSelectedItemsDown::canBeRun()
905 return ((myScreen == myPlaylist
906 && !myPlaylist->main().empty())
907 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
908 && !myPlaylistEditor->Content.empty()));
911 void MoveSelectedItemsDown::run()
913 const char *filteredMsg = "Moving items down is disabled in filtered playlist";
914 if (myScreen == myPlaylist)
916 if (myPlaylist->main().isFiltered())
917 Statusbar::print(filteredMsg);
918 else
919 moveSelectedItemsDown(
920 myPlaylist->main(),
921 std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
923 else if (myScreen == myPlaylistEditor)
925 if (myPlaylistEditor->Content.isFiltered())
926 Statusbar::print(filteredMsg);
927 else
929 auto playlist = myPlaylistEditor->Playlists.current()->value().path();
930 moveSelectedItemsDown(
931 myPlaylistEditor->Content,
932 std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3));
937 bool MoveSelectedItemsTo::canBeRun()
939 return myScreen == myPlaylist
940 || myScreen->isActiveWindow(myPlaylistEditor->Content);
943 void MoveSelectedItemsTo::run()
945 if (myScreen == myPlaylist)
947 if (!myPlaylist->main().empty())
948 moveSelectedItemsTo(myPlaylist->main(), std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
950 else
952 assert(!myPlaylistEditor->Playlists.empty());
953 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
954 auto move_fun = std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3);
955 moveSelectedItemsTo(myPlaylistEditor->Content, move_fun);
959 bool Add::canBeRun()
961 return myScreen != myPlaylistEditor
962 || !myPlaylistEditor->Playlists.empty();
965 void Add::run()
967 using Global::wFooter;
969 std::string path;
971 Statusbar::ScopedLock slock;
972 Statusbar::put() << (myScreen == myPlaylistEditor ? "Add to playlist: " : "Add: ");
973 path = wFooter->prompt();
976 // confirm when one wants to add the whole database
977 if (path.empty())
978 confirmAction("Are you sure you want to add the whole database?");
980 Statusbar::put() << "Adding...";
981 wFooter->refresh();
982 if (myScreen == myPlaylistEditor)
983 Mpd.AddToPlaylist(myPlaylistEditor->Playlists.current()->value().path(), path);
984 else
988 Mpd.Add(path);
990 catch (MPD::ServerError &err)
992 // If a path is not a file or directory, assume it is a playlist.
993 if (err.code() == MPD_SERVER_ERROR_NO_EXIST)
994 Mpd.LoadPlaylist(path);
995 else
996 throw;
1001 bool SeekForward::canBeRun()
1003 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1006 void SeekForward::run()
1008 seek();
1011 bool SeekBackward::canBeRun()
1013 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1016 void SeekBackward::run()
1018 seek();
1021 bool ToggleDisplayMode::canBeRun()
1023 return myScreen == myPlaylist
1024 || myScreen == myBrowser
1025 || myScreen == mySearcher
1026 || myScreen->isActiveWindow(myPlaylistEditor->Content);
1029 void ToggleDisplayMode::run()
1031 if (myScreen == myPlaylist)
1033 switch (Config.playlist_display_mode)
1035 case DisplayMode::Classic:
1036 Config.playlist_display_mode = DisplayMode::Columns;
1037 myPlaylist->main().setItemDisplayer(std::bind(
1038 Display::SongsInColumns, ph::_1, std::cref(myPlaylist->main())
1040 if (Config.titles_visibility)
1041 myPlaylist->main().setTitle(Display::Columns(myPlaylist->main().getWidth()));
1042 else
1043 myPlaylist->main().setTitle("");
1044 break;
1045 case DisplayMode::Columns:
1046 Config.playlist_display_mode = DisplayMode::Classic;
1047 myPlaylist->main().setItemDisplayer(std::bind(
1048 Display::Songs, ph::_1, std::cref(myPlaylist->main()), std::cref(Config.song_list_format)
1050 myPlaylist->main().setTitle("");
1052 Statusbar::printf("Playlist display mode: %1%", Config.playlist_display_mode);
1054 else if (myScreen == myBrowser)
1056 switch (Config.browser_display_mode)
1058 case DisplayMode::Classic:
1059 Config.browser_display_mode = DisplayMode::Columns;
1060 if (Config.titles_visibility)
1061 myBrowser->main().setTitle(Display::Columns(myBrowser->main().getWidth()));
1062 else
1063 myBrowser->main().setTitle("");
1064 break;
1065 case DisplayMode::Columns:
1066 Config.browser_display_mode = DisplayMode::Classic;
1067 myBrowser->main().setTitle("");
1068 break;
1070 Statusbar::printf("Browser display mode: %1%", Config.browser_display_mode);
1072 else if (myScreen == mySearcher)
1074 switch (Config.search_engine_display_mode)
1076 case DisplayMode::Classic:
1077 Config.search_engine_display_mode = DisplayMode::Columns;
1078 break;
1079 case DisplayMode::Columns:
1080 Config.search_engine_display_mode = DisplayMode::Classic;
1081 break;
1083 Statusbar::printf("Search engine display mode: %1%", Config.search_engine_display_mode);
1084 if (mySearcher->main().size() > SearchEngine::StaticOptions)
1085 mySearcher->main().setTitle(
1086 Config.search_engine_display_mode == DisplayMode::Columns
1087 && Config.titles_visibility
1088 ? Display::Columns(mySearcher->main().getWidth())
1089 : ""
1092 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
1094 switch (Config.playlist_editor_display_mode)
1096 case DisplayMode::Classic:
1097 Config.playlist_editor_display_mode = DisplayMode::Columns;
1098 myPlaylistEditor->Content.setItemDisplayer(std::bind(
1099 Display::SongsInColumns, ph::_1, std::cref(myPlaylistEditor->Content)
1101 break;
1102 case DisplayMode::Columns:
1103 Config.playlist_editor_display_mode = DisplayMode::Classic;
1104 myPlaylistEditor->Content.setItemDisplayer(std::bind(
1105 Display::Songs, ph::_1, std::cref(myPlaylistEditor->Content), std::cref(Config.song_list_format)
1107 break;
1109 Statusbar::printf("Playlist editor display mode: %1%", Config.playlist_editor_display_mode);
1113 bool ToggleSeparatorsBetweenAlbums::canBeRun()
1115 return true;
1118 void ToggleSeparatorsBetweenAlbums::run()
1120 Config.playlist_separate_albums = !Config.playlist_separate_albums;
1121 Statusbar::printf("Separators between albums: %1%",
1122 Config.playlist_separate_albums ? "on" : "off"
1126 bool ToggleLyricsUpdateOnSongChange::canBeRun()
1128 return myScreen == myLyrics;
1131 void ToggleLyricsUpdateOnSongChange::run()
1133 Config.now_playing_lyrics = !Config.now_playing_lyrics;
1134 Statusbar::printf("Update lyrics if song changes: %1%",
1135 Config.now_playing_lyrics ? "on" : "off"
1139 void ToggleLyricsFetcher::run()
1141 myLyrics->toggleFetcher();
1144 void ToggleFetchingLyricsInBackground::run()
1146 Config.fetch_lyrics_in_background = !Config.fetch_lyrics_in_background;
1147 Statusbar::printf("Fetching lyrics for playing songs in background: %1%",
1148 Config.fetch_lyrics_in_background ? "on" : "off");
1151 void TogglePlayingSongCentering::run()
1153 Config.autocenter_mode = !Config.autocenter_mode;
1154 Statusbar::printf("Centering playing song: %1%",
1155 Config.autocenter_mode ? "on" : "off"
1157 if (Config.autocenter_mode)
1159 auto s = myPlaylist->nowPlayingSong();
1160 if (!s.empty())
1161 myPlaylist->locateSong(s);
1165 void UpdateDatabase::run()
1167 if (myScreen == myBrowser)
1168 Mpd.UpdateDirectory(myBrowser->currentDirectory());
1169 # ifdef HAVE_TAGLIB_H
1170 else if (myScreen == myTagEditor)
1171 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1172 # endif // HAVE_TAGLIB_H
1173 else
1174 Mpd.UpdateDirectory("/");
1177 bool JumpToPlayingSong::canBeRun()
1179 return myScreen == myPlaylist
1180 || myScreen == myBrowser
1181 || myScreen == myLibrary;
1184 void JumpToPlayingSong::run()
1186 auto s = myPlaylist->nowPlayingSong();
1187 if (s.empty())
1188 return;
1189 if (myScreen == myPlaylist)
1191 myPlaylist->locateSong(s);
1193 else if (myScreen == myBrowser)
1195 myBrowser->locateSong(s);
1197 else if (myScreen == myLibrary)
1199 myLibrary->locateSong(s);
1203 void ToggleRepeat::run()
1205 Mpd.SetRepeat(!Status::State::repeat());
1208 bool Shuffle::canBeRun()
1210 if (myScreen != myPlaylist)
1211 return false;
1212 m_begin = myPlaylist->main().begin();
1213 m_end = myPlaylist->main().end();
1214 return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end);
1217 void Shuffle::run()
1219 if (Config.ask_before_shuffling_playlists)
1220 confirmAction("Do you really want to shuffle selected range?");
1221 auto begin = myPlaylist->main().begin();
1222 Mpd.ShuffleRange(m_begin-begin, m_end-begin);
1223 Statusbar::print("Range shuffled");
1226 void ToggleRandom::run()
1228 Mpd.SetRandom(!Status::State::random());
1231 bool StartSearching::canBeRun()
1233 return myScreen == mySearcher && !mySearcher->main()[0].isInactive();
1236 void StartSearching::run()
1238 mySearcher->main().highlight(SearchEngine::SearchButton);
1239 mySearcher->main().setHighlighting(0);
1240 mySearcher->main().refresh();
1241 mySearcher->main().setHighlighting(1);
1242 mySearcher->runAction();
1245 bool SaveTagChanges::canBeRun()
1247 # ifdef HAVE_TAGLIB_H
1248 return myScreen == myTinyTagEditor
1249 || myScreen->activeWindow() == myTagEditor->TagTypes;
1250 # else
1251 return false;
1252 # endif // HAVE_TAGLIB_H
1255 void SaveTagChanges::run()
1257 # ifdef HAVE_TAGLIB_H
1258 if (myScreen == myTinyTagEditor)
1260 myTinyTagEditor->main().highlight(myTinyTagEditor->main().size()-2); // Save
1261 myTinyTagEditor->runAction();
1263 else if (myScreen->activeWindow() == myTagEditor->TagTypes)
1265 myTagEditor->TagTypes->highlight(myTagEditor->TagTypes->size()-1); // Save
1266 myTagEditor->runAction();
1268 # endif // HAVE_TAGLIB_H
1271 void ToggleSingle::run()
1273 Mpd.SetSingle(!Status::State::single());
1276 void ToggleConsume::run()
1278 Mpd.SetConsume(!Status::State::consume());
1281 void ToggleCrossfade::run()
1283 Mpd.SetCrossfade(Status::State::crossfade() ? 0 : Config.crossfade_time);
1286 void SetCrossfade::run()
1288 using Global::wFooter;
1290 Statusbar::ScopedLock slock;
1291 Statusbar::put() << "Set crossfade to: ";
1292 auto crossfade = fromString<unsigned>(wFooter->prompt());
1293 lowerBoundCheck(crossfade, 0u);
1294 Config.crossfade_time = crossfade;
1295 Mpd.SetCrossfade(crossfade);
1298 void SetVolume::run()
1300 using Global::wFooter;
1302 unsigned volume;
1304 Statusbar::ScopedLock slock;
1305 Statusbar::put() << "Set volume to: ";
1306 volume = fromString<unsigned>(wFooter->prompt());
1307 boundsCheck(volume, 0u, 100u);
1308 Mpd.SetVolume(volume);
1310 Statusbar::printf("Volume set to %1%%%", volume);
1313 bool EnterDirectory::canBeRun()
1315 bool result = false;
1316 if (myScreen == myBrowser && !myBrowser->main().empty())
1318 result = myBrowser->main().current()->value().type()
1319 == MPD::Item::Type::Directory;
1321 #ifdef HAVE_TAGLIB_H
1322 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1323 result = true;
1324 #endif // HAVE_TAGLIB_H
1325 return result;
1328 void EnterDirectory::run()
1330 if (myScreen == myBrowser)
1331 myBrowser->enterDirectory();
1332 #ifdef HAVE_TAGLIB_H
1333 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1335 if (!myTagEditor->enterDirectory())
1336 Statusbar::print("No subdirectories found");
1338 #endif // HAVE_TAGLIB_H
1341 bool EditSong::canBeRun()
1343 # ifdef HAVE_TAGLIB_H
1344 m_song = currentSong(myScreen);
1345 return m_song != nullptr && isMPDMusicDirSet();
1346 # else
1347 return false;
1348 # endif // HAVE_TAGLIB_H
1351 void EditSong::run()
1353 # ifdef HAVE_TAGLIB_H
1354 myTinyTagEditor->SetEdited(*m_song);
1355 myTinyTagEditor->switchTo();
1356 # endif // HAVE_TAGLIB_H
1359 bool EditLibraryTag::canBeRun()
1361 # ifdef HAVE_TAGLIB_H
1362 return myScreen->isActiveWindow(myLibrary->Tags)
1363 && !myLibrary->Tags.empty()
1364 && isMPDMusicDirSet();
1365 # else
1366 return false;
1367 # endif // HAVE_TAGLIB_H
1370 void EditLibraryTag::run()
1372 # ifdef HAVE_TAGLIB_H
1373 using Global::wFooter;
1375 std::string new_tag;
1377 Statusbar::ScopedLock slock;
1378 Statusbar::put() << NC::Format::Bold << tagTypeToString(Config.media_lib_primary_tag) << NC::Format::NoBold << ": ";
1379 new_tag = wFooter->prompt(myLibrary->Tags.current()->value().tag());
1381 if (!new_tag.empty() && new_tag != myLibrary->Tags.current()->value().tag())
1383 Statusbar::print("Updating tags...");
1384 Mpd.StartSearch(true);
1385 Mpd.AddSearch(Config.media_lib_primary_tag, myLibrary->Tags.current()->value().tag());
1386 MPD::MutableSong::SetFunction set = tagTypeToSetFunction(Config.media_lib_primary_tag);
1387 assert(set);
1388 bool success = true;
1389 std::string dir_to_update;
1390 for (MPD::SongIterator s = Mpd.CommitSearchSongs(), end; s != end; ++s)
1392 MPD::MutableSong ms = std::move(*s);
1393 ms.setTags(set, new_tag);
1394 Statusbar::printf("Updating tags in \"%1%\"...", ms.getName());
1395 std::string path = Config.mpd_music_dir + ms.getURI();
1396 if (!Tags::write(ms))
1398 success = false;
1399 Statusbar::printf("Error while writing tags to \"%1%\": %2%",
1400 ms.getName(), strerror(errno));
1401 s.finish();
1402 break;
1404 if (dir_to_update.empty())
1405 dir_to_update = ms.getURI();
1406 else
1407 dir_to_update = getSharedDirectory(dir_to_update, ms.getURI());
1409 if (success)
1411 Mpd.UpdateDirectory(dir_to_update);
1412 Statusbar::print("Tags updated successfully");
1415 # endif // HAVE_TAGLIB_H
1418 bool EditLibraryAlbum::canBeRun()
1420 # ifdef HAVE_TAGLIB_H
1421 return myScreen->isActiveWindow(myLibrary->Albums)
1422 && !myLibrary->Albums.empty()
1423 && isMPDMusicDirSet();
1424 # else
1425 return false;
1426 # endif // HAVE_TAGLIB_H
1429 void EditLibraryAlbum::run()
1431 # ifdef HAVE_TAGLIB_H
1432 using Global::wFooter;
1433 // FIXME: merge this and EditLibraryTag. also, prompt on failure if user wants to continue
1434 std::string new_album;
1436 Statusbar::ScopedLock slock;
1437 Statusbar::put() << NC::Format::Bold << "Album: " << NC::Format::NoBold;
1438 new_album = wFooter->prompt(myLibrary->Albums.current()->value().entry().album());
1440 if (!new_album.empty() && new_album != myLibrary->Albums.current()->value().entry().album())
1442 bool success = 1;
1443 Statusbar::print("Updating tags...");
1444 for (size_t i = 0; i < myLibrary->Songs.size(); ++i)
1446 Statusbar::printf("Updating tags in \"%1%\"...", myLibrary->Songs[i].value().getName());
1447 std::string path = Config.mpd_music_dir + myLibrary->Songs[i].value().getURI();
1448 TagLib::FileRef f(path.c_str());
1449 if (f.isNull())
1451 const char msg[] = "Error while opening file \"%1%\"";
1452 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1453 success = 0;
1454 break;
1456 f.tag()->setAlbum(ToWString(new_album));
1457 if (!f.save())
1459 const char msg[] = "Error while writing tags in \"%1%\"";
1460 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1461 success = 0;
1462 break;
1465 if (success)
1467 Mpd.UpdateDirectory(getSharedDirectory(myLibrary->Songs.beginV(), myLibrary->Songs.endV()));
1468 Statusbar::print("Tags updated successfully");
1471 # endif // HAVE_TAGLIB_H
1474 bool EditDirectoryName::canBeRun()
1476 return ((myScreen == myBrowser
1477 && !myBrowser->main().empty()
1478 && myBrowser->main().current()->value().type() == MPD::Item::Type::Directory)
1479 # ifdef HAVE_TAGLIB_H
1480 || (myScreen->activeWindow() == myTagEditor->Dirs
1481 && !myTagEditor->Dirs->empty()
1482 && myTagEditor->Dirs->choice() > 0)
1483 # endif // HAVE_TAGLIB_H
1484 ) && isMPDMusicDirSet();
1487 void EditDirectoryName::run()
1489 using Global::wFooter;
1490 if (myScreen == myBrowser)
1492 std::string old_dir = myBrowser->main().current()->value().directory().path();
1493 std::string new_dir;
1495 Statusbar::ScopedLock slock;
1496 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1497 new_dir = wFooter->prompt(old_dir);
1499 if (!new_dir.empty() && new_dir != old_dir)
1501 std::string full_old_dir;
1502 if (!myBrowser->isLocal())
1503 full_old_dir += Config.mpd_music_dir;
1504 full_old_dir += old_dir;
1505 std::string full_new_dir;
1506 if (!myBrowser->isLocal())
1507 full_new_dir += Config.mpd_music_dir;
1508 full_new_dir += new_dir;
1509 boost::filesystem::rename(full_old_dir, full_new_dir);
1510 const char msg[] = "Directory renamed to \"%1%\"";
1511 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1512 if (!myBrowser->isLocal())
1513 Mpd.UpdateDirectory(getSharedDirectory(old_dir, new_dir));
1514 myBrowser->requestUpdate();
1517 # ifdef HAVE_TAGLIB_H
1518 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1520 std::string old_dir = myTagEditor->Dirs->current()->value().first, new_dir;
1522 Statusbar::ScopedLock slock;
1523 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1524 new_dir = wFooter->prompt(old_dir);
1526 if (!new_dir.empty() && new_dir != old_dir)
1528 std::string full_old_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + old_dir;
1529 std::string full_new_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + new_dir;
1530 if (rename(full_old_dir.c_str(), full_new_dir.c_str()) == 0)
1532 const char msg[] = "Directory renamed to \"%1%\"";
1533 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1534 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1536 else
1538 const char msg[] = "Couldn't rename \"%1%\": %2%";
1539 Statusbar::printf(msg, wideShorten(old_dir, COLS-const_strlen(msg)-25), strerror(errno));
1543 # endif // HAVE_TAGLIB_H
1546 bool EditPlaylistName::canBeRun()
1548 return (myScreen->isActiveWindow(myPlaylistEditor->Playlists)
1549 && !myPlaylistEditor->Playlists.empty())
1550 || (myScreen == myBrowser
1551 && !myBrowser->main().empty()
1552 && myBrowser->main().current()->value().type() == MPD::Item::Type::Playlist);
1555 void EditPlaylistName::run()
1557 using Global::wFooter;
1558 std::string old_name, new_name;
1559 if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
1560 old_name = myPlaylistEditor->Playlists.current()->value().path();
1561 else
1562 old_name = myBrowser->main().current()->value().playlist().path();
1564 Statusbar::ScopedLock slock;
1565 Statusbar::put() << NC::Format::Bold << "Playlist: " << NC::Format::NoBold;
1566 new_name = wFooter->prompt(old_name);
1568 if (!new_name.empty() && new_name != old_name)
1570 Mpd.Rename(old_name, new_name);
1571 const char msg[] = "Playlist renamed to \"%1%\"";
1572 Statusbar::printf(msg, wideShorten(new_name, COLS-const_strlen(msg)));
1576 bool EditLyrics::canBeRun()
1578 return myScreen == myLyrics;
1581 void EditLyrics::run()
1583 myLyrics->edit();
1586 bool JumpToBrowser::canBeRun()
1588 m_song = currentSong(myScreen);
1589 return m_song != nullptr;
1592 void JumpToBrowser::run()
1594 myBrowser->locateSong(*m_song);
1597 bool JumpToMediaLibrary::canBeRun()
1599 m_song = currentSong(myScreen);
1600 return m_song != nullptr;
1603 void JumpToMediaLibrary::run()
1605 myLibrary->locateSong(*m_song);
1608 bool JumpToPlaylistEditor::canBeRun()
1610 return myScreen == myBrowser
1611 && myBrowser->main().current()->value().type() == MPD::Item::Type::Playlist;
1614 void JumpToPlaylistEditor::run()
1616 myPlaylistEditor->locatePlaylist(myBrowser->main().current()->value().playlist());
1619 void ToggleScreenLock::run()
1621 using Global::wFooter;
1622 using Global::myLockedScreen;
1623 const char *msg_unlockable_screen = "Current screen can't be locked";
1624 if (myLockedScreen != nullptr)
1626 BaseScreen::unlock();
1627 Actions::setResizeFlags();
1628 myScreen->resize();
1629 Statusbar::print("Screen unlocked");
1631 else if (!myScreen->isLockable())
1633 Statusbar::print(msg_unlockable_screen);
1635 else
1637 unsigned part = Config.locked_screen_width_part*100;
1638 if (Config.ask_for_locked_screen_width_part)
1640 Statusbar::ScopedLock slock;
1641 Statusbar::put() << "% of the locked screen's width to be reserved (20-80): ";
1642 part = fromString<unsigned>(wFooter->prompt(boost::lexical_cast<std::string>(part)));
1644 boundsCheck(part, 20u, 80u);
1645 Config.locked_screen_width_part = part/100.0;
1646 if (myScreen->lock())
1647 Statusbar::printf("Screen locked (with %1%%% width)", part);
1648 else
1649 Statusbar::print(msg_unlockable_screen);
1653 bool JumpToTagEditor::canBeRun()
1655 # ifdef HAVE_TAGLIB_H
1656 m_song = currentSong(myScreen);
1657 return m_song != nullptr && isMPDMusicDirSet();
1658 # else
1659 return false;
1660 # endif // HAVE_TAGLIB_H
1663 void JumpToTagEditor::run()
1665 # ifdef HAVE_TAGLIB_H
1666 myTagEditor->LocateSong(*m_song);
1667 # endif // HAVE_TAGLIB_H
1670 bool JumpToPositionInSong::canBeRun()
1672 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1675 void JumpToPositionInSong::run()
1677 using Global::wFooter;
1679 const MPD::Song s = myPlaylist->nowPlayingSong();
1681 std::string spos;
1683 Statusbar::ScopedLock slock;
1684 Statusbar::put() << "Position to go (in %/m:ss/seconds(s)): ";
1685 spos = wFooter->prompt();
1688 boost::regex rx;
1689 boost::smatch what;
1690 if (boost::regex_match(spos, what, rx.assign("([0-9]+):([0-9]{2})"))) // mm:ss
1692 auto mins = fromString<unsigned>(what[1]);
1693 auto secs = fromString<unsigned>(what[2]);
1694 boundsCheck(secs, 0u, 60u);
1695 Mpd.Seek(s.getPosition(), mins * 60 + secs);
1697 else if (boost::regex_match(spos, what, rx.assign("([0-9]+)s"))) // position in seconds
1699 auto secs = fromString<unsigned>(what[1]);
1700 Mpd.Seek(s.getPosition(), secs);
1702 else if (boost::regex_match(spos, what, rx.assign("([0-9]+)[%]{0,1}"))) // position in %
1704 auto percent = fromString<unsigned>(what[1]);
1705 boundsCheck(percent, 0u, 100u);
1706 int secs = (percent * s.getDuration()) / 100.0;
1707 Mpd.Seek(s.getPosition(), secs);
1709 else
1710 Statusbar::print("Invalid format ([m]:[ss], [s]s, [%]%, [%] accepted)");
1713 bool SelectItem::canBeRun()
1715 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1716 return m_list != nullptr
1717 && !m_list->empty()
1718 && m_list->currentP()->isSelectable();
1721 void SelectItem::run()
1723 auto current = m_list->currentP();
1724 current->setSelected(!current->isSelected());
1727 bool SelectRange::canBeRun()
1729 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1730 if (m_list == nullptr)
1731 return false;
1732 m_begin = m_list->beginP();
1733 m_end = m_list->endP();
1734 return findRange(m_begin, m_end);
1737 void SelectRange::run()
1739 for (; m_begin != m_end; ++m_begin)
1740 m_begin->setSelected(true);
1741 Statusbar::print("Range selected");
1744 bool ReverseSelection::canBeRun()
1746 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1747 return m_list != nullptr;
1750 void ReverseSelection::run()
1752 for (auto &p : *m_list)
1753 p.setSelected(!p.isSelected());
1754 Statusbar::print("Selection reversed");
1757 bool RemoveSelection::canBeRun()
1759 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1760 return m_list != nullptr;
1763 void RemoveSelection::run()
1765 for (auto &p : *m_list)
1766 p.setSelected(false);
1767 Statusbar::print("Selection removed");
1770 bool SelectAlbum::canBeRun()
1772 auto *w = myScreen->activeWindow();
1773 if (m_list != static_cast<void *>(w))
1774 m_list = dynamic_cast<NC::List *>(w);
1775 if (m_songs != static_cast<void *>(w))
1776 m_songs = dynamic_cast<SongList *>(w);
1777 return m_list != nullptr && !m_list->empty()
1778 && m_songs != nullptr;
1781 void SelectAlbum::run()
1783 const auto front = m_songs->beginS(), current = m_songs->currentS(), end = m_songs->endS();
1784 auto *s = current->get<Bit::Song>();
1785 if (s == nullptr)
1786 return;
1787 auto get = &MPD::Song::getAlbum;
1788 const std::string tag = s->getTags(get);
1789 // go up
1790 for (auto it = current; it != front;)
1792 --it;
1793 s = it->get<Bit::Song>();
1794 if (s == nullptr || s->getTags(get) != tag)
1795 break;
1796 it->get<Bit::Properties>().setSelected(true);
1798 // go down
1799 for (auto it = current;;)
1801 it->get<Bit::Properties>().setSelected(true);
1802 if (++it == end)
1803 break;
1804 s = it->get<Bit::Song>();
1805 if (s == nullptr || s->getTags(get) != tag)
1806 break;
1808 Statusbar::print("Album around cursor position selected");
1811 bool SelectFoundItems::canBeRun()
1813 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1814 if (m_list == nullptr || m_list->empty())
1815 return false;
1816 m_searchable = dynamic_cast<Searchable *>(myScreen);
1817 return m_searchable != nullptr && m_searchable->allowsSearching();
1820 void SelectFoundItems::run()
1822 auto current_pos = m_list->choice();
1823 myScreen->activeWindow()->scroll(NC::Scroll::Home);
1824 bool found = m_searchable->search(SearchDirection::Forward, false, false);
1825 if (found)
1827 Statusbar::print("Searching for items...");
1828 m_list->currentP()->setSelected(true);
1829 while (m_searchable->search(SearchDirection::Forward, false, true))
1830 m_list->currentP()->setSelected(true);
1831 Statusbar::print("Found items selected");
1833 m_list->highlight(current_pos);
1836 bool AddSelectedItems::canBeRun()
1838 return myScreen != mySelectedItemsAdder;
1841 void AddSelectedItems::run()
1843 mySelectedItemsAdder->switchTo();
1846 void CropMainPlaylist::run()
1848 auto &w = myPlaylist->main();
1849 // cropping doesn't make sense in this case
1850 if (w.size() <= 1)
1851 return;
1852 if (Config.ask_before_clearing_playlists)
1853 confirmAction("Do you really want to crop main playlist?");
1854 Statusbar::print("Cropping playlist...");
1855 selectCurrentIfNoneSelected(w);
1856 cropPlaylist(w, std::bind(&MPD::Connection::Delete, ph::_1, ph::_2));
1857 Statusbar::print("Playlist cropped");
1860 bool CropPlaylist::canBeRun()
1862 return myScreen == myPlaylistEditor;
1865 void CropPlaylist::run()
1867 auto &w = myPlaylistEditor->Content;
1868 // cropping doesn't make sense in this case
1869 if (w.size() <= 1)
1870 return;
1871 assert(!myPlaylistEditor->Playlists.empty());
1872 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
1873 if (Config.ask_before_clearing_playlists)
1874 confirmAction(boost::format("Do you really want to crop playlist \"%1%\"?") % playlist);
1875 selectCurrentIfNoneSelected(w);
1876 Statusbar::printf("Cropping playlist \"%1%\"...", playlist);
1877 cropPlaylist(w, std::bind(&MPD::Connection::PlaylistDelete, ph::_1, playlist, ph::_2));
1878 Statusbar::printf("Playlist \"%1%\" cropped", playlist);
1881 void ClearMainPlaylist::run()
1883 if (!myPlaylist->main().empty() && Config.ask_before_clearing_playlists)
1884 confirmAction("Do you really want to clear main playlist?");
1885 Mpd.ClearMainPlaylist();
1886 Statusbar::print("Playlist cleared");
1887 myPlaylist->main().reset();
1890 bool ClearPlaylist::canBeRun()
1892 return myScreen == myPlaylistEditor;
1895 void ClearPlaylist::run()
1897 if (myPlaylistEditor->Playlists.empty())
1898 return;
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 clear playlist \"%1%\"?") % playlist);
1902 Mpd.ClearPlaylist(playlist);
1903 Statusbar::printf("Playlist \"%1%\" cleared", playlist);
1906 bool SortPlaylist::canBeRun()
1908 if (myScreen != myPlaylist)
1909 return false;
1910 auto first = myPlaylist->main().begin(), last = myPlaylist->main().end();
1911 return findSelectedRangeAndPrintInfoIfNot(first, last);
1914 void SortPlaylist::run()
1916 mySortPlaylistDialog->switchTo();
1919 bool ReversePlaylist::canBeRun()
1921 if (myScreen != myPlaylist)
1922 return false;
1923 m_begin = myPlaylist->main().begin();
1924 m_end = myPlaylist->main().end();
1925 return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end);
1928 void ReversePlaylist::run()
1930 Statusbar::print("Reversing range...");
1931 Mpd.StartCommandsList();
1932 for (--m_end; m_begin < m_end; ++m_begin, --m_end)
1933 Mpd.Swap(m_begin->value().getPosition(), m_end->value().getPosition());
1934 Mpd.CommitCommandsList();
1935 Statusbar::print("Range reversed");
1938 bool ApplyFilter::canBeRun()
1940 m_filterable = dynamic_cast<Filterable *>(myScreen);
1941 return m_filterable != nullptr
1942 && m_filterable->allowsFiltering();
1945 void ApplyFilter::run()
1947 using Global::wFooter;
1949 std::string filter = m_filterable->currentFilter();
1950 if (!filter.empty())
1952 m_filterable->applyFilter(filter);
1953 myScreen->refreshWindow();
1958 Statusbar::ScopedLock slock;
1959 NC::Window::ScopedPromptHook helper(
1960 *wFooter,
1961 Statusbar::Helpers::ApplyFilterImmediately(m_filterable));
1962 Statusbar::put() << "Apply filter: ";
1963 filter = wFooter->prompt(filter);
1965 catch (NC::PromptAborted &)
1967 m_filterable->applyFilter(filter);
1968 throw;
1971 if (filter.empty())
1972 Statusbar::printf("Filtering disabled");
1973 else
1974 Statusbar::printf("Using filter \"%1%\"", filter);
1976 if (myScreen == myPlaylist)
1977 myPlaylist->reloadTotalLength();
1979 listsChangeFinisher();
1982 bool Find::canBeRun()
1984 return myScreen == myHelp
1985 || myScreen == myLyrics
1986 || myScreen == myLastfm;
1989 void Find::run()
1991 using Global::wFooter;
1993 std::string token;
1995 Statusbar::ScopedLock slock;
1996 Statusbar::put() << "Find: ";
1997 token = wFooter->prompt();
2000 Statusbar::print("Searching...");
2001 auto s = static_cast<Screen<NC::Scrollpad> *>(myScreen);
2002 s->main().removeProperties();
2003 if (token.empty() || s->main().setProperties(NC::Format::Reverse, token, NC::Format::NoReverse, Config.regex_type))
2004 Statusbar::print("Done");
2005 else
2006 Statusbar::print("No matching patterns found");
2007 s->main().flush();
2010 bool FindItemBackward::canBeRun()
2012 auto w = dynamic_cast<Searchable *>(myScreen);
2013 return w && w->allowsSearching();
2016 void FindItemForward::run()
2018 findItem(SearchDirection::Forward);
2019 listsChangeFinisher();
2022 bool FindItemForward::canBeRun()
2024 auto w = dynamic_cast<Searchable *>(myScreen);
2025 return w && w->allowsSearching();
2028 void FindItemBackward::run()
2030 findItem(SearchDirection::Backward);
2031 listsChangeFinisher();
2034 bool NextFoundItem::canBeRun()
2036 return dynamic_cast<Searchable *>(myScreen);
2039 void NextFoundItem::run()
2041 Searchable *w = dynamic_cast<Searchable *>(myScreen);
2042 assert(w != nullptr);
2043 w->search(SearchDirection::Forward, Config.wrapped_search, true);
2044 listsChangeFinisher();
2047 bool PreviousFoundItem::canBeRun()
2049 return dynamic_cast<Searchable *>(myScreen);
2052 void PreviousFoundItem::run()
2054 Searchable *w = dynamic_cast<Searchable *>(myScreen);
2055 assert(w != nullptr);
2056 w->search(SearchDirection::Backward, Config.wrapped_search, true);
2057 listsChangeFinisher();
2060 void ToggleFindMode::run()
2062 Config.wrapped_search = !Config.wrapped_search;
2063 Statusbar::printf("Search mode: %1%",
2064 Config.wrapped_search ? "Wrapped" : "Normal"
2068 void ToggleReplayGainMode::run()
2070 using Global::wFooter;
2072 char rgm = 0;
2074 Statusbar::ScopedLock slock;
2075 Statusbar::put() << "Replay gain mode? "
2076 << "[" << NC::Format::Bold << 'o' << NC::Format::NoBold << "ff"
2077 << "/" << NC::Format::Bold << 't' << NC::Format::NoBold << "rack"
2078 << "/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "lbum"
2079 << "] ";
2080 rgm = Statusbar::Helpers::promptReturnOneOf({"t", "a", "o"})[0];
2082 switch (rgm)
2084 case 't':
2085 Mpd.SetReplayGainMode(MPD::rgmTrack);
2086 break;
2087 case 'a':
2088 Mpd.SetReplayGainMode(MPD::rgmAlbum);
2089 break;
2090 case 'o':
2091 Mpd.SetReplayGainMode(MPD::rgmOff);
2092 break;
2093 default: // impossible
2094 throw std::runtime_error(
2095 (boost::format("ToggleReplayGainMode: impossible case reached: %1%") % rgm).str()
2098 Statusbar::printf("Replay gain mode: %1%", Mpd.GetReplayGainMode());
2101 void ToggleAddMode::run()
2103 std::string mode_desc;
2104 switch (Config.space_add_mode)
2106 case SpaceAddMode::AddRemove:
2107 Config.space_add_mode = SpaceAddMode::AlwaysAdd;
2108 mode_desc = "always add an item to playlist";
2109 break;
2110 case SpaceAddMode::AlwaysAdd:
2111 Config.space_add_mode = SpaceAddMode::AddRemove;
2112 mode_desc = "add an item to playlist or remove if already added";
2113 break;
2115 Statusbar::printf("Add mode: %1%", mode_desc);
2118 void ToggleMouse::run()
2120 Config.mouse_support = !Config.mouse_support;
2121 if (Config.mouse_support)
2122 NC::Mouse::enable();
2123 else
2124 NC::Mouse::disable();
2125 Statusbar::printf("Mouse support %1%",
2126 Config.mouse_support ? "enabled" : "disabled"
2130 void ToggleBitrateVisibility::run()
2132 Config.display_bitrate = !Config.display_bitrate;
2133 Statusbar::printf("Bitrate visibility %1%",
2134 Config.display_bitrate ? "enabled" : "disabled"
2138 void AddRandomItems::run()
2140 using Global::wFooter;
2141 char rnd_type = 0;
2143 Statusbar::ScopedLock slock;
2144 Statusbar::put() << "Add random? "
2145 << "[" << NC::Format::Bold << 's' << NC::Format::NoBold << "ongs"
2146 << "/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtists"
2147 << "/" << "album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtists"
2148 << "/" << "al" << NC::Format::Bold << 'b' << NC::Format::NoBold << "ums"
2149 << "] ";
2150 rnd_type = Statusbar::Helpers::promptReturnOneOf({"s", "a", "A", "b"})[0];
2153 mpd_tag_type tag_type = MPD_TAG_ARTIST;
2154 std::string tag_type_str ;
2155 if (rnd_type != 's')
2157 tag_type = charToTagType(rnd_type);
2158 tag_type_str = boost::locale::to_lower(tagTypeToString(tag_type));
2160 else
2161 tag_type_str = "song";
2163 unsigned number;
2165 Statusbar::ScopedLock slock;
2166 Statusbar::put() << "Number of random " << tag_type_str << "s: ";
2167 number = fromString<unsigned>(wFooter->prompt());
2169 if (number > 0)
2171 bool success;
2172 if (rnd_type == 's')
2173 success = Mpd.AddRandomSongs(number, Global::RNG);
2174 else
2175 success = Mpd.AddRandomTag(tag_type, number, Global::RNG);
2176 if (success)
2177 Statusbar::printf("%1% random %2%%3% added to playlist", number, tag_type_str, number == 1 ? "" : "s");
2181 bool ToggleBrowserSortMode::canBeRun()
2183 return myScreen == myBrowser;
2186 void ToggleBrowserSortMode::run()
2188 switch (Config.browser_sort_mode)
2190 case SortMode::Name:
2191 Config.browser_sort_mode = SortMode::ModificationTime;
2192 Statusbar::print("Sort songs by: modification time");
2193 break;
2194 case SortMode::ModificationTime:
2195 Config.browser_sort_mode = SortMode::CustomFormat;
2196 Statusbar::print("Sort songs by: custom format");
2197 break;
2198 case SortMode::CustomFormat:
2199 Config.browser_sort_mode = SortMode::NoOp;
2200 Statusbar::print("Do not sort songs");
2201 break;
2202 case SortMode::NoOp:
2203 Config.browser_sort_mode = SortMode::Name;
2204 Statusbar::print("Sort songs by: name");
2206 if (Config.browser_sort_mode != SortMode::NoOp)
2208 size_t sort_offset = myBrowser->inRootDirectory() ? 0 : 1;
2209 std::sort(myBrowser->main().begin()+sort_offset, myBrowser->main().end(),
2210 LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode)
2215 bool ToggleLibraryTagType::canBeRun()
2217 return (myScreen->isActiveWindow(myLibrary->Tags))
2218 || (myLibrary->columns() == 2 && myScreen->isActiveWindow(myLibrary->Albums));
2221 void ToggleLibraryTagType::run()
2223 using Global::wFooter;
2225 char tag_type = 0;
2227 Statusbar::ScopedLock slock;
2228 Statusbar::put() << "Tag type? "
2229 << "[" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtist"
2230 << "/" << "album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtist"
2231 << "/" << NC::Format::Bold << 'y' << NC::Format::NoBold << "ear"
2232 << "/" << NC::Format::Bold << 'g' << NC::Format::NoBold << "enre"
2233 << "/" << NC::Format::Bold << 'c' << NC::Format::NoBold << "omposer"
2234 << "/" << NC::Format::Bold << 'p' << NC::Format::NoBold << "erformer"
2235 << "] ";
2236 tag_type = Statusbar::Helpers::promptReturnOneOf({"a", "A", "y", "g", "c", "p"})[0];
2238 mpd_tag_type new_tagitem = charToTagType(tag_type);
2239 if (new_tagitem != Config.media_lib_primary_tag)
2241 Config.media_lib_primary_tag = new_tagitem;
2242 std::string item_type = tagTypeToString(Config.media_lib_primary_tag);
2243 myLibrary->Tags.setTitle(Config.titles_visibility ? item_type + "s" : "");
2244 myLibrary->Tags.reset();
2245 item_type = boost::locale::to_lower(item_type);
2246 std::string and_mtime = Config.media_library_sort_by_mtime ?
2247 " and mtime" :
2249 if (myLibrary->columns() == 2)
2251 myLibrary->Songs.clear();
2252 myLibrary->Albums.reset();
2253 myLibrary->Albums.clear();
2254 myLibrary->Albums.setTitle(Config.titles_visibility ? "Albums (sorted by " + item_type + and_mtime + ")" : "");
2255 myLibrary->Albums.display();
2257 else
2259 myLibrary->Tags.clear();
2260 myLibrary->Tags.display();
2262 Statusbar::printf("Switched to the list of %1%s", item_type);
2266 bool ToggleMediaLibrarySortMode::canBeRun()
2268 return myScreen == myLibrary;
2271 void ToggleMediaLibrarySortMode::run()
2273 myLibrary->toggleSortMode();
2276 bool FetchLyricsInBackground::canBeRun()
2278 m_hs = dynamic_cast<HasSongs *>(myScreen);
2279 return m_hs != nullptr && m_hs->itemAvailable();
2282 void FetchLyricsInBackground::run()
2284 auto songs = m_hs->getSelectedSongs();
2285 for (const auto &s : songs)
2286 myLyrics->fetchInBackground(s, true);
2287 Statusbar::print("Selected songs queued for lyrics fetching");
2290 bool RefetchLyrics::canBeRun()
2292 return myScreen == myLyrics;
2295 void RefetchLyrics::run()
2297 myLyrics->refetchCurrent();
2300 bool SetSelectedItemsPriority::canBeRun()
2302 if (Mpd.Version() < 17)
2304 Statusbar::print("Priorities are supported in MPD >= 0.17.0");
2305 return false;
2307 return myScreen == myPlaylist && !myPlaylist->main().empty();
2310 void SetSelectedItemsPriority::run()
2312 using Global::wFooter;
2314 unsigned prio;
2316 Statusbar::ScopedLock slock;
2317 Statusbar::put() << "Set priority [0-255]: ";
2318 prio = fromString<unsigned>(wFooter->prompt());
2319 boundsCheck(prio, 0u, 255u);
2321 myPlaylist->setSelectedItemsPriority(prio);
2324 bool ToggleOutput::canBeRun()
2326 #ifdef ENABLE_OUTPUTS
2327 return myScreen == myOutputs;
2328 #else
2329 return false;
2330 #endif // ENABLE_OUTPUTS
2333 void ToggleOutput::run()
2335 #ifdef ENABLE_OUTPUTS
2336 myOutputs->toggleOutput();
2337 #endif // ENABLE_OUTPUTS
2340 bool ToggleVisualizationType::canBeRun()
2342 # ifdef ENABLE_VISUALIZER
2343 return myScreen == myVisualizer;
2344 # else
2345 return false;
2346 # endif // ENABLE_VISUALIZER
2349 void ToggleVisualizationType::run()
2351 # ifdef ENABLE_VISUALIZER
2352 myVisualizer->ToggleVisualizationType();
2353 # endif // ENABLE_VISUALIZER
2356 bool SetVisualizerSampleMultiplier::canBeRun()
2358 # ifdef ENABLE_VISUALIZER
2359 return myScreen == myVisualizer;
2360 # else
2361 return false;
2362 # endif // ENABLE_VISUALIZER
2365 void SetVisualizerSampleMultiplier::run()
2367 # ifdef ENABLE_VISUALIZER
2368 using Global::wFooter;
2370 double multiplier;
2372 Statusbar::ScopedLock slock;
2373 Statusbar::put() << "Set visualizer sample multiplier: ";
2374 multiplier = fromString<double>(wFooter->prompt());
2375 lowerBoundCheck(multiplier, 0.0);
2376 Config.visualizer_sample_multiplier = multiplier;
2378 Statusbar::printf("Visualizer sample multiplier set to %1%", multiplier);
2379 # endif // ENABLE_VISUALIZER
2382 void ShowSongInfo::run()
2384 mySongInfo->switchTo();
2387 bool ShowArtistInfo::canBeRun()
2389 return myScreen == myLastfm
2390 || (myScreen->isActiveWindow(myLibrary->Tags)
2391 && !myLibrary->Tags.empty()
2392 && Config.media_lib_primary_tag == MPD_TAG_ARTIST)
2393 || currentSong(myScreen);
2396 void ShowArtistInfo::run()
2398 if (myScreen == myLastfm)
2400 myLastfm->switchTo();
2401 return;
2404 std::string artist;
2405 if (myScreen->isActiveWindow(myLibrary->Tags))
2407 assert(!myLibrary->Tags.empty());
2408 assert(Config.media_lib_primary_tag == MPD_TAG_ARTIST);
2409 artist = myLibrary->Tags.current()->value().tag();
2411 else
2413 auto s = currentSong(myScreen);
2414 assert(s);
2415 artist = s->getArtist();
2418 if (!artist.empty())
2420 myLastfm->queueJob(new LastFm::ArtistInfo(artist, Config.lastfm_preferred_language));
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 myLyrics->switchTo();
2446 void Quit::run()
2448 ExitMainLoop = true;
2451 void NextScreen::run()
2453 if (Config.screen_switcher_previous)
2455 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2456 tababble->switchToPreviousScreen();
2458 else if (!Config.screen_sequence.empty())
2460 const auto &seq = Config.screen_sequence;
2461 auto screen_type = std::find(seq.begin(), seq.end(), myScreen->type());
2462 if (++screen_type == seq.end())
2463 toScreen(seq.front())->switchTo();
2464 else
2465 toScreen(*screen_type)->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 const auto &seq = Config.screen_sequence;
2479 auto screen_type = std::find(seq.begin(), seq.end(), myScreen->type());
2480 if (screen_type == seq.begin())
2481 toScreen(seq.back())->switchTo();
2482 else
2483 toScreen(*--screen_type)->switchTo();
2487 bool ShowHelp::canBeRun()
2489 return myScreen != myHelp
2490 # ifdef HAVE_TAGLIB_H
2491 && myScreen != myTinyTagEditor
2492 # endif // HAVE_TAGLIB_H
2496 void ShowHelp::run()
2498 myHelp->switchTo();
2501 bool ShowPlaylist::canBeRun()
2503 return myScreen != myPlaylist
2504 # ifdef HAVE_TAGLIB_H
2505 && myScreen != myTinyTagEditor
2506 # endif // HAVE_TAGLIB_H
2510 void ShowPlaylist::run()
2512 myPlaylist->switchTo();
2515 bool ShowBrowser::canBeRun()
2517 return myScreen != myBrowser
2518 # ifdef HAVE_TAGLIB_H
2519 && myScreen != myTinyTagEditor
2520 # endif // HAVE_TAGLIB_H
2524 void ShowBrowser::run()
2526 myBrowser->switchTo();
2529 bool ChangeBrowseMode::canBeRun()
2531 return myScreen == myBrowser;
2534 void ChangeBrowseMode::run()
2536 myBrowser->changeBrowseMode();
2539 bool ShowSearchEngine::canBeRun()
2541 return myScreen != mySearcher
2542 # ifdef HAVE_TAGLIB_H
2543 && myScreen != myTinyTagEditor
2544 # endif // HAVE_TAGLIB_H
2548 void ShowSearchEngine::run()
2550 mySearcher->switchTo();
2553 bool ResetSearchEngine::canBeRun()
2555 return myScreen == mySearcher;
2558 void ResetSearchEngine::run()
2560 mySearcher->reset();
2563 bool ShowMediaLibrary::canBeRun()
2565 return myScreen != myLibrary
2566 # ifdef HAVE_TAGLIB_H
2567 && myScreen != myTinyTagEditor
2568 # endif // HAVE_TAGLIB_H
2572 void ShowMediaLibrary::run()
2574 myLibrary->switchTo();
2577 bool ToggleMediaLibraryColumnsMode::canBeRun()
2579 return myScreen == myLibrary;
2582 void ToggleMediaLibraryColumnsMode::run()
2584 myLibrary->toggleColumnsMode();
2585 myLibrary->refresh();
2588 bool ShowPlaylistEditor::canBeRun()
2590 return myScreen != myPlaylistEditor
2591 # ifdef HAVE_TAGLIB_H
2592 && myScreen != myTinyTagEditor
2593 # endif // HAVE_TAGLIB_H
2597 void ShowPlaylistEditor::run()
2599 myPlaylistEditor->switchTo();
2602 bool ShowTagEditor::canBeRun()
2604 # ifdef HAVE_TAGLIB_H
2605 return myScreen != myTagEditor
2606 && myScreen != myTinyTagEditor;
2607 # else
2608 return false;
2609 # endif // HAVE_TAGLIB_H
2612 void ShowTagEditor::run()
2614 # ifdef HAVE_TAGLIB_H
2615 if (isMPDMusicDirSet())
2616 myTagEditor->switchTo();
2617 # endif // HAVE_TAGLIB_H
2620 bool ShowOutputs::canBeRun()
2622 # ifdef ENABLE_OUTPUTS
2623 return myScreen != myOutputs
2624 # ifdef HAVE_TAGLIB_H
2625 && myScreen != myTinyTagEditor
2626 # endif // HAVE_TAGLIB_H
2628 # else
2629 return false;
2630 # endif // ENABLE_OUTPUTS
2633 void ShowOutputs::run()
2635 # ifdef ENABLE_OUTPUTS
2636 myOutputs->switchTo();
2637 # endif // ENABLE_OUTPUTS
2640 bool ShowVisualizer::canBeRun()
2642 # ifdef ENABLE_VISUALIZER
2643 return myScreen != myVisualizer
2644 # ifdef HAVE_TAGLIB_H
2645 && myScreen != myTinyTagEditor
2646 # endif // HAVE_TAGLIB_H
2648 # else
2649 return false;
2650 # endif // ENABLE_VISUALIZER
2653 void ShowVisualizer::run()
2655 # ifdef ENABLE_VISUALIZER
2656 myVisualizer->switchTo();
2657 # endif // ENABLE_VISUALIZER
2660 bool ShowClock::canBeRun()
2662 # ifdef ENABLE_CLOCK
2663 return myScreen != myClock
2664 # ifdef HAVE_TAGLIB_H
2665 && myScreen != myTinyTagEditor
2666 # endif // HAVE_TAGLIB_H
2668 # else
2669 return false;
2670 # endif // ENABLE_CLOCK
2673 void ShowClock::run()
2675 # ifdef ENABLE_CLOCK
2676 myClock->switchTo();
2677 # endif // ENABLE_CLOCK
2680 #ifdef HAVE_TAGLIB_H
2681 bool ShowServerInfo::canBeRun()
2683 return myScreen != myTinyTagEditor;
2685 #endif // HAVE_TAGLIB_H
2687 void ShowServerInfo::run()
2689 myServerInfo->switchTo();
2694 namespace {
2696 void populateActions()
2698 auto insert_action = [](Actions::BaseAction *a) {
2699 AvailableActions[static_cast<size_t>(a->type())] = 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::SetVisualizerSampleMultiplier());
2808 insert_action(new Actions::ShowSongInfo());
2809 insert_action(new Actions::ShowArtistInfo());
2810 insert_action(new Actions::ShowLyrics());
2811 insert_action(new Actions::Quit());
2812 insert_action(new Actions::NextScreen());
2813 insert_action(new Actions::PreviousScreen());
2814 insert_action(new Actions::ShowHelp());
2815 insert_action(new Actions::ShowPlaylist());
2816 insert_action(new Actions::ShowBrowser());
2817 insert_action(new Actions::ChangeBrowseMode());
2818 insert_action(new Actions::ShowSearchEngine());
2819 insert_action(new Actions::ResetSearchEngine());
2820 insert_action(new Actions::ShowMediaLibrary());
2821 insert_action(new Actions::ToggleMediaLibraryColumnsMode());
2822 insert_action(new Actions::ShowPlaylistEditor());
2823 insert_action(new Actions::ShowTagEditor());
2824 insert_action(new Actions::ShowOutputs());
2825 insert_action(new Actions::ShowVisualizer());
2826 insert_action(new Actions::ShowClock());
2827 insert_action(new Actions::ShowServerInfo());
2830 bool scrollTagCanBeRun(NC::List *&list, SongList *&songs)
2832 auto w = myScreen->activeWindow();
2833 if (list != static_cast<void *>(w))
2834 list = dynamic_cast<NC::List *>(w);
2835 if (songs != static_cast<void *>(w))
2836 songs = dynamic_cast<SongList *>(w);
2837 return list != nullptr && !list->empty()
2838 && songs != nullptr;
2841 void scrollTagUpRun(NC::List *list, SongList *songs, MPD::Song::GetFunction get)
2843 const auto front = songs->beginS();
2844 auto it = songs->currentS();
2845 if (auto *s = it->get<Bit::Song>())
2847 const std::string tag = s->getTags(get);
2848 while (it != front)
2850 --it;
2851 s = it->get<Bit::Song>();
2852 if (s == nullptr || s->getTags(get) != tag)
2853 break;
2855 list->highlight(it-front);
2859 void scrollTagDownRun(NC::List *list, SongList *songs, MPD::Song::GetFunction get)
2861 const auto front = songs->beginS(), back = --songs->endS();
2862 auto it = songs->currentS();
2863 if (auto *s = it->get<Bit::Song>())
2865 const std::string tag = s->getTags(get);
2866 while (it != back)
2868 ++it;
2869 s = it->get<Bit::Song>();
2870 if (s == nullptr || s->getTags(get) != tag)
2871 break;
2873 list->highlight(it-front);
2877 void seek()
2879 using Global::wHeader;
2880 using Global::wFooter;
2881 using Global::Timer;
2882 using Global::SeekingInProgress;
2884 if (!Status::State::totalTime())
2886 Statusbar::print("Unknown item length");
2887 return;
2890 Progressbar::ScopedLock progressbar_lock;
2891 Statusbar::ScopedLock statusbar_lock;
2893 unsigned songpos = Status::State::elapsedTime();
2894 auto t = Timer;
2896 int old_timeout = wFooter->getTimeout();
2897 wFooter->setTimeout(BaseScreen::defaultWindowTimeout);
2899 // Accept single action of a given type or action chain for which all actions
2900 // can be run and one of them is of the given type. This will still not work
2901 // in some contrived cases, but allows for more flexibility than accepting
2902 // single actions only.
2903 auto hasRunnableAction = [](BindingsConfiguration::BindingIteratorPair &bindings, Actions::Type type) {
2904 bool success = false;
2905 for (auto binding = bindings.first; binding != bindings.second; ++binding)
2907 auto &actions = binding->actions();
2908 for (const auto &action : actions)
2910 if (action->canBeRun())
2912 if (action->type() == type)
2913 success = true;
2915 else
2917 success = false;
2918 break;
2921 if (success)
2922 break;
2924 return success;
2927 SeekingInProgress = true;
2928 while (true)
2930 Status::trace();
2932 unsigned howmuch = Config.incremental_seeking
2933 ? (Timer-t).total_seconds()/2+Config.seek_time
2934 : Config.seek_time;
2936 NC::Key::Type input = readKey(*wFooter);
2938 auto k = Bindings.get(input);
2939 if (hasRunnableAction(k, Actions::Type::SeekForward))
2941 if (songpos < Status::State::totalTime())
2942 songpos = std::min(songpos + howmuch, Status::State::totalTime());
2944 else if (hasRunnableAction(k, Actions::Type::SeekBackward))
2946 if (songpos > 0)
2948 if (songpos < howmuch)
2949 songpos = 0;
2950 else
2951 songpos -= howmuch;
2954 else
2955 break;
2957 *wFooter << NC::Format::Bold;
2958 std::string tracklength;
2959 // FIXME: merge this with the code in status.cpp
2960 switch (Config.design)
2962 case Design::Classic:
2963 tracklength = " [";
2964 if (Config.display_remaining_time)
2966 tracklength += "-";
2967 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
2969 else
2970 tracklength += MPD::Song::ShowTime(songpos);
2971 tracklength += "/";
2972 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
2973 tracklength += "]";
2974 *wFooter << NC::XY(wFooter->getWidth()-tracklength.length(), 1) << tracklength;
2975 break;
2976 case Design::Alternative:
2977 if (Config.display_remaining_time)
2979 tracklength = "-";
2980 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
2982 else
2983 tracklength = MPD::Song::ShowTime(songpos);
2984 tracklength += "/";
2985 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
2986 *wHeader << NC::XY(0, 0) << tracklength << " ";
2987 wHeader->refresh();
2988 break;
2990 *wFooter << NC::Format::NoBold;
2991 Progressbar::draw(songpos, Status::State::totalTime());
2992 wFooter->refresh();
2994 SeekingInProgress = false;
2995 Mpd.Seek(Status::State::currentSongPosition(), songpos);
2997 wFooter->setTimeout(old_timeout);
3000 void findItem(const SearchDirection direction)
3002 using Global::wFooter;
3004 Searchable *w = dynamic_cast<Searchable *>(myScreen);
3005 assert(w != nullptr);
3006 assert(w->allowsSearching());
3008 std::string constraint = w->searchConstraint();
3011 Statusbar::ScopedLock slock;
3012 NC::Window::ScopedPromptHook prompt_hook(
3013 *wFooter,
3014 Statusbar::Helpers::FindImmediately(w, direction));
3015 Statusbar::put() << (boost::format("Find %1%: ") % direction).str();
3016 constraint = wFooter->prompt(constraint);
3018 catch (NC::PromptAborted &)
3020 w->setSearchConstraint(constraint);
3021 w->search(direction, Config.wrapped_search, false);
3022 throw;
3025 if (constraint.empty())
3027 Statusbar::printf("Constraint unset");
3028 w->clearSearchConstraint();
3030 else
3031 Statusbar::printf("Using constraint \"%1%\"", constraint);
3034 void listsChangeFinisher()
3036 if (myScreen == myLibrary
3037 || myScreen == myPlaylistEditor
3038 # ifdef HAVE_TAGLIB_H
3039 || myScreen == myTagEditor
3040 # endif // HAVE_TAGLIB_H
3043 if (myScreen->activeWindow() == &myLibrary->Tags)
3045 myLibrary->Albums.clear();
3046 myLibrary->Albums.refresh();
3047 myLibrary->Songs.clear();
3048 myLibrary->Songs.refresh();
3049 myLibrary->updateTimer();
3051 else if (myScreen->activeWindow() == &myLibrary->Albums)
3053 myLibrary->Songs.clear();
3054 myLibrary->Songs.refresh();
3055 myLibrary->updateTimer();
3057 else if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
3059 myPlaylistEditor->Content.clear();
3060 myPlaylistEditor->Content.refresh();
3061 myPlaylistEditor->updateTimer();
3063 # ifdef HAVE_TAGLIB_H
3064 else if (myScreen->activeWindow() == myTagEditor->Dirs)
3066 myTagEditor->Tags->clear();
3068 # endif // HAVE_TAGLIB_H