bindinds: add support for alt/ctrl/shift modifiers and escape key
[ncmpcpp.git] / src / actions.cpp
bloba2854f4c5a68295fc09879f4366a23b76f705b6c
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/filesystem/operations.hpp>
26 #include <boost/locale/conversion.hpp>
27 #include <boost/lexical_cast.hpp>
28 #include <algorithm>
29 #include <iostream>
30 #include <readline/readline.h>
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/string.h"
60 #include "utility/type_conversions.h"
61 #include "tag_editor.h"
62 #include "tiny_tag_editor.h"
63 #include "visualizer.h"
64 #include "title.h"
65 #include "tags.h"
67 #ifdef HAVE_TAGLIB_H
68 # include "fileref.h"
69 # include "tag.h"
70 #endif // HAVE_TAGLIB_H
72 using Global::myScreen;
74 namespace ph = std::placeholders;
76 namespace {
78 boost::array<
79 Actions::BaseAction *, static_cast<size_t>(Actions::Type::_numberOfActions)
80 > AvailableActions;
82 void populateActions();
84 bool scrollTagCanBeRun(NC::List *&list, SongList *&songs);
85 void scrollTagUpRun(NC::List *list, SongList *songs, MPD::Song::GetFunction get);
86 void scrollTagDownRun(NC::List *list, SongList *songs, MPD::Song::GetFunction get);
88 void seek();
89 void findItem(const SearchDirection direction);
90 void listsChangeFinisher();
94 namespace Actions {
96 bool OriginalStatusbarVisibility;
97 bool ExitMainLoop = false;
99 size_t HeaderHeight;
100 size_t FooterHeight;
101 size_t FooterStartY;
103 void validateScreenSize()
105 using Global::MainHeight;
107 if (COLS < 30 || MainHeight < 5)
109 NC::destroyScreen();
110 std::cout << "Screen is too small to handle ncmpcpp correctly\n";
111 exit(1);
115 void initializeScreens()
117 myHelp = new Help;
118 myPlaylist = new Playlist;
119 myBrowser = new Browser;
120 mySearcher = new SearchEngine;
121 myLibrary = new MediaLibrary;
122 myPlaylistEditor = new PlaylistEditor;
123 myLyrics = new Lyrics;
124 mySelectedItemsAdder = new SelectedItemsAdder;
125 mySongInfo = new SongInfo;
126 myServerInfo = new ServerInfo;
127 mySortPlaylistDialog = new SortPlaylistDialog;
129 # ifdef HAVE_CURL_CURL_H
130 myLastfm = new Lastfm;
131 # endif // HAVE_CURL_CURL_H
133 # ifdef HAVE_TAGLIB_H
134 myTinyTagEditor = new TinyTagEditor;
135 myTagEditor = new TagEditor;
136 # endif // HAVE_TAGLIB_H
138 # ifdef ENABLE_VISUALIZER
139 myVisualizer = new Visualizer;
140 # endif // ENABLE_VISUALIZER
142 # ifdef ENABLE_OUTPUTS
143 myOutputs = new Outputs;
144 # endif // ENABLE_OUTPUTS
146 # ifdef ENABLE_CLOCK
147 myClock = new Clock;
148 # endif // ENABLE_CLOCK
152 void setResizeFlags()
154 myHelp->hasToBeResized = 1;
155 myPlaylist->hasToBeResized = 1;
156 myBrowser->hasToBeResized = 1;
157 mySearcher->hasToBeResized = 1;
158 myLibrary->hasToBeResized = 1;
159 myPlaylistEditor->hasToBeResized = 1;
160 myLyrics->hasToBeResized = 1;
161 mySelectedItemsAdder->hasToBeResized = 1;
162 mySongInfo->hasToBeResized = 1;
163 myServerInfo->hasToBeResized = 1;
164 mySortPlaylistDialog->hasToBeResized = 1;
166 # ifdef HAVE_CURL_CURL_H
167 myLastfm->hasToBeResized = 1;
168 # endif // HAVE_CURL_CURL_H
170 # ifdef HAVE_TAGLIB_H
171 myTinyTagEditor->hasToBeResized = 1;
172 myTagEditor->hasToBeResized = 1;
173 # endif // HAVE_TAGLIB_H
175 # ifdef ENABLE_VISUALIZER
176 myVisualizer->hasToBeResized = 1;
177 # endif // ENABLE_VISUALIZER
179 # ifdef ENABLE_OUTPUTS
180 myOutputs->hasToBeResized = 1;
181 # endif // ENABLE_OUTPUTS
183 # ifdef ENABLE_CLOCK
184 myClock->hasToBeResized = 1;
185 # endif // ENABLE_CLOCK
188 void resizeScreen(bool reload_main_window)
190 using Global::MainHeight;
191 using Global::wHeader;
192 using Global::wFooter;
194 // update internal screen dimensions
195 if (reload_main_window)
197 rl_resize_terminal();
198 endwin();
199 refresh();
202 MainHeight = LINES-(Config.design == Design::Alternative ? 7 : 4);
204 validateScreenSize();
206 if (!Config.header_visibility)
207 MainHeight += 2;
208 if (!Config.statusbar_visibility)
209 ++MainHeight;
211 setResizeFlags();
213 applyToVisibleWindows(&BaseScreen::resize);
215 if (Config.header_visibility || Config.design == Design::Alternative)
216 wHeader->resize(COLS, HeaderHeight);
218 FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1);
219 wFooter->moveTo(0, FooterStartY);
220 wFooter->resize(COLS, Config.statusbar_visibility ? 2 : 1);
222 applyToVisibleWindows(&BaseScreen::refresh);
224 Status::Changes::elapsedTime(false);
225 Status::Changes::playerState();
226 // Note: routines for drawing separator if alternative user
227 // interface is active and header is hidden are placed in
228 // NcmpcppStatusChanges.StatusFlags
229 Status::Changes::flags();
230 drawHeader();
231 wFooter->refresh();
232 refresh();
235 void setWindowsDimensions()
237 using Global::MainStartY;
238 using Global::MainHeight;
240 MainStartY = Config.design == Design::Alternative ? 5 : 2;
241 MainHeight = LINES-(Config.design == Design::Alternative ? 7 : 4);
243 if (!Config.header_visibility)
245 MainStartY -= 2;
246 MainHeight += 2;
248 if (!Config.statusbar_visibility)
249 ++MainHeight;
251 HeaderHeight = Config.design == Design::Alternative ? (Config.header_visibility ? 5 : 3) : 1;
252 FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1);
253 FooterHeight = Config.statusbar_visibility ? 2 : 1;
256 void confirmAction(const boost::format &description)
258 Statusbar::ScopedLock slock;
259 Statusbar::put() << description.str()
260 << " [" << NC::Format::Bold << 'y' << NC::Format::NoBold
261 << '/' << NC::Format::Bold << 'n' << NC::Format::NoBold
262 << "] ";
263 auto answer = Statusbar::Helpers::promptReturnOneOf({"y", "n"});
264 if (answer == "n")
265 throw NC::PromptAborted(std::move(answer));
268 bool isMPDMusicDirSet()
270 if (Config.mpd_music_dir.empty())
272 Statusbar::print("Proper mpd_music_dir variable has to be set in configuration file");
273 return false;
275 return true;
278 BaseAction &get(Actions::Type at)
280 if (AvailableActions[1] == nullptr)
281 populateActions();
282 BaseAction *action = AvailableActions[static_cast<size_t>(at)];
283 // action should be always present if action type in queried
284 assert(action != nullptr);
285 return *action;
288 BaseAction *get(const std::string &name)
290 BaseAction *result = 0;
291 if (AvailableActions[1] == nullptr)
292 populateActions();
293 for (auto it = AvailableActions.begin(); it != AvailableActions.end(); ++it)
295 if (*it != nullptr && (*it)->name() == name)
297 result = *it;
298 break;
301 return result;
304 bool MouseEvent::canBeRun()
306 return Config.mouse_support;
309 void MouseEvent::run()
311 using Global::VolumeState;
312 using Global::wFooter;
314 m_old_mouse_event = m_mouse_event;
315 m_mouse_event = wFooter->getMouseEvent();
317 //Statusbar::printf("(%1%, %2%, %3%)", m_mouse_event.bstate, m_mouse_event.x, m_mouse_event.y);
319 if (m_mouse_event.bstate & BUTTON1_PRESSED
320 && m_mouse_event.y == LINES-(Config.statusbar_visibility ? 2 : 1)
321 ) // progressbar
323 if (Status::State::player() == MPD::psStop)
324 return;
325 Mpd.Seek(Status::State::currentSongPosition(),
326 Status::State::totalTime()*m_mouse_event.x/double(COLS));
328 else if (m_mouse_event.bstate & BUTTON1_PRESSED
329 && (Config.statusbar_visibility || Config.design == Design::Alternative)
330 && Status::State::player() != MPD::psStop
331 && m_mouse_event.y == (Config.design == Design::Alternative ? 1 : LINES-1)
332 && m_mouse_event.x < 9
333 ) // playing/paused
335 Mpd.Toggle();
337 else if ((m_mouse_event.bstate & BUTTON5_PRESSED || m_mouse_event.bstate & BUTTON4_PRESSED)
338 && (Config.header_visibility || Config.design == Design::Alternative)
339 && m_mouse_event.y == 0 && size_t(m_mouse_event.x) > COLS-VolumeState.length()
340 ) // volume
342 if (m_mouse_event.bstate & BUTTON5_PRESSED)
343 get(Type::VolumeDown).execute();
344 else
345 get(Type::VolumeUp).execute();
347 else if (m_mouse_event.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED | BUTTON4_PRESSED | BUTTON5_PRESSED))
348 myScreen->mouseButtonPressed(m_mouse_event);
351 void ScrollUp::run()
353 myScreen->scroll(NC::Scroll::Up);
354 listsChangeFinisher();
357 void ScrollDown::run()
359 myScreen->scroll(NC::Scroll::Down);
360 listsChangeFinisher();
363 bool ScrollUpArtist::canBeRun()
365 return scrollTagCanBeRun(m_list, m_songs);
368 void ScrollUpArtist::run()
370 scrollTagUpRun(m_list, m_songs, &MPD::Song::getArtist);
373 bool ScrollUpAlbum::canBeRun()
375 return scrollTagCanBeRun(m_list, m_songs);
378 void ScrollUpAlbum::run()
380 scrollTagUpRun(m_list, m_songs, &MPD::Song::getAlbum);
383 bool ScrollDownArtist::canBeRun()
385 return scrollTagCanBeRun(m_list, m_songs);
388 void ScrollDownArtist::run()
390 scrollTagDownRun(m_list, m_songs, &MPD::Song::getArtist);
393 bool ScrollDownAlbum::canBeRun()
395 return scrollTagCanBeRun(m_list, m_songs);
398 void ScrollDownAlbum::run()
400 scrollTagDownRun(m_list, m_songs, &MPD::Song::getAlbum);
403 void PageUp::run()
405 myScreen->scroll(NC::Scroll::PageUp);
406 listsChangeFinisher();
409 void PageDown::run()
411 myScreen->scroll(NC::Scroll::PageDown);
412 listsChangeFinisher();
415 void MoveHome::run()
417 myScreen->scroll(NC::Scroll::Home);
418 listsChangeFinisher();
421 void MoveEnd::run()
423 myScreen->scroll(NC::Scroll::End);
424 listsChangeFinisher();
427 void ToggleInterface::run()
429 switch (Config.design)
431 case Design::Classic:
432 Config.design = Design::Alternative;
433 Config.statusbar_visibility = false;
434 break;
435 case Design::Alternative:
436 Config.design = Design::Classic;
437 Config.statusbar_visibility = OriginalStatusbarVisibility;
438 break;
440 setWindowsDimensions();
441 resizeScreen(false);
442 // unlock progressbar
443 Progressbar::ScopedLock();
444 Status::Changes::mixer();
445 Status::Changes::elapsedTime(false);
446 Statusbar::printf("User interface: %1%", Config.design);
449 bool JumpToParentDirectory::canBeRun()
451 return (myScreen == myBrowser)
452 # ifdef HAVE_TAGLIB_H
453 || (myScreen->activeWindow() == myTagEditor->Dirs)
454 # endif // HAVE_TAGLIB_H
458 void JumpToParentDirectory::run()
460 if (myScreen == myBrowser)
462 if (!myBrowser->inRootDirectory())
464 myBrowser->main().reset();
465 myBrowser->enterPressed();
468 # ifdef HAVE_TAGLIB_H
469 else if (myScreen == myTagEditor)
471 if (myTagEditor->CurrentDir() != "/")
473 myTagEditor->Dirs->reset();
474 myTagEditor->enterPressed();
477 # endif // HAVE_TAGLIB_H
480 void PressEnter::run()
482 myScreen->enterPressed();
485 void PressSpace::run()
487 myScreen->spacePressed();
490 bool PreviousColumn::canBeRun()
492 auto hc = hasColumns(myScreen);
493 return hc && hc->previousColumnAvailable();
496 void PreviousColumn::run()
498 hasColumns(myScreen)->previousColumn();
501 bool NextColumn::canBeRun()
503 auto hc = hasColumns(myScreen);
504 return hc && hc->nextColumnAvailable();
507 void NextColumn::run()
509 hasColumns(myScreen)->nextColumn();
512 bool MasterScreen::canBeRun()
514 using Global::myLockedScreen;
515 using Global::myInactiveScreen;
517 return myLockedScreen
518 && myInactiveScreen
519 && myLockedScreen != myScreen
520 && myScreen->isMergable();
523 void MasterScreen::run()
525 using Global::myInactiveScreen;
526 using Global::myLockedScreen;
528 myInactiveScreen = myScreen;
529 myScreen = myLockedScreen;
530 drawHeader();
533 bool SlaveScreen::canBeRun()
535 using Global::myLockedScreen;
536 using Global::myInactiveScreen;
538 return myLockedScreen
539 && myInactiveScreen
540 && myLockedScreen == myScreen
541 && myScreen->isMergable();
544 void SlaveScreen::run()
546 using Global::myInactiveScreen;
547 using Global::myLockedScreen;
549 myScreen = myInactiveScreen;
550 myInactiveScreen = myLockedScreen;
551 drawHeader();
554 void VolumeUp::run()
556 int volume = std::min(Status::State::volume()+Config.volume_change_step, 100u);
557 Mpd.SetVolume(volume);
560 void VolumeDown::run()
562 int volume = std::max(int(Status::State::volume()-Config.volume_change_step), 0);
563 Mpd.SetVolume(volume);
566 bool DeletePlaylistItems::canBeRun()
568 return (myScreen == myPlaylist && !myPlaylist->main().empty())
569 || (myScreen->isActiveWindow(myPlaylistEditor->Content) && !myPlaylistEditor->Content.empty());
572 void DeletePlaylistItems::run()
574 if (myScreen == myPlaylist)
576 Statusbar::print("Deleting items...");
577 auto delete_fun = std::bind(&MPD::Connection::Delete, ph::_1, ph::_2);
578 deleteSelectedSongs(myPlaylist->main(), delete_fun);
579 Statusbar::print("Item(s) deleted");
581 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
583 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
584 auto delete_fun = std::bind(&MPD::Connection::PlaylistDelete, ph::_1, playlist, ph::_2);
585 Statusbar::print("Deleting items...");
586 deleteSelectedSongs(myPlaylistEditor->Content, delete_fun);
587 Statusbar::print("Item(s) deleted");
591 bool DeleteBrowserItems::canBeRun()
593 auto check_if_deletion_allowed = []() {
594 if (Config.allow_for_physical_item_deletion)
595 return true;
596 else
598 Statusbar::print("Flag \"allow_for_physical_item_deletion\" needs to be enabled in configuration file");
599 return false;
602 return myScreen == myBrowser
603 && !myBrowser->main().empty()
604 && isMPDMusicDirSet()
605 && check_if_deletion_allowed();
608 void DeleteBrowserItems::run()
610 auto get_name = [](const MPD::Item &item) -> std::string {
611 std::string iname;
612 switch (item.type())
614 case MPD::Item::Type::Directory:
615 iname = getBasename(item.directory().path());
616 break;
617 case MPD::Item::Type::Song:
618 iname = item.song().getName();
619 break;
620 case MPD::Item::Type::Playlist:
621 iname = getBasename(item.playlist().path());
622 break;
624 return iname;
627 boost::format question;
628 if (hasSelected(myBrowser->main().begin(), myBrowser->main().end()))
629 question = boost::format("Delete selected items?");
630 else
632 const auto &item = myBrowser->main().current()->value();
633 // parent directories are not accepted (and they
634 // can't be selected, so in other cases it's fine).
635 if (myBrowser->isParentDirectory(item))
636 return;
637 const char msg[] = "Delete \"%1%\"?";
638 question = boost::format(msg) % wideShorten(
639 get_name(item), COLS-const_strlen(msg)-5
642 confirmAction(question);
644 auto items = getSelectedOrCurrent(
645 myBrowser->main().begin(),
646 myBrowser->main().end(),
647 myBrowser->main().current()
649 for (const auto &item : items)
651 myBrowser->remove(item->value());
652 const char msg[] = "Deleted %1% \"%2%\"";
653 Statusbar::printf(msg,
654 itemTypeToString(item->value().type()),
655 wideShorten(get_name(item->value()), COLS-const_strlen(msg))
659 if (!myBrowser->isLocal())
660 Mpd.UpdateDirectory(myBrowser->currentDirectory());
661 myBrowser->requestUpdate();
664 bool DeleteStoredPlaylist::canBeRun()
666 return myScreen->isActiveWindow(myPlaylistEditor->Playlists);
669 void DeleteStoredPlaylist::run()
671 if (myPlaylistEditor->Playlists.empty())
672 return;
673 boost::format question;
674 if (hasSelected(myPlaylistEditor->Playlists.begin(), myPlaylistEditor->Playlists.end()))
675 question = boost::format("Delete selected playlists?");
676 else
677 question = boost::format("Delete playlist \"%1%\"?")
678 % wideShorten(myPlaylistEditor->Playlists.current()->value().path(), COLS-question.size()-10);
679 confirmAction(question);
680 auto list = getSelectedOrCurrent(
681 myPlaylistEditor->Playlists.begin(),
682 myPlaylistEditor->Playlists.end(),
683 myPlaylistEditor->Playlists.current()
685 for (const auto &item : list)
686 Mpd.DeletePlaylist(item->value().path());
687 Statusbar::printf("%1% deleted", list.size() == 1 ? "Playlist" : "Playlists");
688 // force playlists update. this happens automatically, but only after call
689 // to Key::read, therefore when we call PlaylistEditor::Update, it won't
690 // yet see it, so let's point that it needs to update it.
691 myPlaylistEditor->requestPlaylistsUpdate();
694 void ReplaySong::run()
696 if (Status::State::player() != MPD::psStop)
697 Mpd.Seek(Status::State::currentSongPosition(), 0);
700 void PreviousSong::run()
702 Mpd.Prev();
705 void NextSong::run()
707 Mpd.Next();
710 void Pause::run()
712 Mpd.Toggle();
715 void SavePlaylist::run()
717 using Global::wFooter;
719 std::string playlist_name;
721 Statusbar::ScopedLock slock;
722 Statusbar::put() << "Save playlist as: ";
723 playlist_name = wFooter->prompt();
727 Mpd.SavePlaylist(playlist_name);
728 Statusbar::printf("Playlist saved as \"%1%\"", playlist_name);
730 catch (MPD::ServerError &e)
732 if (e.code() == MPD_SERVER_ERROR_EXIST)
734 confirmAction(
735 boost::format("Playlist \"%1%\" already exists, overwrite?") % playlist_name
737 Mpd.DeletePlaylist(playlist_name);
738 Mpd.SavePlaylist(playlist_name);
739 Statusbar::print("Playlist overwritten");
741 else
742 throw;
746 void Stop::run()
748 Mpd.Stop();
751 void ExecuteCommand::run()
753 using Global::wFooter;
755 std::string cmd_name;
757 Statusbar::ScopedLock slock;
758 NC::Window::ScopedPromptHook helper(*wFooter,
759 Statusbar::Helpers::TryExecuteImmediateCommand()
761 Statusbar::put() << NC::Format::Bold << ":" << NC::Format::NoBold;
762 cmd_name = wFooter->prompt();
765 auto cmd = Bindings.findCommand(cmd_name);
766 if (cmd)
768 Statusbar::printf(1, "Executing %1%...", cmd_name);
769 bool res = cmd->binding().execute();
770 Statusbar::printf("Execution of command \"%1%\" %2%.",
771 cmd_name, res ? "successful" : "unsuccessful"
774 else
775 Statusbar::printf("No command named \"%1%\"", cmd_name);
778 bool MoveSortOrderUp::canBeRun()
780 return myScreen == mySortPlaylistDialog;
783 void MoveSortOrderUp::run()
785 mySortPlaylistDialog->moveSortOrderUp();
788 bool MoveSortOrderDown::canBeRun()
790 return myScreen == mySortPlaylistDialog;
793 void MoveSortOrderDown::run()
795 mySortPlaylistDialog->moveSortOrderDown();
798 bool MoveSelectedItemsUp::canBeRun()
800 return ((myScreen == myPlaylist
801 && !myPlaylist->main().empty())
802 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
803 && !myPlaylistEditor->Content.empty()));
806 void MoveSelectedItemsUp::run()
808 if (myScreen == myPlaylist)
810 moveSelectedItemsUp(myPlaylist->main(), std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
812 else if (myScreen == myPlaylistEditor)
814 assert(!myPlaylistEditor->Playlists.empty());
815 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
816 auto move_fun = std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3);
817 moveSelectedItemsUp(myPlaylistEditor->Content, move_fun);
821 bool MoveSelectedItemsDown::canBeRun()
823 return ((myScreen == myPlaylist
824 && !myPlaylist->main().empty())
825 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
826 && !myPlaylistEditor->Content.empty()));
829 void MoveSelectedItemsDown::run()
831 if (myScreen == myPlaylist)
833 moveSelectedItemsDown(myPlaylist->main(), std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
835 else if (myScreen == myPlaylistEditor)
837 assert(!myPlaylistEditor->Playlists.empty());
838 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
839 auto move_fun = std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3);
840 moveSelectedItemsDown(myPlaylistEditor->Content, move_fun);
844 bool MoveSelectedItemsTo::canBeRun()
846 return myScreen == myPlaylist
847 || myScreen->isActiveWindow(myPlaylistEditor->Content);
850 void MoveSelectedItemsTo::run()
852 if (myScreen == myPlaylist)
854 if (!myPlaylist->main().empty())
855 moveSelectedItemsTo(myPlaylist->main(), std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
857 else
859 assert(!myPlaylistEditor->Playlists.empty());
860 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
861 auto move_fun = std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3);
862 moveSelectedItemsTo(myPlaylistEditor->Content, move_fun);
866 bool Add::canBeRun()
868 return myScreen != myPlaylistEditor
869 || !myPlaylistEditor->Playlists.empty();
872 void Add::run()
874 using Global::wFooter;
876 std::string path;
878 Statusbar::ScopedLock slock;
879 Statusbar::put() << (myScreen == myPlaylistEditor ? "Add to playlist: " : "Add: ");
880 path = wFooter->prompt();
883 // confirm when one wants to add the whole database
884 if (path.empty())
885 confirmAction("Are you sure you want to add the whole database?");
887 Statusbar::put() << "Adding...";
888 wFooter->refresh();
889 if (myScreen == myPlaylistEditor)
890 Mpd.AddToPlaylist(myPlaylistEditor->Playlists.current()->value().path(), path);
891 else
895 Mpd.Add(path);
897 catch (MPD::ServerError &err)
899 // If a path is not a file or directory, assume it is a playlist.
900 if (err.code() == MPD_SERVER_ERROR_NO_EXIST)
901 Mpd.LoadPlaylist(path);
902 else
903 throw;
908 bool SeekForward::canBeRun()
910 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
913 void SeekForward::run()
915 seek();
918 bool SeekBackward::canBeRun()
920 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
923 void SeekBackward::run()
925 seek();
928 bool ToggleDisplayMode::canBeRun()
930 return myScreen == myPlaylist
931 || myScreen == myBrowser
932 || myScreen == mySearcher
933 || myScreen->isActiveWindow(myPlaylistEditor->Content);
936 void ToggleDisplayMode::run()
938 if (myScreen == myPlaylist)
940 switch (Config.playlist_display_mode)
942 case DisplayMode::Classic:
943 Config.playlist_display_mode = DisplayMode::Columns;
944 myPlaylist->main().setItemDisplayer(std::bind(
945 Display::SongsInColumns, ph::_1, std::cref(myPlaylist->main())
947 if (Config.titles_visibility)
948 myPlaylist->main().setTitle(Display::Columns(myPlaylist->main().getWidth()));
949 else
950 myPlaylist->main().setTitle("");
951 break;
952 case DisplayMode::Columns:
953 Config.playlist_display_mode = DisplayMode::Classic;
954 myPlaylist->main().setItemDisplayer(std::bind(
955 Display::Songs, ph::_1, std::cref(myPlaylist->main()), std::cref(Config.song_list_format)
957 myPlaylist->main().setTitle("");
959 Statusbar::printf("Playlist display mode: %1%", Config.playlist_display_mode);
961 else if (myScreen == myBrowser)
963 switch (Config.browser_display_mode)
965 case DisplayMode::Classic:
966 Config.browser_display_mode = DisplayMode::Columns;
967 if (Config.titles_visibility)
968 myBrowser->main().setTitle(Display::Columns(myBrowser->main().getWidth()));
969 else
970 myBrowser->main().setTitle("");
971 break;
972 case DisplayMode::Columns:
973 Config.browser_display_mode = DisplayMode::Classic;
974 myBrowser->main().setTitle("");
975 break;
977 Statusbar::printf("Browser display mode: %1%", Config.browser_display_mode);
979 else if (myScreen == mySearcher)
981 switch (Config.search_engine_display_mode)
983 case DisplayMode::Classic:
984 Config.search_engine_display_mode = DisplayMode::Columns;
985 break;
986 case DisplayMode::Columns:
987 Config.search_engine_display_mode = DisplayMode::Classic;
988 break;
990 Statusbar::printf("Search engine display mode: %1%", Config.search_engine_display_mode);
991 if (mySearcher->main().size() > SearchEngine::StaticOptions)
992 mySearcher->main().setTitle(
993 Config.search_engine_display_mode == DisplayMode::Columns
994 && Config.titles_visibility
995 ? Display::Columns(mySearcher->main().getWidth())
996 : ""
999 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
1001 switch (Config.playlist_editor_display_mode)
1003 case DisplayMode::Classic:
1004 Config.playlist_editor_display_mode = DisplayMode::Columns;
1005 myPlaylistEditor->Content.setItemDisplayer(std::bind(
1006 Display::SongsInColumns, ph::_1, std::cref(myPlaylistEditor->Content)
1008 break;
1009 case DisplayMode::Columns:
1010 Config.playlist_editor_display_mode = DisplayMode::Classic;
1011 myPlaylistEditor->Content.setItemDisplayer(std::bind(
1012 Display::Songs, ph::_1, std::cref(myPlaylistEditor->Content), std::cref(Config.song_list_format)
1014 break;
1016 Statusbar::printf("Playlist editor display mode: %1%", Config.playlist_editor_display_mode);
1020 bool ToggleSeparatorsBetweenAlbums::canBeRun()
1022 return true;
1025 void ToggleSeparatorsBetweenAlbums::run()
1027 Config.playlist_separate_albums = !Config.playlist_separate_albums;
1028 Statusbar::printf("Separators between albums: %1%",
1029 Config.playlist_separate_albums ? "on" : "off"
1033 #ifndef HAVE_CURL_CURL_H
1034 bool ToggleLyricsFetcher::canBeRun()
1036 return false;
1038 #endif // NOT HAVE_CURL_CURL_H
1040 void ToggleLyricsFetcher::run()
1042 # ifdef HAVE_CURL_CURL_H
1043 myLyrics->ToggleFetcher();
1044 # endif // HAVE_CURL_CURL_H
1047 #ifndef HAVE_CURL_CURL_H
1048 bool ToggleFetchingLyricsInBackground::canBeRun()
1050 return false;
1052 #endif // NOT HAVE_CURL_CURL_H
1054 void ToggleFetchingLyricsInBackground::run()
1056 # ifdef HAVE_CURL_CURL_H
1057 Config.fetch_lyrics_in_background = !Config.fetch_lyrics_in_background;
1058 Statusbar::printf("Fetching lyrics for playing songs in background: %1%",
1059 Config.fetch_lyrics_in_background ? "on" : "off"
1061 # endif // HAVE_CURL_CURL_H
1064 void TogglePlayingSongCentering::run()
1066 Config.autocenter_mode = !Config.autocenter_mode;
1067 Statusbar::printf("Centering playing song: %1%",
1068 Config.autocenter_mode ? "on" : "off"
1070 if (Config.autocenter_mode)
1072 auto s = myPlaylist->nowPlayingSong();
1073 if (!s.empty())
1074 myPlaylist->main().highlight(s.getPosition());
1078 void UpdateDatabase::run()
1080 if (myScreen == myBrowser)
1081 Mpd.UpdateDirectory(myBrowser->currentDirectory());
1082 # ifdef HAVE_TAGLIB_H
1083 else if (myScreen == myTagEditor)
1084 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1085 # endif // HAVE_TAGLIB_H
1086 else
1087 Mpd.UpdateDirectory("/");
1090 bool JumpToPlayingSong::canBeRun()
1092 return myScreen == myPlaylist
1093 || myScreen == myBrowser
1094 || myScreen == myLibrary;
1097 void JumpToPlayingSong::run()
1099 auto s = myPlaylist->nowPlayingSong();
1100 if (s.empty())
1101 return;
1102 if (myScreen == myPlaylist)
1104 myPlaylist->main().highlight(s.getPosition());
1106 else if (myScreen == myBrowser)
1108 myBrowser->locateSong(s);
1110 else if (myScreen == myLibrary)
1112 myLibrary->LocateSong(s);
1116 void ToggleRepeat::run()
1118 Mpd.SetRepeat(!Status::State::repeat());
1121 void Shuffle::run()
1123 auto begin = myPlaylist->main().begin(), end = myPlaylist->main().end();
1124 auto range = getSelectedRange(begin, end);
1125 Mpd.ShuffleRange(range.first-begin, range.second-begin);
1126 Statusbar::print("Range shuffled");
1129 void ToggleRandom::run()
1131 Mpd.SetRandom(!Status::State::random());
1134 bool StartSearching::canBeRun()
1136 return myScreen == mySearcher && !mySearcher->main()[0].isInactive();
1139 void StartSearching::run()
1141 mySearcher->main().highlight(SearchEngine::SearchButton);
1142 mySearcher->main().setHighlighting(0);
1143 mySearcher->main().refresh();
1144 mySearcher->main().setHighlighting(1);
1145 mySearcher->enterPressed();
1148 bool SaveTagChanges::canBeRun()
1150 # ifdef HAVE_TAGLIB_H
1151 return myScreen == myTinyTagEditor
1152 || myScreen->activeWindow() == myTagEditor->TagTypes;
1153 # else
1154 return false;
1155 # endif // HAVE_TAGLIB_H
1158 void SaveTagChanges::run()
1160 # ifdef HAVE_TAGLIB_H
1161 if (myScreen == myTinyTagEditor)
1163 myTinyTagEditor->main().highlight(myTinyTagEditor->main().size()-2); // Save
1164 myTinyTagEditor->enterPressed();
1166 else if (myScreen->activeWindow() == myTagEditor->TagTypes)
1168 myTagEditor->TagTypes->highlight(myTagEditor->TagTypes->size()-1); // Save
1169 myTagEditor->enterPressed();
1171 # endif // HAVE_TAGLIB_H
1174 void ToggleSingle::run()
1176 Mpd.SetSingle(!Status::State::single());
1179 void ToggleConsume::run()
1181 Mpd.SetConsume(!Status::State::consume());
1184 void ToggleCrossfade::run()
1186 Mpd.SetCrossfade(Status::State::crossfade() ? 0 : Config.crossfade_time);
1189 void SetCrossfade::run()
1191 using Global::wFooter;
1193 Statusbar::ScopedLock slock;
1194 Statusbar::put() << "Set crossfade to: ";
1195 auto crossfade = fromString<unsigned>(wFooter->prompt());
1196 lowerBoundCheck(crossfade, 0u);
1197 Config.crossfade_time = crossfade;
1198 Mpd.SetCrossfade(crossfade);
1201 void SetVolume::run()
1203 using Global::wFooter;
1205 unsigned volume;
1207 Statusbar::ScopedLock slock;
1208 Statusbar::put() << "Set volume to: ";
1209 volume = fromString<unsigned>(wFooter->prompt());
1210 boundsCheck(volume, 0u, 100u);
1211 Mpd.SetVolume(volume);
1213 Statusbar::printf("Volume set to %1%%%", volume);
1216 bool EditSong::canBeRun()
1218 # ifdef HAVE_TAGLIB_H
1219 m_song = currentSong(myScreen);
1220 return m_song != nullptr && isMPDMusicDirSet();
1221 # else
1222 return false;
1223 # endif // HAVE_TAGLIB_H
1226 void EditSong::run()
1228 # ifdef HAVE_TAGLIB_H
1229 myTinyTagEditor->SetEdited(*m_song);
1230 myTinyTagEditor->switchTo();
1231 # endif // HAVE_TAGLIB_H
1234 bool EditLibraryTag::canBeRun()
1236 # ifdef HAVE_TAGLIB_H
1237 return myScreen->isActiveWindow(myLibrary->Tags)
1238 && !myLibrary->Tags.empty()
1239 && isMPDMusicDirSet();
1240 # else
1241 return false;
1242 # endif // HAVE_TAGLIB_H
1245 void EditLibraryTag::run()
1247 # ifdef HAVE_TAGLIB_H
1248 using Global::wFooter;
1250 std::string new_tag;
1252 Statusbar::ScopedLock slock;
1253 Statusbar::put() << NC::Format::Bold << tagTypeToString(Config.media_lib_primary_tag) << NC::Format::NoBold << ": ";
1254 new_tag = wFooter->prompt(myLibrary->Tags.current()->value().tag());
1256 if (!new_tag.empty() && new_tag != myLibrary->Tags.current()->value().tag())
1258 Statusbar::print("Updating tags...");
1259 Mpd.StartSearch(true);
1260 Mpd.AddSearch(Config.media_lib_primary_tag, myLibrary->Tags.current()->value().tag());
1261 MPD::MutableSong::SetFunction set = tagTypeToSetFunction(Config.media_lib_primary_tag);
1262 assert(set);
1263 bool success = true;
1264 std::string dir_to_update;
1265 for (MPD::SongIterator s = Mpd.CommitSearchSongs(), end; s != end; ++s)
1267 MPD::MutableSong ms = std::move(*s);
1268 ms.setTags(set, new_tag);
1269 Statusbar::printf("Updating tags in \"%1%\"...", ms.getName());
1270 std::string path = Config.mpd_music_dir + ms.getURI();
1271 if (!Tags::write(ms))
1273 success = false;
1274 const char msg[] = "Error while updating tags in \"%1%\"";
1275 Statusbar::printf(msg, wideShorten(ms.getURI(), COLS-const_strlen(msg)));
1276 s.finish();
1277 break;
1279 if (dir_to_update.empty())
1280 dir_to_update = ms.getURI();
1281 else
1282 dir_to_update = getSharedDirectory(dir_to_update, ms.getURI());
1284 if (success)
1286 Mpd.UpdateDirectory(dir_to_update);
1287 Statusbar::print("Tags updated successfully");
1290 # endif // HAVE_TAGLIB_H
1293 bool EditLibraryAlbum::canBeRun()
1295 # ifdef HAVE_TAGLIB_H
1296 return myScreen->isActiveWindow(myLibrary->Albums)
1297 && !myLibrary->Albums.empty()
1298 && isMPDMusicDirSet();
1299 # else
1300 return false;
1301 # endif // HAVE_TAGLIB_H
1304 void EditLibraryAlbum::run()
1306 # ifdef HAVE_TAGLIB_H
1307 using Global::wFooter;
1308 // FIXME: merge this and EditLibraryTag. also, prompt on failure if user wants to continue
1309 std::string new_album;
1311 Statusbar::ScopedLock slock;
1312 Statusbar::put() << NC::Format::Bold << "Album: " << NC::Format::NoBold;
1313 new_album = wFooter->prompt(myLibrary->Albums.current()->value().entry().album());
1315 if (!new_album.empty() && new_album != myLibrary->Albums.current()->value().entry().album())
1317 bool success = 1;
1318 Statusbar::print("Updating tags...");
1319 for (size_t i = 0; i < myLibrary->Songs.size(); ++i)
1321 Statusbar::printf("Updating tags in \"%1%\"...", myLibrary->Songs[i].value().getName());
1322 std::string path = Config.mpd_music_dir + myLibrary->Songs[i].value().getURI();
1323 TagLib::FileRef f(path.c_str());
1324 if (f.isNull())
1326 const char msg[] = "Error while opening file \"%1%\"";
1327 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1328 success = 0;
1329 break;
1331 f.tag()->setAlbum(ToWString(new_album));
1332 if (!f.save())
1334 const char msg[] = "Error while writing tags in \"%1%\"";
1335 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1336 success = 0;
1337 break;
1340 if (success)
1342 Mpd.UpdateDirectory(getSharedDirectory(myLibrary->Songs.beginV(), myLibrary->Songs.endV()));
1343 Statusbar::print("Tags updated successfully");
1346 # endif // HAVE_TAGLIB_H
1349 bool EditDirectoryName::canBeRun()
1351 return ((myScreen == myBrowser
1352 && !myBrowser->main().empty()
1353 && myBrowser->main().current()->value().type() == MPD::Item::Type::Directory)
1354 # ifdef HAVE_TAGLIB_H
1355 || (myScreen->activeWindow() == myTagEditor->Dirs
1356 && !myTagEditor->Dirs->empty()
1357 && myTagEditor->Dirs->choice() > 0)
1358 # endif // HAVE_TAGLIB_H
1359 ) && isMPDMusicDirSet();
1362 void EditDirectoryName::run()
1364 using Global::wFooter;
1365 if (myScreen == myBrowser)
1367 std::string old_dir = myBrowser->main().current()->value().directory().path();
1368 std::string new_dir;
1370 Statusbar::ScopedLock slock;
1371 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1372 new_dir = wFooter->prompt(old_dir);
1374 if (!new_dir.empty() && new_dir != old_dir)
1376 std::string full_old_dir;
1377 if (!myBrowser->isLocal())
1378 full_old_dir += Config.mpd_music_dir;
1379 full_old_dir += old_dir;
1380 std::string full_new_dir;
1381 if (!myBrowser->isLocal())
1382 full_new_dir += Config.mpd_music_dir;
1383 full_new_dir += new_dir;
1384 boost::filesystem::rename(full_old_dir, full_new_dir);
1385 const char msg[] = "Directory renamed to \"%1%\"";
1386 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1387 if (!myBrowser->isLocal())
1388 Mpd.UpdateDirectory(getSharedDirectory(old_dir, new_dir));
1389 myBrowser->requestUpdate();
1392 # ifdef HAVE_TAGLIB_H
1393 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1395 std::string old_dir = myTagEditor->Dirs->current()->value().first, new_dir;
1397 Statusbar::ScopedLock slock;
1398 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1399 new_dir = wFooter->prompt(old_dir);
1401 if (!new_dir.empty() && new_dir != old_dir)
1403 std::string full_old_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + old_dir;
1404 std::string full_new_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + new_dir;
1405 if (rename(full_old_dir.c_str(), full_new_dir.c_str()) == 0)
1407 const char msg[] = "Directory renamed to \"%1%\"";
1408 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1409 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1411 else
1413 const char msg[] = "Couldn't rename \"%1%\": %2%";
1414 Statusbar::printf(msg, wideShorten(old_dir, COLS-const_strlen(msg)-25), strerror(errno));
1418 # endif // HAVE_TAGLIB_H
1421 bool EditPlaylistName::canBeRun()
1423 return (myScreen->isActiveWindow(myPlaylistEditor->Playlists)
1424 && !myPlaylistEditor->Playlists.empty())
1425 || (myScreen == myBrowser
1426 && !myBrowser->main().empty()
1427 && myBrowser->main().current()->value().type() == MPD::Item::Type::Playlist);
1430 void EditPlaylistName::run()
1432 using Global::wFooter;
1433 std::string old_name, new_name;
1434 if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
1435 old_name = myPlaylistEditor->Playlists.current()->value().path();
1436 else
1437 old_name = myBrowser->main().current()->value().playlist().path();
1439 Statusbar::ScopedLock slock;
1440 Statusbar::put() << NC::Format::Bold << "Playlist: " << NC::Format::NoBold;
1441 new_name = wFooter->prompt(old_name);
1443 if (!new_name.empty() && new_name != old_name)
1445 Mpd.Rename(old_name, new_name);
1446 const char msg[] = "Playlist renamed to \"%1%\"";
1447 Statusbar::printf(msg, wideShorten(new_name, COLS-const_strlen(msg)));
1451 bool EditLyrics::canBeRun()
1453 return myScreen == myLyrics;
1456 void EditLyrics::run()
1458 myLyrics->Edit();
1461 bool JumpToBrowser::canBeRun()
1463 m_song = currentSong(myScreen);
1464 return m_song != nullptr;
1467 void JumpToBrowser::run()
1469 myBrowser->locateSong(*m_song);
1472 bool JumpToMediaLibrary::canBeRun()
1474 m_song = currentSong(myScreen);
1475 return m_song != nullptr;
1478 void JumpToMediaLibrary::run()
1480 myLibrary->LocateSong(*m_song);
1483 bool JumpToPlaylistEditor::canBeRun()
1485 return myScreen == myBrowser
1486 && myBrowser->main().current()->value().type() == MPD::Item::Type::Playlist;
1489 void JumpToPlaylistEditor::run()
1491 myPlaylistEditor->Locate(myBrowser->main().current()->value().playlist());
1494 void ToggleScreenLock::run()
1496 using Global::wFooter;
1497 using Global::myLockedScreen;
1498 const char *msg_unlockable_screen = "Current screen can't be locked";
1499 if (myLockedScreen != nullptr)
1501 BaseScreen::unlock();
1502 Actions::setResizeFlags();
1503 myScreen->resize();
1504 Statusbar::print("Screen unlocked");
1506 else if (!myScreen->isLockable())
1508 Statusbar::print(msg_unlockable_screen);
1510 else
1512 unsigned part = Config.locked_screen_width_part*100;
1513 if (Config.ask_for_locked_screen_width_part)
1515 Statusbar::ScopedLock slock;
1516 Statusbar::put() << "% of the locked screen's width to be reserved (20-80): ";
1517 part = fromString<unsigned>(wFooter->prompt(boost::lexical_cast<std::string>(part)));
1519 boundsCheck(part, 20u, 80u);
1520 Config.locked_screen_width_part = part/100.0;
1521 if (myScreen->lock())
1522 Statusbar::printf("Screen locked (with %1%%% width)", part);
1523 else
1524 Statusbar::print(msg_unlockable_screen);
1528 bool JumpToTagEditor::canBeRun()
1530 # ifdef HAVE_TAGLIB_H
1531 m_song = currentSong(myScreen);
1532 return m_song != nullptr && isMPDMusicDirSet();
1533 # else
1534 return false;
1535 # endif // HAVE_TAGLIB_H
1538 void JumpToTagEditor::run()
1540 # ifdef HAVE_TAGLIB_H
1541 myTagEditor->LocateSong(*m_song);
1542 # endif // HAVE_TAGLIB_H
1545 bool JumpToPositionInSong::canBeRun()
1547 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1550 void JumpToPositionInSong::run()
1552 using Global::wFooter;
1554 const MPD::Song s = myPlaylist->nowPlayingSong();
1556 std::string spos;
1558 Statusbar::ScopedLock slock;
1559 Statusbar::put() << "Position to go (in %/m:ss/seconds(s)): ";
1560 spos = wFooter->prompt();
1563 boost::regex rx;
1564 boost::smatch what;
1565 if (boost::regex_match(spos, what, rx.assign("([0-9]+):([0-9]{2})"))) // mm:ss
1567 auto mins = fromString<unsigned>(what[1]);
1568 auto secs = fromString<unsigned>(what[2]);
1569 boundsCheck(secs, 0u, 60u);
1570 Mpd.Seek(s.getPosition(), mins * 60 + secs);
1572 else if (boost::regex_match(spos, what, rx.assign("([0-9]+)s"))) // position in seconds
1574 auto secs = fromString<unsigned>(what[1]);
1575 Mpd.Seek(s.getPosition(), secs);
1577 else if (boost::regex_match(spos, what, rx.assign("([0-9]+)[%]{0,1}"))) // position in %
1579 auto percent = fromString<unsigned>(what[1]);
1580 boundsCheck(percent, 0u, 100u);
1581 int secs = (percent * s.getDuration()) / 100.0;
1582 Mpd.Seek(s.getPosition(), secs);
1584 else
1585 Statusbar::print("Invalid format ([m]:[ss], [s]s, [%]%, [%] accepted)");
1588 bool SelectItem::canBeRun()
1590 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1591 return m_list != nullptr
1592 && !m_list->empty()
1593 && m_list->currentP()->isSelectable();
1596 void SelectItem::run()
1598 auto current = m_list->currentP();
1599 current->setSelected(!current->isSelected());
1600 myScreen->scroll(NC::Scroll::Down);
1601 listsChangeFinisher();
1604 bool ReverseSelection::canBeRun()
1606 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1607 return m_list != nullptr;
1610 void ReverseSelection::run()
1612 for (auto &p : *m_list)
1613 p.setSelected(!p.isSelected());
1614 Statusbar::print("Selection reversed");
1617 bool RemoveSelection::canBeRun()
1619 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1620 return m_list != nullptr;
1623 void RemoveSelection::run()
1625 for (auto &p : *m_list)
1626 p.setSelected(false);
1627 Statusbar::print("Selection removed");
1630 bool SelectAlbum::canBeRun()
1632 auto *w = myScreen->activeWindow();
1633 if (m_list != static_cast<void *>(w))
1634 m_list = dynamic_cast<NC::List *>(w);
1635 if (m_songs != static_cast<void *>(w))
1636 m_songs = dynamic_cast<SongList *>(w);
1637 return m_list != nullptr && !m_list->empty()
1638 && m_songs != nullptr;
1641 void SelectAlbum::run()
1643 const auto front = m_songs->beginS(), current = m_songs->currentS(), end = m_songs->endS();
1644 auto *s = current->get<Bit::Song>();
1645 if (s == nullptr)
1646 return;
1647 auto get = &MPD::Song::getAlbum;
1648 const std::string tag = s->getTags(get);
1649 // go up
1650 for (auto it = current; it != front;)
1652 --it;
1653 s = it->get<Bit::Song>();
1654 if (s == nullptr || s->getTags(get) != tag)
1655 break;
1656 it->get<Bit::Properties>().setSelected(true);
1658 // go down
1659 for (auto it = current;;)
1661 it->get<Bit::Properties>().setSelected(true);
1662 if (++it == end)
1663 break;
1664 s = it->get<Bit::Song>();
1665 if (s == nullptr || s->getTags(get) != tag)
1666 break;
1668 Statusbar::print("Album around cursor position selected");
1671 bool AddSelectedItems::canBeRun()
1673 return myScreen != mySelectedItemsAdder;
1676 void AddSelectedItems::run()
1678 mySelectedItemsAdder->switchTo();
1681 void CropMainPlaylist::run()
1683 auto &w = myPlaylist->main();
1684 // cropping doesn't make sense in this case
1685 if (w.size() <= 1)
1686 return;
1687 if (Config.ask_before_clearing_playlists)
1688 confirmAction("Do you really want to crop main playlist?");
1689 Statusbar::print("Cropping playlist...");
1690 selectCurrentIfNoneSelected(w);
1691 cropPlaylist(w, std::bind(&MPD::Connection::Delete, ph::_1, ph::_2));
1692 Statusbar::print("Playlist cropped");
1695 bool CropPlaylist::canBeRun()
1697 return myScreen == myPlaylistEditor;
1700 void CropPlaylist::run()
1702 auto &w = myPlaylistEditor->Content;
1703 // cropping doesn't make sense in this case
1704 if (w.size() <= 1)
1705 return;
1706 assert(!myPlaylistEditor->Playlists.empty());
1707 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
1708 if (Config.ask_before_clearing_playlists)
1709 confirmAction(boost::format("Do you really want to crop playlist \"%1%\"?") % playlist);
1710 selectCurrentIfNoneSelected(w);
1711 Statusbar::printf("Cropping playlist \"%1%\"...", playlist);
1712 cropPlaylist(w, std::bind(&MPD::Connection::PlaylistDelete, ph::_1, playlist, ph::_2));
1713 Statusbar::printf("Playlist \"%1%\" cropped", playlist);
1716 void ClearMainPlaylist::run()
1718 if (!myPlaylist->main().empty() && Config.ask_before_clearing_playlists)
1719 confirmAction("Do you really want to clear main playlist?");
1720 Mpd.ClearMainPlaylist();
1721 Statusbar::print("Playlist cleared");
1722 myPlaylist->main().reset();
1725 bool ClearPlaylist::canBeRun()
1727 return myScreen == myPlaylistEditor;
1730 void ClearPlaylist::run()
1732 if (myPlaylistEditor->Playlists.empty())
1733 return;
1734 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
1735 if (Config.ask_before_clearing_playlists)
1736 confirmAction(boost::format("Do you really want to clear playlist \"%1%\"?") % playlist);
1737 Mpd.ClearPlaylist(playlist);
1738 Statusbar::printf("Playlist \"%1%\" cleared", playlist);
1741 bool SortPlaylist::canBeRun()
1743 return myScreen == myPlaylist;
1746 void SortPlaylist::run()
1748 mySortPlaylistDialog->switchTo();
1751 bool ReversePlaylist::canBeRun()
1753 return myScreen == myPlaylist;
1756 void ReversePlaylist::run()
1758 myPlaylist->Reverse();
1761 bool Find::canBeRun()
1763 return myScreen == myHelp
1764 || myScreen == myLyrics
1765 # ifdef HAVE_CURL_CURL_H
1766 || myScreen == myLastfm
1767 # endif // HAVE_CURL_CURL_H
1771 void Find::run()
1773 using Global::wFooter;
1775 std::string token;
1777 Statusbar::ScopedLock slock;
1778 Statusbar::put() << "Find: ";
1779 token = wFooter->prompt();
1782 Statusbar::print("Searching...");
1783 auto s = static_cast<Screen<NC::Scrollpad> *>(myScreen);
1784 s->main().removeProperties();
1785 if (token.empty() || s->main().setProperties(NC::Format::Reverse, token, NC::Format::NoReverse))
1786 Statusbar::print("Done");
1787 else
1788 Statusbar::print("No matching patterns found");
1789 s->main().flush();
1792 bool FindItemBackward::canBeRun()
1794 auto w = dynamic_cast<Searchable *>(myScreen);
1795 return w && w->allowsSearching();
1798 void FindItemForward::run()
1800 findItem(SearchDirection::Forward);
1801 listsChangeFinisher();
1804 bool FindItemForward::canBeRun()
1806 auto w = dynamic_cast<Searchable *>(myScreen);
1807 return w && w->allowsSearching();
1810 void FindItemBackward::run()
1812 findItem(SearchDirection::Backward);
1813 listsChangeFinisher();
1816 bool NextFoundItem::canBeRun()
1818 return dynamic_cast<Searchable *>(myScreen);
1821 void NextFoundItem::run()
1823 Searchable *w = dynamic_cast<Searchable *>(myScreen);
1824 assert(w != nullptr);
1825 w->find(SearchDirection::Forward, Config.wrapped_search, true);
1826 listsChangeFinisher();
1829 bool PreviousFoundItem::canBeRun()
1831 return dynamic_cast<Searchable *>(myScreen);
1834 void PreviousFoundItem::run()
1836 Searchable *w = dynamic_cast<Searchable *>(myScreen);
1837 assert(w != nullptr);
1838 w->find(SearchDirection::Backward, Config.wrapped_search, true);
1839 listsChangeFinisher();
1842 void ToggleFindMode::run()
1844 Config.wrapped_search = !Config.wrapped_search;
1845 Statusbar::printf("Search mode: %1%",
1846 Config.wrapped_search ? "Wrapped" : "Normal"
1850 void ToggleReplayGainMode::run()
1852 using Global::wFooter;
1854 char rgm = 0;
1856 Statusbar::ScopedLock slock;
1857 Statusbar::put() << "Replay gain mode? "
1858 << "[" << NC::Format::Bold << 'o' << NC::Format::NoBold << "ff"
1859 << "/" << NC::Format::Bold << 't' << NC::Format::NoBold << "rack"
1860 << "/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "lbum"
1861 << "] ";
1862 rgm = Statusbar::Helpers::promptReturnOneOf({"t", "a", "o"})[0];
1864 switch (rgm)
1866 case 't':
1867 Mpd.SetReplayGainMode(MPD::rgmTrack);
1868 break;
1869 case 'a':
1870 Mpd.SetReplayGainMode(MPD::rgmAlbum);
1871 break;
1872 case 'o':
1873 Mpd.SetReplayGainMode(MPD::rgmOff);
1874 break;
1875 default: // impossible
1876 throw std::runtime_error(
1877 (boost::format("ToggleReplayGainMode: impossible case reached: %1%") % rgm).str()
1880 Statusbar::printf("Replay gain mode: %1%", Mpd.GetReplayGainMode());
1883 void ToggleAddMode::run()
1885 std::string mode_desc;
1886 switch (Config.space_add_mode)
1888 case SpaceAddMode::AddRemove:
1889 Config.space_add_mode = SpaceAddMode::AlwaysAdd;
1890 mode_desc = "always add an item to playlist";
1891 break;
1892 case SpaceAddMode::AlwaysAdd:
1893 Config.space_add_mode = SpaceAddMode::AddRemove;
1894 mode_desc = "add an item to playlist or remove if already added";
1895 break;
1897 Statusbar::printf("Add mode: %1%", mode_desc);
1900 void ToggleMouse::run()
1902 Config.mouse_support = !Config.mouse_support;
1903 if (Config.mouse_support)
1904 NC::Mouse::enable();
1905 else
1906 NC::Mouse::disable();
1907 Statusbar::printf("Mouse support %1%",
1908 Config.mouse_support ? "enabled" : "disabled"
1912 void ToggleBitrateVisibility::run()
1914 Config.display_bitrate = !Config.display_bitrate;
1915 Statusbar::printf("Bitrate visibility %1%",
1916 Config.display_bitrate ? "enabled" : "disabled"
1920 void AddRandomItems::run()
1922 using Global::wFooter;
1923 char rnd_type = 0;
1925 Statusbar::ScopedLock slock;
1926 Statusbar::put() << "Add random? "
1927 << "[" << NC::Format::Bold << 's' << NC::Format::NoBold << "ongs"
1928 << "/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtists"
1929 << "/" << "album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtists"
1930 << "/" << "al" << NC::Format::Bold << 'b' << NC::Format::NoBold << "ums"
1931 << "] ";
1932 rnd_type = Statusbar::Helpers::promptReturnOneOf({"s", "a", "A", "b"})[0];
1935 mpd_tag_type tag_type = MPD_TAG_ARTIST;
1936 std::string tag_type_str ;
1937 if (rnd_type != 's')
1939 tag_type = charToTagType(rnd_type);
1940 tag_type_str = boost::locale::to_lower(tagTypeToString(tag_type));
1942 else
1943 tag_type_str = "song";
1945 unsigned number;
1947 Statusbar::ScopedLock slock;
1948 Statusbar::put() << "Number of random " << tag_type_str << "s: ";
1949 number = fromString<unsigned>(wFooter->prompt());
1951 if (number && (rnd_type == 's' ? Mpd.AddRandomSongs(number) : Mpd.AddRandomTag(tag_type, number)))
1953 Statusbar::printf("%1% random %2%%3% added to playlist",
1954 number, tag_type_str, number == 1 ? "" : "s"
1959 bool ToggleBrowserSortMode::canBeRun()
1961 return myScreen == myBrowser;
1964 void ToggleBrowserSortMode::run()
1966 switch (Config.browser_sort_mode)
1968 case SortMode::Name:
1969 Config.browser_sort_mode = SortMode::ModificationTime;
1970 Statusbar::print("Sort songs by: modification time");
1971 break;
1972 case SortMode::ModificationTime:
1973 Config.browser_sort_mode = SortMode::CustomFormat;
1974 Statusbar::print("Sort songs by: custom format");
1975 break;
1976 case SortMode::CustomFormat:
1977 Config.browser_sort_mode = SortMode::NoOp;
1978 Statusbar::print("Do not sort songs");
1979 break;
1980 case SortMode::NoOp:
1981 Config.browser_sort_mode = SortMode::Name;
1982 Statusbar::print("Sort songs by: name");
1984 if (Config.browser_sort_mode != SortMode::NoOp)
1986 size_t sort_offset = myBrowser->inRootDirectory() ? 0 : 1;
1987 std::sort(myBrowser->main().begin()+sort_offset, myBrowser->main().end(),
1988 LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode)
1993 bool ToggleLibraryTagType::canBeRun()
1995 return (myScreen->isActiveWindow(myLibrary->Tags))
1996 || (myLibrary->Columns() == 2 && myScreen->isActiveWindow(myLibrary->Albums));
1999 void ToggleLibraryTagType::run()
2001 using Global::wFooter;
2003 char tag_type = 0;
2005 Statusbar::ScopedLock slock;
2006 Statusbar::put() << "Tag type? "
2007 << "[" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtist"
2008 << "/" << "album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtist"
2009 << "/" << NC::Format::Bold << 'y' << NC::Format::NoBold << "ear"
2010 << "/" << NC::Format::Bold << 'g' << NC::Format::NoBold << "enre"
2011 << "/" << NC::Format::Bold << 'c' << NC::Format::NoBold << "omposer"
2012 << "/" << NC::Format::Bold << 'p' << NC::Format::NoBold << "erformer"
2013 << "] ";
2014 tag_type = Statusbar::Helpers::promptReturnOneOf({"a", "A", "y", "g", "c", "p"})[0];
2016 mpd_tag_type new_tagitem = charToTagType(tag_type);
2017 if (new_tagitem != Config.media_lib_primary_tag)
2019 Config.media_lib_primary_tag = new_tagitem;
2020 std::string item_type = tagTypeToString(Config.media_lib_primary_tag);
2021 myLibrary->Tags.setTitle(Config.titles_visibility ? item_type + "s" : "");
2022 myLibrary->Tags.reset();
2023 item_type = boost::locale::to_lower(item_type);
2024 std::string and_mtime = Config.media_library_sort_by_mtime ?
2025 " and mtime" :
2027 if (myLibrary->Columns() == 2)
2029 myLibrary->Songs.clear();
2030 myLibrary->Albums.reset();
2031 myLibrary->Albums.clear();
2032 myLibrary->Albums.setTitle(Config.titles_visibility ? "Albums (sorted by " + item_type + and_mtime + ")" : "");
2033 myLibrary->Albums.display();
2035 else
2037 myLibrary->Tags.clear();
2038 myLibrary->Tags.display();
2040 Statusbar::printf("Switched to the list of %1%s", item_type);
2044 bool ToggleMediaLibrarySortMode::canBeRun()
2046 return myScreen == myLibrary;
2049 void ToggleMediaLibrarySortMode::run()
2051 myLibrary->toggleSortMode();
2054 bool RefetchLyrics::canBeRun()
2056 # ifdef HAVE_CURL_CURL_H
2057 return myScreen == myLyrics;
2058 # else
2059 return false;
2060 # endif // HAVE_CURL_CURL_H
2063 void RefetchLyrics::run()
2065 # ifdef HAVE_CURL_CURL_H
2066 myLyrics->Refetch();
2067 # endif // HAVE_CURL_CURL_H
2070 bool SetSelectedItemsPriority::canBeRun()
2072 if (Mpd.Version() < 17)
2074 Statusbar::print("Priorities are supported in MPD >= 0.17.0");
2075 return false;
2077 return myScreen == myPlaylist && !myPlaylist->main().empty();
2080 void SetSelectedItemsPriority::run()
2082 using Global::wFooter;
2084 unsigned prio;
2086 Statusbar::ScopedLock slock;
2087 Statusbar::put() << "Set priority [0-255]: ";
2088 prio = fromString<unsigned>(wFooter->prompt());
2089 boundsCheck(prio, 0u, 255u);
2091 myPlaylist->SetSelectedItemsPriority(prio);
2094 bool SetVisualizerSampleMultiplier::canBeRun()
2096 # ifdef ENABLE_VISUALIZER
2097 return myScreen == myVisualizer;
2098 # else
2099 return false;
2100 # endif // ENABLE_VISUALIZER
2103 void SetVisualizerSampleMultiplier::run()
2105 # ifdef ENABLE_VISUALIZER
2106 using Global::wFooter;
2108 double multiplier;
2110 Statusbar::ScopedLock slock;
2111 Statusbar::put() << "Set visualizer sample multiplier: ";
2112 multiplier = fromString<double>(wFooter->prompt());
2113 lowerBoundCheck(multiplier, 0.0);
2114 Config.visualizer_sample_multiplier = multiplier;
2116 Statusbar::printf("Visualizer sample multiplier set to %1%", multiplier);
2117 # endif // ENABLE_VISUALIZER
2120 void ShowSongInfo::run()
2122 mySongInfo->switchTo();
2125 bool ShowArtistInfo::canBeRun()
2127 #ifdef HAVE_CURL_CURL_H
2128 return myScreen == myLastfm
2129 || (myScreen->isActiveWindow(myLibrary->Tags)
2130 && !myLibrary->Tags.empty()
2131 && Config.media_lib_primary_tag == MPD_TAG_ARTIST)
2132 || currentSong(myScreen);
2133 # else
2134 return false;
2135 # endif // NOT HAVE_CURL_CURL_H
2138 void ShowArtistInfo::run()
2140 # ifdef HAVE_CURL_CURL_H
2141 if (myScreen == myLastfm)
2143 myLastfm->switchTo();
2144 return;
2147 std::string artist;
2148 if (myScreen->isActiveWindow(myLibrary->Tags))
2150 assert(!myLibrary->Tags.empty());
2151 assert(Config.media_lib_primary_tag == MPD_TAG_ARTIST);
2152 artist = myLibrary->Tags.current()->value().tag();
2154 else
2156 auto s = currentSong(myScreen);
2157 assert(s);
2158 artist = s->getArtist();
2161 if (!artist.empty())
2163 myLastfm->queueJob(LastFm::ArtistInfo(artist, Config.lastfm_preferred_language));
2164 myLastfm->switchTo();
2166 # endif // HAVE_CURL_CURL_H
2169 void ShowLyrics::run()
2171 myLyrics->switchTo();
2174 void Quit::run()
2176 ExitMainLoop = true;
2179 void NextScreen::run()
2181 if (Config.screen_switcher_previous)
2183 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2184 tababble->switchToPreviousScreen();
2186 else if (!Config.screen_sequence.empty())
2188 const auto &seq = Config.screen_sequence;
2189 auto screen_type = std::find(seq.begin(), seq.end(), myScreen->type());
2190 if (++screen_type == seq.end())
2191 toScreen(seq.front())->switchTo();
2192 else
2193 toScreen(*screen_type)->switchTo();
2197 void PreviousScreen::run()
2199 if (Config.screen_switcher_previous)
2201 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2202 tababble->switchToPreviousScreen();
2204 else if (!Config.screen_sequence.empty())
2206 const auto &seq = Config.screen_sequence;
2207 auto screen_type = std::find(seq.begin(), seq.end(), myScreen->type());
2208 if (screen_type == seq.begin())
2209 toScreen(seq.back())->switchTo();
2210 else
2211 toScreen(*--screen_type)->switchTo();
2215 bool ShowHelp::canBeRun()
2217 return myScreen != myHelp
2218 # ifdef HAVE_TAGLIB_H
2219 && myScreen != myTinyTagEditor
2220 # endif // HAVE_TAGLIB_H
2224 void ShowHelp::run()
2226 myHelp->switchTo();
2229 bool ShowPlaylist::canBeRun()
2231 return myScreen != myPlaylist
2232 # ifdef HAVE_TAGLIB_H
2233 && myScreen != myTinyTagEditor
2234 # endif // HAVE_TAGLIB_H
2238 void ShowPlaylist::run()
2240 myPlaylist->switchTo();
2243 bool ShowBrowser::canBeRun()
2245 return myScreen != myBrowser
2246 # ifdef HAVE_TAGLIB_H
2247 && myScreen != myTinyTagEditor
2248 # endif // HAVE_TAGLIB_H
2252 void ShowBrowser::run()
2254 myBrowser->switchTo();
2257 bool ChangeBrowseMode::canBeRun()
2259 return myScreen == myBrowser;
2262 void ChangeBrowseMode::run()
2264 myBrowser->changeBrowseMode();
2267 bool ShowSearchEngine::canBeRun()
2269 return myScreen != mySearcher
2270 # ifdef HAVE_TAGLIB_H
2271 && myScreen != myTinyTagEditor
2272 # endif // HAVE_TAGLIB_H
2276 void ShowSearchEngine::run()
2278 mySearcher->switchTo();
2281 bool ResetSearchEngine::canBeRun()
2283 return myScreen == mySearcher;
2286 void ResetSearchEngine::run()
2288 mySearcher->reset();
2291 bool ShowMediaLibrary::canBeRun()
2293 return myScreen != myLibrary
2294 # ifdef HAVE_TAGLIB_H
2295 && myScreen != myTinyTagEditor
2296 # endif // HAVE_TAGLIB_H
2300 void ShowMediaLibrary::run()
2302 myLibrary->switchTo();
2305 bool ToggleMediaLibraryColumnsMode::canBeRun()
2307 return myScreen == myLibrary;
2310 void ToggleMediaLibraryColumnsMode::run()
2312 myLibrary->toggleColumnsMode();
2313 myLibrary->refresh();
2316 bool ShowPlaylistEditor::canBeRun()
2318 return myScreen != myPlaylistEditor
2319 # ifdef HAVE_TAGLIB_H
2320 && myScreen != myTinyTagEditor
2321 # endif // HAVE_TAGLIB_H
2325 void ShowPlaylistEditor::run()
2327 myPlaylistEditor->switchTo();
2330 bool ShowTagEditor::canBeRun()
2332 # ifdef HAVE_TAGLIB_H
2333 return myScreen != myTagEditor
2334 && myScreen != myTinyTagEditor;
2335 # else
2336 return false;
2337 # endif // HAVE_TAGLIB_H
2340 void ShowTagEditor::run()
2342 # ifdef HAVE_TAGLIB_H
2343 if (isMPDMusicDirSet())
2344 myTagEditor->switchTo();
2345 # endif // HAVE_TAGLIB_H
2348 bool ShowOutputs::canBeRun()
2350 # ifdef ENABLE_OUTPUTS
2351 return myScreen != myOutputs
2352 # ifdef HAVE_TAGLIB_H
2353 && myScreen != myTinyTagEditor
2354 # endif // HAVE_TAGLIB_H
2356 # else
2357 return false;
2358 # endif // ENABLE_OUTPUTS
2361 void ShowOutputs::run()
2363 # ifdef ENABLE_OUTPUTS
2364 myOutputs->switchTo();
2365 # endif // ENABLE_OUTPUTS
2368 bool ShowVisualizer::canBeRun()
2370 # ifdef ENABLE_VISUALIZER
2371 return myScreen != myVisualizer
2372 # ifdef HAVE_TAGLIB_H
2373 && myScreen != myTinyTagEditor
2374 # endif // HAVE_TAGLIB_H
2376 # else
2377 return false;
2378 # endif // ENABLE_VISUALIZER
2381 void ShowVisualizer::run()
2383 # ifdef ENABLE_VISUALIZER
2384 myVisualizer->switchTo();
2385 # endif // ENABLE_VISUALIZER
2388 bool ShowClock::canBeRun()
2390 # ifdef ENABLE_CLOCK
2391 return myScreen != myClock
2392 # ifdef HAVE_TAGLIB_H
2393 && myScreen != myTinyTagEditor
2394 # endif // HAVE_TAGLIB_H
2396 # else
2397 return false;
2398 # endif // ENABLE_CLOCK
2401 void ShowClock::run()
2403 # ifdef ENABLE_CLOCK
2404 myClock->switchTo();
2405 # endif // ENABLE_CLOCK
2408 #ifdef HAVE_TAGLIB_H
2409 bool ShowServerInfo::canBeRun()
2411 return myScreen != myTinyTagEditor;
2413 #endif // HAVE_TAGLIB_H
2415 void ShowServerInfo::run()
2417 myServerInfo->switchTo();
2422 namespace {
2424 void populateActions()
2426 auto insert_action = [](Actions::BaseAction *a) {
2427 AvailableActions[static_cast<size_t>(a->type())] = a;
2429 insert_action(new Actions::Dummy());
2430 insert_action(new Actions::MouseEvent());
2431 insert_action(new Actions::ScrollUp());
2432 insert_action(new Actions::ScrollDown());
2433 insert_action(new Actions::ScrollUpArtist());
2434 insert_action(new Actions::ScrollUpAlbum());
2435 insert_action(new Actions::ScrollDownArtist());
2436 insert_action(new Actions::ScrollDownAlbum());
2437 insert_action(new Actions::PageUp());
2438 insert_action(new Actions::PageDown());
2439 insert_action(new Actions::MoveHome());
2440 insert_action(new Actions::MoveEnd());
2441 insert_action(new Actions::ToggleInterface());
2442 insert_action(new Actions::JumpToParentDirectory());
2443 insert_action(new Actions::PressEnter());
2444 insert_action(new Actions::PressSpace());
2445 insert_action(new Actions::SelectItem());
2446 insert_action(new Actions::PreviousColumn());
2447 insert_action(new Actions::NextColumn());
2448 insert_action(new Actions::MasterScreen());
2449 insert_action(new Actions::SlaveScreen());
2450 insert_action(new Actions::VolumeUp());
2451 insert_action(new Actions::VolumeDown());
2452 insert_action(new Actions::DeletePlaylistItems());
2453 insert_action(new Actions::DeleteStoredPlaylist());
2454 insert_action(new Actions::DeleteBrowserItems());
2455 insert_action(new Actions::ReplaySong());
2456 insert_action(new Actions::PreviousSong());
2457 insert_action(new Actions::NextSong());
2458 insert_action(new Actions::Pause());
2459 insert_action(new Actions::Stop());
2460 insert_action(new Actions::ExecuteCommand());
2461 insert_action(new Actions::SavePlaylist());
2462 insert_action(new Actions::MoveSortOrderUp());
2463 insert_action(new Actions::MoveSortOrderDown());
2464 insert_action(new Actions::MoveSelectedItemsUp());
2465 insert_action(new Actions::MoveSelectedItemsDown());
2466 insert_action(new Actions::MoveSelectedItemsTo());
2467 insert_action(new Actions::Add());
2468 insert_action(new Actions::SeekForward());
2469 insert_action(new Actions::SeekBackward());
2470 insert_action(new Actions::ToggleDisplayMode());
2471 insert_action(new Actions::ToggleSeparatorsBetweenAlbums());
2472 insert_action(new Actions::ToggleLyricsFetcher());
2473 insert_action(new Actions::ToggleFetchingLyricsInBackground());
2474 insert_action(new Actions::TogglePlayingSongCentering());
2475 insert_action(new Actions::UpdateDatabase());
2476 insert_action(new Actions::JumpToPlayingSong());
2477 insert_action(new Actions::ToggleRepeat());
2478 insert_action(new Actions::Shuffle());
2479 insert_action(new Actions::ToggleRandom());
2480 insert_action(new Actions::StartSearching());
2481 insert_action(new Actions::SaveTagChanges());
2482 insert_action(new Actions::ToggleSingle());
2483 insert_action(new Actions::ToggleConsume());
2484 insert_action(new Actions::ToggleCrossfade());
2485 insert_action(new Actions::SetCrossfade());
2486 insert_action(new Actions::SetVolume());
2487 insert_action(new Actions::EditSong());
2488 insert_action(new Actions::EditLibraryTag());
2489 insert_action(new Actions::EditLibraryAlbum());
2490 insert_action(new Actions::EditDirectoryName());
2491 insert_action(new Actions::EditPlaylistName());
2492 insert_action(new Actions::EditLyrics());
2493 insert_action(new Actions::JumpToBrowser());
2494 insert_action(new Actions::JumpToMediaLibrary());
2495 insert_action(new Actions::JumpToPlaylistEditor());
2496 insert_action(new Actions::ToggleScreenLock());
2497 insert_action(new Actions::JumpToTagEditor());
2498 insert_action(new Actions::JumpToPositionInSong());
2499 insert_action(new Actions::ReverseSelection());
2500 insert_action(new Actions::RemoveSelection());
2501 insert_action(new Actions::SelectAlbum());
2502 insert_action(new Actions::AddSelectedItems());
2503 insert_action(new Actions::CropMainPlaylist());
2504 insert_action(new Actions::CropPlaylist());
2505 insert_action(new Actions::ClearMainPlaylist());
2506 insert_action(new Actions::ClearPlaylist());
2507 insert_action(new Actions::SortPlaylist());
2508 insert_action(new Actions::ReversePlaylist());
2509 insert_action(new Actions::Find());
2510 insert_action(new Actions::FindItemForward());
2511 insert_action(new Actions::FindItemBackward());
2512 insert_action(new Actions::NextFoundItem());
2513 insert_action(new Actions::PreviousFoundItem());
2514 insert_action(new Actions::ToggleFindMode());
2515 insert_action(new Actions::ToggleReplayGainMode());
2516 insert_action(new Actions::ToggleAddMode());
2517 insert_action(new Actions::ToggleMouse());
2518 insert_action(new Actions::ToggleBitrateVisibility());
2519 insert_action(new Actions::AddRandomItems());
2520 insert_action(new Actions::ToggleBrowserSortMode());
2521 insert_action(new Actions::ToggleLibraryTagType());
2522 insert_action(new Actions::ToggleMediaLibrarySortMode());
2523 insert_action(new Actions::RefetchLyrics());
2524 insert_action(new Actions::SetSelectedItemsPriority());
2525 insert_action(new Actions::SetVisualizerSampleMultiplier());
2526 insert_action(new Actions::ShowSongInfo());
2527 insert_action(new Actions::ShowArtistInfo());
2528 insert_action(new Actions::ShowLyrics());
2529 insert_action(new Actions::Quit());
2530 insert_action(new Actions::NextScreen());
2531 insert_action(new Actions::PreviousScreen());
2532 insert_action(new Actions::ShowHelp());
2533 insert_action(new Actions::ShowPlaylist());
2534 insert_action(new Actions::ShowBrowser());
2535 insert_action(new Actions::ChangeBrowseMode());
2536 insert_action(new Actions::ShowSearchEngine());
2537 insert_action(new Actions::ResetSearchEngine());
2538 insert_action(new Actions::ShowMediaLibrary());
2539 insert_action(new Actions::ToggleMediaLibraryColumnsMode());
2540 insert_action(new Actions::ShowPlaylistEditor());
2541 insert_action(new Actions::ShowTagEditor());
2542 insert_action(new Actions::ShowOutputs());
2543 insert_action(new Actions::ShowVisualizer());
2544 insert_action(new Actions::ShowClock());
2545 insert_action(new Actions::ShowServerInfo());
2548 bool scrollTagCanBeRun(NC::List *&list, SongList *&songs)
2550 auto w = myScreen->activeWindow();
2551 if (list != static_cast<void *>(w))
2552 list = dynamic_cast<NC::List *>(w);
2553 if (songs != static_cast<void *>(w))
2554 songs = dynamic_cast<SongList *>(w);
2555 return list != nullptr && !list->empty()
2556 && songs != nullptr;
2559 void scrollTagUpRun(NC::List *list, SongList *songs, MPD::Song::GetFunction get)
2561 const auto front = songs->beginS();
2562 auto it = songs->currentS();
2563 if (auto *s = it->get<Bit::Song>())
2565 const std::string tag = s->getTags(get);
2566 while (it != front)
2568 --it;
2569 s = it->get<Bit::Song>();
2570 if (s == nullptr || s->getTags(get) != tag)
2571 break;
2573 list->highlight(it-front);
2577 void scrollTagDownRun(NC::List *list, SongList *songs, MPD::Song::GetFunction get)
2579 const auto front = songs->beginS(), back = --songs->endS();
2580 auto it = songs->currentS();
2581 if (auto *s = it->get<Bit::Song>())
2583 const std::string tag = s->getTags(get);
2584 while (it != back)
2586 ++it;
2587 s = it->get<Bit::Song>();
2588 if (s == nullptr || s->getTags(get) != tag)
2589 break;
2591 list->highlight(it-front);
2595 void seek()
2597 using Global::wHeader;
2598 using Global::wFooter;
2599 using Global::Timer;
2600 using Global::SeekingInProgress;
2602 if (!Status::State::totalTime())
2604 Statusbar::print("Unknown item length");
2605 return;
2608 Progressbar::ScopedLock progressbar_lock;
2609 Statusbar::ScopedLock statusbar_lock;
2611 unsigned songpos = Status::State::elapsedTime();
2612 auto t = Timer;
2614 int old_timeout = wFooter->getTimeout();
2615 wFooter->setTimeout(500);
2617 auto seekForward = &Actions::get(Actions::Type::SeekForward);
2618 auto seekBackward = &Actions::get(Actions::Type::SeekBackward);
2620 SeekingInProgress = true;
2621 while (true)
2623 Status::trace();
2625 unsigned howmuch = Config.incremental_seeking
2626 ? (Timer-t).total_seconds()/2+Config.seek_time
2627 : Config.seek_time;
2629 NC::Key::Type input = readKey(*wFooter);
2630 auto k = Bindings.get(input);
2631 if (k.first == k.second || !k.first->isSingle()) // no single action?
2632 break;
2633 auto a = k.first->action();
2634 if (a == seekForward)
2636 if (songpos < Status::State::totalTime())
2637 songpos = std::min(songpos + howmuch, Status::State::totalTime());
2639 else if (a == seekBackward)
2641 if (songpos > 0)
2643 if (songpos < howmuch)
2644 songpos = 0;
2645 else
2646 songpos -= howmuch;
2649 else
2650 break;
2652 *wFooter << NC::Format::Bold;
2653 std::string tracklength;
2654 // FIXME: merge this with the code in status.cpp
2655 switch (Config.design)
2657 case Design::Classic:
2658 tracklength = " [";
2659 if (Config.display_remaining_time)
2661 tracklength += "-";
2662 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
2664 else
2665 tracklength += MPD::Song::ShowTime(songpos);
2666 tracklength += "/";
2667 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
2668 tracklength += "]";
2669 *wFooter << NC::XY(wFooter->getWidth()-tracklength.length(), 1) << tracklength;
2670 break;
2671 case Design::Alternative:
2672 if (Config.display_remaining_time)
2674 tracklength = "-";
2675 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
2677 else
2678 tracklength = MPD::Song::ShowTime(songpos);
2679 tracklength += "/";
2680 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
2681 *wHeader << NC::XY(0, 0) << tracklength << " ";
2682 wHeader->refresh();
2683 break;
2685 *wFooter << NC::Format::NoBold;
2686 Progressbar::draw(songpos, Status::State::totalTime());
2687 wFooter->refresh();
2689 SeekingInProgress = false;
2690 Mpd.Seek(Status::State::currentSongPosition(), songpos);
2692 wFooter->setTimeout(old_timeout);
2695 void findItem(const SearchDirection direction)
2697 using Global::wFooter;
2699 Searchable *w = dynamic_cast<Searchable *>(myScreen);
2700 assert(w != nullptr);
2701 assert(w->allowsSearching());
2703 std::string constraint;
2705 Statusbar::ScopedLock slock;
2706 NC::Window::ScopedPromptHook prompt_hook(*wFooter,
2707 Statusbar::Helpers::FindImmediately(w, direction)
2709 Statusbar::put() << (boost::format("Find %1%: ") % direction).str();
2710 constraint = wFooter->prompt();
2715 if (constraint.empty())
2717 Statusbar::printf("Constraint unset");
2718 w->clearConstraint();
2720 else
2722 w->setSearchConstraint(constraint);
2723 Statusbar::printf("Using constraint \"%1%\"", constraint);
2726 catch (boost::bad_expression &e)
2728 Statusbar::printf("%1%", e.what());
2732 void listsChangeFinisher()
2734 if (myScreen == myLibrary
2735 || myScreen == myPlaylistEditor
2736 # ifdef HAVE_TAGLIB_H
2737 || myScreen == myTagEditor
2738 # endif // HAVE_TAGLIB_H
2741 if (myScreen->activeWindow() == &myLibrary->Tags)
2743 myLibrary->Albums.clear();
2744 myLibrary->Albums.refresh();
2745 myLibrary->Songs.clear();
2746 myLibrary->Songs.refresh();
2747 myLibrary->updateTimer();
2749 else if (myScreen->activeWindow() == &myLibrary->Albums)
2751 myLibrary->Songs.clear();
2752 myLibrary->Songs.refresh();
2753 myLibrary->updateTimer();
2755 else if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
2757 myPlaylistEditor->Content.clear();
2758 myPlaylistEditor->Content.refresh();
2759 myPlaylistEditor->updateTimer();
2761 # ifdef HAVE_TAGLIB_H
2762 else if (myScreen->activeWindow() == myTagEditor->Dirs)
2764 myTagEditor->Tags->clear();
2766 # endif // HAVE_TAGLIB_H