actions: jump to playing song: enter run() even if there is no playing song
[ncmpcpp.git] / src / actions.cpp
blobd743c5845dbe4d1fcd403a1b666a070fb0b5c25d
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/bind.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 "lastfm.h"
49 #include "lyrics.h"
50 #include "playlist.h"
51 #include "playlist_editor.h"
52 #include "sort_playlist.h"
53 #include "search_engine.h"
54 #include "sel_items_adder.h"
55 #include "server_info.h"
56 #include "song_info.h"
57 #include "outputs.h"
58 #include "utility/string.h"
59 #include "utility/type_conversions.h"
60 #include "tag_editor.h"
61 #include "tiny_tag_editor.h"
62 #include "visualizer.h"
63 #include "title.h"
64 #include "tags.h"
66 #ifdef HAVE_TAGLIB_H
67 # include "fileref.h"
68 # include "tag.h"
69 #endif // HAVE_TAGLIB_H
71 using Global::myScreen;
73 namespace {//
75 enum class Find { Forward, Backward };
77 boost::array<
78 Actions::BaseAction *, static_cast<size_t>(Actions::Type::_numberOfActions)
79 > AvailableActions;
81 void populateActions();
83 void seek();
84 void findItem(const Find direction);
85 void listsChangeFinisher();
89 namespace Actions {//
91 bool OriginalStatusbarVisibility;
92 bool ExitMainLoop = false;
94 size_t HeaderHeight;
95 size_t FooterHeight;
96 size_t FooterStartY;
98 void validateScreenSize()
100 using Global::MainHeight;
102 if (COLS < 30 || MainHeight < 5)
104 NC::destroyScreen();
105 std::cout << "Screen is too small to handle ncmpcpp correctly\n";
106 exit(1);
110 void initializeScreens()
112 myHelp = new Help;
113 myPlaylist = new Playlist;
114 myBrowser = new Browser;
115 mySearcher = new SearchEngine;
116 myLibrary = new MediaLibrary;
117 myPlaylistEditor = new PlaylistEditor;
118 myLyrics = new Lyrics;
119 mySelectedItemsAdder = new SelectedItemsAdder;
120 mySongInfo = new SongInfo;
121 myServerInfo = new ServerInfo;
122 mySortPlaylistDialog = new SortPlaylistDialog;
124 # ifdef HAVE_CURL_CURL_H
125 myLastfm = new Lastfm;
126 # endif // HAVE_CURL_CURL_H
128 # ifdef HAVE_TAGLIB_H
129 myTinyTagEditor = new TinyTagEditor;
130 myTagEditor = new TagEditor;
131 # endif // HAVE_TAGLIB_H
133 # ifdef ENABLE_VISUALIZER
134 myVisualizer = new Visualizer;
135 # endif // ENABLE_VISUALIZER
137 # ifdef ENABLE_OUTPUTS
138 myOutputs = new Outputs;
139 # endif // ENABLE_OUTPUTS
141 # ifdef ENABLE_CLOCK
142 myClock = new Clock;
143 # endif // ENABLE_CLOCK
147 void setResizeFlags()
149 myHelp->hasToBeResized = 1;
150 myPlaylist->hasToBeResized = 1;
151 myBrowser->hasToBeResized = 1;
152 mySearcher->hasToBeResized = 1;
153 myLibrary->hasToBeResized = 1;
154 myPlaylistEditor->hasToBeResized = 1;
155 myLyrics->hasToBeResized = 1;
156 mySelectedItemsAdder->hasToBeResized = 1;
157 mySongInfo->hasToBeResized = 1;
158 myServerInfo->hasToBeResized = 1;
159 mySortPlaylistDialog->hasToBeResized = 1;
161 # ifdef HAVE_CURL_CURL_H
162 myLastfm->hasToBeResized = 1;
163 # endif // HAVE_CURL_CURL_H
165 # ifdef HAVE_TAGLIB_H
166 myTinyTagEditor->hasToBeResized = 1;
167 myTagEditor->hasToBeResized = 1;
168 # endif // HAVE_TAGLIB_H
170 # ifdef ENABLE_VISUALIZER
171 myVisualizer->hasToBeResized = 1;
172 # endif // ENABLE_VISUALIZER
174 # ifdef ENABLE_OUTPUTS
175 myOutputs->hasToBeResized = 1;
176 # endif // ENABLE_OUTPUTS
178 # ifdef ENABLE_CLOCK
179 myClock->hasToBeResized = 1;
180 # endif // ENABLE_CLOCK
183 void resizeScreen(bool reload_main_window)
185 using Global::MainHeight;
186 using Global::wHeader;
187 using Global::wFooter;
189 # if defined(USE_PDCURSES)
190 resize_term(0, 0);
191 # else
192 // update internal screen dimensions
193 if (reload_main_window)
195 rl_resize_terminal();
196 endwin();
197 refresh();
199 # endif
201 MainHeight = LINES-(Config.design == Design::Alternative ? 7 : 4);
203 validateScreenSize();
205 if (!Config.header_visibility)
206 MainHeight += 2;
207 if (!Config.statusbar_visibility)
208 ++MainHeight;
210 setResizeFlags();
212 applyToVisibleWindows(&BaseScreen::resize);
214 if (Config.header_visibility || Config.design == Design::Alternative)
215 wHeader->resize(COLS, HeaderHeight);
217 FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1);
218 wFooter->moveTo(0, FooterStartY);
219 wFooter->resize(COLS, Config.statusbar_visibility ? 2 : 1);
221 applyToVisibleWindows(&BaseScreen::refresh);
223 Status::Changes::elapsedTime(false);
224 Status::Changes::playerState();
225 // Note: routines for drawing separator if alternative user
226 // interface is active and header is hidden are placed in
227 // NcmpcppStatusChanges.StatusFlags
228 Status::Changes::flags();
229 drawHeader();
230 wFooter->refresh();
231 refresh();
234 void setWindowsDimensions()
236 using Global::MainStartY;
237 using Global::MainHeight;
239 MainStartY = Config.design == Design::Alternative ? 5 : 2;
240 MainHeight = LINES-(Config.design == Design::Alternative ? 7 : 4);
242 if (!Config.header_visibility)
244 MainStartY -= 2;
245 MainHeight += 2;
247 if (!Config.statusbar_visibility)
248 ++MainHeight;
250 HeaderHeight = Config.design == Design::Alternative ? (Config.header_visibility ? 5 : 3) : 1;
251 FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1);
252 FooterHeight = Config.statusbar_visibility ? 2 : 1;
255 bool askYesNoQuestion(const boost::format &fmt, void (*callback)())
257 using Global::wFooter;
259 Statusbar::lock();
260 Statusbar::put() << fmt.str() << " [" << NC::Format::Bold << 'y' << NC::Format::NoBold << '/' << NC::Format::Bold << 'n' << NC::Format::NoBold << "]";
261 wFooter->refresh();
262 int answer = 0;
265 if (callback)
266 callback();
267 answer = wFooter->readKey();
269 while (answer != 'y' && answer != 'n');
270 Statusbar::unlock();
271 return answer == 'y';
274 bool isMPDMusicDirSet()
276 if (Config.mpd_music_dir.empty())
278 Statusbar::print("Proper mpd_music_dir variable has to be set in configuration file");
279 return false;
281 return true;
284 BaseAction &get(Actions::Type at)
286 if (AvailableActions[1] == nullptr)
287 populateActions();
288 BaseAction *action = AvailableActions[static_cast<size_t>(at)];
289 // action should be always present if action type in queried
290 assert(action != nullptr);
291 return *action;
294 BaseAction *get(const std::string &name)
296 BaseAction *result = 0;
297 if (AvailableActions[1] == nullptr)
298 populateActions();
299 for (auto it = AvailableActions.begin(); it != AvailableActions.end(); ++it)
301 if (*it != nullptr && (*it)->name() == name)
303 result = *it;
304 break;
307 return result;
310 bool MouseEvent::canBeRun() const
312 return Config.mouse_support;
315 void MouseEvent::run()
317 using Global::VolumeState;
319 m_old_mouse_event = m_mouse_event;
320 getmouse(&m_mouse_event);
321 // workaround shitty ncurses behavior introduced in >=5.8, when we mysteriously get
322 // a few times after ncmpcpp startup 2^27 code instead of BUTTON{1,3}_RELEASED. since that
323 // 2^27 thing shows constantly instead of BUTTON2_PRESSED, it was redefined to be recognized
324 // as BUTTON2_PRESSED. but clearly we don't want to trigger behavior bound to BUTTON2
325 // after BUTTON{1,3} was pressed. so, here is the workaround: if last event was BUTTON{1,3}_PRESSED,
326 // we MUST get BUTTON{1,3}_RELEASED afterwards. if we get BUTTON2_PRESSED, erroneus behavior
327 // is about to occur and we need to prevent that.
328 if (m_old_mouse_event.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED) && m_mouse_event.bstate & BUTTON2_PRESSED)
329 return;
330 if (m_mouse_event.bstate & BUTTON1_PRESSED
331 && m_mouse_event.y == LINES-(Config.statusbar_visibility ? 2 : 1)
332 ) // progressbar
334 if (Status::State::player() == MPD::psStop)
335 return;
336 Mpd.Seek(Status::State::currentSongPosition(),
337 Status::State::totalTime()*m_mouse_event.x/double(COLS));
339 else if (m_mouse_event.bstate & BUTTON1_PRESSED
340 && (Config.statusbar_visibility || Config.design == Design::Alternative)
341 && Status::State::player() != MPD::psStop
342 && m_mouse_event.y == (Config.design == Design::Alternative ? 1 : LINES-1)
343 && m_mouse_event.x < 9
344 ) // playing/paused
346 Mpd.Toggle();
348 else if ((m_mouse_event.bstate & BUTTON2_PRESSED || m_mouse_event.bstate & BUTTON4_PRESSED)
349 && (Config.header_visibility || Config.design == Design::Alternative)
350 && m_mouse_event.y == 0 && size_t(m_mouse_event.x) > COLS-VolumeState.length()
351 ) // volume
353 if (m_mouse_event.bstate & BUTTON2_PRESSED)
354 get(Type::VolumeDown).execute();
355 else
356 get(Type::VolumeUp).execute();
358 else if (m_mouse_event.bstate & (BUTTON1_PRESSED | BUTTON2_PRESSED | BUTTON3_PRESSED | BUTTON4_PRESSED))
359 myScreen->mouseButtonPressed(m_mouse_event);
362 void ScrollUp::run()
364 myScreen->scroll(NC::Scroll::Up);
365 listsChangeFinisher();
368 void ScrollDown::run()
370 myScreen->scroll(NC::Scroll::Down);
371 listsChangeFinisher();
374 bool ScrollUpArtist::canBeRun() const
376 return proxySongList(myScreen);
379 void ScrollUpArtist::run()
381 auto pl = proxySongList(myScreen);
382 assert(pl);
383 if (pl.empty())
384 return;
385 size_t pos = pl.choice();
386 if (MPD::Song *s = pl.getSong(pos))
388 std::string artist = s->getArtist();
389 while (pos > 0)
391 s = pl.getSong(--pos);
392 if (!s || s->getArtist() != artist)
393 break;
395 pl.highlight(pos);
399 bool ScrollUpAlbum::canBeRun() const
401 return proxySongList(myScreen);
404 void ScrollUpAlbum::run()
406 auto pl = proxySongList(myScreen);
407 assert(pl);
408 if (pl.empty())
409 return;
410 size_t pos = pl.choice();
411 if (MPD::Song *s = pl.getSong(pos))
413 std::string album = s->getAlbum();
414 while (pos > 0)
416 s = pl.getSong(--pos);
417 if (!s || s->getAlbum() != album)
418 break;
420 pl.highlight(pos);
424 bool ScrollDownArtist::canBeRun() const
426 return proxySongList(myScreen);
429 void ScrollDownArtist::run()
431 auto pl = proxySongList(myScreen);
432 assert(pl);
433 if (pl.empty())
434 return;
435 size_t pos = pl.choice();
436 if (MPD::Song *s = pl.getSong(pos))
438 std::string artist = s->getArtist();
439 while (pos < pl.size() - 1)
441 s = pl.getSong(++pos);
442 if (!s || s->getArtist() != artist)
443 break;
445 pl.highlight(pos);
449 bool ScrollDownAlbum::canBeRun() const
451 return proxySongList(myScreen);
454 void ScrollDownAlbum::run()
456 auto pl = proxySongList(myScreen);
457 assert(pl);
458 if (pl.empty())
459 return;
460 size_t pos = pl.choice();
461 if (MPD::Song *s = pl.getSong(pos))
463 std::string album = s->getAlbum();
464 while (pos < pl.size() - 1)
466 s = pl.getSong(++pos);
467 if (!s || s->getAlbum() != album)
468 break;
470 pl.highlight(pos);
474 void PageUp::run()
476 myScreen->scroll(NC::Scroll::PageUp);
477 listsChangeFinisher();
480 void PageDown::run()
482 myScreen->scroll(NC::Scroll::PageDown);
483 listsChangeFinisher();
486 void MoveHome::run()
488 myScreen->scroll(NC::Scroll::Home);
489 listsChangeFinisher();
492 void MoveEnd::run()
494 myScreen->scroll(NC::Scroll::End);
495 listsChangeFinisher();
498 void ToggleInterface::run()
500 switch (Config.design)
502 case Design::Classic:
503 Config.design = Design::Alternative;
504 Config.statusbar_visibility = false;
505 break;
506 case Design::Alternative:
507 Config.design = Design::Classic;
508 Config.statusbar_visibility = OriginalStatusbarVisibility;
509 break;
511 setWindowsDimensions();
512 Progressbar::unlock();
513 Statusbar::unlock();
514 resizeScreen(false);
515 Status::Changes::mixer();
516 Status::Changes::elapsedTime(false);
517 Statusbar::printf("User interface: %1%", Config.design);
520 bool JumpToParentDirectory::canBeRun() const
522 return (myScreen == myBrowser)
523 # ifdef HAVE_TAGLIB_H
524 || (myScreen->activeWindow() == myTagEditor->Dirs)
525 # endif // HAVE_TAGLIB_H
529 void JumpToParentDirectory::run()
531 if (myScreen == myBrowser)
533 if (myBrowser->CurrentDir() != "/")
535 myBrowser->main().reset();
536 myBrowser->enterPressed();
539 # ifdef HAVE_TAGLIB_H
540 else if (myScreen == myTagEditor)
542 if (myTagEditor->CurrentDir() != "/")
544 myTagEditor->Dirs->reset();
545 myTagEditor->enterPressed();
548 # endif // HAVE_TAGLIB_H
551 void PressEnter::run()
553 myScreen->enterPressed();
556 void PressSpace::run()
558 myScreen->spacePressed();
561 bool PreviousColumn::canBeRun() const
563 auto hc = hasColumns(myScreen);
564 return hc && hc->previousColumnAvailable();
567 void PreviousColumn::run()
569 hasColumns(myScreen)->previousColumn();
572 bool NextColumn::canBeRun() const
574 auto hc = hasColumns(myScreen);
575 return hc && hc->nextColumnAvailable();
578 void NextColumn::run()
580 hasColumns(myScreen)->nextColumn();
583 bool MasterScreen::canBeRun() const
585 using Global::myLockedScreen;
586 using Global::myInactiveScreen;
588 return myLockedScreen
589 && myInactiveScreen
590 && myLockedScreen != myScreen
591 && myScreen->isMergable();
594 void MasterScreen::run()
596 using Global::myInactiveScreen;
597 using Global::myLockedScreen;
599 myInactiveScreen = myScreen;
600 myScreen = myLockedScreen;
601 drawHeader();
604 bool SlaveScreen::canBeRun() const
606 using Global::myLockedScreen;
607 using Global::myInactiveScreen;
609 return myLockedScreen
610 && myInactiveScreen
611 && myLockedScreen == myScreen
612 && myScreen->isMergable();
615 void SlaveScreen::run()
617 using Global::myInactiveScreen;
618 using Global::myLockedScreen;
620 myScreen = myInactiveScreen;
621 myInactiveScreen = myLockedScreen;
622 drawHeader();
625 void VolumeUp::run()
627 int volume = std::min(Status::State::volume()+Config.volume_change_step, 100u);
628 Mpd.SetVolume(volume);
631 void VolumeDown::run()
633 int volume = std::max(int(Status::State::volume()-Config.volume_change_step), 0);
634 Mpd.SetVolume(volume);
637 bool DeletePlaylistItems::canBeRun() const
639 return (myScreen == myPlaylist && !myPlaylist->main().empty())
640 || (myScreen->isActiveWindow(myPlaylistEditor->Content) && !myPlaylistEditor->Content.empty());
643 void DeletePlaylistItems::run()
645 if (myScreen == myPlaylist)
647 Statusbar::print("Deleting items...");
648 auto delete_fun = boost::bind(&MPD::Connection::Delete, _1, _2);
649 deleteSelectedSongs(myPlaylist->main(), delete_fun);
650 Statusbar::print("Item(s) deleted");
652 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
654 std::string playlist = myPlaylistEditor->Playlists.current().value();
655 auto delete_fun = boost::bind(&MPD::Connection::PlaylistDelete, _1, playlist, _2);
656 Statusbar::print("Deleting items...");
657 deleteSelectedSongs(myPlaylistEditor->Content, delete_fun);
658 Statusbar::print("Item(s) deleted");
662 bool DeleteBrowserItems::canBeRun() const
664 auto check_if_deletion_allowed = []() {
665 if (Config.allow_for_physical_item_deletion)
666 return true;
667 else
669 Statusbar::print("Flag \"allow_for_physical_item_deletion\" needs to be enabled in configuration file");
670 return false;
673 return myScreen == myBrowser
674 && !myBrowser->main().empty()
675 && isMPDMusicDirSet()
676 && check_if_deletion_allowed();
679 void DeleteBrowserItems::run()
681 boost::format question;
682 if (hasSelected(myBrowser->main().begin(), myBrowser->main().end()))
683 question = boost::format("Delete selected items?");
684 else
686 MPD::Item &item = myBrowser->main().current().value();
687 std::string iname = item.type == MPD::itSong ? item.song->getName() : item.name;
688 question = boost::format("Delete %1% \"%2%\"?")
689 % itemTypeToString(item.type) % wideShorten(iname, COLS-question.size()-10);
691 bool yes = askYesNoQuestion(question, Status::trace);
692 if (yes)
694 bool success = true;
695 auto list = getSelectedOrCurrent(myBrowser->main().begin(), myBrowser->main().end(), myBrowser->main().currentI());
696 for (auto it = list.begin(); it != list.end(); ++it)
698 const MPD::Item &i = (*it)->value();
699 std::string iname = i.type == MPD::itSong ? i.song->getName() : i.name;
700 std::string errmsg;
701 if (myBrowser->deleteItem(i, errmsg))
703 const char msg[] = "\"%1%\" deleted";
704 Statusbar::printf(msg, wideShorten(iname, COLS-const_strlen(msg)));
706 else
708 Statusbar::print(errmsg);
709 success = false;
710 break;
713 if (success)
715 if (myBrowser->isLocal())
716 myBrowser->GetDirectory(myBrowser->CurrentDir());
717 else
718 Mpd.UpdateDirectory(myBrowser->CurrentDir());
721 else
722 Statusbar::print("Aborted");
725 bool DeleteStoredPlaylist::canBeRun() const
727 return myScreen->isActiveWindow(myPlaylistEditor->Playlists);
730 void DeleteStoredPlaylist::run()
732 if (myPlaylistEditor->Playlists.empty())
733 return;
734 boost::format question;
735 if (hasSelected(myPlaylistEditor->Playlists.begin(), myPlaylistEditor->Playlists.end()))
736 question = boost::format("Delete selected playlists?");
737 else
738 question = boost::format("Delete playlist \"%1%\"?")
739 % wideShorten(myPlaylistEditor->Playlists.current().value(), COLS-question.size()-10);
740 bool yes = askYesNoQuestion(question, Status::trace);
741 if (yes)
743 auto list = getSelectedOrCurrent(myPlaylistEditor->Playlists.begin(), myPlaylistEditor->Playlists.end(), myPlaylistEditor->Playlists.currentI());
744 for (auto it = list.begin(); it != list.end(); ++it)
745 Mpd.DeletePlaylist((*it)->value());
746 Statusbar::printf("%1% deleted", list.size() == 1 ? "Playlist" : "Playlists");
748 else
749 Statusbar::print("Aborted");
752 void ReplaySong::run()
754 if (Status::State::player() != MPD::psStop)
755 Mpd.Seek(Status::State::currentSongPosition(), 0);
758 void PreviousSong::run()
760 Mpd.Prev();
763 void NextSong::run()
765 Mpd.Next();
768 void Pause::run()
770 Mpd.Toggle();
773 void SavePlaylist::run()
775 using Global::wFooter;
777 Statusbar::lock();
778 Statusbar::put() << "Save playlist as: ";
779 std::string playlist_name = wFooter->getString();
780 Statusbar::unlock();
781 if (playlist_name.find("/") != std::string::npos)
783 Statusbar::print("Playlist name must not contain slashes");
784 return;
786 if (!playlist_name.empty())
788 if (myPlaylist->main().isFiltered())
790 Mpd.StartCommandsList();
791 for (size_t i = 0; i < myPlaylist->main().size(); ++i)
792 Mpd.AddToPlaylist(playlist_name, myPlaylist->main()[i].value());
793 Mpd.CommitCommandsList();
794 Statusbar::printf("Filtered items added to playlist \"%1%\"", playlist_name);
796 else
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 bool yes = askYesNoQuestion(
808 boost::format("Playlist \"%1%\" already exists, overwrite?") % playlist_name,
809 Status::trace
811 if (yes)
813 Mpd.DeletePlaylist(playlist_name);
814 Mpd.SavePlaylist(playlist_name);
815 Statusbar::print("Playlist overwritten");
817 else
818 Statusbar::print("Aborted");
819 if (myScreen == myPlaylist)
820 myPlaylist->EnableHighlighting();
822 else
823 throw e;
827 if (!myBrowser->isLocal()
828 && myBrowser->CurrentDir() == "/"
829 && !myBrowser->main().empty())
830 myBrowser->GetDirectory(myBrowser->CurrentDir());
833 void Stop::run()
835 Mpd.Stop();
838 void ExecuteCommand::run()
840 using Global::wFooter;
841 Statusbar::lock();
842 Statusbar::put() << NC::Format::Bold << ":" << NC::Format::NoBold;
843 wFooter->setGetStringHelper(Statusbar::Helpers::TryExecuteImmediateCommand());
844 std::string cmd_name = wFooter->getString();
845 wFooter->setGetStringHelper(Statusbar::Helpers::getString);
846 Statusbar::unlock();
847 if (cmd_name.empty())
848 return;
849 auto cmd = Bindings.findCommand(cmd_name);
850 if (cmd)
852 Statusbar::printf(1, "Executing %1%...", cmd_name);
853 bool res = cmd->binding().execute();
854 Statusbar::printf("Execution of command \"%1%\" %2%.",
855 cmd_name, res ? "successful" : "unsuccessful"
858 else
859 Statusbar::printf("No command named \"%1%\"", cmd_name);
862 bool MoveSortOrderUp::canBeRun() const
864 return myScreen == mySortPlaylistDialog;
867 void MoveSortOrderUp::run()
869 mySortPlaylistDialog->moveSortOrderUp();
872 bool MoveSortOrderDown::canBeRun() const
874 return myScreen == mySortPlaylistDialog;
877 void MoveSortOrderDown::run()
879 mySortPlaylistDialog->moveSortOrderDown();
882 bool MoveSelectedItemsUp::canBeRun() const
884 return ((myScreen == myPlaylist
885 && !myPlaylist->main().empty()
886 && !myPlaylist->isFiltered())
887 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
888 && !myPlaylistEditor->Content.empty()
889 && !myPlaylistEditor->isContentFiltered()));
892 void MoveSelectedItemsUp::run()
894 if (myScreen == myPlaylist)
896 moveSelectedItemsUp(myPlaylist->main(), boost::bind(&MPD::Connection::Move, _1, _2, _3));
898 else if (myScreen == myPlaylistEditor)
900 assert(!myPlaylistEditor->Playlists.empty());
901 std::string playlist = myPlaylistEditor->Playlists.current().value();
902 auto move_fun = boost::bind(&MPD::Connection::PlaylistMove, _1, playlist, _2, _3);
903 moveSelectedItemsUp(myPlaylistEditor->Content, move_fun);
907 bool MoveSelectedItemsDown::canBeRun() const
909 return ((myScreen == myPlaylist
910 && !myPlaylist->main().empty()
911 && !myPlaylist->isFiltered())
912 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
913 && !myPlaylistEditor->Content.empty()
914 && !myPlaylistEditor->isContentFiltered()));
917 void MoveSelectedItemsDown::run()
919 if (myScreen == myPlaylist)
921 moveSelectedItemsDown(myPlaylist->main(), boost::bind(&MPD::Connection::Move, _1, _2, _3));
923 else if (myScreen == myPlaylistEditor)
925 assert(!myPlaylistEditor->Playlists.empty());
926 std::string playlist = myPlaylistEditor->Playlists.current().value();
927 auto move_fun = boost::bind(&MPD::Connection::PlaylistMove, _1, playlist, _2, _3);
928 moveSelectedItemsDown(myPlaylistEditor->Content, move_fun);
932 bool MoveSelectedItemsTo::canBeRun() const
934 return myScreen == myPlaylist
935 || myScreen->isActiveWindow(myPlaylistEditor->Content);
938 void MoveSelectedItemsTo::run()
940 if (myScreen == myPlaylist)
942 if (!myPlaylist->main().empty())
943 moveSelectedItemsTo(myPlaylist->main(), boost::bind(&MPD::Connection::Move, _1, _2, _3));
945 else
947 assert(!myPlaylistEditor->Playlists.empty());
948 std::string playlist = myPlaylistEditor->Playlists.current().value();
949 auto move_fun = boost::bind(&MPD::Connection::PlaylistMove, _1, playlist, _2, _3);
950 moveSelectedItemsTo(myPlaylistEditor->Content, move_fun);
954 bool Add::canBeRun() const
956 return myScreen != myPlaylistEditor
957 || !myPlaylistEditor->Playlists.empty();
960 void Add::run()
962 using Global::wFooter;
964 Statusbar::lock();
965 Statusbar::put() << (myScreen == myPlaylistEditor ? "Add to playlist: " : "Add: ");
966 std::string path = wFooter->getString();
967 Statusbar::unlock();
968 if (!path.empty())
970 Statusbar::put() << "Adding...";
971 wFooter->refresh();
972 if (myScreen == myPlaylistEditor)
973 Mpd.AddToPlaylist(myPlaylistEditor->Playlists.current().value(), path);
974 else
976 const char lastfm_url[] = "lastfm://";
977 if (path.compare(0, const_strlen(lastfm_url), lastfm_url) == 0
978 || path.find(".asx", path.length()-4) != std::string::npos
979 || path.find(".cue", path.length()-4) != std::string::npos
980 || path.find(".m3u", path.length()-4) != std::string::npos
981 || path.find(".pls", path.length()-4) != std::string::npos
982 || path.find(".xspf", path.length()-5) != std::string::npos)
983 Mpd.LoadPlaylist(path);
984 else
985 Mpd.Add(path);
990 bool SeekForward::canBeRun() const
992 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
995 void SeekForward::run()
997 seek();
1000 bool SeekBackward::canBeRun() const
1002 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1005 void SeekBackward::run()
1007 seek();
1010 bool ToggleDisplayMode::canBeRun() const
1012 return myScreen == myPlaylist
1013 || myScreen == myBrowser
1014 || myScreen == mySearcher
1015 || myScreen->isActiveWindow(myPlaylistEditor->Content);
1018 void ToggleDisplayMode::run()
1020 if (myScreen == myPlaylist)
1022 switch (Config.playlist_display_mode)
1024 case DisplayMode::Classic:
1025 Config.playlist_display_mode = DisplayMode::Columns;
1026 myPlaylist->main().setItemDisplayer(boost::bind(
1027 Display::SongsInColumns, _1, myPlaylist->proxySongList()
1029 if (Config.titles_visibility)
1030 myPlaylist->main().setTitle(Display::Columns(myPlaylist->main().getWidth()));
1031 else
1032 myPlaylist->main().setTitle("");
1033 break;
1034 case DisplayMode::Columns:
1035 Config.playlist_display_mode = DisplayMode::Classic;
1036 myPlaylist->main().setItemDisplayer(boost::bind(
1037 Display::Songs, _1, myPlaylist->proxySongList(), Config.song_list_format
1039 myPlaylist->main().setTitle("");
1041 Statusbar::printf("Playlist display mode: %1%", Config.playlist_display_mode);
1043 else if (myScreen == myBrowser)
1045 switch (Config.browser_display_mode)
1047 case DisplayMode::Classic:
1048 Config.browser_display_mode = DisplayMode::Columns;
1049 if (Config.titles_visibility)
1050 myBrowser->main().setTitle(Display::Columns(myBrowser->main().getWidth()));
1051 else
1052 myBrowser->main().setTitle("");
1053 break;
1054 case DisplayMode::Columns:
1055 Config.browser_display_mode = DisplayMode::Classic;
1056 myBrowser->main().setTitle("");
1057 break;
1059 Statusbar::printf("Browser display mode: %1%", Config.browser_display_mode);
1061 else if (myScreen == mySearcher)
1063 switch (Config.search_engine_display_mode)
1065 case DisplayMode::Classic:
1066 Config.search_engine_display_mode = DisplayMode::Columns;
1067 break;
1068 case DisplayMode::Columns:
1069 Config.search_engine_display_mode = DisplayMode::Classic;
1070 break;
1072 Statusbar::printf("Search engine display mode: %1%", Config.search_engine_display_mode);
1073 if (mySearcher->main().size() > SearchEngine::StaticOptions)
1074 mySearcher->main().setTitle(
1075 Config.search_engine_display_mode == DisplayMode::Columns
1076 && Config.titles_visibility
1077 ? Display::Columns(mySearcher->main().getWidth())
1078 : ""
1081 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
1083 switch (Config.playlist_editor_display_mode)
1085 case DisplayMode::Classic:
1086 Config.playlist_editor_display_mode = DisplayMode::Columns;
1087 myPlaylistEditor->Content.setItemDisplayer(boost::bind(
1088 Display::SongsInColumns, _1, myPlaylistEditor->contentProxyList()
1090 break;
1091 case DisplayMode::Columns:
1092 Config.playlist_editor_display_mode = DisplayMode::Classic;
1093 myPlaylistEditor->Content.setItemDisplayer(boost::bind(
1094 Display::Songs, _1, myPlaylistEditor->contentProxyList(), Config.song_list_format
1096 break;
1098 Statusbar::printf("Playlist editor display mode: %1%", Config.playlist_editor_display_mode);
1102 bool ToggleSeparatorsBetweenAlbums::canBeRun() const
1104 return true;
1107 void ToggleSeparatorsBetweenAlbums::run()
1109 Config.playlist_separate_albums = !Config.playlist_separate_albums;
1110 Statusbar::printf("Separators between albums: %1%",
1111 Config.playlist_separate_albums ? "on" : "off"
1115 #ifndef HAVE_CURL_CURL_H
1116 bool ToggleLyricsFetcher::canBeRun() const
1118 return false;
1120 #endif // NOT HAVE_CURL_CURL_H
1122 void ToggleLyricsFetcher::run()
1124 # ifdef HAVE_CURL_CURL_H
1125 myLyrics->ToggleFetcher();
1126 # endif // HAVE_CURL_CURL_H
1129 #ifndef HAVE_CURL_CURL_H
1130 bool ToggleFetchingLyricsInBackground::canBeRun() const
1132 return false;
1134 #endif // NOT HAVE_CURL_CURL_H
1136 void ToggleFetchingLyricsInBackground::run()
1138 # ifdef HAVE_CURL_CURL_H
1139 Config.fetch_lyrics_in_background = !Config.fetch_lyrics_in_background;
1140 Statusbar::printf("Fetching lyrics for playing songs in background: %1%",
1141 Config.fetch_lyrics_in_background ? "on" : "off"
1143 # endif // HAVE_CURL_CURL_H
1146 void TogglePlayingSongCentering::run()
1148 Config.autocenter_mode = !Config.autocenter_mode;
1149 Statusbar::printf("Centering playing song: %1%",
1150 Config.autocenter_mode ? "on" : "off"
1152 if (Config.autocenter_mode && !myPlaylist->main().isFiltered())
1154 auto s = myPlaylist->nowPlayingSong();
1155 if (!s.empty())
1156 myPlaylist->main().highlight(s.getPosition());
1160 void UpdateDatabase::run()
1162 if (myScreen == myBrowser)
1163 Mpd.UpdateDirectory(myBrowser->CurrentDir());
1164 # ifdef HAVE_TAGLIB_H
1165 else if (myScreen == myTagEditor)
1166 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1167 # endif // HAVE_TAGLIB_H
1168 else
1169 Mpd.UpdateDirectory("/");
1172 bool JumpToPlayingSong::canBeRun() const
1174 return (myScreen == myPlaylist && !myPlaylist->isFiltered())
1175 || myScreen == myBrowser
1176 || myScreen == myLibrary;
1179 void JumpToPlayingSong::run()
1181 auto s = myPlaylist->nowPlayingSong();
1182 if (s.empty())
1183 return;
1184 if (myScreen == myPlaylist)
1186 myPlaylist->main().highlight(s.getPosition());
1188 else if (myScreen == myBrowser)
1190 myBrowser->LocateSong(s);
1191 drawHeader();
1193 else if (myScreen == myLibrary)
1195 myLibrary->LocateSong(s);
1199 void ToggleRepeat::run()
1201 Mpd.SetRepeat(!Status::State::repeat());
1204 void Shuffle::run()
1206 Mpd.Shuffle();
1209 void ToggleRandom::run()
1211 Mpd.SetRandom(!Status::State::random());
1214 bool StartSearching::canBeRun() const
1216 return myScreen == mySearcher && !mySearcher->main()[0].isInactive();
1219 void StartSearching::run()
1221 mySearcher->main().highlight(SearchEngine::SearchButton);
1222 mySearcher->main().setHighlighting(0);
1223 mySearcher->main().refresh();
1224 mySearcher->main().setHighlighting(1);
1225 mySearcher->enterPressed();
1228 bool SaveTagChanges::canBeRun() const
1230 # ifdef HAVE_TAGLIB_H
1231 return myScreen == myTinyTagEditor
1232 || myScreen->activeWindow() == myTagEditor->TagTypes;
1233 # else
1234 return false;
1235 # endif // HAVE_TAGLIB_H
1238 void SaveTagChanges::run()
1240 # ifdef HAVE_TAGLIB_H
1241 if (myScreen == myTinyTagEditor)
1243 myTinyTagEditor->main().highlight(myTinyTagEditor->main().size()-2); // Save
1244 myTinyTagEditor->enterPressed();
1246 else if (myScreen->activeWindow() == myTagEditor->TagTypes)
1248 myTagEditor->TagTypes->highlight(myTagEditor->TagTypes->size()-1); // Save
1249 myTagEditor->enterPressed();
1251 # endif // HAVE_TAGLIB_H
1254 void ToggleSingle::run()
1256 Mpd.SetSingle(!Status::State::single());
1259 void ToggleConsume::run()
1261 Mpd.SetConsume(!Status::State::consume());
1264 void ToggleCrossfade::run()
1266 Mpd.SetCrossfade(Status::State::crossfade() ? 0 : Config.crossfade_time);
1269 void SetCrossfade::run()
1271 using Global::wFooter;
1273 Statusbar::lock();
1274 Statusbar::put() << "Set crossfade to: ";
1275 std::string crossfade = wFooter->getString();
1276 Statusbar::unlock();
1277 int cf = fromString<unsigned>(crossfade);
1278 lowerBoundCheck(cf, 1);
1279 Config.crossfade_time = cf;
1280 Mpd.SetCrossfade(cf);
1283 void SetVolume::run()
1285 using Global::wFooter;
1287 Statusbar::lock();
1288 Statusbar::put() << "Set volume to: ";
1289 std::string strvolume = wFooter->getString();
1290 Statusbar::unlock();
1291 int volume = fromString<unsigned>(strvolume);
1292 boundsCheck(volume, 0, 100);
1293 Mpd.SetVolume(volume);
1294 Statusbar::printf("Volume set to %1%%%", volume);
1297 bool EditSong::canBeRun() const
1299 # ifdef HAVE_TAGLIB_H
1300 return currentSong(myScreen)
1301 && isMPDMusicDirSet();
1302 # else
1303 return false;
1304 # endif // HAVE_TAGLIB_H
1307 void EditSong::run()
1309 # ifdef HAVE_TAGLIB_H
1310 auto s = currentSong(myScreen);
1311 myTinyTagEditor->SetEdited(*s);
1312 myTinyTagEditor->switchTo();
1313 # endif // HAVE_TAGLIB_H
1316 bool EditLibraryTag::canBeRun() const
1318 # ifdef HAVE_TAGLIB_H
1319 return myScreen->isActiveWindow(myLibrary->Tags)
1320 && !myLibrary->Tags.empty()
1321 && isMPDMusicDirSet();
1322 # else
1323 return false;
1324 # endif // HAVE_TAGLIB_H
1327 void EditLibraryTag::run()
1329 # ifdef HAVE_TAGLIB_H
1330 using Global::wFooter;
1332 Statusbar::lock();
1333 Statusbar::put() << NC::Format::Bold << tagTypeToString(Config.media_lib_primary_tag) << NC::Format::NoBold << ": ";
1334 std::string new_tag = wFooter->getString(myLibrary->Tags.current().value().tag());
1335 Statusbar::unlock();
1336 if (!new_tag.empty() && new_tag != myLibrary->Tags.current().value().tag())
1338 Statusbar::print("Updating tags...");
1339 Mpd.StartSearch(1);
1340 Mpd.AddSearch(Config.media_lib_primary_tag, myLibrary->Tags.current().value().tag());
1341 MPD::MutableSong::SetFunction set = tagTypeToSetFunction(Config.media_lib_primary_tag);
1342 assert(set);
1343 bool success = true;
1344 std::string dir_to_update;
1345 Mpd.CommitSearchSongs([set, &dir_to_update, &new_tag, &success](MPD::Song s) {
1346 if (!success)
1347 return;
1348 MPD::MutableSong ms = s;
1349 ms.setTags(set, new_tag, Config.tags_separator);
1350 Statusbar::printf("Updating tags in \"%1%\"...", ms.getName());
1351 std::string path = Config.mpd_music_dir + ms.getURI();
1352 if (!Tags::write(ms))
1354 const char msg[] = "Error while updating tags in \"%1%\"";
1355 Statusbar::printf(msg, wideShorten(ms.getURI(), COLS-const_strlen(msg)));
1356 success = false;
1358 if (dir_to_update.empty())
1359 dir_to_update = s.getURI();
1360 else
1361 dir_to_update = getSharedDirectory(dir_to_update, s.getURI());
1363 if (success)
1365 Mpd.UpdateDirectory(dir_to_update);
1366 Statusbar::print("Tags updated successfully");
1369 # endif // HAVE_TAGLIB_H
1372 bool EditLibraryAlbum::canBeRun() const
1374 # ifdef HAVE_TAGLIB_H
1375 return myScreen->isActiveWindow(myLibrary->Albums)
1376 && !myLibrary->Albums.empty()
1377 && isMPDMusicDirSet();
1378 # else
1379 return false;
1380 # endif // HAVE_TAGLIB_H
1383 void EditLibraryAlbum::run()
1385 # ifdef HAVE_TAGLIB_H
1386 using Global::wFooter;
1388 Statusbar::lock();
1389 Statusbar::put() << NC::Format::Bold << "Album: " << NC::Format::NoBold;
1390 std::string new_album = wFooter->getString(myLibrary->Albums.current().value().entry().album());
1391 Statusbar::unlock();
1392 if (!new_album.empty() && new_album != myLibrary->Albums.current().value().entry().album())
1394 bool success = 1;
1395 Statusbar::print("Updating tags...");
1396 for (size_t i = 0; i < myLibrary->Songs.size(); ++i)
1398 Statusbar::printf("Updating tags in \"%1%\"...", myLibrary->Songs[i].value().getName());
1399 std::string path = Config.mpd_music_dir + myLibrary->Songs[i].value().getURI();
1400 TagLib::FileRef f(path.c_str());
1401 if (f.isNull())
1403 const char msg[] = "Error while opening file \"%1%\"";
1404 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1405 success = 0;
1406 break;
1408 f.tag()->setAlbum(ToWString(new_album));
1409 if (!f.save())
1411 const char msg[] = "Error while writing tags in \"%1%\"";
1412 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1413 success = 0;
1414 break;
1417 if (success)
1419 Mpd.UpdateDirectory(getSharedDirectory(myLibrary->Songs.beginV(), myLibrary->Songs.endV()));
1420 Statusbar::print("Tags updated successfully");
1423 # endif // HAVE_TAGLIB_H
1426 bool EditDirectoryName::canBeRun() const
1428 return ((myScreen == myBrowser
1429 && !myBrowser->main().empty()
1430 && myBrowser->main().current().value().type == MPD::itDirectory)
1431 # ifdef HAVE_TAGLIB_H
1432 || (myScreen->activeWindow() == myTagEditor->Dirs
1433 && !myTagEditor->Dirs->empty()
1434 && myTagEditor->Dirs->choice() > 0)
1435 # endif // HAVE_TAGLIB_H
1436 ) && isMPDMusicDirSet();
1439 void EditDirectoryName::run()
1441 using Global::wFooter;
1443 if (myScreen == myBrowser)
1445 std::string old_dir = myBrowser->main().current().value().name;
1446 Statusbar::lock();
1447 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1448 std::string new_dir = wFooter->getString(old_dir);
1449 Statusbar::unlock();
1450 if (!new_dir.empty() && new_dir != old_dir)
1452 std::string full_old_dir;
1453 if (!myBrowser->isLocal())
1454 full_old_dir += Config.mpd_music_dir;
1455 full_old_dir += old_dir;
1456 std::string full_new_dir;
1457 if (!myBrowser->isLocal())
1458 full_new_dir += Config.mpd_music_dir;
1459 full_new_dir += new_dir;
1460 int rename_result = rename(full_old_dir.c_str(), full_new_dir.c_str());
1461 if (rename_result == 0)
1463 const char msg[] = "Directory renamed to \"%1%\"";
1464 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1465 if (!myBrowser->isLocal())
1466 Mpd.UpdateDirectory(getSharedDirectory(old_dir, new_dir));
1467 myBrowser->GetDirectory(myBrowser->CurrentDir());
1469 else
1471 const char msg[] = "Couldn't rename \"%1%\": %s";
1472 Statusbar::printf(msg, wideShorten(old_dir, COLS-const_strlen(msg)-25), strerror(errno));
1476 # ifdef HAVE_TAGLIB_H
1477 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1479 std::string old_dir = myTagEditor->Dirs->current().value().first;
1480 Statusbar::lock();
1481 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1482 std::string new_dir = wFooter->getString(old_dir);
1483 Statusbar::unlock();
1484 if (!new_dir.empty() && new_dir != old_dir)
1486 std::string full_old_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + old_dir;
1487 std::string full_new_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + new_dir;
1488 if (rename(full_old_dir.c_str(), full_new_dir.c_str()) == 0)
1490 const char msg[] = "Directory renamed to \"%1%\"";
1491 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1492 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1494 else
1496 const char msg[] = "Couldn't rename \"%1%\": %2%";
1497 Statusbar::printf(msg, wideShorten(old_dir, COLS-const_strlen(msg)-25), strerror(errno));
1501 # endif // HAVE_TAGLIB_H
1504 bool EditPlaylistName::canBeRun() const
1506 return (myScreen->isActiveWindow(myPlaylistEditor->Playlists)
1507 && !myPlaylistEditor->Playlists.empty())
1508 || (myScreen == myBrowser
1509 && !myBrowser->main().empty()
1510 && myBrowser->main().current().value().type == MPD::itPlaylist);
1513 void EditPlaylistName::run()
1515 using Global::wFooter;
1517 std::string old_name;
1518 if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
1519 old_name = myPlaylistEditor->Playlists.current().value();
1520 else
1521 old_name = myBrowser->main().current().value().name;
1522 Statusbar::lock();
1523 Statusbar::put() << NC::Format::Bold << "Playlist: " << NC::Format::NoBold;
1524 std::string new_name = wFooter->getString(old_name);
1525 Statusbar::unlock();
1526 if (!new_name.empty() && new_name != old_name)
1528 Mpd.Rename(old_name, new_name);
1529 const char msg[] = "Playlist renamed to \"%1%\"";
1530 Statusbar::printf(msg, wideShorten(new_name, COLS-const_strlen(msg)));
1531 if (!myBrowser->isLocal())
1532 myBrowser->GetDirectory("/");
1536 bool EditLyrics::canBeRun() const
1538 return myScreen == myLyrics;
1541 void EditLyrics::run()
1543 myLyrics->Edit();
1546 bool JumpToBrowser::canBeRun() const
1548 return currentSong(myScreen);
1551 void JumpToBrowser::run()
1553 auto s = currentSong(myScreen);
1554 myBrowser->LocateSong(*s);
1557 bool JumpToMediaLibrary::canBeRun() const
1559 return currentSong(myScreen);
1562 void JumpToMediaLibrary::run()
1564 auto s = currentSong(myScreen);
1565 myLibrary->LocateSong(*s);
1568 bool JumpToPlaylistEditor::canBeRun() const
1570 return myScreen == myBrowser
1571 && myBrowser->main().current().value().type == MPD::itPlaylist;
1574 void JumpToPlaylistEditor::run()
1576 myPlaylistEditor->Locate(myBrowser->main().current().value().name);
1579 void ToggleScreenLock::run()
1581 using Global::wFooter;
1582 using Global::myLockedScreen;
1584 if (myLockedScreen != 0)
1586 BaseScreen::unlock();
1587 Actions::setResizeFlags();
1588 myScreen->resize();
1589 Statusbar::print("Screen unlocked");
1591 else
1593 int part = Config.locked_screen_width_part*100;
1594 if (Config.ask_for_locked_screen_width_part)
1596 Statusbar::lock();
1597 Statusbar::put() << "% of the locked screen's width to be reserved (20-80): ";
1598 std::string strpart = wFooter->getString(boost::lexical_cast<std::string>(part));
1599 Statusbar::unlock();
1600 part = fromString<unsigned>(strpart);
1602 boundsCheck(part, 20, 80);
1603 Config.locked_screen_width_part = part/100.0;
1604 if (myScreen->lock())
1605 Statusbar::printf("Screen locked (with %1%%% width)", part);
1606 else
1607 Statusbar::print("Current screen can't be locked");
1611 bool JumpToTagEditor::canBeRun() const
1613 # ifdef HAVE_TAGLIB_H
1614 return currentSong(myScreen)
1615 && isMPDMusicDirSet();
1616 # else
1617 return false;
1618 # endif // HAVE_TAGLIB_H
1621 void JumpToTagEditor::run()
1623 # ifdef HAVE_TAGLIB_H
1624 auto s = currentSong(myScreen);
1625 myTagEditor->LocateSong(*s);
1626 # endif // HAVE_TAGLIB_H
1629 bool JumpToPositionInSong::canBeRun() const
1631 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1634 void JumpToPositionInSong::run()
1636 using Global::wFooter;
1638 const MPD::Song s = myPlaylist->nowPlayingSong();
1640 Statusbar::lock();
1641 Statusbar::put() << "Position to go (in %/m:ss/seconds(s)): ";
1642 std::string strpos = wFooter->getString();
1643 Statusbar::unlock();
1645 boost::regex rx;
1646 boost::smatch what;
1648 if (boost::regex_match(strpos, what, rx.assign("([0-9]+):([0-9]{2})"))) // mm:ss
1650 int mins = fromString<int>(what[1]);
1651 int secs = fromString<int>(what[2]);
1652 boundsCheck(secs, 0, 60);
1653 Mpd.Seek(s.getPosition(), mins * 60 + secs);
1655 else if (boost::regex_match(strpos, what, rx.assign("([0-9]+)s"))) // position in seconds
1657 int secs = fromString<int>(what[1]);
1658 Mpd.Seek(s.getPosition(), secs);
1660 else if (boost::regex_match(strpos, what, rx.assign("([0-9]+)[%]{0,1}"))) // position in %
1662 int percent = fromString<int>(what[1]);
1663 boundsCheck(percent, 0, 100);
1664 int secs = (percent * s.getDuration()) / 100.0;
1665 Mpd.Seek(s.getPosition(), secs);
1667 else
1668 Statusbar::print("Invalid format ([m]:[ss], [s]s, [%]%, [%] accepted)");
1671 bool ReverseSelection::canBeRun() const
1673 auto w = hasSongs(myScreen);
1674 return w && w->allowsSelection();
1677 void ReverseSelection::run()
1679 auto w = hasSongs(myScreen);
1680 w->reverseSelection();
1681 Statusbar::print("Selection reversed");
1684 bool RemoveSelection::canBeRun() const
1686 return proxySongList(myScreen);
1689 void RemoveSelection::run()
1691 auto pl = proxySongList(myScreen);
1692 for (size_t i = 0; i < pl.size(); ++i)
1693 pl.setSelected(i, false);
1694 Statusbar::print("Selection removed");
1697 bool SelectAlbum::canBeRun() const
1699 auto w = hasSongs(myScreen);
1700 return w && w->allowsSelection() && w->proxySongList();
1703 void SelectAlbum::run()
1705 auto pl = proxySongList(myScreen);
1706 assert(pl);
1707 if (pl.empty())
1708 return;
1709 size_t pos = pl.choice();
1710 if (MPD::Song *s = pl.getSong(pos))
1712 std::string album = s->getAlbum();
1713 // select song under cursor
1714 pl.setSelected(pos, true);
1715 // go up
1716 while (pos > 0)
1718 s = pl.getSong(--pos);
1719 if (!s || s->getAlbum() != album)
1720 break;
1721 else
1722 pl.setSelected(pos, true);
1724 // go down
1725 pos = pl.choice();
1726 while (pos < pl.size() - 1)
1728 s = pl.getSong(++pos);
1729 if (!s || s->getAlbum() != album)
1730 break;
1731 else
1732 pl.setSelected(pos, true);
1734 Statusbar::print("Album around cursor position selected");
1738 bool AddSelectedItems::canBeRun() const
1740 return myScreen != mySelectedItemsAdder;
1743 void AddSelectedItems::run()
1745 mySelectedItemsAdder->switchTo();
1748 void CropMainPlaylist::run()
1750 auto &w = myPlaylist->main();
1751 // cropping doesn't make sense in this case
1752 if (w.size() <= 1)
1753 return;
1754 bool yes = true;
1755 if (Config.ask_before_clearing_playlists)
1756 yes = askYesNoQuestion("Do you really want to crop main playlist?", Status::trace);
1757 if (yes)
1759 Statusbar::print("Cropping playlist...");
1760 selectCurrentIfNoneSelected(w);
1761 cropPlaylist(w, boost::bind(&MPD::Connection::Delete, _1, _2));
1762 Statusbar::print("Playlist cropped");
1766 bool CropPlaylist::canBeRun() const
1768 return myScreen == myPlaylistEditor;
1771 void CropPlaylist::run()
1773 auto &w = myPlaylistEditor->Content;
1774 // cropping doesn't make sense in this case
1775 if (w.size() <= 1)
1776 return;
1777 assert(!myPlaylistEditor->Playlists.empty());
1778 std::string playlist = myPlaylistEditor->Playlists.current().value();
1779 bool yes = true;
1780 if (Config.ask_before_clearing_playlists)
1781 yes = askYesNoQuestion(
1782 boost::format("Do you really want to crop playlist \"%1%\"?") % playlist,
1783 Status::trace
1785 if (yes)
1787 selectCurrentIfNoneSelected(w);
1788 Statusbar::printf("Cropping playlist \"%1%\"...", playlist);
1789 cropPlaylist(w, boost::bind(&MPD::Connection::PlaylistDelete, _1, playlist, _2));
1790 Statusbar::printf("Playlist \"%1%\" cropped", playlist);
1794 void ClearMainPlaylist::run()
1796 bool yes = true;
1797 if (Config.ask_before_clearing_playlists)
1798 yes = askYesNoQuestion("Do you really want to clear main playlist?", Status::trace);
1799 if (yes)
1801 auto delete_fun = boost::bind(&MPD::Connection::Delete, _1, _2);
1802 auto clear_fun = boost::bind(&MPD::Connection::ClearMainPlaylist, _1);
1803 Statusbar::printf("Deleting items...");
1804 clearPlaylist(myPlaylist->main(), delete_fun, clear_fun);
1805 Statusbar::printf("Items deleted");
1806 myPlaylist->main().reset();
1810 bool ClearPlaylist::canBeRun() const
1812 return myScreen == myPlaylistEditor;
1815 void ClearPlaylist::run()
1817 assert(!myPlaylistEditor->Playlists.empty());
1818 std::string playlist = myPlaylistEditor->Playlists.current().value();
1819 bool yes = true;
1820 if (Config.ask_before_clearing_playlists)
1821 yes = askYesNoQuestion(
1822 boost::format("Do you really want to clear playlist \"%1%\"?") % playlist,
1823 Status::trace
1825 if (yes)
1827 auto delete_fun = boost::bind(&MPD::Connection::PlaylistDelete, _1, playlist, _2);
1828 auto clear_fun = boost::bind(&MPD::Connection::ClearPlaylist, _1, playlist);
1829 Statusbar::printf("Deleting items from \"%1%\"...", playlist);
1830 clearPlaylist(myPlaylistEditor->Content, delete_fun, clear_fun);
1831 Statusbar::printf("Items deleted from \"%1%\"", playlist);
1835 bool SortPlaylist::canBeRun() const
1837 return myScreen == myPlaylist;
1840 void SortPlaylist::run()
1842 mySortPlaylistDialog->switchTo();
1845 bool ReversePlaylist::canBeRun() const
1847 return myScreen == myPlaylist;
1850 void ReversePlaylist::run()
1852 myPlaylist->Reverse();
1855 bool ApplyFilter::canBeRun() const
1857 auto w = dynamic_cast<Filterable *>(myScreen);
1858 return w && w->allowsFiltering();
1861 void ApplyFilter::run()
1863 using Global::wFooter;
1865 Filterable *f = dynamic_cast<Filterable *>(myScreen);
1866 std::string filter = f->currentFilter();
1867 // if filter is already here, apply it
1868 if (!filter.empty())
1870 f->applyFilter(filter);
1871 myScreen->refreshWindow();
1874 Statusbar::lock();
1875 Statusbar::put() << NC::Format::Bold << "Apply filter: " << NC::Format::NoBold;
1876 wFooter->setGetStringHelper(Statusbar::Helpers::ApplyFilterImmediately(f, filter));
1877 wFooter->getString(filter);
1878 wFooter->setGetStringHelper(Statusbar::Helpers::getString);
1879 Statusbar::unlock();
1881 filter = f->currentFilter();
1882 if (filter.empty())
1884 myPlaylist->main().clearFilterResults();
1885 Statusbar::printf("Filtering disabled");
1887 else
1889 // apply filter here so even if old one wasn't modified
1890 // (and callback wasn't invoked), it still gets applied.
1891 f->applyFilter(filter);
1892 Statusbar::printf("Using filter \"%1%\"", filter);
1895 if (myScreen == myPlaylist)
1897 myPlaylist->EnableHighlighting();
1898 myPlaylist->reloadTotalLength();
1899 drawHeader();
1901 listsChangeFinisher();
1904 bool Find::canBeRun() const
1906 return myScreen == myHelp
1907 || myScreen == myLyrics
1908 # ifdef HAVE_CURL_CURL_H
1909 || myScreen == myLastfm
1910 # endif // HAVE_CURL_CURL_H
1914 void Find::run()
1916 using Global::wFooter;
1918 Statusbar::lock();
1919 Statusbar::put() << "Find: ";
1920 std::string findme = wFooter->getString();
1921 Statusbar::unlock();
1923 Statusbar::print("Searching...");
1924 auto s = static_cast<Screen<NC::Scrollpad> *>(myScreen);
1925 s->main().removeProperties();
1926 if (findme.empty() || s->main().setProperties(NC::Format::Reverse, findme, NC::Format::NoReverse))
1927 Statusbar::print("Done");
1928 else
1929 Statusbar::print("No matching patterns found");
1930 s->main().flush();
1933 bool FindItemBackward::canBeRun() const
1935 auto w = dynamic_cast<Searchable *>(myScreen);
1936 return w && w->allowsSearching();
1939 void FindItemForward::run()
1941 findItem(::Find::Forward);
1942 listsChangeFinisher();
1945 bool FindItemForward::canBeRun() const
1947 auto w = dynamic_cast<Searchable *>(myScreen);
1948 return w && w->allowsSearching();
1951 void FindItemBackward::run()
1953 findItem(::Find::Backward);
1954 listsChangeFinisher();
1957 bool NextFoundItem::canBeRun() const
1959 return dynamic_cast<Searchable *>(myScreen);
1962 void NextFoundItem::run()
1964 Searchable *w = dynamic_cast<Searchable *>(myScreen);
1965 w->nextFound(Config.wrapped_search);
1966 listsChangeFinisher();
1969 bool PreviousFoundItem::canBeRun() const
1971 return dynamic_cast<Searchable *>(myScreen);
1974 void PreviousFoundItem::run()
1976 Searchable *w = dynamic_cast<Searchable *>(myScreen);
1977 w->prevFound(Config.wrapped_search);
1978 listsChangeFinisher();
1981 void ToggleFindMode::run()
1983 Config.wrapped_search = !Config.wrapped_search;
1984 Statusbar::printf("Search mode: %1%",
1985 Config.wrapped_search ? "Wrapped" : "Normal"
1989 void ToggleReplayGainMode::run()
1991 using Global::wFooter;
1993 Statusbar::lock();
1994 Statusbar::put() << "Replay gain mode? [" << NC::Format::Bold << 'o' << NC::Format::NoBold << "ff/" << NC::Format::Bold << 't' << NC::Format::NoBold << "rack/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "lbum]";
1995 wFooter->refresh();
1996 int answer = 0;
1999 Status::trace();
2000 answer = wFooter->readKey();
2002 while (answer != 'o' && answer != 't' && answer != 'a');
2003 Statusbar::unlock();
2004 Mpd.SetReplayGainMode(answer == 't' ? MPD::rgmTrack : (answer == 'a' ? MPD::rgmAlbum : MPD::rgmOff));
2005 Statusbar::printf("Replay gain mode: %1%", Mpd.GetReplayGainMode());
2008 void ToggleSpaceMode::run()
2010 Config.space_selects = !Config.space_selects;
2011 Statusbar::printf("Space mode: %1% item", Config.space_selects ? "select" : "add");
2014 void ToggleAddMode::run()
2016 std::string mode_desc;
2017 switch (Config.space_add_mode)
2019 case SpaceAddMode::AddRemove:
2020 Config.space_add_mode = SpaceAddMode::AlwaysAdd;
2021 mode_desc = "always add an item to playlist";
2022 break;
2023 case SpaceAddMode::AlwaysAdd:
2024 Config.space_add_mode = SpaceAddMode::AddRemove;
2025 mode_desc = "add an item to playlist or remove if already added";
2026 break;
2028 Statusbar::printf("Add mode: %1%", mode_desc);
2031 void ToggleMouse::run()
2033 Config.mouse_support = !Config.mouse_support;
2034 mousemask(Config.mouse_support ? ALL_MOUSE_EVENTS : 0, 0);
2035 Statusbar::printf("Mouse support %1%",
2036 Config.mouse_support ? "enabled" : "disabled"
2040 void ToggleBitrateVisibility::run()
2042 Config.display_bitrate = !Config.display_bitrate;
2043 Statusbar::printf("Bitrate visibility %1%",
2044 Config.display_bitrate ? "enabled" : "disabled"
2048 void AddRandomItems::run()
2050 using Global::wFooter;
2052 Statusbar::lock();
2053 Statusbar::put() << "Add random? [" << NC::Format::Bold << 's' << NC::Format::NoBold << "ongs/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtists/al" << NC::Format::Bold << 'b' << NC::Format::NoBold << "ums] ";
2054 wFooter->refresh();
2055 int answer = 0;
2058 Status::trace();
2059 answer = wFooter->readKey();
2061 while (answer != 's' && answer != 'a' && answer != 'b');
2062 Statusbar::unlock();
2064 mpd_tag_type tag_type = MPD_TAG_ARTIST;
2065 std::string tag_type_str ;
2066 if (answer != 's')
2068 tag_type = charToTagType(answer);
2069 tag_type_str = boost::locale::to_lower(tagTypeToString(tag_type));
2071 else
2072 tag_type_str = "song";
2074 Statusbar::lock();
2075 Statusbar::put() << "Number of random " << tag_type_str << "s: ";
2076 std::string strnum = wFooter->getString();
2077 Statusbar::unlock();
2078 size_t number = fromString<size_t>(strnum);
2079 if (number && (answer == 's' ? Mpd.AddRandomSongs(number) : Mpd.AddRandomTag(tag_type, number)))
2081 Statusbar::printf("%1% random %2%%3% added to playlist",
2082 number, tag_type_str, number == 1 ? "" : "s"
2087 bool ToggleBrowserSortMode::canBeRun() const
2089 return myScreen == myBrowser;
2092 void ToggleBrowserSortMode::run()
2094 switch (Config.browser_sort_mode)
2096 case SortMode::Name:
2097 Config.browser_sort_mode = SortMode::ModificationTime;
2098 Statusbar::print("Sort songs by: modification time");
2099 break;
2100 case SortMode::ModificationTime:
2101 Config.browser_sort_mode = SortMode::CustomFormat;
2102 Statusbar::print("Sort songs by: custom format");
2103 break;
2104 case SortMode::CustomFormat:
2105 Config.browser_sort_mode = SortMode::NoOp;
2106 Statusbar::print("Do not sort songs");
2107 break;
2108 case SortMode::NoOp:
2109 Config.browser_sort_mode = SortMode::Name;
2110 Statusbar::print("Sort songs by: name");
2112 withUnfilteredMenuReapplyFilter(myBrowser->main(), [] {
2113 if (Config.browser_sort_mode != SortMode::NoOp)
2114 std::sort(myBrowser->main().begin()+(myBrowser->CurrentDir() != "/"), myBrowser->main().end(),
2115 LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode)
2120 bool ToggleLibraryTagType::canBeRun() const
2122 return (myScreen->isActiveWindow(myLibrary->Tags))
2123 || (myLibrary->Columns() == 2 && myScreen->isActiveWindow(myLibrary->Albums));
2126 void ToggleLibraryTagType::run()
2128 using Global::wFooter;
2130 Statusbar::lock();
2131 Statusbar::put() << "Tag type? [" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtist/album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtist/" << NC::Format::Bold << 'y' << NC::Format::NoBold << "ear/" << NC::Format::Bold << 'g' << NC::Format::NoBold << "enre/" << NC::Format::Bold << 'c' << NC::Format::NoBold << "omposer/" << NC::Format::Bold << 'p' << NC::Format::NoBold << "erformer] ";
2132 wFooter->refresh();
2133 int answer = 0;
2136 Status::trace();
2137 answer = wFooter->readKey();
2139 while (answer != 'a' && answer != 'A' && answer != 'y' && answer != 'g' && answer != 'c' && answer != 'p');
2140 Statusbar::unlock();
2141 mpd_tag_type new_tagitem = charToTagType(answer);
2142 if (new_tagitem != Config.media_lib_primary_tag)
2144 Config.media_lib_primary_tag = new_tagitem;
2145 std::string item_type = tagTypeToString(Config.media_lib_primary_tag);
2146 myLibrary->Tags.setTitle(Config.titles_visibility ? item_type + "s" : "");
2147 myLibrary->Tags.reset();
2148 item_type = boost::locale::to_lower(item_type);
2149 std::string and_mtime = Config.media_library_sort_by_mtime ?
2150 " and mtime" :
2152 if (myLibrary->Columns() == 2)
2154 myLibrary->Songs.clear();
2155 myLibrary->Albums.reset();
2156 myLibrary->Albums.clear();
2157 myLibrary->Albums.setTitle(Config.titles_visibility ? "Albums (sorted by " + item_type + and_mtime + ")" : "");
2158 myLibrary->Albums.display();
2160 else
2162 myLibrary->Tags.clear();
2163 myLibrary->Tags.display();
2165 Statusbar::printf("Switched to the list of %1%s", item_type);
2169 bool ToggleMediaLibrarySortMode::canBeRun() const
2171 return myScreen == myLibrary;
2174 void ToggleMediaLibrarySortMode::run()
2176 myLibrary->toggleSortMode();
2179 bool RefetchLyrics::canBeRun() const
2181 # ifdef HAVE_CURL_CURL_H
2182 return myScreen == myLyrics;
2183 # else
2184 return false;
2185 # endif // HAVE_CURL_CURL_H
2188 void RefetchLyrics::run()
2190 # ifdef HAVE_CURL_CURL_H
2191 myLyrics->Refetch();
2192 # endif // HAVE_CURL_CURL_H
2195 bool SetSelectedItemsPriority::canBeRun() const
2197 if (Mpd.Version() < 17)
2199 Statusbar::print("Priorities are supported in MPD >= 0.17.0");
2200 return false;
2202 return myScreen == myPlaylist && !myPlaylist->main().empty();
2205 void SetSelectedItemsPriority::run()
2207 using Global::wFooter;
2209 Statusbar::lock();
2210 Statusbar::put() << "Set priority [0-255]: ";
2211 std::string strprio = wFooter->getString();
2212 Statusbar::unlock();
2213 unsigned prio = fromString<unsigned>(strprio);
2214 boundsCheck(prio, 0u, 255u);
2215 myPlaylist->SetSelectedItemsPriority(prio);
2218 bool SetVisualizerSampleMultiplier::canBeRun() const
2220 # ifdef ENABLE_VISUALIZER
2221 return myScreen == myVisualizer;
2222 # else
2223 return false;
2224 # endif // ENABLE_VISUALIZER
2227 void SetVisualizerSampleMultiplier::run()
2229 # ifdef ENABLE_VISUALIZER
2230 using Global::wFooter;
2232 Statusbar::lock();
2233 Statusbar::put() << "Set visualizer sample multiplier: ";
2234 std::string smultiplier = wFooter->getString();
2235 Statusbar::unlock();
2237 double multiplier = fromString<double>(smultiplier);
2238 lowerBoundCheck(multiplier, 0.0);
2239 Config.visualizer_sample_multiplier = multiplier;
2240 # endif // ENABLE_VISUALIZER
2243 bool FilterPlaylistOnPriorities::canBeRun() const
2245 return myScreen == myPlaylist;
2248 void FilterPlaylistOnPriorities::run()
2250 using Global::wFooter;
2252 Statusbar::lock();
2253 Statusbar::put() << "Show songs with priority higher than: ";
2254 std::string strprio = wFooter->getString();
2255 Statusbar::unlock();
2256 unsigned prio = fromString<unsigned>(strprio);
2257 boundsCheck(prio, 0u, 255u);
2258 myPlaylist->main().filter(myPlaylist->main().begin(), myPlaylist->main().end(),
2259 [prio](const NC::Menu<MPD::Song>::Item &s) {
2260 return s.value().getPrio() > prio;
2262 Statusbar::printf("Playlist filtered (songs with priority higher than %1%)", prio);
2265 void ShowSongInfo::run()
2267 mySongInfo->switchTo();
2270 bool ShowArtistInfo::canBeRun() const
2272 #ifdef HAVE_CURL_CURL_H
2273 return myScreen == myLastfm
2274 || (myScreen->isActiveWindow(myLibrary->Tags)
2275 && !myLibrary->Tags.empty()
2276 && Config.media_lib_primary_tag == MPD_TAG_ARTIST)
2277 || currentSong(myScreen);
2278 # else
2279 return false;
2280 # endif // NOT HAVE_CURL_CURL_H
2283 void ShowArtistInfo::run()
2285 # ifdef HAVE_CURL_CURL_H
2286 if (myScreen == myLastfm)
2288 myLastfm->switchTo();
2289 return;
2292 std::string artist;
2293 if (myScreen->isActiveWindow(myLibrary->Tags))
2295 assert(!myLibrary->Tags.empty());
2296 assert(Config.media_lib_primary_tag == MPD_TAG_ARTIST);
2297 artist = myLibrary->Tags.current().value().tag();
2299 else
2301 auto s = currentSong(myScreen);
2302 assert(s);
2303 artist = s->getArtist();
2306 if (!artist.empty())
2308 myLastfm->queueJob(LastFm::ArtistInfo(artist, Config.lastfm_preferred_language));
2309 myLastfm->switchTo();
2311 # endif // HAVE_CURL_CURL_H
2314 void ShowLyrics::run()
2316 myLyrics->switchTo();
2319 void Quit::run()
2321 ExitMainLoop = true;
2324 void NextScreen::run()
2326 if (Config.screen_switcher_previous)
2328 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2329 tababble->switchToPreviousScreen();
2331 else if (!Config.screen_sequence.empty())
2333 const auto &seq = Config.screen_sequence;
2334 auto screen_type = std::find(seq.begin(), seq.end(), myScreen->type());
2335 if (++screen_type == seq.end())
2336 toScreen(seq.front())->switchTo();
2337 else
2338 toScreen(*screen_type)->switchTo();
2342 void PreviousScreen::run()
2344 if (Config.screen_switcher_previous)
2346 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2347 tababble->switchToPreviousScreen();
2349 else if (!Config.screen_sequence.empty())
2351 const auto &seq = Config.screen_sequence;
2352 auto screen_type = std::find(seq.begin(), seq.end(), myScreen->type());
2353 if (screen_type == seq.begin())
2354 toScreen(seq.back())->switchTo();
2355 else
2356 toScreen(*--screen_type)->switchTo();
2360 bool ShowHelp::canBeRun() const
2362 return myScreen != myHelp
2363 # ifdef HAVE_TAGLIB_H
2364 && myScreen != myTinyTagEditor
2365 # endif // HAVE_TAGLIB_H
2369 void ShowHelp::run()
2371 myHelp->switchTo();
2374 bool ShowPlaylist::canBeRun() const
2376 return myScreen != myPlaylist
2377 # ifdef HAVE_TAGLIB_H
2378 && myScreen != myTinyTagEditor
2379 # endif // HAVE_TAGLIB_H
2383 void ShowPlaylist::run()
2385 myPlaylist->switchTo();
2388 bool ShowBrowser::canBeRun() const
2390 return myScreen != myBrowser
2391 # ifdef HAVE_TAGLIB_H
2392 && myScreen != myTinyTagEditor
2393 # endif // HAVE_TAGLIB_H
2397 void ShowBrowser::run()
2399 myBrowser->switchTo();
2402 bool ChangeBrowseMode::canBeRun() const
2404 return myScreen == myBrowser;
2407 void ChangeBrowseMode::run()
2409 myBrowser->ChangeBrowseMode();
2412 bool ShowSearchEngine::canBeRun() const
2414 return myScreen != mySearcher
2415 # ifdef HAVE_TAGLIB_H
2416 && myScreen != myTinyTagEditor
2417 # endif // HAVE_TAGLIB_H
2421 void ShowSearchEngine::run()
2423 mySearcher->switchTo();
2426 bool ResetSearchEngine::canBeRun() const
2428 return myScreen == mySearcher;
2431 void ResetSearchEngine::run()
2433 mySearcher->reset();
2436 bool ShowMediaLibrary::canBeRun() const
2438 return myScreen != myLibrary
2439 # ifdef HAVE_TAGLIB_H
2440 && myScreen != myTinyTagEditor
2441 # endif // HAVE_TAGLIB_H
2445 void ShowMediaLibrary::run()
2447 myLibrary->switchTo();
2450 bool ToggleMediaLibraryColumnsMode::canBeRun() const
2452 return myScreen == myLibrary;
2455 void ToggleMediaLibraryColumnsMode::run()
2457 myLibrary->toggleColumnsMode();
2458 myLibrary->refresh();
2461 bool ShowPlaylistEditor::canBeRun() const
2463 return myScreen != myPlaylistEditor
2464 # ifdef HAVE_TAGLIB_H
2465 && myScreen != myTinyTagEditor
2466 # endif // HAVE_TAGLIB_H
2470 void ShowPlaylistEditor::run()
2472 myPlaylistEditor->switchTo();
2475 bool ShowTagEditor::canBeRun() const
2477 # ifdef HAVE_TAGLIB_H
2478 return myScreen != myTagEditor
2479 && myScreen != myTinyTagEditor;
2480 # else
2481 return false;
2482 # endif // HAVE_TAGLIB_H
2485 void ShowTagEditor::run()
2487 # ifdef HAVE_TAGLIB_H
2488 if (isMPDMusicDirSet())
2489 myTagEditor->switchTo();
2490 # endif // HAVE_TAGLIB_H
2493 bool ShowOutputs::canBeRun() const
2495 # ifdef ENABLE_OUTPUTS
2496 return myScreen != myOutputs
2497 # ifdef HAVE_TAGLIB_H
2498 && myScreen != myTinyTagEditor
2499 # endif // HAVE_TAGLIB_H
2501 # else
2502 return false;
2503 # endif // ENABLE_OUTPUTS
2506 void ShowOutputs::run()
2508 # ifdef ENABLE_OUTPUTS
2509 myOutputs->switchTo();
2510 # endif // ENABLE_OUTPUTS
2513 bool ShowVisualizer::canBeRun() const
2515 # ifdef ENABLE_VISUALIZER
2516 return myScreen != myVisualizer
2517 # ifdef HAVE_TAGLIB_H
2518 && myScreen != myTinyTagEditor
2519 # endif // HAVE_TAGLIB_H
2521 # else
2522 return false;
2523 # endif // ENABLE_VISUALIZER
2526 void ShowVisualizer::run()
2528 # ifdef ENABLE_VISUALIZER
2529 myVisualizer->switchTo();
2530 # endif // ENABLE_VISUALIZER
2533 bool ShowClock::canBeRun() const
2535 # ifdef ENABLE_CLOCK
2536 return myScreen != myClock
2537 # ifdef HAVE_TAGLIB_H
2538 && myScreen != myTinyTagEditor
2539 # endif // HAVE_TAGLIB_H
2541 # else
2542 return false;
2543 # endif // ENABLE_CLOCK
2546 void ShowClock::run()
2548 # ifdef ENABLE_CLOCK
2549 myClock->switchTo();
2550 # endif // ENABLE_CLOCK
2553 #ifdef HAVE_TAGLIB_H
2554 bool ShowServerInfo::canBeRun() const
2556 return myScreen != myTinyTagEditor;
2558 #endif // HAVE_TAGLIB_H
2560 void ShowServerInfo::run()
2562 myServerInfo->switchTo();
2567 namespace {//
2569 void populateActions()
2571 auto insert_action = [](Actions::BaseAction *a) {
2572 AvailableActions[static_cast<size_t>(a->type())] = a;
2574 insert_action(new Actions::Dummy());
2575 insert_action(new Actions::MouseEvent());
2576 insert_action(new Actions::ScrollUp());
2577 insert_action(new Actions::ScrollDown());
2578 insert_action(new Actions::ScrollUpArtist());
2579 insert_action(new Actions::ScrollUpAlbum());
2580 insert_action(new Actions::ScrollDownArtist());
2581 insert_action(new Actions::ScrollDownAlbum());
2582 insert_action(new Actions::PageUp());
2583 insert_action(new Actions::PageDown());
2584 insert_action(new Actions::MoveHome());
2585 insert_action(new Actions::MoveEnd());
2586 insert_action(new Actions::ToggleInterface());
2587 insert_action(new Actions::JumpToParentDirectory());
2588 insert_action(new Actions::PressEnter());
2589 insert_action(new Actions::PressSpace());
2590 insert_action(new Actions::PreviousColumn());
2591 insert_action(new Actions::NextColumn());
2592 insert_action(new Actions::MasterScreen());
2593 insert_action(new Actions::SlaveScreen());
2594 insert_action(new Actions::VolumeUp());
2595 insert_action(new Actions::VolumeDown());
2596 insert_action(new Actions::DeletePlaylistItems());
2597 insert_action(new Actions::DeleteStoredPlaylist());
2598 insert_action(new Actions::DeleteBrowserItems());
2599 insert_action(new Actions::ReplaySong());
2600 insert_action(new Actions::PreviousSong());
2601 insert_action(new Actions::NextSong());
2602 insert_action(new Actions::Pause());
2603 insert_action(new Actions::Stop());
2604 insert_action(new Actions::ExecuteCommand());
2605 insert_action(new Actions::SavePlaylist());
2606 insert_action(new Actions::MoveSortOrderUp());
2607 insert_action(new Actions::MoveSortOrderDown());
2608 insert_action(new Actions::MoveSelectedItemsUp());
2609 insert_action(new Actions::MoveSelectedItemsDown());
2610 insert_action(new Actions::MoveSelectedItemsTo());
2611 insert_action(new Actions::Add());
2612 insert_action(new Actions::SeekForward());
2613 insert_action(new Actions::SeekBackward());
2614 insert_action(new Actions::ToggleDisplayMode());
2615 insert_action(new Actions::ToggleSeparatorsBetweenAlbums());
2616 insert_action(new Actions::ToggleLyricsFetcher());
2617 insert_action(new Actions::ToggleFetchingLyricsInBackground());
2618 insert_action(new Actions::TogglePlayingSongCentering());
2619 insert_action(new Actions::UpdateDatabase());
2620 insert_action(new Actions::JumpToPlayingSong());
2621 insert_action(new Actions::ToggleRepeat());
2622 insert_action(new Actions::Shuffle());
2623 insert_action(new Actions::ToggleRandom());
2624 insert_action(new Actions::StartSearching());
2625 insert_action(new Actions::SaveTagChanges());
2626 insert_action(new Actions::ToggleSingle());
2627 insert_action(new Actions::ToggleConsume());
2628 insert_action(new Actions::ToggleCrossfade());
2629 insert_action(new Actions::SetCrossfade());
2630 insert_action(new Actions::SetVolume());
2631 insert_action(new Actions::EditSong());
2632 insert_action(new Actions::EditLibraryTag());
2633 insert_action(new Actions::EditLibraryAlbum());
2634 insert_action(new Actions::EditDirectoryName());
2635 insert_action(new Actions::EditPlaylistName());
2636 insert_action(new Actions::EditLyrics());
2637 insert_action(new Actions::JumpToBrowser());
2638 insert_action(new Actions::JumpToMediaLibrary());
2639 insert_action(new Actions::JumpToPlaylistEditor());
2640 insert_action(new Actions::ToggleScreenLock());
2641 insert_action(new Actions::JumpToTagEditor());
2642 insert_action(new Actions::JumpToPositionInSong());
2643 insert_action(new Actions::ReverseSelection());
2644 insert_action(new Actions::RemoveSelection());
2645 insert_action(new Actions::SelectAlbum());
2646 insert_action(new Actions::AddSelectedItems());
2647 insert_action(new Actions::CropMainPlaylist());
2648 insert_action(new Actions::CropPlaylist());
2649 insert_action(new Actions::ClearMainPlaylist());
2650 insert_action(new Actions::ClearPlaylist());
2651 insert_action(new Actions::SortPlaylist());
2652 insert_action(new Actions::ReversePlaylist());
2653 insert_action(new Actions::ApplyFilter());
2654 insert_action(new Actions::Find());
2655 insert_action(new Actions::FindItemForward());
2656 insert_action(new Actions::FindItemBackward());
2657 insert_action(new Actions::NextFoundItem());
2658 insert_action(new Actions::PreviousFoundItem());
2659 insert_action(new Actions::ToggleFindMode());
2660 insert_action(new Actions::ToggleReplayGainMode());
2661 insert_action(new Actions::ToggleSpaceMode());
2662 insert_action(new Actions::ToggleAddMode());
2663 insert_action(new Actions::ToggleMouse());
2664 insert_action(new Actions::ToggleBitrateVisibility());
2665 insert_action(new Actions::AddRandomItems());
2666 insert_action(new Actions::ToggleBrowserSortMode());
2667 insert_action(new Actions::ToggleLibraryTagType());
2668 insert_action(new Actions::ToggleMediaLibrarySortMode());
2669 insert_action(new Actions::RefetchLyrics());
2670 insert_action(new Actions::SetSelectedItemsPriority());
2671 insert_action(new Actions::SetVisualizerSampleMultiplier());
2672 insert_action(new Actions::FilterPlaylistOnPriorities());
2673 insert_action(new Actions::ShowSongInfo());
2674 insert_action(new Actions::ShowArtistInfo());
2675 insert_action(new Actions::ShowLyrics());
2676 insert_action(new Actions::Quit());
2677 insert_action(new Actions::NextScreen());
2678 insert_action(new Actions::PreviousScreen());
2679 insert_action(new Actions::ShowHelp());
2680 insert_action(new Actions::ShowPlaylist());
2681 insert_action(new Actions::ShowBrowser());
2682 insert_action(new Actions::ChangeBrowseMode());
2683 insert_action(new Actions::ShowSearchEngine());
2684 insert_action(new Actions::ResetSearchEngine());
2685 insert_action(new Actions::ShowMediaLibrary());
2686 insert_action(new Actions::ToggleMediaLibraryColumnsMode());
2687 insert_action(new Actions::ShowPlaylistEditor());
2688 insert_action(new Actions::ShowTagEditor());
2689 insert_action(new Actions::ShowOutputs());
2690 insert_action(new Actions::ShowVisualizer());
2691 insert_action(new Actions::ShowClock());
2692 insert_action(new Actions::ShowServerInfo());
2695 void seek()
2697 using Global::wHeader;
2698 using Global::wFooter;
2699 using Global::Timer;
2700 using Global::SeekingInProgress;
2702 if (!Status::State::totalTime())
2704 Statusbar::print("Unknown item length");
2705 return;
2708 Progressbar::lock();
2709 Statusbar::lock();
2711 unsigned songpos = Status::State::elapsedTime();
2712 auto t = Timer;
2714 int old_timeout = wFooter->getTimeout();
2715 wFooter->setTimeout(500);
2717 auto seekForward = &Actions::get(Actions::Type::SeekForward);
2718 auto seekBackward = &Actions::get(Actions::Type::SeekBackward);
2720 SeekingInProgress = true;
2721 while (true)
2723 Status::trace();
2725 unsigned howmuch = Config.incremental_seeking
2726 ? (Timer-t).seconds()/2+Config.seek_time
2727 : Config.seek_time;
2729 Key input = Key::read(*wFooter);
2730 auto k = Bindings.get(input);
2731 if (k.first == k.second || !k.first->isSingle()) // no single action?
2732 break;
2733 auto a = k.first->action();
2734 if (a == seekForward)
2736 if (songpos < Status::State::totalTime())
2737 songpos = std::min(songpos + howmuch, Status::State::totalTime());
2739 else if (a == seekBackward)
2741 if (songpos > 0)
2743 if (songpos < howmuch)
2744 songpos = 0;
2745 else
2746 songpos -= howmuch;
2749 else
2750 break;
2752 *wFooter << NC::Format::Bold;
2753 std::string tracklength;
2754 switch (Config.design)
2756 case Design::Classic:
2757 tracklength = " [";
2758 if (Config.display_remaining_time)
2760 tracklength += "-";
2761 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
2763 else
2764 tracklength += MPD::Song::ShowTime(songpos);
2765 tracklength += "/";
2766 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
2767 tracklength += "]";
2768 *wFooter << NC::XY(wFooter->getWidth()-tracklength.length(), 1) << tracklength;
2769 break;
2770 case Design::Alternative:
2771 if (Config.display_remaining_time)
2773 tracklength = "-";
2774 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
2776 else
2777 tracklength = MPD::Song::ShowTime(songpos);
2778 tracklength += "/";
2779 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
2780 *wHeader << NC::XY(0, 0) << tracklength << " ";
2781 wHeader->refresh();
2782 break;
2784 *wFooter << NC::Format::NoBold;
2785 Progressbar::draw(songpos, Status::State::totalTime());
2786 wFooter->refresh();
2788 SeekingInProgress = false;
2789 Mpd.Seek(Status::State::currentSongPosition(), songpos);
2791 wFooter->setTimeout(old_timeout);
2793 Progressbar::unlock();
2794 Statusbar::unlock();
2797 void findItem(const Find direction)
2799 using Global::wFooter;
2801 Searchable *w = dynamic_cast<Searchable *>(myScreen);
2802 assert(w);
2803 assert(w->allowsSearching());
2805 Statusbar::lock();
2806 Statusbar::put() << "Find " << (direction == Find::Forward ? "forward" : "backward") << ": ";
2807 std::string findme = wFooter->getString();
2808 Statusbar::unlock();
2810 if (!findme.empty())
2811 Statusbar::print("Searching...");
2813 bool success = w->search(findme);
2815 if (findme.empty())
2816 return;
2818 if (success)
2819 Statusbar::print("Searching finished");
2820 else
2821 Statusbar::printf("Unable to find \"%1%\"", findme);
2823 if (direction == ::Find::Forward)
2824 w->nextFound(Config.wrapped_search);
2825 else
2826 w->prevFound(Config.wrapped_search);
2828 if (myScreen == myPlaylist)
2829 myPlaylist->EnableHighlighting();
2832 void listsChangeFinisher()
2834 if (myScreen == myLibrary
2835 || myScreen == myPlaylistEditor
2836 # ifdef HAVE_TAGLIB_H
2837 || myScreen == myTagEditor
2838 # endif // HAVE_TAGLIB_H
2841 if (myScreen->activeWindow() == &myLibrary->Tags)
2843 myLibrary->Albums.clear();
2844 myLibrary->Albums.refresh();
2845 myLibrary->Songs.clear();
2846 myLibrary->Songs.refresh();
2847 myLibrary->updateTimer();
2849 else if (myScreen->activeWindow() == &myLibrary->Albums)
2851 myLibrary->Songs.clear();
2852 myLibrary->Songs.refresh();
2853 myLibrary->updateTimer();
2855 else if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
2857 myPlaylistEditor->Content.clear();
2858 myPlaylistEditor->Content.refresh();
2859 myPlaylistEditor->updateTimer();
2861 # ifdef HAVE_TAGLIB_H
2862 else if (myScreen->activeWindow() == myTagEditor->Dirs)
2864 myTagEditor->Tags->clear();
2866 # endif // HAVE_TAGLIB_H