Implement filtering in playlist editor
[ncmpcpp.git] / src / actions.cpp
blob1e0ca3d0cdbfdeb2050dc743d9d5603f54d39ff5
1 /***************************************************************************
2 * Copyright (C) 2008-2014 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;
139 # ifdef HAVE_CURL_CURL_H
140 myLastfm = new Lastfm;
141 # endif // HAVE_CURL_CURL_H
143 # ifdef HAVE_TAGLIB_H
144 myTinyTagEditor = new TinyTagEditor;
145 myTagEditor = new TagEditor;
146 # endif // HAVE_TAGLIB_H
148 # ifdef ENABLE_VISUALIZER
149 myVisualizer = new Visualizer;
150 # endif // ENABLE_VISUALIZER
152 # ifdef ENABLE_OUTPUTS
153 myOutputs = new Outputs;
154 # endif // ENABLE_OUTPUTS
156 # ifdef ENABLE_CLOCK
157 myClock = new Clock;
158 # endif // ENABLE_CLOCK
162 void setResizeFlags()
164 myHelp->hasToBeResized = 1;
165 myPlaylist->hasToBeResized = 1;
166 myBrowser->hasToBeResized = 1;
167 mySearcher->hasToBeResized = 1;
168 myLibrary->hasToBeResized = 1;
169 myPlaylistEditor->hasToBeResized = 1;
170 myLyrics->hasToBeResized = 1;
171 mySelectedItemsAdder->hasToBeResized = 1;
172 mySongInfo->hasToBeResized = 1;
173 myServerInfo->hasToBeResized = 1;
174 mySortPlaylistDialog->hasToBeResized = 1;
176 # ifdef HAVE_CURL_CURL_H
177 myLastfm->hasToBeResized = 1;
178 # endif // HAVE_CURL_CURL_H
180 # ifdef HAVE_TAGLIB_H
181 myTinyTagEditor->hasToBeResized = 1;
182 myTagEditor->hasToBeResized = 1;
183 # endif // HAVE_TAGLIB_H
185 # ifdef ENABLE_VISUALIZER
186 myVisualizer->hasToBeResized = 1;
187 # endif // ENABLE_VISUALIZER
189 # ifdef ENABLE_OUTPUTS
190 myOutputs->hasToBeResized = 1;
191 # endif // ENABLE_OUTPUTS
193 # ifdef ENABLE_CLOCK
194 myClock->hasToBeResized = 1;
195 # endif // ENABLE_CLOCK
198 void resizeScreen(bool reload_main_window)
200 using Global::MainHeight;
201 using Global::wHeader;
202 using Global::wFooter;
204 // update internal screen dimensions
205 if (reload_main_window)
207 rl_resize_terminal();
208 endwin();
209 refresh();
212 MainHeight = LINES-(Config.design == Design::Alternative ? 7 : 4);
214 validateScreenSize();
216 if (!Config.header_visibility)
217 MainHeight += 2;
218 if (!Config.statusbar_visibility)
219 ++MainHeight;
221 setResizeFlags();
223 applyToVisibleWindows(&BaseScreen::resize);
225 if (Config.header_visibility || Config.design == Design::Alternative)
226 wHeader->resize(COLS, HeaderHeight);
228 FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1);
229 wFooter->moveTo(0, FooterStartY);
230 wFooter->resize(COLS, Config.statusbar_visibility ? 2 : 1);
232 applyToVisibleWindows(&BaseScreen::refresh);
234 Status::Changes::elapsedTime(false);
235 Status::Changes::playerState();
236 // Note: routines for drawing separator if alternative user
237 // interface is active and header is hidden are placed in
238 // NcmpcppStatusChanges.StatusFlags
239 Status::Changes::flags();
240 drawHeader();
241 wFooter->refresh();
242 refresh();
245 void setWindowsDimensions()
247 using Global::MainStartY;
248 using Global::MainHeight;
250 MainStartY = Config.design == Design::Alternative ? 5 : 2;
251 MainHeight = LINES-(Config.design == Design::Alternative ? 7 : 4);
253 if (!Config.header_visibility)
255 MainStartY -= 2;
256 MainHeight += 2;
258 if (!Config.statusbar_visibility)
259 ++MainHeight;
261 HeaderHeight = Config.design == Design::Alternative ? (Config.header_visibility ? 5 : 3) : 2;
262 FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1);
263 FooterHeight = Config.statusbar_visibility ? 2 : 1;
266 void confirmAction(const boost::format &description)
268 Statusbar::ScopedLock slock;
269 Statusbar::put() << description.str()
270 << " [" << NC::Format::Bold << 'y' << NC::Format::NoBold
271 << '/' << NC::Format::Bold << 'n' << NC::Format::NoBold
272 << "] ";
273 auto answer = Statusbar::Helpers::promptReturnOneOf({"y", "n"});
274 if (answer == "n")
275 throw NC::PromptAborted(std::move(answer));
278 bool isMPDMusicDirSet()
280 if (Config.mpd_music_dir.empty())
282 Statusbar::print("Proper mpd_music_dir variable has to be set in configuration file");
283 return false;
285 return true;
288 BaseAction &get(Actions::Type at)
290 if (AvailableActions[1] == nullptr)
291 populateActions();
292 BaseAction *action = AvailableActions[static_cast<size_t>(at)];
293 // action should be always present if action type in queried
294 assert(action != nullptr);
295 return *action;
298 BaseAction *get(const std::string &name)
300 BaseAction *result = 0;
301 if (AvailableActions[1] == nullptr)
302 populateActions();
303 for (auto it = AvailableActions.begin(); it != AvailableActions.end(); ++it)
305 if (*it != nullptr && (*it)->name() == name)
307 result = *it;
308 break;
311 return result;
314 UpdateEnvironment::UpdateEnvironment()
315 : BaseAction(Type::UpdateEnvironment, "update_environment")
316 , m_past(boost::posix_time::from_time_t(0))
319 void UpdateEnvironment::run(bool update_timer, bool refresh_window)
321 using Global::Timer;
323 // update timer, status if necessary etc.
324 Status::trace(update_timer, true);
326 // header stuff
327 if ((myScreen == myPlaylist || myScreen == myBrowser || myScreen == myLyrics)
328 && (Timer - m_past > boost::posix_time::milliseconds(500))
331 drawHeader();
332 m_past = Timer;
335 if (refresh_window)
336 myScreen->refreshWindow();
339 void UpdateEnvironment::run()
341 run(true, true);
344 bool MouseEvent::canBeRun()
346 return Config.mouse_support;
349 void MouseEvent::run()
351 using Global::VolumeState;
352 using Global::wFooter;
354 m_old_mouse_event = m_mouse_event;
355 m_mouse_event = wFooter->getMouseEvent();
357 //Statusbar::printf("(%1%, %2%, %3%)", m_mouse_event.bstate, m_mouse_event.x, m_mouse_event.y);
359 if (m_mouse_event.bstate & BUTTON1_PRESSED
360 && m_mouse_event.y == LINES-(Config.statusbar_visibility ? 2 : 1)
361 ) // progressbar
363 if (Status::State::player() == MPD::psStop)
364 return;
365 Mpd.Seek(Status::State::currentSongPosition(),
366 Status::State::totalTime()*m_mouse_event.x/double(COLS));
368 else if (m_mouse_event.bstate & BUTTON1_PRESSED
369 && (Config.statusbar_visibility || Config.design == Design::Alternative)
370 && Status::State::player() != MPD::psStop
371 && m_mouse_event.y == (Config.design == Design::Alternative ? 1 : LINES-1)
372 && m_mouse_event.x < 9
373 ) // playing/paused
375 Mpd.Toggle();
377 else if ((m_mouse_event.bstate & BUTTON5_PRESSED || m_mouse_event.bstate & BUTTON4_PRESSED)
378 && (Config.header_visibility || Config.design == Design::Alternative)
379 && m_mouse_event.y == 0 && size_t(m_mouse_event.x) > COLS-VolumeState.length()
380 ) // volume
382 if (m_mouse_event.bstate & BUTTON5_PRESSED)
383 get(Type::VolumeDown).execute();
384 else
385 get(Type::VolumeUp).execute();
387 else if (m_mouse_event.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED | BUTTON4_PRESSED | BUTTON5_PRESSED))
388 myScreen->mouseButtonPressed(m_mouse_event);
391 void ScrollUp::run()
393 myScreen->scroll(NC::Scroll::Up);
394 listsChangeFinisher();
397 void ScrollDown::run()
399 myScreen->scroll(NC::Scroll::Down);
400 listsChangeFinisher();
403 bool ScrollUpArtist::canBeRun()
405 return scrollTagCanBeRun(m_list, m_songs);
408 void ScrollUpArtist::run()
410 scrollTagUpRun(m_list, m_songs, &MPD::Song::getArtist);
413 bool ScrollUpAlbum::canBeRun()
415 return scrollTagCanBeRun(m_list, m_songs);
418 void ScrollUpAlbum::run()
420 scrollTagUpRun(m_list, m_songs, &MPD::Song::getAlbum);
423 bool ScrollDownArtist::canBeRun()
425 return scrollTagCanBeRun(m_list, m_songs);
428 void ScrollDownArtist::run()
430 scrollTagDownRun(m_list, m_songs, &MPD::Song::getArtist);
433 bool ScrollDownAlbum::canBeRun()
435 return scrollTagCanBeRun(m_list, m_songs);
438 void ScrollDownAlbum::run()
440 scrollTagDownRun(m_list, m_songs, &MPD::Song::getAlbum);
443 void PageUp::run()
445 myScreen->scroll(NC::Scroll::PageUp);
446 listsChangeFinisher();
449 void PageDown::run()
451 myScreen->scroll(NC::Scroll::PageDown);
452 listsChangeFinisher();
455 void MoveHome::run()
457 myScreen->scroll(NC::Scroll::Home);
458 listsChangeFinisher();
461 void MoveEnd::run()
463 myScreen->scroll(NC::Scroll::End);
464 listsChangeFinisher();
467 void ToggleInterface::run()
469 switch (Config.design)
471 case Design::Classic:
472 Config.design = Design::Alternative;
473 Config.statusbar_visibility = false;
474 break;
475 case Design::Alternative:
476 Config.design = Design::Classic;
477 Config.statusbar_visibility = OriginalStatusbarVisibility;
478 break;
480 setWindowsDimensions();
481 resizeScreen(false);
482 // unlock progressbar
483 Progressbar::ScopedLock();
484 Status::Changes::mixer();
485 Status::Changes::elapsedTime(false);
486 Statusbar::printf("User interface: %1%", Config.design);
489 bool JumpToParentDirectory::canBeRun()
491 return (myScreen == myBrowser)
492 # ifdef HAVE_TAGLIB_H
493 || (myScreen->activeWindow() == myTagEditor->Dirs)
494 # endif // HAVE_TAGLIB_H
498 void JumpToParentDirectory::run()
500 if (myScreen == myBrowser)
502 if (!myBrowser->inRootDirectory())
504 myBrowser->main().reset();
505 myBrowser->enterDirectory();
508 # ifdef HAVE_TAGLIB_H
509 else if (myScreen == myTagEditor)
511 if (myTagEditor->CurrentDir() != "/")
513 myTagEditor->Dirs->reset();
514 myTagEditor->enterDirectory();
517 # endif // HAVE_TAGLIB_H
520 bool RunAction::canBeRun()
522 m_ha = dynamic_cast<HasActions *>(myScreen);
523 return m_ha != nullptr
524 && m_ha->actionRunnable();
527 void RunAction::run()
529 m_ha->runAction();
532 bool PreviousColumn::canBeRun()
534 m_hc = dynamic_cast<HasColumns *>(myScreen);
535 return m_hc != nullptr
536 && m_hc->previousColumnAvailable();
539 void PreviousColumn::run()
541 m_hc->previousColumn();
544 bool NextColumn::canBeRun()
546 m_hc = dynamic_cast<HasColumns *>(myScreen);
547 return m_hc != nullptr
548 && m_hc->nextColumnAvailable();
551 void NextColumn::run()
553 m_hc->nextColumn();
556 bool MasterScreen::canBeRun()
558 using Global::myLockedScreen;
559 using Global::myInactiveScreen;
561 return myLockedScreen
562 && myInactiveScreen
563 && myLockedScreen != myScreen
564 && myScreen->isMergable();
567 void MasterScreen::run()
569 using Global::myInactiveScreen;
570 using Global::myLockedScreen;
572 myInactiveScreen = myScreen;
573 myScreen = myLockedScreen;
574 drawHeader();
577 bool SlaveScreen::canBeRun()
579 using Global::myLockedScreen;
580 using Global::myInactiveScreen;
582 return myLockedScreen
583 && myInactiveScreen
584 && myLockedScreen == myScreen
585 && myScreen->isMergable();
588 void SlaveScreen::run()
590 using Global::myInactiveScreen;
591 using Global::myLockedScreen;
593 myScreen = myInactiveScreen;
594 myInactiveScreen = myLockedScreen;
595 drawHeader();
598 void VolumeUp::run()
600 int volume = std::min(Status::State::volume()+Config.volume_change_step, 100u);
601 Mpd.SetVolume(volume);
604 void VolumeDown::run()
606 int volume = std::max(int(Status::State::volume()-Config.volume_change_step), 0);
607 Mpd.SetVolume(volume);
610 bool AddItemToPlaylist::canBeRun()
612 m_hs = dynamic_cast<HasSongs *>(myScreen);
613 return m_hs != nullptr && m_hs->itemAvailable();
616 void AddItemToPlaylist::run()
618 bool success = m_hs->addItemToPlaylist(false);
619 if (success)
621 myScreen->scroll(NC::Scroll::Down);
622 listsChangeFinisher();
626 bool PlayItem::canBeRun()
628 m_hs = dynamic_cast<HasSongs *>(myScreen);
629 return m_hs != nullptr && m_hs->itemAvailable();
632 void PlayItem::run()
634 bool success = m_hs->addItemToPlaylist(true);
635 if (success)
636 listsChangeFinisher();
639 bool DeletePlaylistItems::canBeRun()
641 return (myScreen == myPlaylist && !myPlaylist->main().empty())
642 || (myScreen->isActiveWindow(myPlaylistEditor->Content) && !myPlaylistEditor->Content.empty());
645 void DeletePlaylistItems::run()
647 if (myScreen == myPlaylist)
649 Statusbar::print("Deleting items...");
650 auto delete_fun = std::bind(&MPD::Connection::Delete, ph::_1, ph::_2);
651 deleteSelectedSongs(myPlaylist->main(), delete_fun);
652 Statusbar::print("Item(s) deleted");
654 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
656 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
657 auto delete_fun = std::bind(&MPD::Connection::PlaylistDelete, ph::_1, playlist, ph::_2);
658 Statusbar::print("Deleting items...");
659 deleteSelectedSongs(myPlaylistEditor->Content, delete_fun);
660 Statusbar::print("Item(s) deleted");
664 bool DeleteBrowserItems::canBeRun()
666 auto check_if_deletion_allowed = []() {
667 if (Config.allow_for_physical_item_deletion)
668 return true;
669 else
671 Statusbar::print("Flag \"allow_for_physical_item_deletion\" needs to be enabled in configuration file");
672 return false;
675 return myScreen == myBrowser
676 && !myBrowser->main().empty()
677 && isMPDMusicDirSet()
678 && check_if_deletion_allowed();
681 void DeleteBrowserItems::run()
683 auto get_name = [](const MPD::Item &item) -> std::string {
684 std::string iname;
685 switch (item.type())
687 case MPD::Item::Type::Directory:
688 iname = getBasename(item.directory().path());
689 break;
690 case MPD::Item::Type::Song:
691 iname = item.song().getName();
692 break;
693 case MPD::Item::Type::Playlist:
694 iname = getBasename(item.playlist().path());
695 break;
697 return iname;
700 boost::format question;
701 if (hasSelected(myBrowser->main().begin(), myBrowser->main().end()))
702 question = boost::format("Delete selected items?");
703 else
705 const auto &item = myBrowser->main().current()->value();
706 // parent directories are not accepted (and they
707 // can't be selected, so in other cases it's fine).
708 if (myBrowser->isParentDirectory(item))
709 return;
710 const char msg[] = "Delete \"%1%\"?";
711 question = boost::format(msg) % wideShorten(
712 get_name(item), COLS-const_strlen(msg)-5
715 confirmAction(question);
717 auto items = getSelectedOrCurrent(
718 myBrowser->main().begin(),
719 myBrowser->main().end(),
720 myBrowser->main().current()
722 for (const auto &item : items)
724 myBrowser->remove(item->value());
725 const char msg[] = "Deleted %1% \"%2%\"";
726 Statusbar::printf(msg,
727 itemTypeToString(item->value().type()),
728 wideShorten(get_name(item->value()), COLS-const_strlen(msg))
732 if (!myBrowser->isLocal())
733 Mpd.UpdateDirectory(myBrowser->currentDirectory());
734 myBrowser->requestUpdate();
737 bool DeleteStoredPlaylist::canBeRun()
739 return myScreen->isActiveWindow(myPlaylistEditor->Playlists);
742 void DeleteStoredPlaylist::run()
744 if (myPlaylistEditor->Playlists.empty())
745 return;
746 boost::format question;
747 if (hasSelected(myPlaylistEditor->Playlists.begin(), myPlaylistEditor->Playlists.end()))
748 question = boost::format("Delete selected playlists?");
749 else
750 question = boost::format("Delete playlist \"%1%\"?")
751 % wideShorten(myPlaylistEditor->Playlists.current()->value().path(), COLS-question.size()-10);
752 confirmAction(question);
753 auto list = getSelectedOrCurrent(
754 myPlaylistEditor->Playlists.begin(),
755 myPlaylistEditor->Playlists.end(),
756 myPlaylistEditor->Playlists.current()
758 for (const auto &item : list)
759 Mpd.DeletePlaylist(item->value().path());
760 Statusbar::printf("%1% deleted", list.size() == 1 ? "Playlist" : "Playlists");
761 // force playlists update. this happens automatically, but only after call
762 // to Key::read, therefore when we call PlaylistEditor::Update, it won't
763 // yet see it, so let's point that it needs to update it.
764 myPlaylistEditor->requestPlaylistsUpdate();
767 void ReplaySong::run()
769 if (Status::State::player() != MPD::psStop)
770 Mpd.Seek(Status::State::currentSongPosition(), 0);
773 void PreviousSong::run()
775 Mpd.Prev();
778 void NextSong::run()
780 Mpd.Next();
783 void Pause::run()
785 Mpd.Toggle();
788 void SavePlaylist::run()
790 using Global::wFooter;
792 std::string playlist_name;
794 Statusbar::ScopedLock slock;
795 Statusbar::put() << "Save playlist as: ";
796 playlist_name = wFooter->prompt();
800 Mpd.SavePlaylist(playlist_name);
801 Statusbar::printf("Playlist saved as \"%1%\"", playlist_name);
803 catch (MPD::ServerError &e)
805 if (e.code() == MPD_SERVER_ERROR_EXIST)
807 confirmAction(
808 boost::format("Playlist \"%1%\" already exists, overwrite?") % playlist_name
810 Mpd.DeletePlaylist(playlist_name);
811 Mpd.SavePlaylist(playlist_name);
812 Statusbar::print("Playlist overwritten");
814 else
815 throw;
819 void Stop::run()
821 Mpd.Stop();
824 void ExecuteCommand::run()
826 using Global::wFooter;
828 std::string cmd_name;
830 Statusbar::ScopedLock slock;
831 NC::Window::ScopedPromptHook helper(*wFooter,
832 Statusbar::Helpers::TryExecuteImmediateCommand()
834 Statusbar::put() << NC::Format::Bold << ":" << NC::Format::NoBold;
835 cmd_name = wFooter->prompt();
838 auto cmd = Bindings.findCommand(cmd_name);
839 if (cmd)
841 Statusbar::printf(1, "Executing %1%...", cmd_name);
842 bool res = cmd->binding().execute();
843 Statusbar::printf("Execution of command \"%1%\" %2%.",
844 cmd_name, res ? "successful" : "unsuccessful"
847 else
848 Statusbar::printf("No command named \"%1%\"", cmd_name);
851 bool MoveSortOrderUp::canBeRun()
853 return myScreen == mySortPlaylistDialog;
856 void MoveSortOrderUp::run()
858 mySortPlaylistDialog->moveSortOrderUp();
861 bool MoveSortOrderDown::canBeRun()
863 return myScreen == mySortPlaylistDialog;
866 void MoveSortOrderDown::run()
868 mySortPlaylistDialog->moveSortOrderDown();
871 bool MoveSelectedItemsUp::canBeRun()
873 return ((myScreen == myPlaylist
874 && !myPlaylist->main().empty())
875 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
876 && !myPlaylistEditor->Content.empty()));
879 void MoveSelectedItemsUp::run()
881 const char *filteredMsg = "Moving items up is disabled in filtered playlist";
882 if (myScreen == myPlaylist)
884 if (myPlaylist->main().isFiltered())
885 Statusbar::print(filteredMsg);
886 else
887 moveSelectedItemsUp(
888 myPlaylist->main(),
889 std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
891 else if (myScreen == myPlaylistEditor)
893 if (myPlaylistEditor->Content.isFiltered())
894 Statusbar::print(filteredMsg);
895 else
897 auto playlist = myPlaylistEditor->Playlists.current()->value().path();
898 moveSelectedItemsUp(
899 myPlaylistEditor->Content,
900 std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3));
905 bool MoveSelectedItemsDown::canBeRun()
907 return ((myScreen == myPlaylist
908 && !myPlaylist->main().empty())
909 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
910 && !myPlaylistEditor->Content.empty()));
913 void MoveSelectedItemsDown::run()
915 const char *filteredMsg = "Moving items down is disabled in filtered playlist";
916 if (myScreen == myPlaylist)
918 if (myPlaylist->main().isFiltered())
919 Statusbar::print(filteredMsg);
920 else
921 moveSelectedItemsDown(
922 myPlaylist->main(),
923 std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
925 else if (myScreen == myPlaylistEditor)
927 if (myPlaylistEditor->Content.isFiltered())
928 Statusbar::print(filteredMsg);
929 else
931 auto playlist = myPlaylistEditor->Playlists.current()->value().path();
932 moveSelectedItemsDown(
933 myPlaylistEditor->Content,
934 std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3));
939 bool MoveSelectedItemsTo::canBeRun()
941 return myScreen == myPlaylist
942 || myScreen->isActiveWindow(myPlaylistEditor->Content);
945 void MoveSelectedItemsTo::run()
947 if (myScreen == myPlaylist)
949 if (!myPlaylist->main().empty())
950 moveSelectedItemsTo(myPlaylist->main(), std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
952 else
954 assert(!myPlaylistEditor->Playlists.empty());
955 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
956 auto move_fun = std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3);
957 moveSelectedItemsTo(myPlaylistEditor->Content, move_fun);
961 bool Add::canBeRun()
963 return myScreen != myPlaylistEditor
964 || !myPlaylistEditor->Playlists.empty();
967 void Add::run()
969 using Global::wFooter;
971 std::string path;
973 Statusbar::ScopedLock slock;
974 Statusbar::put() << (myScreen == myPlaylistEditor ? "Add to playlist: " : "Add: ");
975 path = wFooter->prompt();
978 // confirm when one wants to add the whole database
979 if (path.empty())
980 confirmAction("Are you sure you want to add the whole database?");
982 Statusbar::put() << "Adding...";
983 wFooter->refresh();
984 if (myScreen == myPlaylistEditor)
985 Mpd.AddToPlaylist(myPlaylistEditor->Playlists.current()->value().path(), path);
986 else
990 Mpd.Add(path);
992 catch (MPD::ServerError &err)
994 // If a path is not a file or directory, assume it is a playlist.
995 if (err.code() == MPD_SERVER_ERROR_NO_EXIST)
996 Mpd.LoadPlaylist(path);
997 else
998 throw;
1003 bool SeekForward::canBeRun()
1005 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1008 void SeekForward::run()
1010 seek();
1013 bool SeekBackward::canBeRun()
1015 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1018 void SeekBackward::run()
1020 seek();
1023 bool ToggleDisplayMode::canBeRun()
1025 return myScreen == myPlaylist
1026 || myScreen == myBrowser
1027 || myScreen == mySearcher
1028 || myScreen->isActiveWindow(myPlaylistEditor->Content);
1031 void ToggleDisplayMode::run()
1033 if (myScreen == myPlaylist)
1035 switch (Config.playlist_display_mode)
1037 case DisplayMode::Classic:
1038 Config.playlist_display_mode = DisplayMode::Columns;
1039 myPlaylist->main().setItemDisplayer(std::bind(
1040 Display::SongsInColumns, ph::_1, std::cref(myPlaylist->main())
1042 if (Config.titles_visibility)
1043 myPlaylist->main().setTitle(Display::Columns(myPlaylist->main().getWidth()));
1044 else
1045 myPlaylist->main().setTitle("");
1046 break;
1047 case DisplayMode::Columns:
1048 Config.playlist_display_mode = DisplayMode::Classic;
1049 myPlaylist->main().setItemDisplayer(std::bind(
1050 Display::Songs, ph::_1, std::cref(myPlaylist->main()), std::cref(Config.song_list_format)
1052 myPlaylist->main().setTitle("");
1054 Statusbar::printf("Playlist display mode: %1%", Config.playlist_display_mode);
1056 else if (myScreen == myBrowser)
1058 switch (Config.browser_display_mode)
1060 case DisplayMode::Classic:
1061 Config.browser_display_mode = DisplayMode::Columns;
1062 if (Config.titles_visibility)
1063 myBrowser->main().setTitle(Display::Columns(myBrowser->main().getWidth()));
1064 else
1065 myBrowser->main().setTitle("");
1066 break;
1067 case DisplayMode::Columns:
1068 Config.browser_display_mode = DisplayMode::Classic;
1069 myBrowser->main().setTitle("");
1070 break;
1072 Statusbar::printf("Browser display mode: %1%", Config.browser_display_mode);
1074 else if (myScreen == mySearcher)
1076 switch (Config.search_engine_display_mode)
1078 case DisplayMode::Classic:
1079 Config.search_engine_display_mode = DisplayMode::Columns;
1080 break;
1081 case DisplayMode::Columns:
1082 Config.search_engine_display_mode = DisplayMode::Classic;
1083 break;
1085 Statusbar::printf("Search engine display mode: %1%", Config.search_engine_display_mode);
1086 if (mySearcher->main().size() > SearchEngine::StaticOptions)
1087 mySearcher->main().setTitle(
1088 Config.search_engine_display_mode == DisplayMode::Columns
1089 && Config.titles_visibility
1090 ? Display::Columns(mySearcher->main().getWidth())
1091 : ""
1094 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
1096 switch (Config.playlist_editor_display_mode)
1098 case DisplayMode::Classic:
1099 Config.playlist_editor_display_mode = DisplayMode::Columns;
1100 myPlaylistEditor->Content.setItemDisplayer(std::bind(
1101 Display::SongsInColumns, ph::_1, std::cref(myPlaylistEditor->Content)
1103 break;
1104 case DisplayMode::Columns:
1105 Config.playlist_editor_display_mode = DisplayMode::Classic;
1106 myPlaylistEditor->Content.setItemDisplayer(std::bind(
1107 Display::Songs, ph::_1, std::cref(myPlaylistEditor->Content), std::cref(Config.song_list_format)
1109 break;
1111 Statusbar::printf("Playlist editor display mode: %1%", Config.playlist_editor_display_mode);
1115 bool ToggleSeparatorsBetweenAlbums::canBeRun()
1117 return true;
1120 void ToggleSeparatorsBetweenAlbums::run()
1122 Config.playlist_separate_albums = !Config.playlist_separate_albums;
1123 Statusbar::printf("Separators between albums: %1%",
1124 Config.playlist_separate_albums ? "on" : "off"
1128 bool ToggleLyricsUpdateOnSongChange::canBeRun()
1130 return myScreen == myLyrics;
1133 void ToggleLyricsUpdateOnSongChange::run()
1135 Config.now_playing_lyrics = !Config.now_playing_lyrics;
1136 Statusbar::printf("Update lyrics if song changes: %1%",
1137 Config.now_playing_lyrics ? "on" : "off"
1141 #ifndef HAVE_CURL_CURL_H
1142 bool ToggleLyricsFetcher::canBeRun()
1144 return false;
1146 #endif // NOT HAVE_CURL_CURL_H
1148 void ToggleLyricsFetcher::run()
1150 # ifdef HAVE_CURL_CURL_H
1151 myLyrics->ToggleFetcher();
1152 # endif // HAVE_CURL_CURL_H
1155 #ifndef HAVE_CURL_CURL_H
1156 bool ToggleFetchingLyricsInBackground::canBeRun()
1158 return false;
1160 #endif // NOT HAVE_CURL_CURL_H
1162 void ToggleFetchingLyricsInBackground::run()
1164 # ifdef HAVE_CURL_CURL_H
1165 Config.fetch_lyrics_in_background = !Config.fetch_lyrics_in_background;
1166 Statusbar::printf("Fetching lyrics for playing songs in background: %1%",
1167 Config.fetch_lyrics_in_background ? "on" : "off"
1169 # endif // HAVE_CURL_CURL_H
1172 void TogglePlayingSongCentering::run()
1174 Config.autocenter_mode = !Config.autocenter_mode;
1175 Statusbar::printf("Centering playing song: %1%",
1176 Config.autocenter_mode ? "on" : "off"
1178 if (Config.autocenter_mode)
1180 auto s = myPlaylist->nowPlayingSong();
1181 if (!s.empty())
1182 myPlaylist->locateSong(s);
1186 void UpdateDatabase::run()
1188 if (myScreen == myBrowser)
1189 Mpd.UpdateDirectory(myBrowser->currentDirectory());
1190 # ifdef HAVE_TAGLIB_H
1191 else if (myScreen == myTagEditor)
1192 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1193 # endif // HAVE_TAGLIB_H
1194 else
1195 Mpd.UpdateDirectory("/");
1198 bool JumpToPlayingSong::canBeRun()
1200 return myScreen == myPlaylist
1201 || myScreen == myBrowser
1202 || myScreen == myLibrary;
1205 void JumpToPlayingSong::run()
1207 auto s = myPlaylist->nowPlayingSong();
1208 if (s.empty())
1209 return;
1210 if (myScreen == myPlaylist)
1212 myPlaylist->locateSong(s);
1214 else if (myScreen == myBrowser)
1216 myBrowser->locateSong(s);
1218 else if (myScreen == myLibrary)
1220 myLibrary->locateSong(s);
1224 void ToggleRepeat::run()
1226 Mpd.SetRepeat(!Status::State::repeat());
1229 bool Shuffle::canBeRun()
1231 if (myScreen != myPlaylist)
1232 return false;
1233 m_begin = myPlaylist->main().begin();
1234 m_end = myPlaylist->main().end();
1235 return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end);
1238 void Shuffle::run()
1240 if (Config.ask_before_shuffling_playlists)
1241 confirmAction("Do you really want to shuffle selected range?");
1242 auto begin = myPlaylist->main().begin();
1243 Mpd.ShuffleRange(m_begin-begin, m_end-begin);
1244 Statusbar::print("Range shuffled");
1247 void ToggleRandom::run()
1249 Mpd.SetRandom(!Status::State::random());
1252 bool StartSearching::canBeRun()
1254 return myScreen == mySearcher && !mySearcher->main()[0].isInactive();
1257 void StartSearching::run()
1259 mySearcher->main().highlight(SearchEngine::SearchButton);
1260 mySearcher->main().setHighlighting(0);
1261 mySearcher->main().refresh();
1262 mySearcher->main().setHighlighting(1);
1263 mySearcher->runAction();
1266 bool SaveTagChanges::canBeRun()
1268 # ifdef HAVE_TAGLIB_H
1269 return myScreen == myTinyTagEditor
1270 || myScreen->activeWindow() == myTagEditor->TagTypes;
1271 # else
1272 return false;
1273 # endif // HAVE_TAGLIB_H
1276 void SaveTagChanges::run()
1278 # ifdef HAVE_TAGLIB_H
1279 if (myScreen == myTinyTagEditor)
1281 myTinyTagEditor->main().highlight(myTinyTagEditor->main().size()-2); // Save
1282 myTinyTagEditor->runAction();
1284 else if (myScreen->activeWindow() == myTagEditor->TagTypes)
1286 myTagEditor->TagTypes->highlight(myTagEditor->TagTypes->size()-1); // Save
1287 myTagEditor->runAction();
1289 # endif // HAVE_TAGLIB_H
1292 void ToggleSingle::run()
1294 Mpd.SetSingle(!Status::State::single());
1297 void ToggleConsume::run()
1299 Mpd.SetConsume(!Status::State::consume());
1302 void ToggleCrossfade::run()
1304 Mpd.SetCrossfade(Status::State::crossfade() ? 0 : Config.crossfade_time);
1307 void SetCrossfade::run()
1309 using Global::wFooter;
1311 Statusbar::ScopedLock slock;
1312 Statusbar::put() << "Set crossfade to: ";
1313 auto crossfade = fromString<unsigned>(wFooter->prompt());
1314 lowerBoundCheck(crossfade, 0u);
1315 Config.crossfade_time = crossfade;
1316 Mpd.SetCrossfade(crossfade);
1319 void SetVolume::run()
1321 using Global::wFooter;
1323 unsigned volume;
1325 Statusbar::ScopedLock slock;
1326 Statusbar::put() << "Set volume to: ";
1327 volume = fromString<unsigned>(wFooter->prompt());
1328 boundsCheck(volume, 0u, 100u);
1329 Mpd.SetVolume(volume);
1331 Statusbar::printf("Volume set to %1%%%", volume);
1334 bool EnterDirectory::canBeRun()
1336 bool result = false;
1337 if (myScreen == myBrowser && !myBrowser->main().empty())
1339 result = myBrowser->main().current()->value().type()
1340 == MPD::Item::Type::Directory;
1342 #ifdef HAVE_TAGLIB_H
1343 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1344 result = true;
1345 #endif // HAVE_TAGLIB_H
1346 return result;
1349 void EnterDirectory::run()
1351 if (myScreen == myBrowser)
1352 myBrowser->enterDirectory();
1353 #ifdef HAVE_TAGLIB_H
1354 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1356 if (!myTagEditor->enterDirectory())
1357 Statusbar::print("No subdirectories found");
1359 #endif // HAVE_TAGLIB_H
1362 bool EditSong::canBeRun()
1364 # ifdef HAVE_TAGLIB_H
1365 m_song = currentSong(myScreen);
1366 return m_song != nullptr && isMPDMusicDirSet();
1367 # else
1368 return false;
1369 # endif // HAVE_TAGLIB_H
1372 void EditSong::run()
1374 # ifdef HAVE_TAGLIB_H
1375 myTinyTagEditor->SetEdited(*m_song);
1376 myTinyTagEditor->switchTo();
1377 # endif // HAVE_TAGLIB_H
1380 bool EditLibraryTag::canBeRun()
1382 # ifdef HAVE_TAGLIB_H
1383 return myScreen->isActiveWindow(myLibrary->Tags)
1384 && !myLibrary->Tags.empty()
1385 && isMPDMusicDirSet();
1386 # else
1387 return false;
1388 # endif // HAVE_TAGLIB_H
1391 void EditLibraryTag::run()
1393 # ifdef HAVE_TAGLIB_H
1394 using Global::wFooter;
1396 std::string new_tag;
1398 Statusbar::ScopedLock slock;
1399 Statusbar::put() << NC::Format::Bold << tagTypeToString(Config.media_lib_primary_tag) << NC::Format::NoBold << ": ";
1400 new_tag = wFooter->prompt(myLibrary->Tags.current()->value().tag());
1402 if (!new_tag.empty() && new_tag != myLibrary->Tags.current()->value().tag())
1404 Statusbar::print("Updating tags...");
1405 Mpd.StartSearch(true);
1406 Mpd.AddSearch(Config.media_lib_primary_tag, myLibrary->Tags.current()->value().tag());
1407 MPD::MutableSong::SetFunction set = tagTypeToSetFunction(Config.media_lib_primary_tag);
1408 assert(set);
1409 bool success = true;
1410 std::string dir_to_update;
1411 for (MPD::SongIterator s = Mpd.CommitSearchSongs(), end; s != end; ++s)
1413 MPD::MutableSong ms = std::move(*s);
1414 ms.setTags(set, new_tag);
1415 Statusbar::printf("Updating tags in \"%1%\"...", ms.getName());
1416 std::string path = Config.mpd_music_dir + ms.getURI();
1417 if (!Tags::write(ms))
1419 success = false;
1420 const char msg[] = "Error while updating tags in \"%1%\"";
1421 Statusbar::printf(msg, wideShorten(ms.getURI(), COLS-const_strlen(msg)));
1422 s.finish();
1423 break;
1425 if (dir_to_update.empty())
1426 dir_to_update = ms.getURI();
1427 else
1428 dir_to_update = getSharedDirectory(dir_to_update, ms.getURI());
1430 if (success)
1432 Mpd.UpdateDirectory(dir_to_update);
1433 Statusbar::print("Tags updated successfully");
1436 # endif // HAVE_TAGLIB_H
1439 bool EditLibraryAlbum::canBeRun()
1441 # ifdef HAVE_TAGLIB_H
1442 return myScreen->isActiveWindow(myLibrary->Albums)
1443 && !myLibrary->Albums.empty()
1444 && isMPDMusicDirSet();
1445 # else
1446 return false;
1447 # endif // HAVE_TAGLIB_H
1450 void EditLibraryAlbum::run()
1452 # ifdef HAVE_TAGLIB_H
1453 using Global::wFooter;
1454 // FIXME: merge this and EditLibraryTag. also, prompt on failure if user wants to continue
1455 std::string new_album;
1457 Statusbar::ScopedLock slock;
1458 Statusbar::put() << NC::Format::Bold << "Album: " << NC::Format::NoBold;
1459 new_album = wFooter->prompt(myLibrary->Albums.current()->value().entry().album());
1461 if (!new_album.empty() && new_album != myLibrary->Albums.current()->value().entry().album())
1463 bool success = 1;
1464 Statusbar::print("Updating tags...");
1465 for (size_t i = 0; i < myLibrary->Songs.size(); ++i)
1467 Statusbar::printf("Updating tags in \"%1%\"...", myLibrary->Songs[i].value().getName());
1468 std::string path = Config.mpd_music_dir + myLibrary->Songs[i].value().getURI();
1469 TagLib::FileRef f(path.c_str());
1470 if (f.isNull())
1472 const char msg[] = "Error while opening file \"%1%\"";
1473 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1474 success = 0;
1475 break;
1477 f.tag()->setAlbum(ToWString(new_album));
1478 if (!f.save())
1480 const char msg[] = "Error while writing tags in \"%1%\"";
1481 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1482 success = 0;
1483 break;
1486 if (success)
1488 Mpd.UpdateDirectory(getSharedDirectory(myLibrary->Songs.beginV(), myLibrary->Songs.endV()));
1489 Statusbar::print("Tags updated successfully");
1492 # endif // HAVE_TAGLIB_H
1495 bool EditDirectoryName::canBeRun()
1497 return ((myScreen == myBrowser
1498 && !myBrowser->main().empty()
1499 && myBrowser->main().current()->value().type() == MPD::Item::Type::Directory)
1500 # ifdef HAVE_TAGLIB_H
1501 || (myScreen->activeWindow() == myTagEditor->Dirs
1502 && !myTagEditor->Dirs->empty()
1503 && myTagEditor->Dirs->choice() > 0)
1504 # endif // HAVE_TAGLIB_H
1505 ) && isMPDMusicDirSet();
1508 void EditDirectoryName::run()
1510 using Global::wFooter;
1511 if (myScreen == myBrowser)
1513 std::string old_dir = myBrowser->main().current()->value().directory().path();
1514 std::string new_dir;
1516 Statusbar::ScopedLock slock;
1517 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1518 new_dir = wFooter->prompt(old_dir);
1520 if (!new_dir.empty() && new_dir != old_dir)
1522 std::string full_old_dir;
1523 if (!myBrowser->isLocal())
1524 full_old_dir += Config.mpd_music_dir;
1525 full_old_dir += old_dir;
1526 std::string full_new_dir;
1527 if (!myBrowser->isLocal())
1528 full_new_dir += Config.mpd_music_dir;
1529 full_new_dir += new_dir;
1530 boost::filesystem::rename(full_old_dir, full_new_dir);
1531 const char msg[] = "Directory renamed to \"%1%\"";
1532 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1533 if (!myBrowser->isLocal())
1534 Mpd.UpdateDirectory(getSharedDirectory(old_dir, new_dir));
1535 myBrowser->requestUpdate();
1538 # ifdef HAVE_TAGLIB_H
1539 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1541 std::string old_dir = myTagEditor->Dirs->current()->value().first, new_dir;
1543 Statusbar::ScopedLock slock;
1544 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1545 new_dir = wFooter->prompt(old_dir);
1547 if (!new_dir.empty() && new_dir != old_dir)
1549 std::string full_old_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + old_dir;
1550 std::string full_new_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + new_dir;
1551 if (rename(full_old_dir.c_str(), full_new_dir.c_str()) == 0)
1553 const char msg[] = "Directory renamed to \"%1%\"";
1554 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1555 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1557 else
1559 const char msg[] = "Couldn't rename \"%1%\": %2%";
1560 Statusbar::printf(msg, wideShorten(old_dir, COLS-const_strlen(msg)-25), strerror(errno));
1564 # endif // HAVE_TAGLIB_H
1567 bool EditPlaylistName::canBeRun()
1569 return (myScreen->isActiveWindow(myPlaylistEditor->Playlists)
1570 && !myPlaylistEditor->Playlists.empty())
1571 || (myScreen == myBrowser
1572 && !myBrowser->main().empty()
1573 && myBrowser->main().current()->value().type() == MPD::Item::Type::Playlist);
1576 void EditPlaylistName::run()
1578 using Global::wFooter;
1579 std::string old_name, new_name;
1580 if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
1581 old_name = myPlaylistEditor->Playlists.current()->value().path();
1582 else
1583 old_name = myBrowser->main().current()->value().playlist().path();
1585 Statusbar::ScopedLock slock;
1586 Statusbar::put() << NC::Format::Bold << "Playlist: " << NC::Format::NoBold;
1587 new_name = wFooter->prompt(old_name);
1589 if (!new_name.empty() && new_name != old_name)
1591 Mpd.Rename(old_name, new_name);
1592 const char msg[] = "Playlist renamed to \"%1%\"";
1593 Statusbar::printf(msg, wideShorten(new_name, COLS-const_strlen(msg)));
1597 bool EditLyrics::canBeRun()
1599 return myScreen == myLyrics;
1602 void EditLyrics::run()
1604 myLyrics->Edit();
1607 bool JumpToBrowser::canBeRun()
1609 m_song = currentSong(myScreen);
1610 return m_song != nullptr;
1613 void JumpToBrowser::run()
1615 myBrowser->locateSong(*m_song);
1618 bool JumpToMediaLibrary::canBeRun()
1620 m_song = currentSong(myScreen);
1621 return m_song != nullptr;
1624 void JumpToMediaLibrary::run()
1626 myLibrary->locateSong(*m_song);
1629 bool JumpToPlaylistEditor::canBeRun()
1631 return myScreen == myBrowser
1632 && myBrowser->main().current()->value().type() == MPD::Item::Type::Playlist;
1635 void JumpToPlaylistEditor::run()
1637 myPlaylistEditor->locatePlaylist(myBrowser->main().current()->value().playlist());
1640 void ToggleScreenLock::run()
1642 using Global::wFooter;
1643 using Global::myLockedScreen;
1644 const char *msg_unlockable_screen = "Current screen can't be locked";
1645 if (myLockedScreen != nullptr)
1647 BaseScreen::unlock();
1648 Actions::setResizeFlags();
1649 myScreen->resize();
1650 Statusbar::print("Screen unlocked");
1652 else if (!myScreen->isLockable())
1654 Statusbar::print(msg_unlockable_screen);
1656 else
1658 unsigned part = Config.locked_screen_width_part*100;
1659 if (Config.ask_for_locked_screen_width_part)
1661 Statusbar::ScopedLock slock;
1662 Statusbar::put() << "% of the locked screen's width to be reserved (20-80): ";
1663 part = fromString<unsigned>(wFooter->prompt(boost::lexical_cast<std::string>(part)));
1665 boundsCheck(part, 20u, 80u);
1666 Config.locked_screen_width_part = part/100.0;
1667 if (myScreen->lock())
1668 Statusbar::printf("Screen locked (with %1%%% width)", part);
1669 else
1670 Statusbar::print(msg_unlockable_screen);
1674 bool JumpToTagEditor::canBeRun()
1676 # ifdef HAVE_TAGLIB_H
1677 m_song = currentSong(myScreen);
1678 return m_song != nullptr && isMPDMusicDirSet();
1679 # else
1680 return false;
1681 # endif // HAVE_TAGLIB_H
1684 void JumpToTagEditor::run()
1686 # ifdef HAVE_TAGLIB_H
1687 myTagEditor->LocateSong(*m_song);
1688 # endif // HAVE_TAGLIB_H
1691 bool JumpToPositionInSong::canBeRun()
1693 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1696 void JumpToPositionInSong::run()
1698 using Global::wFooter;
1700 const MPD::Song s = myPlaylist->nowPlayingSong();
1702 std::string spos;
1704 Statusbar::ScopedLock slock;
1705 Statusbar::put() << "Position to go (in %/m:ss/seconds(s)): ";
1706 spos = wFooter->prompt();
1709 boost::regex rx;
1710 boost::smatch what;
1711 if (boost::regex_match(spos, what, rx.assign("([0-9]+):([0-9]{2})"))) // mm:ss
1713 auto mins = fromString<unsigned>(what[1]);
1714 auto secs = fromString<unsigned>(what[2]);
1715 boundsCheck(secs, 0u, 60u);
1716 Mpd.Seek(s.getPosition(), mins * 60 + secs);
1718 else if (boost::regex_match(spos, what, rx.assign("([0-9]+)s"))) // position in seconds
1720 auto secs = fromString<unsigned>(what[1]);
1721 Mpd.Seek(s.getPosition(), secs);
1723 else if (boost::regex_match(spos, what, rx.assign("([0-9]+)[%]{0,1}"))) // position in %
1725 auto percent = fromString<unsigned>(what[1]);
1726 boundsCheck(percent, 0u, 100u);
1727 int secs = (percent * s.getDuration()) / 100.0;
1728 Mpd.Seek(s.getPosition(), secs);
1730 else
1731 Statusbar::print("Invalid format ([m]:[ss], [s]s, [%]%, [%] accepted)");
1734 bool SelectItem::canBeRun()
1736 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1737 return m_list != nullptr
1738 && !m_list->empty()
1739 && m_list->currentP()->isSelectable();
1742 void SelectItem::run()
1744 auto current = m_list->currentP();
1745 current->setSelected(!current->isSelected());
1748 bool SelectRange::canBeRun()
1750 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1751 if (m_list == nullptr)
1752 return false;
1753 m_begin = m_list->beginP();
1754 m_end = m_list->endP();
1755 return findRange(m_begin, m_end);
1758 void SelectRange::run()
1760 for (; m_begin != m_end; ++m_begin)
1761 m_begin->setSelected(true);
1762 Statusbar::print("Range selected");
1765 bool ReverseSelection::canBeRun()
1767 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1768 return m_list != nullptr;
1771 void ReverseSelection::run()
1773 for (auto &p : *m_list)
1774 p.setSelected(!p.isSelected());
1775 Statusbar::print("Selection reversed");
1778 bool RemoveSelection::canBeRun()
1780 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1781 return m_list != nullptr;
1784 void RemoveSelection::run()
1786 for (auto &p : *m_list)
1787 p.setSelected(false);
1788 Statusbar::print("Selection removed");
1791 bool SelectAlbum::canBeRun()
1793 auto *w = myScreen->activeWindow();
1794 if (m_list != static_cast<void *>(w))
1795 m_list = dynamic_cast<NC::List *>(w);
1796 if (m_songs != static_cast<void *>(w))
1797 m_songs = dynamic_cast<SongList *>(w);
1798 return m_list != nullptr && !m_list->empty()
1799 && m_songs != nullptr;
1802 void SelectAlbum::run()
1804 const auto front = m_songs->beginS(), current = m_songs->currentS(), end = m_songs->endS();
1805 auto *s = current->get<Bit::Song>();
1806 if (s == nullptr)
1807 return;
1808 auto get = &MPD::Song::getAlbum;
1809 const std::string tag = s->getTags(get);
1810 // go up
1811 for (auto it = current; it != front;)
1813 --it;
1814 s = it->get<Bit::Song>();
1815 if (s == nullptr || s->getTags(get) != tag)
1816 break;
1817 it->get<Bit::Properties>().setSelected(true);
1819 // go down
1820 for (auto it = current;;)
1822 it->get<Bit::Properties>().setSelected(true);
1823 if (++it == end)
1824 break;
1825 s = it->get<Bit::Song>();
1826 if (s == nullptr || s->getTags(get) != tag)
1827 break;
1829 Statusbar::print("Album around cursor position selected");
1832 bool SelectFoundItems::canBeRun()
1834 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1835 if (m_list == nullptr || m_list->empty())
1836 return false;
1837 m_searchable = dynamic_cast<Searchable *>(myScreen);
1838 return m_searchable != nullptr && m_searchable->allowsSearching();
1841 void SelectFoundItems::run()
1843 auto current_pos = m_list->choice();
1844 myScreen->activeWindow()->scroll(NC::Scroll::Home);
1845 bool found = m_searchable->search(SearchDirection::Forward, false, false);
1846 if (found)
1848 Statusbar::print("Searching for items...");
1849 m_list->currentP()->setSelected(true);
1850 while (m_searchable->search(SearchDirection::Forward, false, true))
1851 m_list->currentP()->setSelected(true);
1852 Statusbar::print("Found items selected");
1854 m_list->highlight(current_pos);
1857 bool AddSelectedItems::canBeRun()
1859 return myScreen != mySelectedItemsAdder;
1862 void AddSelectedItems::run()
1864 mySelectedItemsAdder->switchTo();
1867 void CropMainPlaylist::run()
1869 auto &w = myPlaylist->main();
1870 // cropping doesn't make sense in this case
1871 if (w.size() <= 1)
1872 return;
1873 if (Config.ask_before_clearing_playlists)
1874 confirmAction("Do you really want to crop main playlist?");
1875 Statusbar::print("Cropping playlist...");
1876 selectCurrentIfNoneSelected(w);
1877 cropPlaylist(w, std::bind(&MPD::Connection::Delete, ph::_1, ph::_2));
1878 Statusbar::print("Playlist cropped");
1881 bool CropPlaylist::canBeRun()
1883 return myScreen == myPlaylistEditor;
1886 void CropPlaylist::run()
1888 auto &w = myPlaylistEditor->Content;
1889 // cropping doesn't make sense in this case
1890 if (w.size() <= 1)
1891 return;
1892 assert(!myPlaylistEditor->Playlists.empty());
1893 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
1894 if (Config.ask_before_clearing_playlists)
1895 confirmAction(boost::format("Do you really want to crop playlist \"%1%\"?") % playlist);
1896 selectCurrentIfNoneSelected(w);
1897 Statusbar::printf("Cropping playlist \"%1%\"...", playlist);
1898 cropPlaylist(w, std::bind(&MPD::Connection::PlaylistDelete, ph::_1, playlist, ph::_2));
1899 Statusbar::printf("Playlist \"%1%\" cropped", playlist);
1902 void ClearMainPlaylist::run()
1904 if (!myPlaylist->main().empty() && Config.ask_before_clearing_playlists)
1905 confirmAction("Do you really want to clear main playlist?");
1906 Mpd.ClearMainPlaylist();
1907 Statusbar::print("Playlist cleared");
1908 myPlaylist->main().reset();
1911 bool ClearPlaylist::canBeRun()
1913 return myScreen == myPlaylistEditor;
1916 void ClearPlaylist::run()
1918 if (myPlaylistEditor->Playlists.empty())
1919 return;
1920 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
1921 if (Config.ask_before_clearing_playlists)
1922 confirmAction(boost::format("Do you really want to clear playlist \"%1%\"?") % playlist);
1923 Mpd.ClearPlaylist(playlist);
1924 Statusbar::printf("Playlist \"%1%\" cleared", playlist);
1927 bool SortPlaylist::canBeRun()
1929 if (myScreen != myPlaylist)
1930 return false;
1931 auto first = myPlaylist->main().begin(), last = myPlaylist->main().end();
1932 return findSelectedRangeAndPrintInfoIfNot(first, last);
1935 void SortPlaylist::run()
1937 mySortPlaylistDialog->switchTo();
1940 bool ReversePlaylist::canBeRun()
1942 if (myScreen != myPlaylist)
1943 return false;
1944 m_begin = myPlaylist->main().begin();
1945 m_end = myPlaylist->main().end();
1946 return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end);
1949 void ReversePlaylist::run()
1951 Statusbar::print("Reversing range...");
1952 Mpd.StartCommandsList();
1953 for (--m_end; m_begin < m_end; ++m_begin, --m_end)
1954 Mpd.Swap(m_begin->value().getPosition(), m_end->value().getPosition());
1955 Mpd.CommitCommandsList();
1956 Statusbar::print("Range reversed");
1959 bool ApplyFilter::canBeRun()
1961 m_searchable = dynamic_cast<Searchable *>(myScreen);
1962 return m_searchable != nullptr;
1965 void ApplyFilter::run()
1967 using Global::wFooter;
1969 std::string filter = m_searchable->currentFilter();
1970 if (!filter.empty())
1972 m_searchable->applyFilter(filter);
1973 myScreen->refreshWindow();
1978 Statusbar::ScopedLock slock;
1979 NC::Window::ScopedPromptHook helper(
1980 *wFooter,
1981 Statusbar::Helpers::ApplyFilterImmediately(m_searchable));
1982 Statusbar::put() << "Apply filter: ";
1983 filter = wFooter->prompt(filter);
1985 catch (NC::PromptAborted &)
1987 m_searchable->applyFilter(filter);
1988 throw;
1991 if (filter.empty())
1992 Statusbar::printf("Filtering disabled");
1993 else
1994 Statusbar::printf("Using filter \"%1%\"", filter);
1996 if (myScreen == myPlaylist)
1997 myPlaylist->reloadTotalLength();
1999 listsChangeFinisher();
2002 bool Find::canBeRun()
2004 return myScreen == myHelp
2005 || myScreen == myLyrics
2006 # ifdef HAVE_CURL_CURL_H
2007 || myScreen == myLastfm
2008 # endif // HAVE_CURL_CURL_H
2012 void Find::run()
2014 using Global::wFooter;
2016 std::string token;
2018 Statusbar::ScopedLock slock;
2019 Statusbar::put() << "Find: ";
2020 token = wFooter->prompt();
2023 Statusbar::print("Searching...");
2024 auto s = static_cast<Screen<NC::Scrollpad> *>(myScreen);
2025 s->main().removeProperties();
2026 if (token.empty() || s->main().setProperties(NC::Format::Reverse, token, NC::Format::NoReverse, Config.regex_type))
2027 Statusbar::print("Done");
2028 else
2029 Statusbar::print("No matching patterns found");
2030 s->main().flush();
2033 bool FindItemBackward::canBeRun()
2035 auto w = dynamic_cast<Searchable *>(myScreen);
2036 return w && w->allowsSearching();
2039 void FindItemForward::run()
2041 findItem(SearchDirection::Forward);
2042 listsChangeFinisher();
2045 bool FindItemForward::canBeRun()
2047 auto w = dynamic_cast<Searchable *>(myScreen);
2048 return w && w->allowsSearching();
2051 void FindItemBackward::run()
2053 findItem(SearchDirection::Backward);
2054 listsChangeFinisher();
2057 bool NextFoundItem::canBeRun()
2059 return dynamic_cast<Searchable *>(myScreen);
2062 void NextFoundItem::run()
2064 Searchable *w = dynamic_cast<Searchable *>(myScreen);
2065 assert(w != nullptr);
2066 w->search(SearchDirection::Forward, Config.wrapped_search, true);
2067 listsChangeFinisher();
2070 bool PreviousFoundItem::canBeRun()
2072 return dynamic_cast<Searchable *>(myScreen);
2075 void PreviousFoundItem::run()
2077 Searchable *w = dynamic_cast<Searchable *>(myScreen);
2078 assert(w != nullptr);
2079 w->search(SearchDirection::Backward, Config.wrapped_search, true);
2080 listsChangeFinisher();
2083 void ToggleFindMode::run()
2085 Config.wrapped_search = !Config.wrapped_search;
2086 Statusbar::printf("Search mode: %1%",
2087 Config.wrapped_search ? "Wrapped" : "Normal"
2091 void ToggleReplayGainMode::run()
2093 using Global::wFooter;
2095 char rgm = 0;
2097 Statusbar::ScopedLock slock;
2098 Statusbar::put() << "Replay gain mode? "
2099 << "[" << NC::Format::Bold << 'o' << NC::Format::NoBold << "ff"
2100 << "/" << NC::Format::Bold << 't' << NC::Format::NoBold << "rack"
2101 << "/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "lbum"
2102 << "] ";
2103 rgm = Statusbar::Helpers::promptReturnOneOf({"t", "a", "o"})[0];
2105 switch (rgm)
2107 case 't':
2108 Mpd.SetReplayGainMode(MPD::rgmTrack);
2109 break;
2110 case 'a':
2111 Mpd.SetReplayGainMode(MPD::rgmAlbum);
2112 break;
2113 case 'o':
2114 Mpd.SetReplayGainMode(MPD::rgmOff);
2115 break;
2116 default: // impossible
2117 throw std::runtime_error(
2118 (boost::format("ToggleReplayGainMode: impossible case reached: %1%") % rgm).str()
2121 Statusbar::printf("Replay gain mode: %1%", Mpd.GetReplayGainMode());
2124 void ToggleAddMode::run()
2126 std::string mode_desc;
2127 switch (Config.space_add_mode)
2129 case SpaceAddMode::AddRemove:
2130 Config.space_add_mode = SpaceAddMode::AlwaysAdd;
2131 mode_desc = "always add an item to playlist";
2132 break;
2133 case SpaceAddMode::AlwaysAdd:
2134 Config.space_add_mode = SpaceAddMode::AddRemove;
2135 mode_desc = "add an item to playlist or remove if already added";
2136 break;
2138 Statusbar::printf("Add mode: %1%", mode_desc);
2141 void ToggleMouse::run()
2143 Config.mouse_support = !Config.mouse_support;
2144 if (Config.mouse_support)
2145 NC::Mouse::enable();
2146 else
2147 NC::Mouse::disable();
2148 Statusbar::printf("Mouse support %1%",
2149 Config.mouse_support ? "enabled" : "disabled"
2153 void ToggleBitrateVisibility::run()
2155 Config.display_bitrate = !Config.display_bitrate;
2156 Statusbar::printf("Bitrate visibility %1%",
2157 Config.display_bitrate ? "enabled" : "disabled"
2161 void AddRandomItems::run()
2163 using Global::wFooter;
2164 char rnd_type = 0;
2166 Statusbar::ScopedLock slock;
2167 Statusbar::put() << "Add random? "
2168 << "[" << NC::Format::Bold << 's' << NC::Format::NoBold << "ongs"
2169 << "/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtists"
2170 << "/" << "album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtists"
2171 << "/" << "al" << NC::Format::Bold << 'b' << NC::Format::NoBold << "ums"
2172 << "] ";
2173 rnd_type = Statusbar::Helpers::promptReturnOneOf({"s", "a", "A", "b"})[0];
2176 mpd_tag_type tag_type = MPD_TAG_ARTIST;
2177 std::string tag_type_str ;
2178 if (rnd_type != 's')
2180 tag_type = charToTagType(rnd_type);
2181 tag_type_str = boost::locale::to_lower(tagTypeToString(tag_type));
2183 else
2184 tag_type_str = "song";
2186 unsigned number;
2188 Statusbar::ScopedLock slock;
2189 Statusbar::put() << "Number of random " << tag_type_str << "s: ";
2190 number = fromString<unsigned>(wFooter->prompt());
2192 if (number > 0)
2194 bool success;
2195 if (rnd_type == 's')
2196 success = Mpd.AddRandomSongs(number, Global::RNG);
2197 else
2198 success = Mpd.AddRandomTag(tag_type, number, Global::RNG);
2199 if (success)
2200 Statusbar::printf("%1% random %2%%3% added to playlist", number, tag_type_str, number == 1 ? "" : "s");
2204 bool ToggleBrowserSortMode::canBeRun()
2206 return myScreen == myBrowser;
2209 void ToggleBrowserSortMode::run()
2211 switch (Config.browser_sort_mode)
2213 case SortMode::Name:
2214 Config.browser_sort_mode = SortMode::ModificationTime;
2215 Statusbar::print("Sort songs by: modification time");
2216 break;
2217 case SortMode::ModificationTime:
2218 Config.browser_sort_mode = SortMode::CustomFormat;
2219 Statusbar::print("Sort songs by: custom format");
2220 break;
2221 case SortMode::CustomFormat:
2222 Config.browser_sort_mode = SortMode::NoOp;
2223 Statusbar::print("Do not sort songs");
2224 break;
2225 case SortMode::NoOp:
2226 Config.browser_sort_mode = SortMode::Name;
2227 Statusbar::print("Sort songs by: name");
2229 if (Config.browser_sort_mode != SortMode::NoOp)
2231 size_t sort_offset = myBrowser->inRootDirectory() ? 0 : 1;
2232 std::sort(myBrowser->main().begin()+sort_offset, myBrowser->main().end(),
2233 LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode)
2238 bool ToggleLibraryTagType::canBeRun()
2240 return (myScreen->isActiveWindow(myLibrary->Tags))
2241 || (myLibrary->columns() == 2 && myScreen->isActiveWindow(myLibrary->Albums));
2244 void ToggleLibraryTagType::run()
2246 using Global::wFooter;
2248 char tag_type = 0;
2250 Statusbar::ScopedLock slock;
2251 Statusbar::put() << "Tag type? "
2252 << "[" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtist"
2253 << "/" << "album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtist"
2254 << "/" << NC::Format::Bold << 'y' << NC::Format::NoBold << "ear"
2255 << "/" << NC::Format::Bold << 'g' << NC::Format::NoBold << "enre"
2256 << "/" << NC::Format::Bold << 'c' << NC::Format::NoBold << "omposer"
2257 << "/" << NC::Format::Bold << 'p' << NC::Format::NoBold << "erformer"
2258 << "] ";
2259 tag_type = Statusbar::Helpers::promptReturnOneOf({"a", "A", "y", "g", "c", "p"})[0];
2261 mpd_tag_type new_tagitem = charToTagType(tag_type);
2262 if (new_tagitem != Config.media_lib_primary_tag)
2264 Config.media_lib_primary_tag = new_tagitem;
2265 std::string item_type = tagTypeToString(Config.media_lib_primary_tag);
2266 myLibrary->Tags.setTitle(Config.titles_visibility ? item_type + "s" : "");
2267 myLibrary->Tags.reset();
2268 item_type = boost::locale::to_lower(item_type);
2269 std::string and_mtime = Config.media_library_sort_by_mtime ?
2270 " and mtime" :
2272 if (myLibrary->columns() == 2)
2274 myLibrary->Songs.clear();
2275 myLibrary->Albums.reset();
2276 myLibrary->Albums.clear();
2277 myLibrary->Albums.setTitle(Config.titles_visibility ? "Albums (sorted by " + item_type + and_mtime + ")" : "");
2278 myLibrary->Albums.display();
2280 else
2282 myLibrary->Tags.clear();
2283 myLibrary->Tags.display();
2285 Statusbar::printf("Switched to the list of %1%s", item_type);
2289 bool ToggleMediaLibrarySortMode::canBeRun()
2291 return myScreen == myLibrary;
2294 void ToggleMediaLibrarySortMode::run()
2296 myLibrary->toggleSortMode();
2299 bool RefetchLyrics::canBeRun()
2301 # ifdef HAVE_CURL_CURL_H
2302 return myScreen == myLyrics;
2303 # else
2304 return false;
2305 # endif // HAVE_CURL_CURL_H
2308 void RefetchLyrics::run()
2310 # ifdef HAVE_CURL_CURL_H
2311 myLyrics->Refetch();
2312 # endif // HAVE_CURL_CURL_H
2315 bool SetSelectedItemsPriority::canBeRun()
2317 if (Mpd.Version() < 17)
2319 Statusbar::print("Priorities are supported in MPD >= 0.17.0");
2320 return false;
2322 return myScreen == myPlaylist && !myPlaylist->main().empty();
2325 void SetSelectedItemsPriority::run()
2327 using Global::wFooter;
2329 unsigned prio;
2331 Statusbar::ScopedLock slock;
2332 Statusbar::put() << "Set priority [0-255]: ";
2333 prio = fromString<unsigned>(wFooter->prompt());
2334 boundsCheck(prio, 0u, 255u);
2336 myPlaylist->setSelectedItemsPriority(prio);
2339 bool ToggleOutput::canBeRun()
2341 #ifdef ENABLE_OUTPUTS
2342 return myScreen == myOutputs;
2343 #else
2344 return false;
2345 #endif // ENABLE_OUTPUTS
2348 void ToggleOutput::run()
2350 #ifdef ENABLE_OUTPUTS
2351 myOutputs->toggleOutput();
2352 #endif // ENABLE_OUTPUTS
2355 bool ToggleVisualizationType::canBeRun()
2357 # ifdef ENABLE_VISUALIZER
2358 return myScreen == myVisualizer;
2359 # else
2360 return false;
2361 # endif // ENABLE_VISUALIZER
2364 void ToggleVisualizationType::run()
2366 # ifdef ENABLE_VISUALIZER
2367 myVisualizer->ToggleVisualizationType();
2368 # endif // ENABLE_VISUALIZER
2371 bool SetVisualizerSampleMultiplier::canBeRun()
2373 # ifdef ENABLE_VISUALIZER
2374 return myScreen == myVisualizer;
2375 # else
2376 return false;
2377 # endif // ENABLE_VISUALIZER
2380 void SetVisualizerSampleMultiplier::run()
2382 # ifdef ENABLE_VISUALIZER
2383 using Global::wFooter;
2385 double multiplier;
2387 Statusbar::ScopedLock slock;
2388 Statusbar::put() << "Set visualizer sample multiplier: ";
2389 multiplier = fromString<double>(wFooter->prompt());
2390 lowerBoundCheck(multiplier, 0.0);
2391 Config.visualizer_sample_multiplier = multiplier;
2393 Statusbar::printf("Visualizer sample multiplier set to %1%", multiplier);
2394 # endif // ENABLE_VISUALIZER
2397 void ShowSongInfo::run()
2399 mySongInfo->switchTo();
2402 bool ShowArtistInfo::canBeRun()
2404 #ifdef HAVE_CURL_CURL_H
2405 return myScreen == myLastfm
2406 || (myScreen->isActiveWindow(myLibrary->Tags)
2407 && !myLibrary->Tags.empty()
2408 && Config.media_lib_primary_tag == MPD_TAG_ARTIST)
2409 || currentSong(myScreen);
2410 # else
2411 return false;
2412 # endif // NOT HAVE_CURL_CURL_H
2415 void ShowArtistInfo::run()
2417 # ifdef HAVE_CURL_CURL_H
2418 if (myScreen == myLastfm)
2420 myLastfm->switchTo();
2421 return;
2424 std::string artist;
2425 if (myScreen->isActiveWindow(myLibrary->Tags))
2427 assert(!myLibrary->Tags.empty());
2428 assert(Config.media_lib_primary_tag == MPD_TAG_ARTIST);
2429 artist = myLibrary->Tags.current()->value().tag();
2431 else
2433 auto s = currentSong(myScreen);
2434 assert(s);
2435 artist = s->getArtist();
2438 if (!artist.empty())
2440 myLastfm->queueJob(new LastFm::ArtistInfo(artist, Config.lastfm_preferred_language));
2441 myLastfm->switchTo();
2443 # endif // HAVE_CURL_CURL_H
2446 void ShowLyrics::run()
2448 myLyrics->switchTo();
2451 void Quit::run()
2453 ExitMainLoop = true;
2456 void NextScreen::run()
2458 if (Config.screen_switcher_previous)
2460 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2461 tababble->switchToPreviousScreen();
2463 else if (!Config.screen_sequence.empty())
2465 const auto &seq = Config.screen_sequence;
2466 auto screen_type = std::find(seq.begin(), seq.end(), myScreen->type());
2467 if (++screen_type == seq.end())
2468 toScreen(seq.front())->switchTo();
2469 else
2470 toScreen(*screen_type)->switchTo();
2474 void PreviousScreen::run()
2476 if (Config.screen_switcher_previous)
2478 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2479 tababble->switchToPreviousScreen();
2481 else if (!Config.screen_sequence.empty())
2483 const auto &seq = Config.screen_sequence;
2484 auto screen_type = std::find(seq.begin(), seq.end(), myScreen->type());
2485 if (screen_type == seq.begin())
2486 toScreen(seq.back())->switchTo();
2487 else
2488 toScreen(*--screen_type)->switchTo();
2492 bool ShowHelp::canBeRun()
2494 return myScreen != myHelp
2495 # ifdef HAVE_TAGLIB_H
2496 && myScreen != myTinyTagEditor
2497 # endif // HAVE_TAGLIB_H
2501 void ShowHelp::run()
2503 myHelp->switchTo();
2506 bool ShowPlaylist::canBeRun()
2508 return myScreen != myPlaylist
2509 # ifdef HAVE_TAGLIB_H
2510 && myScreen != myTinyTagEditor
2511 # endif // HAVE_TAGLIB_H
2515 void ShowPlaylist::run()
2517 myPlaylist->switchTo();
2520 bool ShowBrowser::canBeRun()
2522 return myScreen != myBrowser
2523 # ifdef HAVE_TAGLIB_H
2524 && myScreen != myTinyTagEditor
2525 # endif // HAVE_TAGLIB_H
2529 void ShowBrowser::run()
2531 myBrowser->switchTo();
2534 bool ChangeBrowseMode::canBeRun()
2536 return myScreen == myBrowser;
2539 void ChangeBrowseMode::run()
2541 myBrowser->changeBrowseMode();
2544 bool ShowSearchEngine::canBeRun()
2546 return myScreen != mySearcher
2547 # ifdef HAVE_TAGLIB_H
2548 && myScreen != myTinyTagEditor
2549 # endif // HAVE_TAGLIB_H
2553 void ShowSearchEngine::run()
2555 mySearcher->switchTo();
2558 bool ResetSearchEngine::canBeRun()
2560 return myScreen == mySearcher;
2563 void ResetSearchEngine::run()
2565 mySearcher->reset();
2568 bool ShowMediaLibrary::canBeRun()
2570 return myScreen != myLibrary
2571 # ifdef HAVE_TAGLIB_H
2572 && myScreen != myTinyTagEditor
2573 # endif // HAVE_TAGLIB_H
2577 void ShowMediaLibrary::run()
2579 myLibrary->switchTo();
2582 bool ToggleMediaLibraryColumnsMode::canBeRun()
2584 return myScreen == myLibrary;
2587 void ToggleMediaLibraryColumnsMode::run()
2589 myLibrary->toggleColumnsMode();
2590 myLibrary->refresh();
2593 bool ShowPlaylistEditor::canBeRun()
2595 return myScreen != myPlaylistEditor
2596 # ifdef HAVE_TAGLIB_H
2597 && myScreen != myTinyTagEditor
2598 # endif // HAVE_TAGLIB_H
2602 void ShowPlaylistEditor::run()
2604 myPlaylistEditor->switchTo();
2607 bool ShowTagEditor::canBeRun()
2609 # ifdef HAVE_TAGLIB_H
2610 return myScreen != myTagEditor
2611 && myScreen != myTinyTagEditor;
2612 # else
2613 return false;
2614 # endif // HAVE_TAGLIB_H
2617 void ShowTagEditor::run()
2619 # ifdef HAVE_TAGLIB_H
2620 if (isMPDMusicDirSet())
2621 myTagEditor->switchTo();
2622 # endif // HAVE_TAGLIB_H
2625 bool ShowOutputs::canBeRun()
2627 # ifdef ENABLE_OUTPUTS
2628 return myScreen != myOutputs
2629 # ifdef HAVE_TAGLIB_H
2630 && myScreen != myTinyTagEditor
2631 # endif // HAVE_TAGLIB_H
2633 # else
2634 return false;
2635 # endif // ENABLE_OUTPUTS
2638 void ShowOutputs::run()
2640 # ifdef ENABLE_OUTPUTS
2641 myOutputs->switchTo();
2642 # endif // ENABLE_OUTPUTS
2645 bool ShowVisualizer::canBeRun()
2647 # ifdef ENABLE_VISUALIZER
2648 return myScreen != myVisualizer
2649 # ifdef HAVE_TAGLIB_H
2650 && myScreen != myTinyTagEditor
2651 # endif // HAVE_TAGLIB_H
2653 # else
2654 return false;
2655 # endif // ENABLE_VISUALIZER
2658 void ShowVisualizer::run()
2660 # ifdef ENABLE_VISUALIZER
2661 myVisualizer->switchTo();
2662 # endif // ENABLE_VISUALIZER
2665 bool ShowClock::canBeRun()
2667 # ifdef ENABLE_CLOCK
2668 return myScreen != myClock
2669 # ifdef HAVE_TAGLIB_H
2670 && myScreen != myTinyTagEditor
2671 # endif // HAVE_TAGLIB_H
2673 # else
2674 return false;
2675 # endif // ENABLE_CLOCK
2678 void ShowClock::run()
2680 # ifdef ENABLE_CLOCK
2681 myClock->switchTo();
2682 # endif // ENABLE_CLOCK
2685 #ifdef HAVE_TAGLIB_H
2686 bool ShowServerInfo::canBeRun()
2688 return myScreen != myTinyTagEditor;
2690 #endif // HAVE_TAGLIB_H
2692 void ShowServerInfo::run()
2694 myServerInfo->switchTo();
2699 namespace {
2701 void populateActions()
2703 auto insert_action = [](Actions::BaseAction *a) {
2704 AvailableActions[static_cast<size_t>(a->type())] = a;
2706 insert_action(new Actions::Dummy());
2707 insert_action(new Actions::UpdateEnvironment());
2708 insert_action(new Actions::MouseEvent());
2709 insert_action(new Actions::ScrollUp());
2710 insert_action(new Actions::ScrollDown());
2711 insert_action(new Actions::ScrollUpArtist());
2712 insert_action(new Actions::ScrollUpAlbum());
2713 insert_action(new Actions::ScrollDownArtist());
2714 insert_action(new Actions::ScrollDownAlbum());
2715 insert_action(new Actions::PageUp());
2716 insert_action(new Actions::PageDown());
2717 insert_action(new Actions::MoveHome());
2718 insert_action(new Actions::MoveEnd());
2719 insert_action(new Actions::ToggleInterface());
2720 insert_action(new Actions::JumpToParentDirectory());
2721 insert_action(new Actions::RunAction());
2722 insert_action(new Actions::SelectItem());
2723 insert_action(new Actions::SelectRange());
2724 insert_action(new Actions::PreviousColumn());
2725 insert_action(new Actions::NextColumn());
2726 insert_action(new Actions::MasterScreen());
2727 insert_action(new Actions::SlaveScreen());
2728 insert_action(new Actions::VolumeUp());
2729 insert_action(new Actions::VolumeDown());
2730 insert_action(new Actions::AddItemToPlaylist());
2731 insert_action(new Actions::DeletePlaylistItems());
2732 insert_action(new Actions::DeleteStoredPlaylist());
2733 insert_action(new Actions::DeleteBrowserItems());
2734 insert_action(new Actions::ReplaySong());
2735 insert_action(new Actions::PreviousSong());
2736 insert_action(new Actions::NextSong());
2737 insert_action(new Actions::Pause());
2738 insert_action(new Actions::Stop());
2739 insert_action(new Actions::ExecuteCommand());
2740 insert_action(new Actions::SavePlaylist());
2741 insert_action(new Actions::MoveSortOrderUp());
2742 insert_action(new Actions::MoveSortOrderDown());
2743 insert_action(new Actions::MoveSelectedItemsUp());
2744 insert_action(new Actions::MoveSelectedItemsDown());
2745 insert_action(new Actions::MoveSelectedItemsTo());
2746 insert_action(new Actions::Add());
2747 insert_action(new Actions::PlayItem());
2748 insert_action(new Actions::SeekForward());
2749 insert_action(new Actions::SeekBackward());
2750 insert_action(new Actions::ToggleDisplayMode());
2751 insert_action(new Actions::ToggleSeparatorsBetweenAlbums());
2752 insert_action(new Actions::ToggleLyricsUpdateOnSongChange());
2753 insert_action(new Actions::ToggleLyricsFetcher());
2754 insert_action(new Actions::ToggleFetchingLyricsInBackground());
2755 insert_action(new Actions::TogglePlayingSongCentering());
2756 insert_action(new Actions::UpdateDatabase());
2757 insert_action(new Actions::JumpToPlayingSong());
2758 insert_action(new Actions::ToggleRepeat());
2759 insert_action(new Actions::Shuffle());
2760 insert_action(new Actions::ToggleRandom());
2761 insert_action(new Actions::StartSearching());
2762 insert_action(new Actions::SaveTagChanges());
2763 insert_action(new Actions::ToggleSingle());
2764 insert_action(new Actions::ToggleConsume());
2765 insert_action(new Actions::ToggleCrossfade());
2766 insert_action(new Actions::SetCrossfade());
2767 insert_action(new Actions::SetVolume());
2768 insert_action(new Actions::EnterDirectory());
2769 insert_action(new Actions::EditSong());
2770 insert_action(new Actions::EditLibraryTag());
2771 insert_action(new Actions::EditLibraryAlbum());
2772 insert_action(new Actions::EditDirectoryName());
2773 insert_action(new Actions::EditPlaylistName());
2774 insert_action(new Actions::EditLyrics());
2775 insert_action(new Actions::JumpToBrowser());
2776 insert_action(new Actions::JumpToMediaLibrary());
2777 insert_action(new Actions::JumpToPlaylistEditor());
2778 insert_action(new Actions::ToggleScreenLock());
2779 insert_action(new Actions::JumpToTagEditor());
2780 insert_action(new Actions::JumpToPositionInSong());
2781 insert_action(new Actions::ReverseSelection());
2782 insert_action(new Actions::RemoveSelection());
2783 insert_action(new Actions::SelectAlbum());
2784 insert_action(new Actions::SelectFoundItems());
2785 insert_action(new Actions::AddSelectedItems());
2786 insert_action(new Actions::CropMainPlaylist());
2787 insert_action(new Actions::CropPlaylist());
2788 insert_action(new Actions::ClearMainPlaylist());
2789 insert_action(new Actions::ClearPlaylist());
2790 insert_action(new Actions::SortPlaylist());
2791 insert_action(new Actions::ReversePlaylist());
2792 insert_action(new Actions::ApplyFilter());
2793 insert_action(new Actions::Find());
2794 insert_action(new Actions::FindItemForward());
2795 insert_action(new Actions::FindItemBackward());
2796 insert_action(new Actions::NextFoundItem());
2797 insert_action(new Actions::PreviousFoundItem());
2798 insert_action(new Actions::ToggleFindMode());
2799 insert_action(new Actions::ToggleReplayGainMode());
2800 insert_action(new Actions::ToggleAddMode());
2801 insert_action(new Actions::ToggleMouse());
2802 insert_action(new Actions::ToggleBitrateVisibility());
2803 insert_action(new Actions::AddRandomItems());
2804 insert_action(new Actions::ToggleBrowserSortMode());
2805 insert_action(new Actions::ToggleLibraryTagType());
2806 insert_action(new Actions::ToggleMediaLibrarySortMode());
2807 insert_action(new Actions::RefetchLyrics());
2808 insert_action(new Actions::SetSelectedItemsPriority());
2809 insert_action(new Actions::ToggleOutput());
2810 insert_action(new Actions::ToggleVisualizationType());
2811 insert_action(new Actions::SetVisualizerSampleMultiplier());
2812 insert_action(new Actions::ShowSongInfo());
2813 insert_action(new Actions::ShowArtistInfo());
2814 insert_action(new Actions::ShowLyrics());
2815 insert_action(new Actions::Quit());
2816 insert_action(new Actions::NextScreen());
2817 insert_action(new Actions::PreviousScreen());
2818 insert_action(new Actions::ShowHelp());
2819 insert_action(new Actions::ShowPlaylist());
2820 insert_action(new Actions::ShowBrowser());
2821 insert_action(new Actions::ChangeBrowseMode());
2822 insert_action(new Actions::ShowSearchEngine());
2823 insert_action(new Actions::ResetSearchEngine());
2824 insert_action(new Actions::ShowMediaLibrary());
2825 insert_action(new Actions::ToggleMediaLibraryColumnsMode());
2826 insert_action(new Actions::ShowPlaylistEditor());
2827 insert_action(new Actions::ShowTagEditor());
2828 insert_action(new Actions::ShowOutputs());
2829 insert_action(new Actions::ShowVisualizer());
2830 insert_action(new Actions::ShowClock());
2831 insert_action(new Actions::ShowServerInfo());
2834 bool scrollTagCanBeRun(NC::List *&list, SongList *&songs)
2836 auto w = myScreen->activeWindow();
2837 if (list != static_cast<void *>(w))
2838 list = dynamic_cast<NC::List *>(w);
2839 if (songs != static_cast<void *>(w))
2840 songs = dynamic_cast<SongList *>(w);
2841 return list != nullptr && !list->empty()
2842 && songs != nullptr;
2845 void scrollTagUpRun(NC::List *list, SongList *songs, MPD::Song::GetFunction get)
2847 const auto front = songs->beginS();
2848 auto it = songs->currentS();
2849 if (auto *s = it->get<Bit::Song>())
2851 const std::string tag = s->getTags(get);
2852 while (it != front)
2854 --it;
2855 s = it->get<Bit::Song>();
2856 if (s == nullptr || s->getTags(get) != tag)
2857 break;
2859 list->highlight(it-front);
2863 void scrollTagDownRun(NC::List *list, SongList *songs, MPD::Song::GetFunction get)
2865 const auto front = songs->beginS(), back = --songs->endS();
2866 auto it = songs->currentS();
2867 if (auto *s = it->get<Bit::Song>())
2869 const std::string tag = s->getTags(get);
2870 while (it != back)
2872 ++it;
2873 s = it->get<Bit::Song>();
2874 if (s == nullptr || s->getTags(get) != tag)
2875 break;
2877 list->highlight(it-front);
2881 void seek()
2883 using Global::wHeader;
2884 using Global::wFooter;
2885 using Global::Timer;
2886 using Global::SeekingInProgress;
2888 if (!Status::State::totalTime())
2890 Statusbar::print("Unknown item length");
2891 return;
2894 Progressbar::ScopedLock progressbar_lock;
2895 Statusbar::ScopedLock statusbar_lock;
2897 unsigned songpos = Status::State::elapsedTime();
2898 auto t = Timer;
2900 int old_timeout = wFooter->getTimeout();
2901 wFooter->setTimeout(BaseScreen::defaultWindowTimeout);
2903 // Accept single action of a given type or action chain for which all actions
2904 // can be run and one of them is of the given type. This will still not work
2905 // in some contrived cases, but allows for more flexibility than accepting
2906 // single actions only.
2907 auto hasRunnableAction = [](BindingsConfiguration::BindingIteratorPair &bindings, Actions::Type type) {
2908 bool success = false;
2909 for (auto binding = bindings.first; binding != bindings.second; ++binding)
2911 auto &actions = binding->actions();
2912 for (const auto &action : actions)
2914 if (action->canBeRun())
2916 if (action->type() == type)
2917 success = true;
2919 else
2921 success = false;
2922 break;
2925 if (success)
2926 break;
2928 return success;
2931 SeekingInProgress = true;
2932 while (true)
2934 Status::trace();
2936 unsigned howmuch = Config.incremental_seeking
2937 ? (Timer-t).total_seconds()/2+Config.seek_time
2938 : Config.seek_time;
2940 NC::Key::Type input = readKey(*wFooter);
2942 auto k = Bindings.get(input);
2943 if (hasRunnableAction(k, Actions::Type::SeekForward))
2945 if (songpos < Status::State::totalTime())
2946 songpos = std::min(songpos + howmuch, Status::State::totalTime());
2948 else if (hasRunnableAction(k, Actions::Type::SeekBackward))
2950 if (songpos > 0)
2952 if (songpos < howmuch)
2953 songpos = 0;
2954 else
2955 songpos -= howmuch;
2958 else
2959 break;
2961 *wFooter << NC::Format::Bold;
2962 std::string tracklength;
2963 // FIXME: merge this with the code in status.cpp
2964 switch (Config.design)
2966 case Design::Classic:
2967 tracklength = " [";
2968 if (Config.display_remaining_time)
2970 tracklength += "-";
2971 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
2973 else
2974 tracklength += MPD::Song::ShowTime(songpos);
2975 tracklength += "/";
2976 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
2977 tracklength += "]";
2978 *wFooter << NC::XY(wFooter->getWidth()-tracklength.length(), 1) << tracklength;
2979 break;
2980 case Design::Alternative:
2981 if (Config.display_remaining_time)
2983 tracklength = "-";
2984 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
2986 else
2987 tracklength = MPD::Song::ShowTime(songpos);
2988 tracklength += "/";
2989 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
2990 *wHeader << NC::XY(0, 0) << tracklength << " ";
2991 wHeader->refresh();
2992 break;
2994 *wFooter << NC::Format::NoBold;
2995 Progressbar::draw(songpos, Status::State::totalTime());
2996 wFooter->refresh();
2998 SeekingInProgress = false;
2999 Mpd.Seek(Status::State::currentSongPosition(), songpos);
3001 wFooter->setTimeout(old_timeout);
3004 void findItem(const SearchDirection direction)
3006 using Global::wFooter;
3008 Searchable *w = dynamic_cast<Searchable *>(myScreen);
3009 assert(w != nullptr);
3010 assert(w->allowsSearching());
3012 std::string constraint = w->searchConstraint();
3015 Statusbar::ScopedLock slock;
3016 NC::Window::ScopedPromptHook prompt_hook(
3017 *wFooter,
3018 Statusbar::Helpers::FindImmediately(w, direction));
3019 Statusbar::put() << (boost::format("Find %1%: ") % direction).str();
3020 constraint = wFooter->prompt(constraint);
3022 catch (NC::PromptAborted &)
3024 w->setSearchConstraint(constraint);
3025 w->search(direction, Config.wrapped_search, false);
3026 throw;
3029 if (constraint.empty())
3031 Statusbar::printf("Constraint unset");
3032 w->clearSearchConstraint();
3034 else
3035 Statusbar::printf("Using constraint \"%1%\"", constraint);
3038 void listsChangeFinisher()
3040 if (myScreen == myLibrary
3041 || myScreen == myPlaylistEditor
3042 # ifdef HAVE_TAGLIB_H
3043 || myScreen == myTagEditor
3044 # endif // HAVE_TAGLIB_H
3047 if (myScreen->activeWindow() == &myLibrary->Tags)
3049 myLibrary->Albums.clear();
3050 myLibrary->Albums.refresh();
3051 myLibrary->Songs.clear();
3052 myLibrary->Songs.refresh();
3053 myLibrary->updateTimer();
3055 else if (myScreen->activeWindow() == &myLibrary->Albums)
3057 myLibrary->Songs.clear();
3058 myLibrary->Songs.refresh();
3059 myLibrary->updateTimer();
3061 else if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
3063 myPlaylistEditor->Content.clear();
3064 myPlaylistEditor->Content.refresh();
3065 myPlaylistEditor->updateTimer();
3067 # ifdef HAVE_TAGLIB_H
3068 else if (myScreen->activeWindow() == myTagEditor->Dirs)
3070 myTagEditor->Tags->clear();
3072 # endif // HAVE_TAGLIB_H