actions: use unique_ptr for storing actions
[ncmpcpp.git] / src / actions.cpp
blobf68b83dfbd76ecb13a5ceb4756c359931c8074e4
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 std::vector<std::unique_ptr<Actions::BaseAction>> AvailableActions;
81 void populateActions();
83 bool scrollTagCanBeRun(NC::List *&list, SongList *&songs);
84 void scrollTagUpRun(NC::List *list, SongList *songs, MPD::Song::GetFunction get);
85 void scrollTagDownRun(NC::List *list, 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;
102 namespace Actions {
104 bool OriginalStatusbarVisibility;
105 bool ExitMainLoop = false;
107 size_t HeaderHeight;
108 size_t FooterHeight;
109 size_t FooterStartY;
111 void validateScreenSize()
113 using Global::MainHeight;
115 if (COLS < 30 || MainHeight < 5)
117 NC::destroyScreen();
118 std::cout << "Screen is too small to handle ncmpcpp correctly\n";
119 exit(1);
123 void initializeScreens()
125 myHelp = new Help;
126 myPlaylist = new Playlist;
127 myBrowser = new Browser;
128 mySearcher = new SearchEngine;
129 myLibrary = new MediaLibrary;
130 myPlaylistEditor = new PlaylistEditor;
131 myLyrics = new Lyrics;
132 mySelectedItemsAdder = new SelectedItemsAdder;
133 mySongInfo = new SongInfo;
134 myServerInfo = new ServerInfo;
135 mySortPlaylistDialog = new SortPlaylistDialog;
136 myLastfm = new Lastfm;
138 # ifdef HAVE_TAGLIB_H
139 myTinyTagEditor = new TinyTagEditor;
140 myTagEditor = new TagEditor;
141 # endif // HAVE_TAGLIB_H
143 # ifdef ENABLE_VISUALIZER
144 myVisualizer = new Visualizer;
145 # endif // ENABLE_VISUALIZER
147 # ifdef ENABLE_OUTPUTS
148 myOutputs = new Outputs;
149 # endif // ENABLE_OUTPUTS
151 # ifdef ENABLE_CLOCK
152 myClock = new Clock;
153 # endif // ENABLE_CLOCK
157 void setResizeFlags()
159 myHelp->hasToBeResized = 1;
160 myPlaylist->hasToBeResized = 1;
161 myBrowser->hasToBeResized = 1;
162 mySearcher->hasToBeResized = 1;
163 myLibrary->hasToBeResized = 1;
164 myPlaylistEditor->hasToBeResized = 1;
165 myLyrics->hasToBeResized = 1;
166 mySelectedItemsAdder->hasToBeResized = 1;
167 mySongInfo->hasToBeResized = 1;
168 myServerInfo->hasToBeResized = 1;
169 mySortPlaylistDialog->hasToBeResized = 1;
170 myLastfm->hasToBeResized = 1;
172 # ifdef HAVE_TAGLIB_H
173 myTinyTagEditor->hasToBeResized = 1;
174 myTagEditor->hasToBeResized = 1;
175 # endif // HAVE_TAGLIB_H
177 # ifdef ENABLE_VISUALIZER
178 myVisualizer->hasToBeResized = 1;
179 # endif // ENABLE_VISUALIZER
181 # ifdef ENABLE_OUTPUTS
182 myOutputs->hasToBeResized = 1;
183 # endif // ENABLE_OUTPUTS
185 # ifdef ENABLE_CLOCK
186 myClock->hasToBeResized = 1;
187 # endif // ENABLE_CLOCK
190 void resizeScreen(bool reload_main_window)
192 using Global::MainHeight;
193 using Global::wHeader;
194 using Global::wFooter;
196 // update internal screen dimensions
197 if (reload_main_window)
199 rl_resize_terminal();
200 endwin();
201 refresh();
204 MainHeight = LINES-(Config.design == Design::Alternative ? 7 : 4);
206 validateScreenSize();
208 if (!Config.header_visibility)
209 MainHeight += 2;
210 if (!Config.statusbar_visibility)
211 ++MainHeight;
213 setResizeFlags();
215 applyToVisibleWindows(&BaseScreen::resize);
217 if (Config.header_visibility || Config.design == Design::Alternative)
218 wHeader->resize(COLS, HeaderHeight);
220 FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1);
221 wFooter->moveTo(0, FooterStartY);
222 wFooter->resize(COLS, Config.statusbar_visibility ? 2 : 1);
224 applyToVisibleWindows(&BaseScreen::refresh);
226 Status::Changes::elapsedTime(false);
227 Status::Changes::playerState();
228 // Note: routines for drawing separator if alternative user
229 // interface is active and header is hidden are placed in
230 // NcmpcppStatusChanges.StatusFlags
231 Status::Changes::flags();
232 drawHeader();
233 wFooter->refresh();
234 refresh();
237 void setWindowsDimensions()
239 using Global::MainStartY;
240 using Global::MainHeight;
242 MainStartY = Config.design == Design::Alternative ? 5 : 2;
243 MainHeight = LINES-(Config.design == Design::Alternative ? 7 : 4);
245 if (!Config.header_visibility)
247 MainStartY -= 2;
248 MainHeight += 2;
250 if (!Config.statusbar_visibility)
251 ++MainHeight;
253 HeaderHeight = Config.design == Design::Alternative ? (Config.header_visibility ? 5 : 3) : 2;
254 FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1);
255 FooterHeight = Config.statusbar_visibility ? 2 : 1;
258 void confirmAction(const boost::format &description)
260 Statusbar::ScopedLock slock;
261 Statusbar::put() << description.str()
262 << " [" << NC::Format::Bold << 'y' << NC::Format::NoBold
263 << '/' << NC::Format::Bold << 'n' << NC::Format::NoBold
264 << "] ";
265 auto answer = Statusbar::Helpers::promptReturnOneOf({"y", "n"});
266 if (answer == "n")
267 throw NC::PromptAborted(std::move(answer));
270 bool isMPDMusicDirSet()
272 if (Config.mpd_music_dir.empty())
274 Statusbar::print("Proper mpd_music_dir variable has to be set in configuration file");
275 return false;
277 return true;
280 BaseAction &get(Actions::Type at)
282 if (AvailableActions.empty())
283 populateActions();
284 BaseAction *action = AvailableActions.at(static_cast<size_t>(at)).get();
285 // action should be always present if action type in queried
286 assert(action != nullptr);
287 return *action;
290 BaseAction *get(const std::string &name)
292 BaseAction *result = nullptr;
293 if (AvailableActions.empty())
294 populateActions();
295 for (const auto &action : AvailableActions)
297 if (action->name() == name)
299 result = action.get();
300 break;
303 return result;
306 UpdateEnvironment::UpdateEnvironment()
307 : BaseAction(Type::UpdateEnvironment, "update_environment")
308 , m_past(boost::posix_time::from_time_t(0))
311 void UpdateEnvironment::run(bool update_timer, bool refresh_window)
313 using Global::Timer;
315 // update timer, status if necessary etc.
316 Status::trace(update_timer, true);
318 // show lyrics consumer notification if appropriate
319 if (auto message = myLyrics->tryTakeConsumerMessage())
320 Statusbar::print(*message);
322 // header stuff
323 if ((myScreen == myPlaylist || myScreen == myBrowser || myScreen == myLyrics)
324 && (Timer - m_past > boost::posix_time::milliseconds(500))
327 drawHeader();
328 m_past = Timer;
331 if (refresh_window)
332 myScreen->refreshWindow();
335 void UpdateEnvironment::run()
337 run(true, true);
340 bool MouseEvent::canBeRun()
342 return Config.mouse_support;
345 void MouseEvent::run()
347 using Global::VolumeState;
348 using Global::wFooter;
350 m_old_mouse_event = m_mouse_event;
351 m_mouse_event = wFooter->getMouseEvent();
353 //Statusbar::printf("(%1%, %2%, %3%)", m_mouse_event.bstate, m_mouse_event.x, m_mouse_event.y);
355 if (m_mouse_event.bstate & BUTTON1_PRESSED
356 && m_mouse_event.y == LINES-(Config.statusbar_visibility ? 2 : 1)
357 ) // progressbar
359 if (Status::State::player() == MPD::psStop)
360 return;
361 Mpd.Seek(Status::State::currentSongPosition(),
362 Status::State::totalTime()*m_mouse_event.x/double(COLS));
364 else if (m_mouse_event.bstate & BUTTON1_PRESSED
365 && (Config.statusbar_visibility || Config.design == Design::Alternative)
366 && Status::State::player() != MPD::psStop
367 && m_mouse_event.y == (Config.design == Design::Alternative ? 1 : LINES-1)
368 && m_mouse_event.x < 9
369 ) // playing/paused
371 Mpd.Toggle();
373 else if ((m_mouse_event.bstate & BUTTON5_PRESSED || m_mouse_event.bstate & BUTTON4_PRESSED)
374 && (Config.header_visibility || Config.design == Design::Alternative)
375 && m_mouse_event.y == 0 && size_t(m_mouse_event.x) > COLS-VolumeState.length()
376 ) // volume
378 if (m_mouse_event.bstate & BUTTON5_PRESSED)
379 get(Type::VolumeDown).execute();
380 else
381 get(Type::VolumeUp).execute();
383 else if (m_mouse_event.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED | BUTTON4_PRESSED | BUTTON5_PRESSED))
384 myScreen->mouseButtonPressed(m_mouse_event);
387 void ScrollUp::run()
389 myScreen->scroll(NC::Scroll::Up);
390 listsChangeFinisher();
393 void ScrollDown::run()
395 myScreen->scroll(NC::Scroll::Down);
396 listsChangeFinisher();
399 bool ScrollUpArtist::canBeRun()
401 return scrollTagCanBeRun(m_list, m_songs);
404 void ScrollUpArtist::run()
406 scrollTagUpRun(m_list, m_songs, &MPD::Song::getArtist);
409 bool ScrollUpAlbum::canBeRun()
411 return scrollTagCanBeRun(m_list, m_songs);
414 void ScrollUpAlbum::run()
416 scrollTagUpRun(m_list, m_songs, &MPD::Song::getAlbum);
419 bool ScrollDownArtist::canBeRun()
421 return scrollTagCanBeRun(m_list, m_songs);
424 void ScrollDownArtist::run()
426 scrollTagDownRun(m_list, m_songs, &MPD::Song::getArtist);
429 bool ScrollDownAlbum::canBeRun()
431 return scrollTagCanBeRun(m_list, m_songs);
434 void ScrollDownAlbum::run()
436 scrollTagDownRun(m_list, m_songs, &MPD::Song::getAlbum);
439 void PageUp::run()
441 myScreen->scroll(NC::Scroll::PageUp);
442 listsChangeFinisher();
445 void PageDown::run()
447 myScreen->scroll(NC::Scroll::PageDown);
448 listsChangeFinisher();
451 void MoveHome::run()
453 myScreen->scroll(NC::Scroll::Home);
454 listsChangeFinisher();
457 void MoveEnd::run()
459 myScreen->scroll(NC::Scroll::End);
460 listsChangeFinisher();
463 void ToggleInterface::run()
465 switch (Config.design)
467 case Design::Classic:
468 Config.design = Design::Alternative;
469 Config.statusbar_visibility = false;
470 break;
471 case Design::Alternative:
472 Config.design = Design::Classic;
473 Config.statusbar_visibility = OriginalStatusbarVisibility;
474 break;
476 setWindowsDimensions();
477 resizeScreen(false);
478 // unlock progressbar
479 Progressbar::ScopedLock();
480 Status::Changes::mixer();
481 Status::Changes::elapsedTime(false);
482 Statusbar::printf("User interface: %1%", Config.design);
485 bool JumpToParentDirectory::canBeRun()
487 return (myScreen == myBrowser)
488 # ifdef HAVE_TAGLIB_H
489 || (myScreen->activeWindow() == myTagEditor->Dirs)
490 # endif // HAVE_TAGLIB_H
494 void JumpToParentDirectory::run()
496 if (myScreen == myBrowser)
498 if (!myBrowser->inRootDirectory())
500 myBrowser->main().reset();
501 myBrowser->enterDirectory();
504 # ifdef HAVE_TAGLIB_H
505 else if (myScreen == myTagEditor)
507 if (myTagEditor->CurrentDir() != "/")
509 myTagEditor->Dirs->reset();
510 myTagEditor->enterDirectory();
513 # endif // HAVE_TAGLIB_H
516 bool RunAction::canBeRun()
518 m_ha = dynamic_cast<HasActions *>(myScreen);
519 return m_ha != nullptr
520 && m_ha->actionRunnable();
523 void RunAction::run()
525 m_ha->runAction();
528 bool PreviousColumn::canBeRun()
530 m_hc = dynamic_cast<HasColumns *>(myScreen);
531 return m_hc != nullptr
532 && m_hc->previousColumnAvailable();
535 void PreviousColumn::run()
537 m_hc->previousColumn();
540 bool NextColumn::canBeRun()
542 m_hc = dynamic_cast<HasColumns *>(myScreen);
543 return m_hc != nullptr
544 && m_hc->nextColumnAvailable();
547 void NextColumn::run()
549 m_hc->nextColumn();
552 bool MasterScreen::canBeRun()
554 using Global::myLockedScreen;
555 using Global::myInactiveScreen;
557 return myLockedScreen
558 && myInactiveScreen
559 && myLockedScreen != myScreen
560 && myScreen->isMergable();
563 void MasterScreen::run()
565 using Global::myInactiveScreen;
566 using Global::myLockedScreen;
568 myInactiveScreen = myScreen;
569 myScreen = myLockedScreen;
570 drawHeader();
573 bool SlaveScreen::canBeRun()
575 using Global::myLockedScreen;
576 using Global::myInactiveScreen;
578 return myLockedScreen
579 && myInactiveScreen
580 && myLockedScreen == myScreen
581 && myScreen->isMergable();
584 void SlaveScreen::run()
586 using Global::myInactiveScreen;
587 using Global::myLockedScreen;
589 myScreen = myInactiveScreen;
590 myInactiveScreen = myLockedScreen;
591 drawHeader();
594 void VolumeUp::run()
596 int volume = std::min(Status::State::volume()+Config.volume_change_step, 100u);
597 Mpd.SetVolume(volume);
600 void VolumeDown::run()
602 int volume = std::max(int(Status::State::volume()-Config.volume_change_step), 0);
603 Mpd.SetVolume(volume);
606 bool AddItemToPlaylist::canBeRun()
608 m_hs = dynamic_cast<HasSongs *>(myScreen);
609 return m_hs != nullptr && m_hs->itemAvailable();
612 void AddItemToPlaylist::run()
614 bool success = m_hs->addItemToPlaylist(false);
615 if (success)
617 myScreen->scroll(NC::Scroll::Down);
618 listsChangeFinisher();
622 bool PlayItem::canBeRun()
624 m_hs = dynamic_cast<HasSongs *>(myScreen);
625 return m_hs != nullptr && m_hs->itemAvailable();
628 void PlayItem::run()
630 bool success = m_hs->addItemToPlaylist(true);
631 if (success)
632 listsChangeFinisher();
635 bool DeletePlaylistItems::canBeRun()
637 return (myScreen == myPlaylist && !myPlaylist->main().empty())
638 || (myScreen->isActiveWindow(myPlaylistEditor->Content) && !myPlaylistEditor->Content.empty());
641 void DeletePlaylistItems::run()
643 if (myScreen == myPlaylist)
645 Statusbar::print("Deleting items...");
646 auto delete_fun = std::bind(&MPD::Connection::Delete, ph::_1, ph::_2);
647 deleteSelectedSongs(myPlaylist->main(), delete_fun);
648 Statusbar::print("Item(s) deleted");
650 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
652 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
653 auto delete_fun = std::bind(&MPD::Connection::PlaylistDelete, ph::_1, playlist, ph::_2);
654 Statusbar::print("Deleting items...");
655 deleteSelectedSongs(myPlaylistEditor->Content, delete_fun);
656 Statusbar::print("Item(s) deleted");
660 bool DeleteBrowserItems::canBeRun()
662 auto check_if_deletion_allowed = []() {
663 if (Config.allow_for_physical_item_deletion)
664 return true;
665 else
667 Statusbar::print("Flag \"allow_for_physical_item_deletion\" needs to be enabled in configuration file");
668 return false;
671 return myScreen == myBrowser
672 && !myBrowser->main().empty()
673 && isMPDMusicDirSet()
674 && check_if_deletion_allowed();
677 void DeleteBrowserItems::run()
679 auto get_name = [](const MPD::Item &item) -> std::string {
680 std::string iname;
681 switch (item.type())
683 case MPD::Item::Type::Directory:
684 iname = getBasename(item.directory().path());
685 break;
686 case MPD::Item::Type::Song:
687 iname = item.song().getName();
688 break;
689 case MPD::Item::Type::Playlist:
690 iname = getBasename(item.playlist().path());
691 break;
693 return iname;
696 boost::format question;
697 if (hasSelected(myBrowser->main().begin(), myBrowser->main().end()))
698 question = boost::format("Delete selected items?");
699 else
701 const auto &item = myBrowser->main().current()->value();
702 // parent directories are not accepted (and they
703 // can't be selected, so in other cases it's fine).
704 if (myBrowser->isParentDirectory(item))
705 return;
706 const char msg[] = "Delete \"%1%\"?";
707 question = boost::format(msg) % wideShorten(
708 get_name(item), COLS-const_strlen(msg)-5
711 confirmAction(question);
713 auto items = getSelectedOrCurrent(
714 myBrowser->main().begin(),
715 myBrowser->main().end(),
716 myBrowser->main().current()
718 for (const auto &item : items)
720 myBrowser->remove(item->value());
721 const char msg[] = "Deleted %1% \"%2%\"";
722 Statusbar::printf(msg,
723 itemTypeToString(item->value().type()),
724 wideShorten(get_name(item->value()), COLS-const_strlen(msg))
728 if (!myBrowser->isLocal())
729 Mpd.UpdateDirectory(myBrowser->currentDirectory());
730 myBrowser->requestUpdate();
733 bool DeleteStoredPlaylist::canBeRun()
735 return myScreen->isActiveWindow(myPlaylistEditor->Playlists);
738 void DeleteStoredPlaylist::run()
740 if (myPlaylistEditor->Playlists.empty())
741 return;
742 boost::format question;
743 if (hasSelected(myPlaylistEditor->Playlists.begin(), myPlaylistEditor->Playlists.end()))
744 question = boost::format("Delete selected playlists?");
745 else
746 question = boost::format("Delete playlist \"%1%\"?")
747 % wideShorten(myPlaylistEditor->Playlists.current()->value().path(), COLS-question.size()-10);
748 confirmAction(question);
749 auto list = getSelectedOrCurrent(
750 myPlaylistEditor->Playlists.begin(),
751 myPlaylistEditor->Playlists.end(),
752 myPlaylistEditor->Playlists.current()
754 for (const auto &item : list)
755 Mpd.DeletePlaylist(item->value().path());
756 Statusbar::printf("%1% deleted", list.size() == 1 ? "Playlist" : "Playlists");
757 // force playlists update. this happens automatically, but only after call
758 // to Key::read, therefore when we call PlaylistEditor::Update, it won't
759 // yet see it, so let's point that it needs to update it.
760 myPlaylistEditor->requestPlaylistsUpdate();
763 void ReplaySong::run()
765 if (Status::State::player() != MPD::psStop)
766 Mpd.Seek(Status::State::currentSongPosition(), 0);
769 void PreviousSong::run()
771 Mpd.Prev();
774 void NextSong::run()
776 Mpd.Next();
779 void Pause::run()
781 Mpd.Toggle();
784 void SavePlaylist::run()
786 using Global::wFooter;
788 std::string playlist_name;
790 Statusbar::ScopedLock slock;
791 Statusbar::put() << "Save playlist as: ";
792 playlist_name = wFooter->prompt();
796 Mpd.SavePlaylist(playlist_name);
797 Statusbar::printf("Playlist saved as \"%1%\"", playlist_name);
799 catch (MPD::ServerError &e)
801 if (e.code() == MPD_SERVER_ERROR_EXIST)
803 confirmAction(
804 boost::format("Playlist \"%1%\" already exists, overwrite?") % playlist_name
806 Mpd.DeletePlaylist(playlist_name);
807 Mpd.SavePlaylist(playlist_name);
808 Statusbar::print("Playlist overwritten");
810 else
811 throw;
815 void Stop::run()
817 Mpd.Stop();
820 void ExecuteCommand::run()
822 using Global::wFooter;
824 std::string cmd_name;
826 Statusbar::ScopedLock slock;
827 NC::Window::ScopedPromptHook helper(*wFooter,
828 Statusbar::Helpers::TryExecuteImmediateCommand()
830 Statusbar::put() << NC::Format::Bold << ":" << NC::Format::NoBold;
831 cmd_name = wFooter->prompt();
834 auto cmd = Bindings.findCommand(cmd_name);
835 if (cmd)
837 Statusbar::printf(1, "Executing %1%...", cmd_name);
838 bool res = cmd->binding().execute();
839 Statusbar::printf("Execution of command \"%1%\" %2%.",
840 cmd_name, res ? "successful" : "unsuccessful"
843 else
844 Statusbar::printf("No command named \"%1%\"", cmd_name);
847 bool MoveSortOrderUp::canBeRun()
849 return myScreen == mySortPlaylistDialog;
852 void MoveSortOrderUp::run()
854 mySortPlaylistDialog->moveSortOrderUp();
857 bool MoveSortOrderDown::canBeRun()
859 return myScreen == mySortPlaylistDialog;
862 void MoveSortOrderDown::run()
864 mySortPlaylistDialog->moveSortOrderDown();
867 bool MoveSelectedItemsUp::canBeRun()
869 return ((myScreen == myPlaylist
870 && !myPlaylist->main().empty())
871 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
872 && !myPlaylistEditor->Content.empty()));
875 void MoveSelectedItemsUp::run()
877 const char *filteredMsg = "Moving items up is disabled in filtered playlist";
878 if (myScreen == myPlaylist)
880 if (myPlaylist->main().isFiltered())
881 Statusbar::print(filteredMsg);
882 else
883 moveSelectedItemsUp(
884 myPlaylist->main(),
885 std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
887 else if (myScreen == myPlaylistEditor)
889 if (myPlaylistEditor->Content.isFiltered())
890 Statusbar::print(filteredMsg);
891 else
893 auto playlist = myPlaylistEditor->Playlists.current()->value().path();
894 moveSelectedItemsUp(
895 myPlaylistEditor->Content,
896 std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3));
901 bool MoveSelectedItemsDown::canBeRun()
903 return ((myScreen == myPlaylist
904 && !myPlaylist->main().empty())
905 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
906 && !myPlaylistEditor->Content.empty()));
909 void MoveSelectedItemsDown::run()
911 const char *filteredMsg = "Moving items down is disabled in filtered playlist";
912 if (myScreen == myPlaylist)
914 if (myPlaylist->main().isFiltered())
915 Statusbar::print(filteredMsg);
916 else
917 moveSelectedItemsDown(
918 myPlaylist->main(),
919 std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
921 else if (myScreen == myPlaylistEditor)
923 if (myPlaylistEditor->Content.isFiltered())
924 Statusbar::print(filteredMsg);
925 else
927 auto playlist = myPlaylistEditor->Playlists.current()->value().path();
928 moveSelectedItemsDown(
929 myPlaylistEditor->Content,
930 std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3));
935 bool MoveSelectedItemsTo::canBeRun()
937 return myScreen == myPlaylist
938 || myScreen->isActiveWindow(myPlaylistEditor->Content);
941 void MoveSelectedItemsTo::run()
943 if (myScreen == myPlaylist)
945 if (!myPlaylist->main().empty())
946 moveSelectedItemsTo(myPlaylist->main(), std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
948 else
950 assert(!myPlaylistEditor->Playlists.empty());
951 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
952 auto move_fun = std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3);
953 moveSelectedItemsTo(myPlaylistEditor->Content, move_fun);
957 bool Add::canBeRun()
959 return myScreen != myPlaylistEditor
960 || !myPlaylistEditor->Playlists.empty();
963 void Add::run()
965 using Global::wFooter;
967 std::string path;
969 Statusbar::ScopedLock slock;
970 Statusbar::put() << (myScreen == myPlaylistEditor ? "Add to playlist: " : "Add: ");
971 path = wFooter->prompt();
974 // confirm when one wants to add the whole database
975 if (path.empty())
976 confirmAction("Are you sure you want to add the whole database?");
978 Statusbar::put() << "Adding...";
979 wFooter->refresh();
980 if (myScreen == myPlaylistEditor)
981 Mpd.AddToPlaylist(myPlaylistEditor->Playlists.current()->value().path(), path);
982 else
986 Mpd.Add(path);
988 catch (MPD::ServerError &err)
990 // If a path is not a file or directory, assume it is a playlist.
991 if (err.code() == MPD_SERVER_ERROR_NO_EXIST)
992 Mpd.LoadPlaylist(path);
993 else
994 throw;
999 bool SeekForward::canBeRun()
1001 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1004 void SeekForward::run()
1006 seek();
1009 bool SeekBackward::canBeRun()
1011 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1014 void SeekBackward::run()
1016 seek();
1019 bool ToggleDisplayMode::canBeRun()
1021 return myScreen == myPlaylist
1022 || myScreen == myBrowser
1023 || myScreen == mySearcher
1024 || myScreen->isActiveWindow(myPlaylistEditor->Content);
1027 void ToggleDisplayMode::run()
1029 if (myScreen == myPlaylist)
1031 switch (Config.playlist_display_mode)
1033 case DisplayMode::Classic:
1034 Config.playlist_display_mode = DisplayMode::Columns;
1035 myPlaylist->main().setItemDisplayer(std::bind(
1036 Display::SongsInColumns, ph::_1, std::cref(myPlaylist->main())
1038 if (Config.titles_visibility)
1039 myPlaylist->main().setTitle(Display::Columns(myPlaylist->main().getWidth()));
1040 else
1041 myPlaylist->main().setTitle("");
1042 break;
1043 case DisplayMode::Columns:
1044 Config.playlist_display_mode = DisplayMode::Classic;
1045 myPlaylist->main().setItemDisplayer(std::bind(
1046 Display::Songs, ph::_1, std::cref(myPlaylist->main()), std::cref(Config.song_list_format)
1048 myPlaylist->main().setTitle("");
1050 Statusbar::printf("Playlist display mode: %1%", Config.playlist_display_mode);
1052 else if (myScreen == myBrowser)
1054 switch (Config.browser_display_mode)
1056 case DisplayMode::Classic:
1057 Config.browser_display_mode = DisplayMode::Columns;
1058 if (Config.titles_visibility)
1059 myBrowser->main().setTitle(Display::Columns(myBrowser->main().getWidth()));
1060 else
1061 myBrowser->main().setTitle("");
1062 break;
1063 case DisplayMode::Columns:
1064 Config.browser_display_mode = DisplayMode::Classic;
1065 myBrowser->main().setTitle("");
1066 break;
1068 Statusbar::printf("Browser display mode: %1%", Config.browser_display_mode);
1070 else if (myScreen == mySearcher)
1072 switch (Config.search_engine_display_mode)
1074 case DisplayMode::Classic:
1075 Config.search_engine_display_mode = DisplayMode::Columns;
1076 break;
1077 case DisplayMode::Columns:
1078 Config.search_engine_display_mode = DisplayMode::Classic;
1079 break;
1081 Statusbar::printf("Search engine display mode: %1%", Config.search_engine_display_mode);
1082 if (mySearcher->main().size() > SearchEngine::StaticOptions)
1083 mySearcher->main().setTitle(
1084 Config.search_engine_display_mode == DisplayMode::Columns
1085 && Config.titles_visibility
1086 ? Display::Columns(mySearcher->main().getWidth())
1087 : ""
1090 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
1092 switch (Config.playlist_editor_display_mode)
1094 case DisplayMode::Classic:
1095 Config.playlist_editor_display_mode = DisplayMode::Columns;
1096 myPlaylistEditor->Content.setItemDisplayer(std::bind(
1097 Display::SongsInColumns, ph::_1, std::cref(myPlaylistEditor->Content)
1099 break;
1100 case DisplayMode::Columns:
1101 Config.playlist_editor_display_mode = DisplayMode::Classic;
1102 myPlaylistEditor->Content.setItemDisplayer(std::bind(
1103 Display::Songs, ph::_1, std::cref(myPlaylistEditor->Content), std::cref(Config.song_list_format)
1105 break;
1107 Statusbar::printf("Playlist editor display mode: %1%", Config.playlist_editor_display_mode);
1111 bool ToggleSeparatorsBetweenAlbums::canBeRun()
1113 return true;
1116 void ToggleSeparatorsBetweenAlbums::run()
1118 Config.playlist_separate_albums = !Config.playlist_separate_albums;
1119 Statusbar::printf("Separators between albums: %1%",
1120 Config.playlist_separate_albums ? "on" : "off"
1124 bool ToggleLyricsUpdateOnSongChange::canBeRun()
1126 return myScreen == myLyrics;
1129 void ToggleLyricsUpdateOnSongChange::run()
1131 Config.now_playing_lyrics = !Config.now_playing_lyrics;
1132 Statusbar::printf("Update lyrics if song changes: %1%",
1133 Config.now_playing_lyrics ? "on" : "off"
1137 void ToggleLyricsFetcher::run()
1139 myLyrics->toggleFetcher();
1142 void ToggleFetchingLyricsInBackground::run()
1144 Config.fetch_lyrics_in_background = !Config.fetch_lyrics_in_background;
1145 Statusbar::printf("Fetching lyrics for playing songs in background: %1%",
1146 Config.fetch_lyrics_in_background ? "on" : "off");
1149 void TogglePlayingSongCentering::run()
1151 Config.autocenter_mode = !Config.autocenter_mode;
1152 Statusbar::printf("Centering playing song: %1%",
1153 Config.autocenter_mode ? "on" : "off"
1155 if (Config.autocenter_mode)
1157 auto s = myPlaylist->nowPlayingSong();
1158 if (!s.empty())
1159 myPlaylist->locateSong(s);
1163 void UpdateDatabase::run()
1165 if (myScreen == myBrowser)
1166 Mpd.UpdateDirectory(myBrowser->currentDirectory());
1167 # ifdef HAVE_TAGLIB_H
1168 else if (myScreen == myTagEditor)
1169 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1170 # endif // HAVE_TAGLIB_H
1171 else
1172 Mpd.UpdateDirectory("/");
1175 bool JumpToPlayingSong::canBeRun()
1177 return myScreen == myPlaylist
1178 || myScreen == myBrowser
1179 || myScreen == myLibrary;
1182 void JumpToPlayingSong::run()
1184 auto s = myPlaylist->nowPlayingSong();
1185 if (s.empty())
1186 return;
1187 if (myScreen == myPlaylist)
1189 myPlaylist->locateSong(s);
1191 else if (myScreen == myBrowser)
1193 myBrowser->locateSong(s);
1195 else if (myScreen == myLibrary)
1197 myLibrary->locateSong(s);
1201 void ToggleRepeat::run()
1203 Mpd.SetRepeat(!Status::State::repeat());
1206 bool Shuffle::canBeRun()
1208 if (myScreen != myPlaylist)
1209 return false;
1210 m_begin = myPlaylist->main().begin();
1211 m_end = myPlaylist->main().end();
1212 return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end);
1215 void Shuffle::run()
1217 if (Config.ask_before_shuffling_playlists)
1218 confirmAction("Do you really want to shuffle selected range?");
1219 auto begin = myPlaylist->main().begin();
1220 Mpd.ShuffleRange(m_begin-begin, m_end-begin);
1221 Statusbar::print("Range shuffled");
1224 void ToggleRandom::run()
1226 Mpd.SetRandom(!Status::State::random());
1229 bool StartSearching::canBeRun()
1231 return myScreen == mySearcher && !mySearcher->main()[0].isInactive();
1234 void StartSearching::run()
1236 mySearcher->main().highlight(SearchEngine::SearchButton);
1237 mySearcher->main().setHighlighting(0);
1238 mySearcher->main().refresh();
1239 mySearcher->main().setHighlighting(1);
1240 mySearcher->runAction();
1243 bool SaveTagChanges::canBeRun()
1245 # ifdef HAVE_TAGLIB_H
1246 return myScreen == myTinyTagEditor
1247 || myScreen->activeWindow() == myTagEditor->TagTypes;
1248 # else
1249 return false;
1250 # endif // HAVE_TAGLIB_H
1253 void SaveTagChanges::run()
1255 # ifdef HAVE_TAGLIB_H
1256 if (myScreen == myTinyTagEditor)
1258 myTinyTagEditor->main().highlight(myTinyTagEditor->main().size()-2); // Save
1259 myTinyTagEditor->runAction();
1261 else if (myScreen->activeWindow() == myTagEditor->TagTypes)
1263 myTagEditor->TagTypes->highlight(myTagEditor->TagTypes->size()-1); // Save
1264 myTagEditor->runAction();
1266 # endif // HAVE_TAGLIB_H
1269 void ToggleSingle::run()
1271 Mpd.SetSingle(!Status::State::single());
1274 void ToggleConsume::run()
1276 Mpd.SetConsume(!Status::State::consume());
1279 void ToggleCrossfade::run()
1281 Mpd.SetCrossfade(Status::State::crossfade() ? 0 : Config.crossfade_time);
1284 void SetCrossfade::run()
1286 using Global::wFooter;
1288 Statusbar::ScopedLock slock;
1289 Statusbar::put() << "Set crossfade to: ";
1290 auto crossfade = fromString<unsigned>(wFooter->prompt());
1291 lowerBoundCheck(crossfade, 0u);
1292 Config.crossfade_time = crossfade;
1293 Mpd.SetCrossfade(crossfade);
1296 void SetVolume::run()
1298 using Global::wFooter;
1300 unsigned volume;
1302 Statusbar::ScopedLock slock;
1303 Statusbar::put() << "Set volume to: ";
1304 volume = fromString<unsigned>(wFooter->prompt());
1305 boundsCheck(volume, 0u, 100u);
1306 Mpd.SetVolume(volume);
1308 Statusbar::printf("Volume set to %1%%%", volume);
1311 bool EnterDirectory::canBeRun()
1313 bool result = false;
1314 if (myScreen == myBrowser && !myBrowser->main().empty())
1316 result = myBrowser->main().current()->value().type()
1317 == MPD::Item::Type::Directory;
1319 #ifdef HAVE_TAGLIB_H
1320 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1321 result = true;
1322 #endif // HAVE_TAGLIB_H
1323 return result;
1326 void EnterDirectory::run()
1328 if (myScreen == myBrowser)
1329 myBrowser->enterDirectory();
1330 #ifdef HAVE_TAGLIB_H
1331 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1333 if (!myTagEditor->enterDirectory())
1334 Statusbar::print("No subdirectories found");
1336 #endif // HAVE_TAGLIB_H
1339 bool EditSong::canBeRun()
1341 # ifdef HAVE_TAGLIB_H
1342 m_song = currentSong(myScreen);
1343 return m_song != nullptr && isMPDMusicDirSet();
1344 # else
1345 return false;
1346 # endif // HAVE_TAGLIB_H
1349 void EditSong::run()
1351 # ifdef HAVE_TAGLIB_H
1352 myTinyTagEditor->SetEdited(*m_song);
1353 myTinyTagEditor->switchTo();
1354 # endif // HAVE_TAGLIB_H
1357 bool EditLibraryTag::canBeRun()
1359 # ifdef HAVE_TAGLIB_H
1360 return myScreen->isActiveWindow(myLibrary->Tags)
1361 && !myLibrary->Tags.empty()
1362 && isMPDMusicDirSet();
1363 # else
1364 return false;
1365 # endif // HAVE_TAGLIB_H
1368 void EditLibraryTag::run()
1370 # ifdef HAVE_TAGLIB_H
1371 using Global::wFooter;
1373 std::string new_tag;
1375 Statusbar::ScopedLock slock;
1376 Statusbar::put() << NC::Format::Bold << tagTypeToString(Config.media_lib_primary_tag) << NC::Format::NoBold << ": ";
1377 new_tag = wFooter->prompt(myLibrary->Tags.current()->value().tag());
1379 if (!new_tag.empty() && new_tag != myLibrary->Tags.current()->value().tag())
1381 Statusbar::print("Updating tags...");
1382 Mpd.StartSearch(true);
1383 Mpd.AddSearch(Config.media_lib_primary_tag, myLibrary->Tags.current()->value().tag());
1384 MPD::MutableSong::SetFunction set = tagTypeToSetFunction(Config.media_lib_primary_tag);
1385 assert(set);
1386 bool success = true;
1387 std::string dir_to_update;
1388 for (MPD::SongIterator s = Mpd.CommitSearchSongs(), end; s != end; ++s)
1390 MPD::MutableSong ms = std::move(*s);
1391 ms.setTags(set, new_tag);
1392 Statusbar::printf("Updating tags in \"%1%\"...", ms.getName());
1393 std::string path = Config.mpd_music_dir + ms.getURI();
1394 if (!Tags::write(ms))
1396 success = false;
1397 Statusbar::printf("Error while writing tags to \"%1%\": %2%",
1398 ms.getName(), strerror(errno));
1399 s.finish();
1400 break;
1402 if (dir_to_update.empty())
1403 dir_to_update = ms.getURI();
1404 else
1405 dir_to_update = getSharedDirectory(dir_to_update, ms.getURI());
1407 if (success)
1409 Mpd.UpdateDirectory(dir_to_update);
1410 Statusbar::print("Tags updated successfully");
1413 # endif // HAVE_TAGLIB_H
1416 bool EditLibraryAlbum::canBeRun()
1418 # ifdef HAVE_TAGLIB_H
1419 return myScreen->isActiveWindow(myLibrary->Albums)
1420 && !myLibrary->Albums.empty()
1421 && isMPDMusicDirSet();
1422 # else
1423 return false;
1424 # endif // HAVE_TAGLIB_H
1427 void EditLibraryAlbum::run()
1429 # ifdef HAVE_TAGLIB_H
1430 using Global::wFooter;
1431 // FIXME: merge this and EditLibraryTag. also, prompt on failure if user wants to continue
1432 std::string new_album;
1434 Statusbar::ScopedLock slock;
1435 Statusbar::put() << NC::Format::Bold << "Album: " << NC::Format::NoBold;
1436 new_album = wFooter->prompt(myLibrary->Albums.current()->value().entry().album());
1438 if (!new_album.empty() && new_album != myLibrary->Albums.current()->value().entry().album())
1440 bool success = 1;
1441 Statusbar::print("Updating tags...");
1442 for (size_t i = 0; i < myLibrary->Songs.size(); ++i)
1444 Statusbar::printf("Updating tags in \"%1%\"...", myLibrary->Songs[i].value().getName());
1445 std::string path = Config.mpd_music_dir + myLibrary->Songs[i].value().getURI();
1446 TagLib::FileRef f(path.c_str());
1447 if (f.isNull())
1449 const char msg[] = "Error while opening file \"%1%\"";
1450 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1451 success = 0;
1452 break;
1454 f.tag()->setAlbum(ToWString(new_album));
1455 if (!f.save())
1457 const char msg[] = "Error while writing tags in \"%1%\"";
1458 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1459 success = 0;
1460 break;
1463 if (success)
1465 Mpd.UpdateDirectory(getSharedDirectory(myLibrary->Songs.beginV(), myLibrary->Songs.endV()));
1466 Statusbar::print("Tags updated successfully");
1469 # endif // HAVE_TAGLIB_H
1472 bool EditDirectoryName::canBeRun()
1474 return ((myScreen == myBrowser
1475 && !myBrowser->main().empty()
1476 && myBrowser->main().current()->value().type() == MPD::Item::Type::Directory)
1477 # ifdef HAVE_TAGLIB_H
1478 || (myScreen->activeWindow() == myTagEditor->Dirs
1479 && !myTagEditor->Dirs->empty()
1480 && myTagEditor->Dirs->choice() > 0)
1481 # endif // HAVE_TAGLIB_H
1482 ) && isMPDMusicDirSet();
1485 void EditDirectoryName::run()
1487 using Global::wFooter;
1488 if (myScreen == myBrowser)
1490 std::string old_dir = myBrowser->main().current()->value().directory().path();
1491 std::string new_dir;
1493 Statusbar::ScopedLock slock;
1494 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1495 new_dir = wFooter->prompt(old_dir);
1497 if (!new_dir.empty() && new_dir != old_dir)
1499 std::string full_old_dir;
1500 if (!myBrowser->isLocal())
1501 full_old_dir += Config.mpd_music_dir;
1502 full_old_dir += old_dir;
1503 std::string full_new_dir;
1504 if (!myBrowser->isLocal())
1505 full_new_dir += Config.mpd_music_dir;
1506 full_new_dir += new_dir;
1507 boost::filesystem::rename(full_old_dir, full_new_dir);
1508 const char msg[] = "Directory renamed to \"%1%\"";
1509 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1510 if (!myBrowser->isLocal())
1511 Mpd.UpdateDirectory(getSharedDirectory(old_dir, new_dir));
1512 myBrowser->requestUpdate();
1515 # ifdef HAVE_TAGLIB_H
1516 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1518 std::string old_dir = myTagEditor->Dirs->current()->value().first, new_dir;
1520 Statusbar::ScopedLock slock;
1521 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1522 new_dir = wFooter->prompt(old_dir);
1524 if (!new_dir.empty() && new_dir != old_dir)
1526 std::string full_old_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + old_dir;
1527 std::string full_new_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + new_dir;
1528 if (rename(full_old_dir.c_str(), full_new_dir.c_str()) == 0)
1530 const char msg[] = "Directory renamed to \"%1%\"";
1531 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1532 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1534 else
1536 const char msg[] = "Couldn't rename \"%1%\": %2%";
1537 Statusbar::printf(msg, wideShorten(old_dir, COLS-const_strlen(msg)-25), strerror(errno));
1541 # endif // HAVE_TAGLIB_H
1544 bool EditPlaylistName::canBeRun()
1546 return (myScreen->isActiveWindow(myPlaylistEditor->Playlists)
1547 && !myPlaylistEditor->Playlists.empty())
1548 || (myScreen == myBrowser
1549 && !myBrowser->main().empty()
1550 && myBrowser->main().current()->value().type() == MPD::Item::Type::Playlist);
1553 void EditPlaylistName::run()
1555 using Global::wFooter;
1556 std::string old_name, new_name;
1557 if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
1558 old_name = myPlaylistEditor->Playlists.current()->value().path();
1559 else
1560 old_name = myBrowser->main().current()->value().playlist().path();
1562 Statusbar::ScopedLock slock;
1563 Statusbar::put() << NC::Format::Bold << "Playlist: " << NC::Format::NoBold;
1564 new_name = wFooter->prompt(old_name);
1566 if (!new_name.empty() && new_name != old_name)
1568 Mpd.Rename(old_name, new_name);
1569 const char msg[] = "Playlist renamed to \"%1%\"";
1570 Statusbar::printf(msg, wideShorten(new_name, COLS-const_strlen(msg)));
1574 bool EditLyrics::canBeRun()
1576 return myScreen == myLyrics;
1579 void EditLyrics::run()
1581 myLyrics->edit();
1584 bool JumpToBrowser::canBeRun()
1586 m_song = currentSong(myScreen);
1587 return m_song != nullptr;
1590 void JumpToBrowser::run()
1592 myBrowser->locateSong(*m_song);
1595 bool JumpToMediaLibrary::canBeRun()
1597 m_song = currentSong(myScreen);
1598 return m_song != nullptr;
1601 void JumpToMediaLibrary::run()
1603 myLibrary->locateSong(*m_song);
1606 bool JumpToPlaylistEditor::canBeRun()
1608 return myScreen == myBrowser
1609 && myBrowser->main().current()->value().type() == MPD::Item::Type::Playlist;
1612 void JumpToPlaylistEditor::run()
1614 myPlaylistEditor->locatePlaylist(myBrowser->main().current()->value().playlist());
1617 void ToggleScreenLock::run()
1619 using Global::wFooter;
1620 using Global::myLockedScreen;
1621 const char *msg_unlockable_screen = "Current screen can't be locked";
1622 if (myLockedScreen != nullptr)
1624 BaseScreen::unlock();
1625 Actions::setResizeFlags();
1626 myScreen->resize();
1627 Statusbar::print("Screen unlocked");
1629 else if (!myScreen->isLockable())
1631 Statusbar::print(msg_unlockable_screen);
1633 else
1635 unsigned part = Config.locked_screen_width_part*100;
1636 if (Config.ask_for_locked_screen_width_part)
1638 Statusbar::ScopedLock slock;
1639 Statusbar::put() << "% of the locked screen's width to be reserved (20-80): ";
1640 part = fromString<unsigned>(wFooter->prompt(boost::lexical_cast<std::string>(part)));
1642 boundsCheck(part, 20u, 80u);
1643 Config.locked_screen_width_part = part/100.0;
1644 if (myScreen->lock())
1645 Statusbar::printf("Screen locked (with %1%%% width)", part);
1646 else
1647 Statusbar::print(msg_unlockable_screen);
1651 bool JumpToTagEditor::canBeRun()
1653 # ifdef HAVE_TAGLIB_H
1654 m_song = currentSong(myScreen);
1655 return m_song != nullptr && isMPDMusicDirSet();
1656 # else
1657 return false;
1658 # endif // HAVE_TAGLIB_H
1661 void JumpToTagEditor::run()
1663 # ifdef HAVE_TAGLIB_H
1664 myTagEditor->LocateSong(*m_song);
1665 # endif // HAVE_TAGLIB_H
1668 bool JumpToPositionInSong::canBeRun()
1670 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1673 void JumpToPositionInSong::run()
1675 using Global::wFooter;
1677 const MPD::Song s = myPlaylist->nowPlayingSong();
1679 std::string spos;
1681 Statusbar::ScopedLock slock;
1682 Statusbar::put() << "Position to go (in %/m:ss/seconds(s)): ";
1683 spos = wFooter->prompt();
1686 boost::regex rx;
1687 boost::smatch what;
1688 if (boost::regex_match(spos, what, rx.assign("([0-9]+):([0-9]{2})"))) // mm:ss
1690 auto mins = fromString<unsigned>(what[1]);
1691 auto secs = fromString<unsigned>(what[2]);
1692 boundsCheck(secs, 0u, 60u);
1693 Mpd.Seek(s.getPosition(), mins * 60 + secs);
1695 else if (boost::regex_match(spos, what, rx.assign("([0-9]+)s"))) // position in seconds
1697 auto secs = fromString<unsigned>(what[1]);
1698 Mpd.Seek(s.getPosition(), secs);
1700 else if (boost::regex_match(spos, what, rx.assign("([0-9]+)[%]{0,1}"))) // position in %
1702 auto percent = fromString<unsigned>(what[1]);
1703 boundsCheck(percent, 0u, 100u);
1704 int secs = (percent * s.getDuration()) / 100.0;
1705 Mpd.Seek(s.getPosition(), secs);
1707 else
1708 Statusbar::print("Invalid format ([m]:[ss], [s]s, [%]%, [%] accepted)");
1711 bool SelectItem::canBeRun()
1713 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1714 return m_list != nullptr
1715 && !m_list->empty()
1716 && m_list->currentP()->isSelectable();
1719 void SelectItem::run()
1721 auto current = m_list->currentP();
1722 current->setSelected(!current->isSelected());
1725 bool SelectRange::canBeRun()
1727 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1728 if (m_list == nullptr)
1729 return false;
1730 m_begin = m_list->beginP();
1731 m_end = m_list->endP();
1732 return findRange(m_begin, m_end);
1735 void SelectRange::run()
1737 for (; m_begin != m_end; ++m_begin)
1738 m_begin->setSelected(true);
1739 Statusbar::print("Range selected");
1742 bool ReverseSelection::canBeRun()
1744 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1745 return m_list != nullptr;
1748 void ReverseSelection::run()
1750 for (auto &p : *m_list)
1751 p.setSelected(!p.isSelected());
1752 Statusbar::print("Selection reversed");
1755 bool RemoveSelection::canBeRun()
1757 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1758 return m_list != nullptr;
1761 void RemoveSelection::run()
1763 for (auto &p : *m_list)
1764 p.setSelected(false);
1765 Statusbar::print("Selection removed");
1768 bool SelectAlbum::canBeRun()
1770 auto *w = myScreen->activeWindow();
1771 if (m_list != static_cast<void *>(w))
1772 m_list = dynamic_cast<NC::List *>(w);
1773 if (m_songs != static_cast<void *>(w))
1774 m_songs = dynamic_cast<SongList *>(w);
1775 return m_list != nullptr && !m_list->empty()
1776 && m_songs != nullptr;
1779 void SelectAlbum::run()
1781 const auto front = m_songs->beginS(), current = m_songs->currentS(), end = m_songs->endS();
1782 auto *s = current->get<Bit::Song>();
1783 if (s == nullptr)
1784 return;
1785 auto get = &MPD::Song::getAlbum;
1786 const std::string tag = s->getTags(get);
1787 // go up
1788 for (auto it = current; it != front;)
1790 --it;
1791 s = it->get<Bit::Song>();
1792 if (s == nullptr || s->getTags(get) != tag)
1793 break;
1794 it->get<Bit::Properties>().setSelected(true);
1796 // go down
1797 for (auto it = current;;)
1799 it->get<Bit::Properties>().setSelected(true);
1800 if (++it == end)
1801 break;
1802 s = it->get<Bit::Song>();
1803 if (s == nullptr || s->getTags(get) != tag)
1804 break;
1806 Statusbar::print("Album around cursor position selected");
1809 bool SelectFoundItems::canBeRun()
1811 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1812 if (m_list == nullptr || m_list->empty())
1813 return false;
1814 m_searchable = dynamic_cast<Searchable *>(myScreen);
1815 return m_searchable != nullptr && m_searchable->allowsSearching();
1818 void SelectFoundItems::run()
1820 auto current_pos = m_list->choice();
1821 myScreen->activeWindow()->scroll(NC::Scroll::Home);
1822 bool found = m_searchable->search(SearchDirection::Forward, false, false);
1823 if (found)
1825 Statusbar::print("Searching for items...");
1826 m_list->currentP()->setSelected(true);
1827 while (m_searchable->search(SearchDirection::Forward, false, true))
1828 m_list->currentP()->setSelected(true);
1829 Statusbar::print("Found items selected");
1831 m_list->highlight(current_pos);
1834 bool AddSelectedItems::canBeRun()
1836 return myScreen != mySelectedItemsAdder;
1839 void AddSelectedItems::run()
1841 mySelectedItemsAdder->switchTo();
1844 void CropMainPlaylist::run()
1846 auto &w = myPlaylist->main();
1847 // cropping doesn't make sense in this case
1848 if (w.size() <= 1)
1849 return;
1850 if (Config.ask_before_clearing_playlists)
1851 confirmAction("Do you really want to crop main playlist?");
1852 Statusbar::print("Cropping playlist...");
1853 selectCurrentIfNoneSelected(w);
1854 cropPlaylist(w, std::bind(&MPD::Connection::Delete, ph::_1, ph::_2));
1855 Statusbar::print("Playlist cropped");
1858 bool CropPlaylist::canBeRun()
1860 return myScreen == myPlaylistEditor;
1863 void CropPlaylist::run()
1865 auto &w = myPlaylistEditor->Content;
1866 // cropping doesn't make sense in this case
1867 if (w.size() <= 1)
1868 return;
1869 assert(!myPlaylistEditor->Playlists.empty());
1870 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
1871 if (Config.ask_before_clearing_playlists)
1872 confirmAction(boost::format("Do you really want to crop playlist \"%1%\"?") % playlist);
1873 selectCurrentIfNoneSelected(w);
1874 Statusbar::printf("Cropping playlist \"%1%\"...", playlist);
1875 cropPlaylist(w, std::bind(&MPD::Connection::PlaylistDelete, ph::_1, playlist, ph::_2));
1876 Statusbar::printf("Playlist \"%1%\" cropped", playlist);
1879 void ClearMainPlaylist::run()
1881 if (!myPlaylist->main().empty() && Config.ask_before_clearing_playlists)
1882 confirmAction("Do you really want to clear main playlist?");
1883 Mpd.ClearMainPlaylist();
1884 Statusbar::print("Playlist cleared");
1885 myPlaylist->main().reset();
1888 bool ClearPlaylist::canBeRun()
1890 return myScreen == myPlaylistEditor;
1893 void ClearPlaylist::run()
1895 if (myPlaylistEditor->Playlists.empty())
1896 return;
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 clear playlist \"%1%\"?") % playlist);
1900 Mpd.ClearPlaylist(playlist);
1901 Statusbar::printf("Playlist \"%1%\" cleared", playlist);
1904 bool SortPlaylist::canBeRun()
1906 if (myScreen != myPlaylist)
1907 return false;
1908 auto first = myPlaylist->main().begin(), last = myPlaylist->main().end();
1909 return findSelectedRangeAndPrintInfoIfNot(first, last);
1912 void SortPlaylist::run()
1914 mySortPlaylistDialog->switchTo();
1917 bool ReversePlaylist::canBeRun()
1919 if (myScreen != myPlaylist)
1920 return false;
1921 m_begin = myPlaylist->main().begin();
1922 m_end = myPlaylist->main().end();
1923 return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end);
1926 void ReversePlaylist::run()
1928 Statusbar::print("Reversing range...");
1929 Mpd.StartCommandsList();
1930 for (--m_end; m_begin < m_end; ++m_begin, --m_end)
1931 Mpd.Swap(m_begin->value().getPosition(), m_end->value().getPosition());
1932 Mpd.CommitCommandsList();
1933 Statusbar::print("Range reversed");
1936 bool ApplyFilter::canBeRun()
1938 m_filterable = dynamic_cast<Filterable *>(myScreen);
1939 return m_filterable != nullptr
1940 && m_filterable->allowsFiltering();
1943 void ApplyFilter::run()
1945 using Global::wFooter;
1947 std::string filter = m_filterable->currentFilter();
1948 if (!filter.empty())
1950 m_filterable->applyFilter(filter);
1951 myScreen->refreshWindow();
1956 Statusbar::ScopedLock slock;
1957 NC::Window::ScopedPromptHook helper(
1958 *wFooter,
1959 Statusbar::Helpers::ApplyFilterImmediately(m_filterable));
1960 Statusbar::put() << "Apply filter: ";
1961 filter = wFooter->prompt(filter);
1963 catch (NC::PromptAborted &)
1965 m_filterable->applyFilter(filter);
1966 throw;
1969 if (filter.empty())
1970 Statusbar::printf("Filtering disabled");
1971 else
1972 Statusbar::printf("Using filter \"%1%\"", filter);
1974 if (myScreen == myPlaylist)
1975 myPlaylist->reloadTotalLength();
1977 listsChangeFinisher();
1980 bool Find::canBeRun()
1982 return myScreen == myHelp
1983 || myScreen == myLyrics
1984 || myScreen == myLastfm;
1987 void Find::run()
1989 using Global::wFooter;
1991 std::string token;
1993 Statusbar::ScopedLock slock;
1994 Statusbar::put() << "Find: ";
1995 token = wFooter->prompt();
1998 Statusbar::print("Searching...");
1999 auto s = static_cast<Screen<NC::Scrollpad> *>(myScreen);
2000 s->main().removeProperties();
2001 if (token.empty() || s->main().setProperties(NC::Format::Reverse, token, NC::Format::NoReverse, Config.regex_type))
2002 Statusbar::print("Done");
2003 else
2004 Statusbar::print("No matching patterns found");
2005 s->main().flush();
2008 bool FindItemBackward::canBeRun()
2010 auto w = dynamic_cast<Searchable *>(myScreen);
2011 return w && w->allowsSearching();
2014 void FindItemForward::run()
2016 findItem(SearchDirection::Forward);
2017 listsChangeFinisher();
2020 bool FindItemForward::canBeRun()
2022 auto w = dynamic_cast<Searchable *>(myScreen);
2023 return w && w->allowsSearching();
2026 void FindItemBackward::run()
2028 findItem(SearchDirection::Backward);
2029 listsChangeFinisher();
2032 bool NextFoundItem::canBeRun()
2034 return dynamic_cast<Searchable *>(myScreen);
2037 void NextFoundItem::run()
2039 Searchable *w = dynamic_cast<Searchable *>(myScreen);
2040 assert(w != nullptr);
2041 w->search(SearchDirection::Forward, Config.wrapped_search, true);
2042 listsChangeFinisher();
2045 bool PreviousFoundItem::canBeRun()
2047 return dynamic_cast<Searchable *>(myScreen);
2050 void PreviousFoundItem::run()
2052 Searchable *w = dynamic_cast<Searchable *>(myScreen);
2053 assert(w != nullptr);
2054 w->search(SearchDirection::Backward, Config.wrapped_search, true);
2055 listsChangeFinisher();
2058 void ToggleFindMode::run()
2060 Config.wrapped_search = !Config.wrapped_search;
2061 Statusbar::printf("Search mode: %1%",
2062 Config.wrapped_search ? "Wrapped" : "Normal"
2066 void ToggleReplayGainMode::run()
2068 using Global::wFooter;
2070 char rgm = 0;
2072 Statusbar::ScopedLock slock;
2073 Statusbar::put() << "Replay gain mode? "
2074 << "[" << NC::Format::Bold << 'o' << NC::Format::NoBold << "ff"
2075 << "/" << NC::Format::Bold << 't' << NC::Format::NoBold << "rack"
2076 << "/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "lbum"
2077 << "] ";
2078 rgm = Statusbar::Helpers::promptReturnOneOf({"t", "a", "o"})[0];
2080 switch (rgm)
2082 case 't':
2083 Mpd.SetReplayGainMode(MPD::rgmTrack);
2084 break;
2085 case 'a':
2086 Mpd.SetReplayGainMode(MPD::rgmAlbum);
2087 break;
2088 case 'o':
2089 Mpd.SetReplayGainMode(MPD::rgmOff);
2090 break;
2091 default: // impossible
2092 throw std::runtime_error(
2093 (boost::format("ToggleReplayGainMode: impossible case reached: %1%") % rgm).str()
2096 Statusbar::printf("Replay gain mode: %1%", Mpd.GetReplayGainMode());
2099 void ToggleAddMode::run()
2101 std::string mode_desc;
2102 switch (Config.space_add_mode)
2104 case SpaceAddMode::AddRemove:
2105 Config.space_add_mode = SpaceAddMode::AlwaysAdd;
2106 mode_desc = "always add an item to playlist";
2107 break;
2108 case SpaceAddMode::AlwaysAdd:
2109 Config.space_add_mode = SpaceAddMode::AddRemove;
2110 mode_desc = "add an item to playlist or remove if already added";
2111 break;
2113 Statusbar::printf("Add mode: %1%", mode_desc);
2116 void ToggleMouse::run()
2118 Config.mouse_support = !Config.mouse_support;
2119 if (Config.mouse_support)
2120 NC::Mouse::enable();
2121 else
2122 NC::Mouse::disable();
2123 Statusbar::printf("Mouse support %1%",
2124 Config.mouse_support ? "enabled" : "disabled"
2128 void ToggleBitrateVisibility::run()
2130 Config.display_bitrate = !Config.display_bitrate;
2131 Statusbar::printf("Bitrate visibility %1%",
2132 Config.display_bitrate ? "enabled" : "disabled"
2136 void AddRandomItems::run()
2138 using Global::wFooter;
2139 char rnd_type = 0;
2141 Statusbar::ScopedLock slock;
2142 Statusbar::put() << "Add random? "
2143 << "[" << NC::Format::Bold << 's' << NC::Format::NoBold << "ongs"
2144 << "/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtists"
2145 << "/" << "album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtists"
2146 << "/" << "al" << NC::Format::Bold << 'b' << NC::Format::NoBold << "ums"
2147 << "] ";
2148 rnd_type = Statusbar::Helpers::promptReturnOneOf({"s", "a", "A", "b"})[0];
2151 mpd_tag_type tag_type = MPD_TAG_ARTIST;
2152 std::string tag_type_str ;
2153 if (rnd_type != 's')
2155 tag_type = charToTagType(rnd_type);
2156 tag_type_str = boost::locale::to_lower(tagTypeToString(tag_type));
2158 else
2159 tag_type_str = "song";
2161 unsigned number;
2163 Statusbar::ScopedLock slock;
2164 Statusbar::put() << "Number of random " << tag_type_str << "s: ";
2165 number = fromString<unsigned>(wFooter->prompt());
2167 if (number > 0)
2169 bool success;
2170 if (rnd_type == 's')
2171 success = Mpd.AddRandomSongs(number, Global::RNG);
2172 else
2173 success = Mpd.AddRandomTag(tag_type, number, Global::RNG);
2174 if (success)
2175 Statusbar::printf("%1% random %2%%3% added to playlist", number, tag_type_str, number == 1 ? "" : "s");
2179 bool ToggleBrowserSortMode::canBeRun()
2181 return myScreen == myBrowser;
2184 void ToggleBrowserSortMode::run()
2186 switch (Config.browser_sort_mode)
2188 case SortMode::Name:
2189 Config.browser_sort_mode = SortMode::ModificationTime;
2190 Statusbar::print("Sort songs by: modification time");
2191 break;
2192 case SortMode::ModificationTime:
2193 Config.browser_sort_mode = SortMode::CustomFormat;
2194 Statusbar::print("Sort songs by: custom format");
2195 break;
2196 case SortMode::CustomFormat:
2197 Config.browser_sort_mode = SortMode::NoOp;
2198 Statusbar::print("Do not sort songs");
2199 break;
2200 case SortMode::NoOp:
2201 Config.browser_sort_mode = SortMode::Name;
2202 Statusbar::print("Sort songs by: name");
2204 if (Config.browser_sort_mode != SortMode::NoOp)
2206 size_t sort_offset = myBrowser->inRootDirectory() ? 0 : 1;
2207 std::sort(myBrowser->main().begin()+sort_offset, myBrowser->main().end(),
2208 LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode)
2213 bool ToggleLibraryTagType::canBeRun()
2215 return (myScreen->isActiveWindow(myLibrary->Tags))
2216 || (myLibrary->columns() == 2 && myScreen->isActiveWindow(myLibrary->Albums));
2219 void ToggleLibraryTagType::run()
2221 using Global::wFooter;
2223 char tag_type = 0;
2225 Statusbar::ScopedLock slock;
2226 Statusbar::put() << "Tag type? "
2227 << "[" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtist"
2228 << "/" << "album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtist"
2229 << "/" << NC::Format::Bold << 'y' << NC::Format::NoBold << "ear"
2230 << "/" << NC::Format::Bold << 'g' << NC::Format::NoBold << "enre"
2231 << "/" << NC::Format::Bold << 'c' << NC::Format::NoBold << "omposer"
2232 << "/" << NC::Format::Bold << 'p' << NC::Format::NoBold << "erformer"
2233 << "] ";
2234 tag_type = Statusbar::Helpers::promptReturnOneOf({"a", "A", "y", "g", "c", "p"})[0];
2236 mpd_tag_type new_tagitem = charToTagType(tag_type);
2237 if (new_tagitem != Config.media_lib_primary_tag)
2239 Config.media_lib_primary_tag = new_tagitem;
2240 std::string item_type = tagTypeToString(Config.media_lib_primary_tag);
2241 myLibrary->Tags.setTitle(Config.titles_visibility ? item_type + "s" : "");
2242 myLibrary->Tags.reset();
2243 item_type = boost::locale::to_lower(item_type);
2244 std::string and_mtime = Config.media_library_sort_by_mtime ?
2245 " and mtime" :
2247 if (myLibrary->columns() == 2)
2249 myLibrary->Songs.clear();
2250 myLibrary->Albums.reset();
2251 myLibrary->Albums.clear();
2252 myLibrary->Albums.setTitle(Config.titles_visibility ? "Albums (sorted by " + item_type + and_mtime + ")" : "");
2253 myLibrary->Albums.display();
2255 else
2257 myLibrary->Tags.clear();
2258 myLibrary->Tags.display();
2260 Statusbar::printf("Switched to the list of %1%s", item_type);
2264 bool ToggleMediaLibrarySortMode::canBeRun()
2266 return myScreen == myLibrary;
2269 void ToggleMediaLibrarySortMode::run()
2271 myLibrary->toggleSortMode();
2274 bool FetchLyricsInBackground::canBeRun()
2276 m_hs = dynamic_cast<HasSongs *>(myScreen);
2277 return m_hs != nullptr && m_hs->itemAvailable();
2280 void FetchLyricsInBackground::run()
2282 auto songs = m_hs->getSelectedSongs();
2283 for (const auto &s : songs)
2284 myLyrics->fetchInBackground(s, true);
2285 Statusbar::print("Selected songs queued for lyrics fetching");
2288 bool RefetchLyrics::canBeRun()
2290 return myScreen == myLyrics;
2293 void RefetchLyrics::run()
2295 myLyrics->refetchCurrent();
2298 bool SetSelectedItemsPriority::canBeRun()
2300 if (Mpd.Version() < 17)
2302 Statusbar::print("Priorities are supported in MPD >= 0.17.0");
2303 return false;
2305 return myScreen == myPlaylist && !myPlaylist->main().empty();
2308 void SetSelectedItemsPriority::run()
2310 using Global::wFooter;
2312 unsigned prio;
2314 Statusbar::ScopedLock slock;
2315 Statusbar::put() << "Set priority [0-255]: ";
2316 prio = fromString<unsigned>(wFooter->prompt());
2317 boundsCheck(prio, 0u, 255u);
2319 myPlaylist->setSelectedItemsPriority(prio);
2322 bool ToggleOutput::canBeRun()
2324 #ifdef ENABLE_OUTPUTS
2325 return myScreen == myOutputs;
2326 #else
2327 return false;
2328 #endif // ENABLE_OUTPUTS
2331 void ToggleOutput::run()
2333 #ifdef ENABLE_OUTPUTS
2334 myOutputs->toggleOutput();
2335 #endif // ENABLE_OUTPUTS
2338 bool ToggleVisualizationType::canBeRun()
2340 # ifdef ENABLE_VISUALIZER
2341 return myScreen == myVisualizer;
2342 # else
2343 return false;
2344 # endif // ENABLE_VISUALIZER
2347 void ToggleVisualizationType::run()
2349 # ifdef ENABLE_VISUALIZER
2350 myVisualizer->ToggleVisualizationType();
2351 # endif // ENABLE_VISUALIZER
2354 bool SetVisualizerSampleMultiplier::canBeRun()
2356 # ifdef ENABLE_VISUALIZER
2357 return myScreen == myVisualizer;
2358 # else
2359 return false;
2360 # endif // ENABLE_VISUALIZER
2363 void SetVisualizerSampleMultiplier::run()
2365 # ifdef ENABLE_VISUALIZER
2366 using Global::wFooter;
2368 double multiplier;
2370 Statusbar::ScopedLock slock;
2371 Statusbar::put() << "Set visualizer sample multiplier: ";
2372 multiplier = fromString<double>(wFooter->prompt());
2373 lowerBoundCheck(multiplier, 0.0);
2374 Config.visualizer_sample_multiplier = multiplier;
2376 Statusbar::printf("Visualizer sample multiplier set to %1%", multiplier);
2377 # endif // ENABLE_VISUALIZER
2380 void ShowSongInfo::run()
2382 mySongInfo->switchTo();
2385 bool ShowArtistInfo::canBeRun()
2387 return myScreen == myLastfm
2388 || (myScreen->isActiveWindow(myLibrary->Tags)
2389 && !myLibrary->Tags.empty()
2390 && Config.media_lib_primary_tag == MPD_TAG_ARTIST)
2391 || currentSong(myScreen);
2394 void ShowArtistInfo::run()
2396 if (myScreen == myLastfm)
2398 myLastfm->switchTo();
2399 return;
2402 std::string artist;
2403 if (myScreen->isActiveWindow(myLibrary->Tags))
2405 assert(!myLibrary->Tags.empty());
2406 assert(Config.media_lib_primary_tag == MPD_TAG_ARTIST);
2407 artist = myLibrary->Tags.current()->value().tag();
2409 else
2411 auto s = currentSong(myScreen);
2412 assert(s);
2413 artist = s->getArtist();
2416 if (!artist.empty())
2418 myLastfm->queueJob(new LastFm::ArtistInfo(artist, Config.lastfm_preferred_language));
2419 myLastfm->switchTo();
2423 bool ShowLyrics::canBeRun()
2425 if (myScreen == myLyrics)
2427 m_song = nullptr;
2428 return true;
2430 else
2432 m_song = currentSong(myScreen);
2433 return m_song != nullptr;
2437 void ShowLyrics::run()
2439 if (m_song != nullptr)
2440 myLyrics->fetch(*m_song);
2441 myLyrics->switchTo();
2444 void Quit::run()
2446 ExitMainLoop = true;
2449 void NextScreen::run()
2451 if (Config.screen_switcher_previous)
2453 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2454 tababble->switchToPreviousScreen();
2456 else if (!Config.screen_sequence.empty())
2458 const auto &seq = Config.screen_sequence;
2459 auto screen_type = std::find(seq.begin(), seq.end(), myScreen->type());
2460 if (++screen_type == seq.end())
2461 toScreen(seq.front())->switchTo();
2462 else
2463 toScreen(*screen_type)->switchTo();
2467 void PreviousScreen::run()
2469 if (Config.screen_switcher_previous)
2471 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2472 tababble->switchToPreviousScreen();
2474 else if (!Config.screen_sequence.empty())
2476 const auto &seq = Config.screen_sequence;
2477 auto screen_type = std::find(seq.begin(), seq.end(), myScreen->type());
2478 if (screen_type == seq.begin())
2479 toScreen(seq.back())->switchTo();
2480 else
2481 toScreen(*--screen_type)->switchTo();
2485 bool ShowHelp::canBeRun()
2487 return myScreen != myHelp
2488 # ifdef HAVE_TAGLIB_H
2489 && myScreen != myTinyTagEditor
2490 # endif // HAVE_TAGLIB_H
2494 void ShowHelp::run()
2496 myHelp->switchTo();
2499 bool ShowPlaylist::canBeRun()
2501 return myScreen != myPlaylist
2502 # ifdef HAVE_TAGLIB_H
2503 && myScreen != myTinyTagEditor
2504 # endif // HAVE_TAGLIB_H
2508 void ShowPlaylist::run()
2510 myPlaylist->switchTo();
2513 bool ShowBrowser::canBeRun()
2515 return myScreen != myBrowser
2516 # ifdef HAVE_TAGLIB_H
2517 && myScreen != myTinyTagEditor
2518 # endif // HAVE_TAGLIB_H
2522 void ShowBrowser::run()
2524 myBrowser->switchTo();
2527 bool ChangeBrowseMode::canBeRun()
2529 return myScreen == myBrowser;
2532 void ChangeBrowseMode::run()
2534 myBrowser->changeBrowseMode();
2537 bool ShowSearchEngine::canBeRun()
2539 return myScreen != mySearcher
2540 # ifdef HAVE_TAGLIB_H
2541 && myScreen != myTinyTagEditor
2542 # endif // HAVE_TAGLIB_H
2546 void ShowSearchEngine::run()
2548 mySearcher->switchTo();
2551 bool ResetSearchEngine::canBeRun()
2553 return myScreen == mySearcher;
2556 void ResetSearchEngine::run()
2558 mySearcher->reset();
2561 bool ShowMediaLibrary::canBeRun()
2563 return myScreen != myLibrary
2564 # ifdef HAVE_TAGLIB_H
2565 && myScreen != myTinyTagEditor
2566 # endif // HAVE_TAGLIB_H
2570 void ShowMediaLibrary::run()
2572 myLibrary->switchTo();
2575 bool ToggleMediaLibraryColumnsMode::canBeRun()
2577 return myScreen == myLibrary;
2580 void ToggleMediaLibraryColumnsMode::run()
2582 myLibrary->toggleColumnsMode();
2583 myLibrary->refresh();
2586 bool ShowPlaylistEditor::canBeRun()
2588 return myScreen != myPlaylistEditor
2589 # ifdef HAVE_TAGLIB_H
2590 && myScreen != myTinyTagEditor
2591 # endif // HAVE_TAGLIB_H
2595 void ShowPlaylistEditor::run()
2597 myPlaylistEditor->switchTo();
2600 bool ShowTagEditor::canBeRun()
2602 # ifdef HAVE_TAGLIB_H
2603 return myScreen != myTagEditor
2604 && myScreen != myTinyTagEditor;
2605 # else
2606 return false;
2607 # endif // HAVE_TAGLIB_H
2610 void ShowTagEditor::run()
2612 # ifdef HAVE_TAGLIB_H
2613 if (isMPDMusicDirSet())
2614 myTagEditor->switchTo();
2615 # endif // HAVE_TAGLIB_H
2618 bool ShowOutputs::canBeRun()
2620 # ifdef ENABLE_OUTPUTS
2621 return myScreen != myOutputs
2622 # ifdef HAVE_TAGLIB_H
2623 && myScreen != myTinyTagEditor
2624 # endif // HAVE_TAGLIB_H
2626 # else
2627 return false;
2628 # endif // ENABLE_OUTPUTS
2631 void ShowOutputs::run()
2633 # ifdef ENABLE_OUTPUTS
2634 myOutputs->switchTo();
2635 # endif // ENABLE_OUTPUTS
2638 bool ShowVisualizer::canBeRun()
2640 # ifdef ENABLE_VISUALIZER
2641 return myScreen != myVisualizer
2642 # ifdef HAVE_TAGLIB_H
2643 && myScreen != myTinyTagEditor
2644 # endif // HAVE_TAGLIB_H
2646 # else
2647 return false;
2648 # endif // ENABLE_VISUALIZER
2651 void ShowVisualizer::run()
2653 # ifdef ENABLE_VISUALIZER
2654 myVisualizer->switchTo();
2655 # endif // ENABLE_VISUALIZER
2658 bool ShowClock::canBeRun()
2660 # ifdef ENABLE_CLOCK
2661 return myScreen != myClock
2662 # ifdef HAVE_TAGLIB_H
2663 && myScreen != myTinyTagEditor
2664 # endif // HAVE_TAGLIB_H
2666 # else
2667 return false;
2668 # endif // ENABLE_CLOCK
2671 void ShowClock::run()
2673 # ifdef ENABLE_CLOCK
2674 myClock->switchTo();
2675 # endif // ENABLE_CLOCK
2678 #ifdef HAVE_TAGLIB_H
2679 bool ShowServerInfo::canBeRun()
2681 return myScreen != myTinyTagEditor;
2683 #endif // HAVE_TAGLIB_H
2685 void ShowServerInfo::run()
2687 myServerInfo->switchTo();
2692 namespace {
2694 void populateActions()
2696 AvailableActions.resize(static_cast<size_t>(Actions::Type::_numberOfActions));
2697 auto insert_action = [](Actions::BaseAction *a) {
2698 AvailableActions.at(static_cast<size_t>(a->type())).reset(a);
2700 insert_action(new Actions::Dummy());
2701 insert_action(new Actions::UpdateEnvironment());
2702 insert_action(new Actions::MouseEvent());
2703 insert_action(new Actions::ScrollUp());
2704 insert_action(new Actions::ScrollDown());
2705 insert_action(new Actions::ScrollUpArtist());
2706 insert_action(new Actions::ScrollUpAlbum());
2707 insert_action(new Actions::ScrollDownArtist());
2708 insert_action(new Actions::ScrollDownAlbum());
2709 insert_action(new Actions::PageUp());
2710 insert_action(new Actions::PageDown());
2711 insert_action(new Actions::MoveHome());
2712 insert_action(new Actions::MoveEnd());
2713 insert_action(new Actions::ToggleInterface());
2714 insert_action(new Actions::JumpToParentDirectory());
2715 insert_action(new Actions::RunAction());
2716 insert_action(new Actions::SelectItem());
2717 insert_action(new Actions::SelectRange());
2718 insert_action(new Actions::PreviousColumn());
2719 insert_action(new Actions::NextColumn());
2720 insert_action(new Actions::MasterScreen());
2721 insert_action(new Actions::SlaveScreen());
2722 insert_action(new Actions::VolumeUp());
2723 insert_action(new Actions::VolumeDown());
2724 insert_action(new Actions::AddItemToPlaylist());
2725 insert_action(new Actions::DeletePlaylistItems());
2726 insert_action(new Actions::DeleteStoredPlaylist());
2727 insert_action(new Actions::DeleteBrowserItems());
2728 insert_action(new Actions::ReplaySong());
2729 insert_action(new Actions::PreviousSong());
2730 insert_action(new Actions::NextSong());
2731 insert_action(new Actions::Pause());
2732 insert_action(new Actions::Stop());
2733 insert_action(new Actions::ExecuteCommand());
2734 insert_action(new Actions::SavePlaylist());
2735 insert_action(new Actions::MoveSortOrderUp());
2736 insert_action(new Actions::MoveSortOrderDown());
2737 insert_action(new Actions::MoveSelectedItemsUp());
2738 insert_action(new Actions::MoveSelectedItemsDown());
2739 insert_action(new Actions::MoveSelectedItemsTo());
2740 insert_action(new Actions::Add());
2741 insert_action(new Actions::PlayItem());
2742 insert_action(new Actions::SeekForward());
2743 insert_action(new Actions::SeekBackward());
2744 insert_action(new Actions::ToggleDisplayMode());
2745 insert_action(new Actions::ToggleSeparatorsBetweenAlbums());
2746 insert_action(new Actions::ToggleLyricsUpdateOnSongChange());
2747 insert_action(new Actions::ToggleLyricsFetcher());
2748 insert_action(new Actions::ToggleFetchingLyricsInBackground());
2749 insert_action(new Actions::TogglePlayingSongCentering());
2750 insert_action(new Actions::UpdateDatabase());
2751 insert_action(new Actions::JumpToPlayingSong());
2752 insert_action(new Actions::ToggleRepeat());
2753 insert_action(new Actions::Shuffle());
2754 insert_action(new Actions::ToggleRandom());
2755 insert_action(new Actions::StartSearching());
2756 insert_action(new Actions::SaveTagChanges());
2757 insert_action(new Actions::ToggleSingle());
2758 insert_action(new Actions::ToggleConsume());
2759 insert_action(new Actions::ToggleCrossfade());
2760 insert_action(new Actions::SetCrossfade());
2761 insert_action(new Actions::SetVolume());
2762 insert_action(new Actions::EnterDirectory());
2763 insert_action(new Actions::EditSong());
2764 insert_action(new Actions::EditLibraryTag());
2765 insert_action(new Actions::EditLibraryAlbum());
2766 insert_action(new Actions::EditDirectoryName());
2767 insert_action(new Actions::EditPlaylistName());
2768 insert_action(new Actions::EditLyrics());
2769 insert_action(new Actions::JumpToBrowser());
2770 insert_action(new Actions::JumpToMediaLibrary());
2771 insert_action(new Actions::JumpToPlaylistEditor());
2772 insert_action(new Actions::ToggleScreenLock());
2773 insert_action(new Actions::JumpToTagEditor());
2774 insert_action(new Actions::JumpToPositionInSong());
2775 insert_action(new Actions::ReverseSelection());
2776 insert_action(new Actions::RemoveSelection());
2777 insert_action(new Actions::SelectAlbum());
2778 insert_action(new Actions::SelectFoundItems());
2779 insert_action(new Actions::AddSelectedItems());
2780 insert_action(new Actions::CropMainPlaylist());
2781 insert_action(new Actions::CropPlaylist());
2782 insert_action(new Actions::ClearMainPlaylist());
2783 insert_action(new Actions::ClearPlaylist());
2784 insert_action(new Actions::SortPlaylist());
2785 insert_action(new Actions::ReversePlaylist());
2786 insert_action(new Actions::ApplyFilter());
2787 insert_action(new Actions::Find());
2788 insert_action(new Actions::FindItemForward());
2789 insert_action(new Actions::FindItemBackward());
2790 insert_action(new Actions::NextFoundItem());
2791 insert_action(new Actions::PreviousFoundItem());
2792 insert_action(new Actions::ToggleFindMode());
2793 insert_action(new Actions::ToggleReplayGainMode());
2794 insert_action(new Actions::ToggleAddMode());
2795 insert_action(new Actions::ToggleMouse());
2796 insert_action(new Actions::ToggleBitrateVisibility());
2797 insert_action(new Actions::AddRandomItems());
2798 insert_action(new Actions::ToggleBrowserSortMode());
2799 insert_action(new Actions::ToggleLibraryTagType());
2800 insert_action(new Actions::ToggleMediaLibrarySortMode());
2801 insert_action(new Actions::FetchLyricsInBackground());
2802 insert_action(new Actions::RefetchLyrics());
2803 insert_action(new Actions::SetSelectedItemsPriority());
2804 insert_action(new Actions::ToggleOutput());
2805 insert_action(new Actions::ToggleVisualizationType());
2806 insert_action(new Actions::SetVisualizerSampleMultiplier());
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, 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, SongList *songs, MPD::Song::GetFunction get)
2848 const auto front = songs->beginS();
2849 auto it = songs->currentS();
2850 if (auto *s = it->get<Bit::Song>())
2852 const std::string tag = s->getTags(get);
2853 while (it != front)
2855 --it;
2856 s = it->get<Bit::Song>();
2857 if (s == nullptr || s->getTags(get) != tag)
2858 break;
2860 list->highlight(it-front);
2864 void scrollTagDownRun(NC::List *list, SongList *songs, MPD::Song::GetFunction get)
2866 const auto front = songs->beginS(), back = --songs->endS();
2867 auto it = songs->currentS();
2868 if (auto *s = it->get<Bit::Song>())
2870 const std::string tag = s->getTags(get);
2871 while (it != back)
2873 ++it;
2874 s = it->get<Bit::Song>();
2875 if (s == nullptr || s->getTags(get) != tag)
2876 break;
2878 list->highlight(it-front);
2882 void seek()
2884 using Global::wHeader;
2885 using Global::wFooter;
2886 using Global::Timer;
2887 using Global::SeekingInProgress;
2889 if (!Status::State::totalTime())
2891 Statusbar::print("Unknown item length");
2892 return;
2895 Progressbar::ScopedLock progressbar_lock;
2896 Statusbar::ScopedLock statusbar_lock;
2898 unsigned songpos = Status::State::elapsedTime();
2899 auto t = Timer;
2901 int old_timeout = wFooter->getTimeout();
2902 wFooter->setTimeout(BaseScreen::defaultWindowTimeout);
2904 // Accept single action of a given type or action chain for which all actions
2905 // can be run and one of them is of the given type. This will still not work
2906 // in some contrived cases, but allows for more flexibility than accepting
2907 // single actions only.
2908 auto hasRunnableAction = [](BindingsConfiguration::BindingIteratorPair &bindings, Actions::Type type) {
2909 bool success = false;
2910 for (auto binding = bindings.first; binding != bindings.second; ++binding)
2912 auto &actions = binding->actions();
2913 for (const auto &action : actions)
2915 if (action->canBeRun())
2917 if (action->type() == type)
2918 success = true;
2920 else
2922 success = false;
2923 break;
2926 if (success)
2927 break;
2929 return success;
2932 SeekingInProgress = true;
2933 while (true)
2935 Status::trace();
2937 unsigned howmuch = Config.incremental_seeking
2938 ? (Timer-t).total_seconds()/2+Config.seek_time
2939 : Config.seek_time;
2941 NC::Key::Type input = readKey(*wFooter);
2943 auto k = Bindings.get(input);
2944 if (hasRunnableAction(k, Actions::Type::SeekForward))
2946 if (songpos < Status::State::totalTime())
2947 songpos = std::min(songpos + howmuch, Status::State::totalTime());
2949 else if (hasRunnableAction(k, Actions::Type::SeekBackward))
2951 if (songpos > 0)
2953 if (songpos < howmuch)
2954 songpos = 0;
2955 else
2956 songpos -= howmuch;
2959 else
2960 break;
2962 *wFooter << NC::Format::Bold;
2963 std::string tracklength;
2964 // FIXME: merge this with the code in status.cpp
2965 switch (Config.design)
2967 case Design::Classic:
2968 tracklength = " [";
2969 if (Config.display_remaining_time)
2971 tracklength += "-";
2972 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
2974 else
2975 tracklength += MPD::Song::ShowTime(songpos);
2976 tracklength += "/";
2977 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
2978 tracklength += "]";
2979 *wFooter << NC::XY(wFooter->getWidth()-tracklength.length(), 1) << tracklength;
2980 break;
2981 case Design::Alternative:
2982 if (Config.display_remaining_time)
2984 tracklength = "-";
2985 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
2987 else
2988 tracklength = MPD::Song::ShowTime(songpos);
2989 tracklength += "/";
2990 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
2991 *wHeader << NC::XY(0, 0) << tracklength << " ";
2992 wHeader->refresh();
2993 break;
2995 *wFooter << NC::Format::NoBold;
2996 Progressbar::draw(songpos, Status::State::totalTime());
2997 wFooter->refresh();
2999 SeekingInProgress = false;
3000 Mpd.Seek(Status::State::currentSongPosition(), songpos);
3002 wFooter->setTimeout(old_timeout);
3005 void findItem(const SearchDirection direction)
3007 using Global::wFooter;
3009 Searchable *w = dynamic_cast<Searchable *>(myScreen);
3010 assert(w != nullptr);
3011 assert(w->allowsSearching());
3013 std::string constraint = w->searchConstraint();
3016 Statusbar::ScopedLock slock;
3017 NC::Window::ScopedPromptHook prompt_hook(
3018 *wFooter,
3019 Statusbar::Helpers::FindImmediately(w, direction));
3020 Statusbar::put() << (boost::format("Find %1%: ") % direction).str();
3021 constraint = wFooter->prompt(constraint);
3023 catch (NC::PromptAborted &)
3025 w->setSearchConstraint(constraint);
3026 w->search(direction, Config.wrapped_search, false);
3027 throw;
3030 if (constraint.empty())
3032 Statusbar::printf("Constraint unset");
3033 w->clearSearchConstraint();
3035 else
3036 Statusbar::printf("Using constraint \"%1%\"", constraint);
3039 void listsChangeFinisher()
3041 if (myScreen == myLibrary
3042 || myScreen == myPlaylistEditor
3043 # ifdef HAVE_TAGLIB_H
3044 || myScreen == myTagEditor
3045 # endif // HAVE_TAGLIB_H
3048 if (myScreen->activeWindow() == &myLibrary->Tags)
3050 myLibrary->Albums.clear();
3051 myLibrary->Albums.refresh();
3052 myLibrary->Songs.clear();
3053 myLibrary->Songs.refresh();
3054 myLibrary->updateTimer();
3056 else if (myScreen->activeWindow() == &myLibrary->Albums)
3058 myLibrary->Songs.clear();
3059 myLibrary->Songs.refresh();
3060 myLibrary->updateTimer();
3062 else if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
3064 myPlaylistEditor->Content.clear();
3065 myPlaylistEditor->Content.refresh();
3066 myPlaylistEditor->updateTimer();
3068 # ifdef HAVE_TAGLIB_H
3069 else if (myScreen->activeWindow() == myTagEditor->Dirs)
3071 myTagEditor->Tags->clear();
3073 # endif // HAVE_TAGLIB_H