actions: seek: fix incremental seeking so it doesn't reset aftet 30 seconds
[ncmpcpp.git] / src / actions.cpp
blob16a1044805b5718c59a80d71a7e24a97b0a5d55c
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);
322 # if NCURSES_MOUSE_VERSION == 1
323 // workaround shitty ncurses behavior introduced in >=5.8, when we mysteriously get
324 // a few times after ncmpcpp startup 2^27 code instead of BUTTON{1,3}_RELEASED. since that
325 // 2^27 thing shows constantly instead of BUTTON2_PRESSED, it was redefined to be recognized
326 // as BUTTON5_PRESSED. but clearly we don't want to trigger behavior bound to BUTTON5
327 // after BUTTON{1,3} was pressed. so, here is the workaround: if last event was BUTTON{1,3}_PRESSED,
328 // we MUST get BUTTON{1,3}_RELEASED afterwards. if we get BUTTON5_PRESSED, erroneus behavior
329 // is about to occur and we need to prevent that.
330 if (m_old_mouse_event.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED) && m_mouse_event.bstate & BUTTON5_PRESSED)
331 return;
332 # endif // NCURSES_MOUSE_VERSION == 1
334 if (m_mouse_event.bstate & BUTTON1_PRESSED
335 && m_mouse_event.y == LINES-(Config.statusbar_visibility ? 2 : 1)
336 ) // progressbar
338 if (Status::State::player() == MPD::psStop)
339 return;
340 Mpd.Seek(Status::State::currentSongPosition(),
341 Status::State::totalTime()*m_mouse_event.x/double(COLS));
343 else if (m_mouse_event.bstate & BUTTON1_PRESSED
344 && (Config.statusbar_visibility || Config.design == Design::Alternative)
345 && Status::State::player() != MPD::psStop
346 && m_mouse_event.y == (Config.design == Design::Alternative ? 1 : LINES-1)
347 && m_mouse_event.x < 9
348 ) // playing/paused
350 Mpd.Toggle();
352 else if ((m_mouse_event.bstate & BUTTON5_PRESSED || m_mouse_event.bstate & BUTTON4_PRESSED)
353 && (Config.header_visibility || Config.design == Design::Alternative)
354 && m_mouse_event.y == 0 && size_t(m_mouse_event.x) > COLS-VolumeState.length()
355 ) // volume
357 if (m_mouse_event.bstate & BUTTON5_PRESSED)
358 get(Type::VolumeDown).execute();
359 else
360 get(Type::VolumeUp).execute();
362 else if (m_mouse_event.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED | BUTTON4_PRESSED | BUTTON5_PRESSED))
363 myScreen->mouseButtonPressed(m_mouse_event);
366 void ScrollUp::run()
368 myScreen->scroll(NC::Scroll::Up);
369 listsChangeFinisher();
372 void ScrollDown::run()
374 myScreen->scroll(NC::Scroll::Down);
375 listsChangeFinisher();
378 bool ScrollUpArtist::canBeRun() const
380 return proxySongList(myScreen);
383 void ScrollUpArtist::run()
385 auto pl = proxySongList(myScreen);
386 assert(pl);
387 if (pl.empty())
388 return;
389 size_t pos = pl.choice();
390 if (MPD::Song *s = pl.getSong(pos))
392 std::string artist = s->getArtist();
393 while (pos > 0)
395 s = pl.getSong(--pos);
396 if (!s || s->getArtist() != artist)
397 break;
399 pl.highlight(pos);
403 bool ScrollUpAlbum::canBeRun() const
405 return proxySongList(myScreen);
408 void ScrollUpAlbum::run()
410 auto pl = proxySongList(myScreen);
411 assert(pl);
412 if (pl.empty())
413 return;
414 size_t pos = pl.choice();
415 if (MPD::Song *s = pl.getSong(pos))
417 std::string album = s->getAlbum();
418 while (pos > 0)
420 s = pl.getSong(--pos);
421 if (!s || s->getAlbum() != album)
422 break;
424 pl.highlight(pos);
428 bool ScrollDownArtist::canBeRun() const
430 return proxySongList(myScreen);
433 void ScrollDownArtist::run()
435 auto pl = proxySongList(myScreen);
436 assert(pl);
437 if (pl.empty())
438 return;
439 size_t pos = pl.choice();
440 if (MPD::Song *s = pl.getSong(pos))
442 std::string artist = s->getArtist();
443 while (pos < pl.size() - 1)
445 s = pl.getSong(++pos);
446 if (!s || s->getArtist() != artist)
447 break;
449 pl.highlight(pos);
453 bool ScrollDownAlbum::canBeRun() const
455 return proxySongList(myScreen);
458 void ScrollDownAlbum::run()
460 auto pl = proxySongList(myScreen);
461 assert(pl);
462 if (pl.empty())
463 return;
464 size_t pos = pl.choice();
465 if (MPD::Song *s = pl.getSong(pos))
467 std::string album = s->getAlbum();
468 while (pos < pl.size() - 1)
470 s = pl.getSong(++pos);
471 if (!s || s->getAlbum() != album)
472 break;
474 pl.highlight(pos);
478 void PageUp::run()
480 myScreen->scroll(NC::Scroll::PageUp);
481 listsChangeFinisher();
484 void PageDown::run()
486 myScreen->scroll(NC::Scroll::PageDown);
487 listsChangeFinisher();
490 void MoveHome::run()
492 myScreen->scroll(NC::Scroll::Home);
493 listsChangeFinisher();
496 void MoveEnd::run()
498 myScreen->scroll(NC::Scroll::End);
499 listsChangeFinisher();
502 void ToggleInterface::run()
504 switch (Config.design)
506 case Design::Classic:
507 Config.design = Design::Alternative;
508 Config.statusbar_visibility = false;
509 break;
510 case Design::Alternative:
511 Config.design = Design::Classic;
512 Config.statusbar_visibility = OriginalStatusbarVisibility;
513 break;
515 setWindowsDimensions();
516 Progressbar::unlock();
517 Statusbar::unlock();
518 resizeScreen(false);
519 Status::Changes::mixer();
520 Status::Changes::elapsedTime(false);
521 Statusbar::printf("User interface: %1%", Config.design);
524 bool JumpToParentDirectory::canBeRun() const
526 return (myScreen == myBrowser)
527 # ifdef HAVE_TAGLIB_H
528 || (myScreen->activeWindow() == myTagEditor->Dirs)
529 # endif // HAVE_TAGLIB_H
533 void JumpToParentDirectory::run()
535 if (myScreen == myBrowser)
537 if (myBrowser->CurrentDir() != "/")
539 myBrowser->main().reset();
540 myBrowser->enterPressed();
543 # ifdef HAVE_TAGLIB_H
544 else if (myScreen == myTagEditor)
546 if (myTagEditor->CurrentDir() != "/")
548 myTagEditor->Dirs->reset();
549 myTagEditor->enterPressed();
552 # endif // HAVE_TAGLIB_H
555 void PressEnter::run()
557 myScreen->enterPressed();
560 void PressSpace::run()
562 myScreen->spacePressed();
565 bool PreviousColumn::canBeRun() const
567 auto hc = hasColumns(myScreen);
568 return hc && hc->previousColumnAvailable();
571 void PreviousColumn::run()
573 hasColumns(myScreen)->previousColumn();
576 bool NextColumn::canBeRun() const
578 auto hc = hasColumns(myScreen);
579 return hc && hc->nextColumnAvailable();
582 void NextColumn::run()
584 hasColumns(myScreen)->nextColumn();
587 bool MasterScreen::canBeRun() const
589 using Global::myLockedScreen;
590 using Global::myInactiveScreen;
592 return myLockedScreen
593 && myInactiveScreen
594 && myLockedScreen != myScreen
595 && myScreen->isMergable();
598 void MasterScreen::run()
600 using Global::myInactiveScreen;
601 using Global::myLockedScreen;
603 myInactiveScreen = myScreen;
604 myScreen = myLockedScreen;
605 drawHeader();
608 bool SlaveScreen::canBeRun() const
610 using Global::myLockedScreen;
611 using Global::myInactiveScreen;
613 return myLockedScreen
614 && myInactiveScreen
615 && myLockedScreen == myScreen
616 && myScreen->isMergable();
619 void SlaveScreen::run()
621 using Global::myInactiveScreen;
622 using Global::myLockedScreen;
624 myScreen = myInactiveScreen;
625 myInactiveScreen = myLockedScreen;
626 drawHeader();
629 void VolumeUp::run()
631 int volume = std::min(Status::State::volume()+Config.volume_change_step, 100u);
632 Mpd.SetVolume(volume);
635 void VolumeDown::run()
637 int volume = std::max(int(Status::State::volume()-Config.volume_change_step), 0);
638 Mpd.SetVolume(volume);
641 bool DeletePlaylistItems::canBeRun() const
643 return (myScreen == myPlaylist && !myPlaylist->main().empty())
644 || (myScreen->isActiveWindow(myPlaylistEditor->Content) && !myPlaylistEditor->Content.empty());
647 void DeletePlaylistItems::run()
649 if (myScreen == myPlaylist)
651 Statusbar::print("Deleting items...");
652 auto delete_fun = boost::bind(&MPD::Connection::Delete, _1, _2);
653 deleteSelectedSongs(myPlaylist->main(), delete_fun);
654 Statusbar::print("Item(s) deleted");
656 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
658 std::string playlist = myPlaylistEditor->Playlists.current().value();
659 auto delete_fun = boost::bind(&MPD::Connection::PlaylistDelete, _1, playlist, _2);
660 Statusbar::print("Deleting items...");
661 deleteSelectedSongs(myPlaylistEditor->Content, delete_fun);
662 Statusbar::print("Item(s) deleted");
666 bool DeleteBrowserItems::canBeRun() const
668 auto check_if_deletion_allowed = []() {
669 if (Config.allow_for_physical_item_deletion)
670 return true;
671 else
673 Statusbar::print("Flag \"allow_for_physical_item_deletion\" needs to be enabled in configuration file");
674 return false;
677 return myScreen == myBrowser
678 && !myBrowser->main().empty()
679 && isMPDMusicDirSet()
680 && check_if_deletion_allowed();
683 void DeleteBrowserItems::run()
685 boost::format question;
686 if (hasSelected(myBrowser->main().begin(), myBrowser->main().end()))
687 question = boost::format("Delete selected items?");
688 else
690 MPD::Item &item = myBrowser->main().current().value();
691 std::string iname = item.type == MPD::itSong ? item.song->getName() : item.name;
692 question = boost::format("Delete %1% \"%2%\"?")
693 % itemTypeToString(item.type) % wideShorten(iname, COLS-question.size()-10);
695 bool yes = askYesNoQuestion(question, Status::trace);
696 if (yes)
698 bool success = true;
699 auto list = getSelectedOrCurrent(myBrowser->main().begin(), myBrowser->main().end(), myBrowser->main().currentI());
700 for (auto it = list.begin(); it != list.end(); ++it)
702 const MPD::Item &i = (*it)->value();
703 std::string iname = i.type == MPD::itSong ? i.song->getName() : i.name;
704 std::string errmsg;
705 if (myBrowser->deleteItem(i, errmsg))
707 const char msg[] = "\"%1%\" deleted";
708 Statusbar::printf(msg, wideShorten(iname, COLS-const_strlen(msg)));
710 else
712 Statusbar::print(errmsg);
713 success = false;
714 break;
717 if (success)
719 if (myBrowser->isLocal())
720 myBrowser->GetDirectory(myBrowser->CurrentDir());
721 else
722 Mpd.UpdateDirectory(myBrowser->CurrentDir());
725 else
726 Statusbar::print("Aborted");
729 bool DeleteStoredPlaylist::canBeRun() const
731 return myScreen->isActiveWindow(myPlaylistEditor->Playlists);
734 void DeleteStoredPlaylist::run()
736 if (myPlaylistEditor->Playlists.empty())
737 return;
738 boost::format question;
739 if (hasSelected(myPlaylistEditor->Playlists.begin(), myPlaylistEditor->Playlists.end()))
740 question = boost::format("Delete selected playlists?");
741 else
742 question = boost::format("Delete playlist \"%1%\"?")
743 % wideShorten(myPlaylistEditor->Playlists.current().value(), COLS-question.size()-10);
744 bool yes = askYesNoQuestion(question, Status::trace);
745 if (yes)
747 auto list = getSelectedOrCurrent(myPlaylistEditor->Playlists.begin(), myPlaylistEditor->Playlists.end(), myPlaylistEditor->Playlists.currentI());
748 for (auto it = list.begin(); it != list.end(); ++it)
749 Mpd.DeletePlaylist((*it)->value());
750 Statusbar::printf("%1% deleted", list.size() == 1 ? "Playlist" : "Playlists");
751 // force playlists update. this happens automatically, but only after call
752 // to Key::read, therefore when we call PlaylistEditor::Update, it won't
753 // yet see it, so let's point that it needs to update it.
754 myPlaylistEditor->requestPlaylistsUpdate();
756 else
757 Statusbar::print("Aborted");
760 void ReplaySong::run()
762 if (Status::State::player() != MPD::psStop)
763 Mpd.Seek(Status::State::currentSongPosition(), 0);
766 void PreviousSong::run()
768 Mpd.Prev();
771 void NextSong::run()
773 Mpd.Next();
776 void Pause::run()
778 Mpd.Toggle();
781 void SavePlaylist::run()
783 using Global::wFooter;
785 Statusbar::lock();
786 Statusbar::put() << "Save playlist as: ";
787 std::string playlist_name = wFooter->getString();
788 Statusbar::unlock();
789 if (playlist_name.find("/") != std::string::npos)
791 Statusbar::print("Playlist name must not contain slashes");
792 return;
794 if (!playlist_name.empty())
796 if (myPlaylist->main().isFiltered())
798 Mpd.StartCommandsList();
799 for (size_t i = 0; i < myPlaylist->main().size(); ++i)
800 Mpd.AddToPlaylist(playlist_name, myPlaylist->main()[i].value());
801 Mpd.CommitCommandsList();
802 Statusbar::printf("Filtered items added to playlist \"%1%\"", playlist_name);
804 else
808 Mpd.SavePlaylist(playlist_name);
809 Statusbar::printf("Playlist saved as \"%1%\"", playlist_name);
811 catch (MPD::ServerError &e)
813 if (e.code() == MPD_SERVER_ERROR_EXIST)
815 bool yes = askYesNoQuestion(
816 boost::format("Playlist \"%1%\" already exists, overwrite?") % playlist_name,
817 Status::trace
819 if (yes)
821 Mpd.DeletePlaylist(playlist_name);
822 Mpd.SavePlaylist(playlist_name);
823 Statusbar::print("Playlist overwritten");
825 else
826 Statusbar::print("Aborted");
827 if (myScreen == myPlaylist)
828 myPlaylist->EnableHighlighting();
830 else
831 throw e;
835 if (!myBrowser->isLocal()
836 && myBrowser->CurrentDir() == "/"
837 && !myBrowser->main().empty())
838 myBrowser->GetDirectory(myBrowser->CurrentDir());
841 void Stop::run()
843 Mpd.Stop();
846 void ExecuteCommand::run()
848 using Global::wFooter;
849 Statusbar::lock();
850 Statusbar::put() << NC::Format::Bold << ":" << NC::Format::NoBold;
851 wFooter->setGetStringHelper(Statusbar::Helpers::TryExecuteImmediateCommand());
852 std::string cmd_name = wFooter->getString();
853 wFooter->setGetStringHelper(Statusbar::Helpers::getString);
854 Statusbar::unlock();
855 if (cmd_name.empty())
856 return;
857 auto cmd = Bindings.findCommand(cmd_name);
858 if (cmd)
860 Statusbar::printf(1, "Executing %1%...", cmd_name);
861 bool res = cmd->binding().execute();
862 Statusbar::printf("Execution of command \"%1%\" %2%.",
863 cmd_name, res ? "successful" : "unsuccessful"
866 else
867 Statusbar::printf("No command named \"%1%\"", cmd_name);
870 bool MoveSortOrderUp::canBeRun() const
872 return myScreen == mySortPlaylistDialog;
875 void MoveSortOrderUp::run()
877 mySortPlaylistDialog->moveSortOrderUp();
880 bool MoveSortOrderDown::canBeRun() const
882 return myScreen == mySortPlaylistDialog;
885 void MoveSortOrderDown::run()
887 mySortPlaylistDialog->moveSortOrderDown();
890 bool MoveSelectedItemsUp::canBeRun() const
892 return ((myScreen == myPlaylist
893 && !myPlaylist->main().empty()
894 && !myPlaylist->isFiltered())
895 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
896 && !myPlaylistEditor->Content.empty()
897 && !myPlaylistEditor->isContentFiltered()));
900 void MoveSelectedItemsUp::run()
902 if (myScreen == myPlaylist)
904 moveSelectedItemsUp(myPlaylist->main(), boost::bind(&MPD::Connection::Move, _1, _2, _3));
906 else if (myScreen == myPlaylistEditor)
908 assert(!myPlaylistEditor->Playlists.empty());
909 std::string playlist = myPlaylistEditor->Playlists.current().value();
910 auto move_fun = boost::bind(&MPD::Connection::PlaylistMove, _1, playlist, _2, _3);
911 moveSelectedItemsUp(myPlaylistEditor->Content, move_fun);
915 bool MoveSelectedItemsDown::canBeRun() const
917 return ((myScreen == myPlaylist
918 && !myPlaylist->main().empty()
919 && !myPlaylist->isFiltered())
920 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
921 && !myPlaylistEditor->Content.empty()
922 && !myPlaylistEditor->isContentFiltered()));
925 void MoveSelectedItemsDown::run()
927 if (myScreen == myPlaylist)
929 moveSelectedItemsDown(myPlaylist->main(), boost::bind(&MPD::Connection::Move, _1, _2, _3));
931 else if (myScreen == myPlaylistEditor)
933 assert(!myPlaylistEditor->Playlists.empty());
934 std::string playlist = myPlaylistEditor->Playlists.current().value();
935 auto move_fun = boost::bind(&MPD::Connection::PlaylistMove, _1, playlist, _2, _3);
936 moveSelectedItemsDown(myPlaylistEditor->Content, move_fun);
940 bool MoveSelectedItemsTo::canBeRun() const
942 return myScreen == myPlaylist
943 || myScreen->isActiveWindow(myPlaylistEditor->Content);
946 void MoveSelectedItemsTo::run()
948 if (myScreen == myPlaylist)
950 if (!myPlaylist->main().empty())
951 moveSelectedItemsTo(myPlaylist->main(), boost::bind(&MPD::Connection::Move, _1, _2, _3));
953 else
955 assert(!myPlaylistEditor->Playlists.empty());
956 std::string playlist = myPlaylistEditor->Playlists.current().value();
957 auto move_fun = boost::bind(&MPD::Connection::PlaylistMove, _1, playlist, _2, _3);
958 moveSelectedItemsTo(myPlaylistEditor->Content, move_fun);
962 bool Add::canBeRun() const
964 return myScreen != myPlaylistEditor
965 || !myPlaylistEditor->Playlists.empty();
968 void Add::run()
970 using Global::wFooter;
972 Statusbar::lock();
973 Statusbar::put() << (myScreen == myPlaylistEditor ? "Add to playlist: " : "Add: ");
974 std::string path = wFooter->getString();
975 Statusbar::unlock();
976 if (!path.empty())
978 Statusbar::put() << "Adding...";
979 wFooter->refresh();
980 if (myScreen == myPlaylistEditor)
981 Mpd.AddToPlaylist(myPlaylistEditor->Playlists.current().value(), path);
982 else
984 const char lastfm_url[] = "lastfm://";
985 if (path.compare(0, const_strlen(lastfm_url), lastfm_url) == 0
986 || path.find(".asx", path.length()-4) != std::string::npos
987 || path.find(".cue", path.length()-4) != std::string::npos
988 || path.find(".m3u", path.length()-4) != std::string::npos
989 || path.find(".pls", path.length()-4) != std::string::npos
990 || path.find(".xspf", path.length()-5) != std::string::npos)
991 Mpd.LoadPlaylist(path);
992 else
993 Mpd.Add(path);
998 bool SeekForward::canBeRun() const
1000 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1003 void SeekForward::run()
1005 seek();
1008 bool SeekBackward::canBeRun() const
1010 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1013 void SeekBackward::run()
1015 seek();
1018 bool ToggleDisplayMode::canBeRun() const
1020 return myScreen == myPlaylist
1021 || myScreen == myBrowser
1022 || myScreen == mySearcher
1023 || myScreen->isActiveWindow(myPlaylistEditor->Content);
1026 void ToggleDisplayMode::run()
1028 if (myScreen == myPlaylist)
1030 switch (Config.playlist_display_mode)
1032 case DisplayMode::Classic:
1033 Config.playlist_display_mode = DisplayMode::Columns;
1034 myPlaylist->main().setItemDisplayer(boost::bind(
1035 Display::SongsInColumns, _1, myPlaylist->proxySongList()
1037 if (Config.titles_visibility)
1038 myPlaylist->main().setTitle(Display::Columns(myPlaylist->main().getWidth()));
1039 else
1040 myPlaylist->main().setTitle("");
1041 break;
1042 case DisplayMode::Columns:
1043 Config.playlist_display_mode = DisplayMode::Classic;
1044 myPlaylist->main().setItemDisplayer(boost::bind(
1045 Display::Songs, _1, myPlaylist->proxySongList(), Config.song_list_format
1047 myPlaylist->main().setTitle("");
1049 Statusbar::printf("Playlist display mode: %1%", Config.playlist_display_mode);
1051 else if (myScreen == myBrowser)
1053 switch (Config.browser_display_mode)
1055 case DisplayMode::Classic:
1056 Config.browser_display_mode = DisplayMode::Columns;
1057 if (Config.titles_visibility)
1058 myBrowser->main().setTitle(Display::Columns(myBrowser->main().getWidth()));
1059 else
1060 myBrowser->main().setTitle("");
1061 break;
1062 case DisplayMode::Columns:
1063 Config.browser_display_mode = DisplayMode::Classic;
1064 myBrowser->main().setTitle("");
1065 break;
1067 Statusbar::printf("Browser display mode: %1%", Config.browser_display_mode);
1069 else if (myScreen == mySearcher)
1071 switch (Config.search_engine_display_mode)
1073 case DisplayMode::Classic:
1074 Config.search_engine_display_mode = DisplayMode::Columns;
1075 break;
1076 case DisplayMode::Columns:
1077 Config.search_engine_display_mode = DisplayMode::Classic;
1078 break;
1080 Statusbar::printf("Search engine display mode: %1%", Config.search_engine_display_mode);
1081 if (mySearcher->main().size() > SearchEngine::StaticOptions)
1082 mySearcher->main().setTitle(
1083 Config.search_engine_display_mode == DisplayMode::Columns
1084 && Config.titles_visibility
1085 ? Display::Columns(mySearcher->main().getWidth())
1086 : ""
1089 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
1091 switch (Config.playlist_editor_display_mode)
1093 case DisplayMode::Classic:
1094 Config.playlist_editor_display_mode = DisplayMode::Columns;
1095 myPlaylistEditor->Content.setItemDisplayer(boost::bind(
1096 Display::SongsInColumns, _1, myPlaylistEditor->contentProxyList()
1098 break;
1099 case DisplayMode::Columns:
1100 Config.playlist_editor_display_mode = DisplayMode::Classic;
1101 myPlaylistEditor->Content.setItemDisplayer(boost::bind(
1102 Display::Songs, _1, myPlaylistEditor->contentProxyList(), Config.song_list_format
1104 break;
1106 Statusbar::printf("Playlist editor display mode: %1%", Config.playlist_editor_display_mode);
1110 bool ToggleSeparatorsBetweenAlbums::canBeRun() const
1112 return true;
1115 void ToggleSeparatorsBetweenAlbums::run()
1117 Config.playlist_separate_albums = !Config.playlist_separate_albums;
1118 Statusbar::printf("Separators between albums: %1%",
1119 Config.playlist_separate_albums ? "on" : "off"
1123 #ifndef HAVE_CURL_CURL_H
1124 bool ToggleLyricsFetcher::canBeRun() const
1126 return false;
1128 #endif // NOT HAVE_CURL_CURL_H
1130 void ToggleLyricsFetcher::run()
1132 # ifdef HAVE_CURL_CURL_H
1133 myLyrics->ToggleFetcher();
1134 # endif // HAVE_CURL_CURL_H
1137 #ifndef HAVE_CURL_CURL_H
1138 bool ToggleFetchingLyricsInBackground::canBeRun() const
1140 return false;
1142 #endif // NOT HAVE_CURL_CURL_H
1144 void ToggleFetchingLyricsInBackground::run()
1146 # ifdef HAVE_CURL_CURL_H
1147 Config.fetch_lyrics_in_background = !Config.fetch_lyrics_in_background;
1148 Statusbar::printf("Fetching lyrics for playing songs in background: %1%",
1149 Config.fetch_lyrics_in_background ? "on" : "off"
1151 # endif // HAVE_CURL_CURL_H
1154 void TogglePlayingSongCentering::run()
1156 Config.autocenter_mode = !Config.autocenter_mode;
1157 Statusbar::printf("Centering playing song: %1%",
1158 Config.autocenter_mode ? "on" : "off"
1160 if (Config.autocenter_mode && !myPlaylist->main().isFiltered())
1162 auto s = myPlaylist->nowPlayingSong();
1163 if (!s.empty())
1164 myPlaylist->main().highlight(s.getPosition());
1168 void UpdateDatabase::run()
1170 if (myScreen == myBrowser)
1171 Mpd.UpdateDirectory(myBrowser->CurrentDir());
1172 # ifdef HAVE_TAGLIB_H
1173 else if (myScreen == myTagEditor)
1174 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1175 # endif // HAVE_TAGLIB_H
1176 else
1177 Mpd.UpdateDirectory("/");
1180 bool JumpToPlayingSong::canBeRun() const
1182 return (myScreen == myPlaylist && !myPlaylist->isFiltered())
1183 || myScreen == myBrowser
1184 || myScreen == myLibrary;
1187 void JumpToPlayingSong::run()
1189 auto s = myPlaylist->nowPlayingSong();
1190 if (s.empty())
1191 return;
1192 if (myScreen == myPlaylist)
1194 myPlaylist->main().highlight(s.getPosition());
1196 else if (myScreen == myBrowser)
1198 myBrowser->LocateSong(s);
1199 drawHeader();
1201 else if (myScreen == myLibrary)
1203 myLibrary->LocateSong(s);
1207 void ToggleRepeat::run()
1209 Mpd.SetRepeat(!Status::State::repeat());
1212 void Shuffle::run()
1214 Mpd.Shuffle();
1217 void ToggleRandom::run()
1219 Mpd.SetRandom(!Status::State::random());
1222 bool StartSearching::canBeRun() const
1224 return myScreen == mySearcher && !mySearcher->main()[0].isInactive();
1227 void StartSearching::run()
1229 mySearcher->main().highlight(SearchEngine::SearchButton);
1230 mySearcher->main().setHighlighting(0);
1231 mySearcher->main().refresh();
1232 mySearcher->main().setHighlighting(1);
1233 mySearcher->enterPressed();
1236 bool SaveTagChanges::canBeRun() const
1238 # ifdef HAVE_TAGLIB_H
1239 return myScreen == myTinyTagEditor
1240 || myScreen->activeWindow() == myTagEditor->TagTypes;
1241 # else
1242 return false;
1243 # endif // HAVE_TAGLIB_H
1246 void SaveTagChanges::run()
1248 # ifdef HAVE_TAGLIB_H
1249 if (myScreen == myTinyTagEditor)
1251 myTinyTagEditor->main().highlight(myTinyTagEditor->main().size()-2); // Save
1252 myTinyTagEditor->enterPressed();
1254 else if (myScreen->activeWindow() == myTagEditor->TagTypes)
1256 myTagEditor->TagTypes->highlight(myTagEditor->TagTypes->size()-1); // Save
1257 myTagEditor->enterPressed();
1259 # endif // HAVE_TAGLIB_H
1262 void ToggleSingle::run()
1264 Mpd.SetSingle(!Status::State::single());
1267 void ToggleConsume::run()
1269 Mpd.SetConsume(!Status::State::consume());
1272 void ToggleCrossfade::run()
1274 Mpd.SetCrossfade(Status::State::crossfade() ? 0 : Config.crossfade_time);
1277 void SetCrossfade::run()
1279 using Global::wFooter;
1281 Statusbar::lock();
1282 Statusbar::put() << "Set crossfade to: ";
1283 std::string crossfade = wFooter->getString();
1284 Statusbar::unlock();
1285 int cf = fromString<unsigned>(crossfade);
1286 lowerBoundCheck(cf, 1);
1287 Config.crossfade_time = cf;
1288 Mpd.SetCrossfade(cf);
1291 void SetVolume::run()
1293 using Global::wFooter;
1295 Statusbar::lock();
1296 Statusbar::put() << "Set volume to: ";
1297 std::string strvolume = wFooter->getString();
1298 Statusbar::unlock();
1299 int volume = fromString<unsigned>(strvolume);
1300 boundsCheck(volume, 0, 100);
1301 Mpd.SetVolume(volume);
1302 Statusbar::printf("Volume set to %1%%%", volume);
1305 bool EditSong::canBeRun() const
1307 # ifdef HAVE_TAGLIB_H
1308 return currentSong(myScreen)
1309 && isMPDMusicDirSet();
1310 # else
1311 return false;
1312 # endif // HAVE_TAGLIB_H
1315 void EditSong::run()
1317 # ifdef HAVE_TAGLIB_H
1318 auto s = currentSong(myScreen);
1319 myTinyTagEditor->SetEdited(*s);
1320 myTinyTagEditor->switchTo();
1321 # endif // HAVE_TAGLIB_H
1324 bool EditLibraryTag::canBeRun() const
1326 # ifdef HAVE_TAGLIB_H
1327 return myScreen->isActiveWindow(myLibrary->Tags)
1328 && !myLibrary->Tags.empty()
1329 && isMPDMusicDirSet();
1330 # else
1331 return false;
1332 # endif // HAVE_TAGLIB_H
1335 void EditLibraryTag::run()
1337 # ifdef HAVE_TAGLIB_H
1338 using Global::wFooter;
1340 Statusbar::lock();
1341 Statusbar::put() << NC::Format::Bold << tagTypeToString(Config.media_lib_primary_tag) << NC::Format::NoBold << ": ";
1342 std::string new_tag = wFooter->getString(myLibrary->Tags.current().value().tag());
1343 Statusbar::unlock();
1344 if (!new_tag.empty() && new_tag != myLibrary->Tags.current().value().tag())
1346 Statusbar::print("Updating tags...");
1347 Mpd.StartSearch(1);
1348 Mpd.AddSearch(Config.media_lib_primary_tag, myLibrary->Tags.current().value().tag());
1349 MPD::MutableSong::SetFunction set = tagTypeToSetFunction(Config.media_lib_primary_tag);
1350 assert(set);
1351 bool success = true;
1352 std::string dir_to_update;
1353 Mpd.CommitSearchSongs([set, &dir_to_update, &new_tag, &success](MPD::Song s) {
1354 if (!success)
1355 return;
1356 MPD::MutableSong ms = s;
1357 ms.setTags(set, new_tag, Config.tags_separator);
1358 Statusbar::printf("Updating tags in \"%1%\"...", ms.getName());
1359 std::string path = Config.mpd_music_dir + ms.getURI();
1360 if (!Tags::write(ms))
1362 const char msg[] = "Error while updating tags in \"%1%\"";
1363 Statusbar::printf(msg, wideShorten(ms.getURI(), COLS-const_strlen(msg)));
1364 success = false;
1366 if (dir_to_update.empty())
1367 dir_to_update = s.getURI();
1368 else
1369 dir_to_update = getSharedDirectory(dir_to_update, s.getURI());
1371 if (success)
1373 Mpd.UpdateDirectory(dir_to_update);
1374 Statusbar::print("Tags updated successfully");
1377 # endif // HAVE_TAGLIB_H
1380 bool EditLibraryAlbum::canBeRun() const
1382 # ifdef HAVE_TAGLIB_H
1383 return myScreen->isActiveWindow(myLibrary->Albums)
1384 && !myLibrary->Albums.empty()
1385 && isMPDMusicDirSet();
1386 # else
1387 return false;
1388 # endif // HAVE_TAGLIB_H
1391 void EditLibraryAlbum::run()
1393 # ifdef HAVE_TAGLIB_H
1394 using Global::wFooter;
1396 Statusbar::lock();
1397 Statusbar::put() << NC::Format::Bold << "Album: " << NC::Format::NoBold;
1398 std::string new_album = wFooter->getString(myLibrary->Albums.current().value().entry().album());
1399 Statusbar::unlock();
1400 if (!new_album.empty() && new_album != myLibrary->Albums.current().value().entry().album())
1402 bool success = 1;
1403 Statusbar::print("Updating tags...");
1404 for (size_t i = 0; i < myLibrary->Songs.size(); ++i)
1406 Statusbar::printf("Updating tags in \"%1%\"...", myLibrary->Songs[i].value().getName());
1407 std::string path = Config.mpd_music_dir + myLibrary->Songs[i].value().getURI();
1408 TagLib::FileRef f(path.c_str());
1409 if (f.isNull())
1411 const char msg[] = "Error while opening file \"%1%\"";
1412 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1413 success = 0;
1414 break;
1416 f.tag()->setAlbum(ToWString(new_album));
1417 if (!f.save())
1419 const char msg[] = "Error while writing tags in \"%1%\"";
1420 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1421 success = 0;
1422 break;
1425 if (success)
1427 Mpd.UpdateDirectory(getSharedDirectory(myLibrary->Songs.beginV(), myLibrary->Songs.endV()));
1428 Statusbar::print("Tags updated successfully");
1431 # endif // HAVE_TAGLIB_H
1434 bool EditDirectoryName::canBeRun() const
1436 return ((myScreen == myBrowser
1437 && !myBrowser->main().empty()
1438 && myBrowser->main().current().value().type == MPD::itDirectory)
1439 # ifdef HAVE_TAGLIB_H
1440 || (myScreen->activeWindow() == myTagEditor->Dirs
1441 && !myTagEditor->Dirs->empty()
1442 && myTagEditor->Dirs->choice() > 0)
1443 # endif // HAVE_TAGLIB_H
1444 ) && isMPDMusicDirSet();
1447 void EditDirectoryName::run()
1449 using Global::wFooter;
1451 if (myScreen == myBrowser)
1453 std::string old_dir = myBrowser->main().current().value().name;
1454 Statusbar::lock();
1455 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1456 std::string new_dir = wFooter->getString(old_dir);
1457 Statusbar::unlock();
1458 if (!new_dir.empty() && new_dir != old_dir)
1460 std::string full_old_dir;
1461 if (!myBrowser->isLocal())
1462 full_old_dir += Config.mpd_music_dir;
1463 full_old_dir += old_dir;
1464 std::string full_new_dir;
1465 if (!myBrowser->isLocal())
1466 full_new_dir += Config.mpd_music_dir;
1467 full_new_dir += new_dir;
1468 int rename_result = rename(full_old_dir.c_str(), full_new_dir.c_str());
1469 if (rename_result == 0)
1471 const char msg[] = "Directory renamed to \"%1%\"";
1472 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1473 if (!myBrowser->isLocal())
1474 Mpd.UpdateDirectory(getSharedDirectory(old_dir, new_dir));
1475 myBrowser->GetDirectory(myBrowser->CurrentDir());
1477 else
1479 const char msg[] = "Couldn't rename \"%1%\": %s";
1480 Statusbar::printf(msg, wideShorten(old_dir, COLS-const_strlen(msg)-25), strerror(errno));
1484 # ifdef HAVE_TAGLIB_H
1485 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1487 std::string old_dir = myTagEditor->Dirs->current().value().first;
1488 Statusbar::lock();
1489 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1490 std::string new_dir = wFooter->getString(old_dir);
1491 Statusbar::unlock();
1492 if (!new_dir.empty() && new_dir != old_dir)
1494 std::string full_old_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + old_dir;
1495 std::string full_new_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + new_dir;
1496 if (rename(full_old_dir.c_str(), full_new_dir.c_str()) == 0)
1498 const char msg[] = "Directory renamed to \"%1%\"";
1499 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1500 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1502 else
1504 const char msg[] = "Couldn't rename \"%1%\": %2%";
1505 Statusbar::printf(msg, wideShorten(old_dir, COLS-const_strlen(msg)-25), strerror(errno));
1509 # endif // HAVE_TAGLIB_H
1512 bool EditPlaylistName::canBeRun() const
1514 return (myScreen->isActiveWindow(myPlaylistEditor->Playlists)
1515 && !myPlaylistEditor->Playlists.empty())
1516 || (myScreen == myBrowser
1517 && !myBrowser->main().empty()
1518 && myBrowser->main().current().value().type == MPD::itPlaylist);
1521 void EditPlaylistName::run()
1523 using Global::wFooter;
1525 std::string old_name;
1526 if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
1527 old_name = myPlaylistEditor->Playlists.current().value();
1528 else
1529 old_name = myBrowser->main().current().value().name;
1530 Statusbar::lock();
1531 Statusbar::put() << NC::Format::Bold << "Playlist: " << NC::Format::NoBold;
1532 std::string new_name = wFooter->getString(old_name);
1533 Statusbar::unlock();
1534 if (!new_name.empty() && new_name != old_name)
1536 Mpd.Rename(old_name, new_name);
1537 const char msg[] = "Playlist renamed to \"%1%\"";
1538 Statusbar::printf(msg, wideShorten(new_name, COLS-const_strlen(msg)));
1539 if (!myBrowser->isLocal())
1540 myBrowser->GetDirectory("/");
1544 bool EditLyrics::canBeRun() const
1546 return myScreen == myLyrics;
1549 void EditLyrics::run()
1551 myLyrics->Edit();
1554 bool JumpToBrowser::canBeRun() const
1556 return currentSong(myScreen);
1559 void JumpToBrowser::run()
1561 auto s = currentSong(myScreen);
1562 myBrowser->LocateSong(*s);
1565 bool JumpToMediaLibrary::canBeRun() const
1567 return currentSong(myScreen);
1570 void JumpToMediaLibrary::run()
1572 auto s = currentSong(myScreen);
1573 myLibrary->LocateSong(*s);
1576 bool JumpToPlaylistEditor::canBeRun() const
1578 return myScreen == myBrowser
1579 && myBrowser->main().current().value().type == MPD::itPlaylist;
1582 void JumpToPlaylistEditor::run()
1584 myPlaylistEditor->Locate(myBrowser->main().current().value().name);
1587 void ToggleScreenLock::run()
1589 using Global::wFooter;
1590 using Global::myLockedScreen;
1592 if (myLockedScreen != 0)
1594 BaseScreen::unlock();
1595 Actions::setResizeFlags();
1596 myScreen->resize();
1597 Statusbar::print("Screen unlocked");
1599 else
1601 int part = Config.locked_screen_width_part*100;
1602 if (Config.ask_for_locked_screen_width_part)
1604 Statusbar::lock();
1605 Statusbar::put() << "% of the locked screen's width to be reserved (20-80): ";
1606 std::string strpart = wFooter->getString(boost::lexical_cast<std::string>(part));
1607 Statusbar::unlock();
1608 part = fromString<unsigned>(strpart);
1610 boundsCheck(part, 20, 80);
1611 Config.locked_screen_width_part = part/100.0;
1612 if (myScreen->lock())
1613 Statusbar::printf("Screen locked (with %1%%% width)", part);
1614 else
1615 Statusbar::print("Current screen can't be locked");
1619 bool JumpToTagEditor::canBeRun() const
1621 # ifdef HAVE_TAGLIB_H
1622 return currentSong(myScreen)
1623 && isMPDMusicDirSet();
1624 # else
1625 return false;
1626 # endif // HAVE_TAGLIB_H
1629 void JumpToTagEditor::run()
1631 # ifdef HAVE_TAGLIB_H
1632 auto s = currentSong(myScreen);
1633 myTagEditor->LocateSong(*s);
1634 # endif // HAVE_TAGLIB_H
1637 bool JumpToPositionInSong::canBeRun() const
1639 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1642 void JumpToPositionInSong::run()
1644 using Global::wFooter;
1646 const MPD::Song s = myPlaylist->nowPlayingSong();
1648 Statusbar::lock();
1649 Statusbar::put() << "Position to go (in %/m:ss/seconds(s)): ";
1650 std::string strpos = wFooter->getString();
1651 Statusbar::unlock();
1653 boost::regex rx;
1654 boost::smatch what;
1656 if (boost::regex_match(strpos, what, rx.assign("([0-9]+):([0-9]{2})"))) // mm:ss
1658 int mins = fromString<int>(what[1]);
1659 int secs = fromString<int>(what[2]);
1660 boundsCheck(secs, 0, 60);
1661 Mpd.Seek(s.getPosition(), mins * 60 + secs);
1663 else if (boost::regex_match(strpos, what, rx.assign("([0-9]+)s"))) // position in seconds
1665 int secs = fromString<int>(what[1]);
1666 Mpd.Seek(s.getPosition(), secs);
1668 else if (boost::regex_match(strpos, what, rx.assign("([0-9]+)[%]{0,1}"))) // position in %
1670 int percent = fromString<int>(what[1]);
1671 boundsCheck(percent, 0, 100);
1672 int secs = (percent * s.getDuration()) / 100.0;
1673 Mpd.Seek(s.getPosition(), secs);
1675 else
1676 Statusbar::print("Invalid format ([m]:[ss], [s]s, [%]%, [%] accepted)");
1679 bool ReverseSelection::canBeRun() const
1681 auto w = hasSongs(myScreen);
1682 return w && w->allowsSelection();
1685 void ReverseSelection::run()
1687 auto w = hasSongs(myScreen);
1688 w->reverseSelection();
1689 Statusbar::print("Selection reversed");
1692 bool RemoveSelection::canBeRun() const
1694 return proxySongList(myScreen);
1697 void RemoveSelection::run()
1699 auto pl = proxySongList(myScreen);
1700 for (size_t i = 0; i < pl.size(); ++i)
1701 pl.setSelected(i, false);
1702 Statusbar::print("Selection removed");
1705 bool SelectAlbum::canBeRun() const
1707 auto w = hasSongs(myScreen);
1708 return w && w->allowsSelection() && w->proxySongList();
1711 void SelectAlbum::run()
1713 auto pl = proxySongList(myScreen);
1714 assert(pl);
1715 if (pl.empty())
1716 return;
1717 size_t pos = pl.choice();
1718 if (MPD::Song *s = pl.getSong(pos))
1720 std::string album = s->getAlbum();
1721 // select song under cursor
1722 pl.setSelected(pos, true);
1723 // go up
1724 while (pos > 0)
1726 s = pl.getSong(--pos);
1727 if (!s || s->getAlbum() != album)
1728 break;
1729 else
1730 pl.setSelected(pos, true);
1732 // go down
1733 pos = pl.choice();
1734 while (pos < pl.size() - 1)
1736 s = pl.getSong(++pos);
1737 if (!s || s->getAlbum() != album)
1738 break;
1739 else
1740 pl.setSelected(pos, true);
1742 Statusbar::print("Album around cursor position selected");
1746 bool AddSelectedItems::canBeRun() const
1748 return myScreen != mySelectedItemsAdder;
1751 void AddSelectedItems::run()
1753 mySelectedItemsAdder->switchTo();
1756 void CropMainPlaylist::run()
1758 auto &w = myPlaylist->main();
1759 // cropping doesn't make sense in this case
1760 if (w.size() <= 1)
1761 return;
1762 bool yes = true;
1763 if (Config.ask_before_clearing_playlists)
1764 yes = askYesNoQuestion("Do you really want to crop main playlist?", Status::trace);
1765 if (yes)
1767 Statusbar::print("Cropping playlist...");
1768 selectCurrentIfNoneSelected(w);
1769 cropPlaylist(w, boost::bind(&MPD::Connection::Delete, _1, _2));
1770 Statusbar::print("Playlist cropped");
1774 bool CropPlaylist::canBeRun() const
1776 return myScreen == myPlaylistEditor;
1779 void CropPlaylist::run()
1781 auto &w = myPlaylistEditor->Content;
1782 // cropping doesn't make sense in this case
1783 if (w.size() <= 1)
1784 return;
1785 assert(!myPlaylistEditor->Playlists.empty());
1786 std::string playlist = myPlaylistEditor->Playlists.current().value();
1787 bool yes = true;
1788 if (Config.ask_before_clearing_playlists)
1789 yes = askYesNoQuestion(
1790 boost::format("Do you really want to crop playlist \"%1%\"?") % playlist,
1791 Status::trace
1793 if (yes)
1795 selectCurrentIfNoneSelected(w);
1796 Statusbar::printf("Cropping playlist \"%1%\"...", playlist);
1797 cropPlaylist(w, boost::bind(&MPD::Connection::PlaylistDelete, _1, playlist, _2));
1798 Statusbar::printf("Playlist \"%1%\" cropped", playlist);
1802 void ClearMainPlaylist::run()
1804 bool yes = true;
1805 if (Config.ask_before_clearing_playlists)
1806 yes = askYesNoQuestion("Do you really want to clear main playlist?", Status::trace);
1807 if (yes)
1809 auto delete_fun = boost::bind(&MPD::Connection::Delete, _1, _2);
1810 auto clear_fun = boost::bind(&MPD::Connection::ClearMainPlaylist, _1);
1811 Statusbar::printf("Deleting items...");
1812 clearPlaylist(myPlaylist->main(), delete_fun, clear_fun);
1813 Statusbar::printf("Items deleted");
1814 myPlaylist->main().reset();
1818 bool ClearPlaylist::canBeRun() const
1820 return myScreen == myPlaylistEditor;
1823 void ClearPlaylist::run()
1825 if (myPlaylistEditor->Playlists.empty())
1826 return;
1827 std::string playlist = myPlaylistEditor->Playlists.current().value();
1828 bool yes = true;
1829 if (Config.ask_before_clearing_playlists)
1830 yes = askYesNoQuestion(
1831 boost::format("Do you really want to clear playlist \"%1%\"?") % playlist,
1832 Status::trace
1834 if (yes)
1836 auto delete_fun = boost::bind(&MPD::Connection::PlaylistDelete, _1, playlist, _2);
1837 auto clear_fun = boost::bind(&MPD::Connection::ClearPlaylist, _1, playlist);
1838 Statusbar::printf("Deleting items from \"%1%\"...", playlist);
1839 clearPlaylist(myPlaylistEditor->Content, delete_fun, clear_fun);
1840 Statusbar::printf("Items deleted from \"%1%\"", playlist);
1844 bool SortPlaylist::canBeRun() const
1846 return myScreen == myPlaylist;
1849 void SortPlaylist::run()
1851 mySortPlaylistDialog->switchTo();
1854 bool ReversePlaylist::canBeRun() const
1856 return myScreen == myPlaylist;
1859 void ReversePlaylist::run()
1861 myPlaylist->Reverse();
1864 bool ApplyFilter::canBeRun() const
1866 auto w = dynamic_cast<Filterable *>(myScreen);
1867 return w && w->allowsFiltering();
1870 void ApplyFilter::run()
1872 using Global::wFooter;
1874 Filterable *f = dynamic_cast<Filterable *>(myScreen);
1875 std::string filter = f->currentFilter();
1876 // if filter is already here, apply it
1877 if (!filter.empty())
1879 f->applyFilter(filter);
1880 myScreen->refreshWindow();
1883 Statusbar::lock();
1884 Statusbar::put() << NC::Format::Bold << "Apply filter: " << NC::Format::NoBold;
1885 wFooter->setGetStringHelper(Statusbar::Helpers::ApplyFilterImmediately(f, filter));
1886 wFooter->getString(filter);
1887 wFooter->setGetStringHelper(Statusbar::Helpers::getString);
1888 Statusbar::unlock();
1890 filter = f->currentFilter();
1891 if (filter.empty())
1893 myPlaylist->main().clearFilterResults();
1894 Statusbar::printf("Filtering disabled");
1896 else
1898 // apply filter here so even if old one wasn't modified
1899 // (and callback wasn't invoked), it still gets applied.
1900 f->applyFilter(filter);
1901 Statusbar::printf("Using filter \"%1%\"", filter);
1904 if (myScreen == myPlaylist)
1906 myPlaylist->EnableHighlighting();
1907 myPlaylist->reloadTotalLength();
1908 drawHeader();
1910 listsChangeFinisher();
1913 bool Find::canBeRun() const
1915 return myScreen == myHelp
1916 || myScreen == myLyrics
1917 # ifdef HAVE_CURL_CURL_H
1918 || myScreen == myLastfm
1919 # endif // HAVE_CURL_CURL_H
1923 void Find::run()
1925 using Global::wFooter;
1927 Statusbar::lock();
1928 Statusbar::put() << "Find: ";
1929 std::string findme = wFooter->getString();
1930 Statusbar::unlock();
1932 Statusbar::print("Searching...");
1933 auto s = static_cast<Screen<NC::Scrollpad> *>(myScreen);
1934 s->main().removeProperties();
1935 if (findme.empty() || s->main().setProperties(NC::Format::Reverse, findme, NC::Format::NoReverse))
1936 Statusbar::print("Done");
1937 else
1938 Statusbar::print("No matching patterns found");
1939 s->main().flush();
1942 bool FindItemBackward::canBeRun() const
1944 auto w = dynamic_cast<Searchable *>(myScreen);
1945 return w && w->allowsSearching();
1948 void FindItemForward::run()
1950 findItem(::Find::Forward);
1951 listsChangeFinisher();
1954 bool FindItemForward::canBeRun() const
1956 auto w = dynamic_cast<Searchable *>(myScreen);
1957 return w && w->allowsSearching();
1960 void FindItemBackward::run()
1962 findItem(::Find::Backward);
1963 listsChangeFinisher();
1966 bool NextFoundItem::canBeRun() const
1968 return dynamic_cast<Searchable *>(myScreen);
1971 void NextFoundItem::run()
1973 Searchable *w = dynamic_cast<Searchable *>(myScreen);
1974 w->nextFound(Config.wrapped_search);
1975 listsChangeFinisher();
1978 bool PreviousFoundItem::canBeRun() const
1980 return dynamic_cast<Searchable *>(myScreen);
1983 void PreviousFoundItem::run()
1985 Searchable *w = dynamic_cast<Searchable *>(myScreen);
1986 w->prevFound(Config.wrapped_search);
1987 listsChangeFinisher();
1990 void ToggleFindMode::run()
1992 Config.wrapped_search = !Config.wrapped_search;
1993 Statusbar::printf("Search mode: %1%",
1994 Config.wrapped_search ? "Wrapped" : "Normal"
1998 void ToggleReplayGainMode::run()
2000 using Global::wFooter;
2002 Statusbar::lock();
2003 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]";
2004 wFooter->refresh();
2005 int answer = 0;
2008 Status::trace();
2009 answer = wFooter->readKey();
2011 while (answer != 'o' && answer != 't' && answer != 'a');
2012 Statusbar::unlock();
2013 Mpd.SetReplayGainMode(answer == 't' ? MPD::rgmTrack : (answer == 'a' ? MPD::rgmAlbum : MPD::rgmOff));
2014 Statusbar::printf("Replay gain mode: %1%", Mpd.GetReplayGainMode());
2017 void ToggleSpaceMode::run()
2019 Config.space_selects = !Config.space_selects;
2020 Statusbar::printf("Space mode: %1% item", Config.space_selects ? "select" : "add");
2023 void ToggleAddMode::run()
2025 std::string mode_desc;
2026 switch (Config.space_add_mode)
2028 case SpaceAddMode::AddRemove:
2029 Config.space_add_mode = SpaceAddMode::AlwaysAdd;
2030 mode_desc = "always add an item to playlist";
2031 break;
2032 case SpaceAddMode::AlwaysAdd:
2033 Config.space_add_mode = SpaceAddMode::AddRemove;
2034 mode_desc = "add an item to playlist or remove if already added";
2035 break;
2037 Statusbar::printf("Add mode: %1%", mode_desc);
2040 void ToggleMouse::run()
2042 Config.mouse_support = !Config.mouse_support;
2043 mousemask(Config.mouse_support ? ALL_MOUSE_EVENTS : 0, 0);
2044 Statusbar::printf("Mouse support %1%",
2045 Config.mouse_support ? "enabled" : "disabled"
2049 void ToggleBitrateVisibility::run()
2051 Config.display_bitrate = !Config.display_bitrate;
2052 Statusbar::printf("Bitrate visibility %1%",
2053 Config.display_bitrate ? "enabled" : "disabled"
2057 void AddRandomItems::run()
2059 using Global::wFooter;
2061 Statusbar::lock();
2062 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] ";
2063 wFooter->refresh();
2064 int answer = 0;
2067 Status::trace();
2068 answer = wFooter->readKey();
2070 while (answer != 's' && answer != 'a' && answer != 'b');
2071 Statusbar::unlock();
2073 mpd_tag_type tag_type = MPD_TAG_ARTIST;
2074 std::string tag_type_str ;
2075 if (answer != 's')
2077 tag_type = charToTagType(answer);
2078 tag_type_str = boost::locale::to_lower(tagTypeToString(tag_type));
2080 else
2081 tag_type_str = "song";
2083 Statusbar::lock();
2084 Statusbar::put() << "Number of random " << tag_type_str << "s: ";
2085 std::string strnum = wFooter->getString();
2086 Statusbar::unlock();
2087 size_t number = fromString<size_t>(strnum);
2088 if (number && (answer == 's' ? Mpd.AddRandomSongs(number) : Mpd.AddRandomTag(tag_type, number)))
2090 Statusbar::printf("%1% random %2%%3% added to playlist",
2091 number, tag_type_str, number == 1 ? "" : "s"
2096 bool ToggleBrowserSortMode::canBeRun() const
2098 return myScreen == myBrowser;
2101 void ToggleBrowserSortMode::run()
2103 switch (Config.browser_sort_mode)
2105 case SortMode::Name:
2106 Config.browser_sort_mode = SortMode::ModificationTime;
2107 Statusbar::print("Sort songs by: modification time");
2108 break;
2109 case SortMode::ModificationTime:
2110 Config.browser_sort_mode = SortMode::CustomFormat;
2111 Statusbar::print("Sort songs by: custom format");
2112 break;
2113 case SortMode::CustomFormat:
2114 Config.browser_sort_mode = SortMode::NoOp;
2115 Statusbar::print("Do not sort songs");
2116 break;
2117 case SortMode::NoOp:
2118 Config.browser_sort_mode = SortMode::Name;
2119 Statusbar::print("Sort songs by: name");
2121 withUnfilteredMenuReapplyFilter(myBrowser->main(), [] {
2122 if (Config.browser_sort_mode != SortMode::NoOp)
2123 std::sort(myBrowser->main().begin()+(myBrowser->CurrentDir() != "/"), myBrowser->main().end(),
2124 LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode)
2129 bool ToggleLibraryTagType::canBeRun() const
2131 return (myScreen->isActiveWindow(myLibrary->Tags))
2132 || (myLibrary->Columns() == 2 && myScreen->isActiveWindow(myLibrary->Albums));
2135 void ToggleLibraryTagType::run()
2137 using Global::wFooter;
2139 Statusbar::lock();
2140 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] ";
2141 wFooter->refresh();
2142 int answer = 0;
2145 Status::trace();
2146 answer = wFooter->readKey();
2148 while (answer != 'a' && answer != 'A' && answer != 'y' && answer != 'g' && answer != 'c' && answer != 'p');
2149 Statusbar::unlock();
2150 mpd_tag_type new_tagitem = charToTagType(answer);
2151 if (new_tagitem != Config.media_lib_primary_tag)
2153 Config.media_lib_primary_tag = new_tagitem;
2154 std::string item_type = tagTypeToString(Config.media_lib_primary_tag);
2155 myLibrary->Tags.setTitle(Config.titles_visibility ? item_type + "s" : "");
2156 myLibrary->Tags.reset();
2157 item_type = boost::locale::to_lower(item_type);
2158 std::string and_mtime = Config.media_library_sort_by_mtime ?
2159 " and mtime" :
2161 if (myLibrary->Columns() == 2)
2163 myLibrary->Songs.clear();
2164 myLibrary->Albums.reset();
2165 myLibrary->Albums.clear();
2166 myLibrary->Albums.setTitle(Config.titles_visibility ? "Albums (sorted by " + item_type + and_mtime + ")" : "");
2167 myLibrary->Albums.display();
2169 else
2171 myLibrary->Tags.clear();
2172 myLibrary->Tags.display();
2174 Statusbar::printf("Switched to the list of %1%s", item_type);
2178 bool ToggleMediaLibrarySortMode::canBeRun() const
2180 return myScreen == myLibrary;
2183 void ToggleMediaLibrarySortMode::run()
2185 myLibrary->toggleSortMode();
2188 bool RefetchLyrics::canBeRun() const
2190 # ifdef HAVE_CURL_CURL_H
2191 return myScreen == myLyrics;
2192 # else
2193 return false;
2194 # endif // HAVE_CURL_CURL_H
2197 void RefetchLyrics::run()
2199 # ifdef HAVE_CURL_CURL_H
2200 myLyrics->Refetch();
2201 # endif // HAVE_CURL_CURL_H
2204 bool SetSelectedItemsPriority::canBeRun() const
2206 if (Mpd.Version() < 17)
2208 Statusbar::print("Priorities are supported in MPD >= 0.17.0");
2209 return false;
2211 return myScreen == myPlaylist && !myPlaylist->main().empty();
2214 void SetSelectedItemsPriority::run()
2216 using Global::wFooter;
2218 Statusbar::lock();
2219 Statusbar::put() << "Set priority [0-255]: ";
2220 std::string strprio = wFooter->getString();
2221 Statusbar::unlock();
2222 unsigned prio = fromString<unsigned>(strprio);
2223 boundsCheck(prio, 0u, 255u);
2224 myPlaylist->SetSelectedItemsPriority(prio);
2227 bool SetVisualizerSampleMultiplier::canBeRun() const
2229 # ifdef ENABLE_VISUALIZER
2230 return myScreen == myVisualizer;
2231 # else
2232 return false;
2233 # endif // ENABLE_VISUALIZER
2236 void SetVisualizerSampleMultiplier::run()
2238 # ifdef ENABLE_VISUALIZER
2239 using Global::wFooter;
2241 Statusbar::lock();
2242 Statusbar::put() << "Set visualizer sample multiplier: ";
2243 std::string smultiplier = wFooter->getString();
2244 Statusbar::unlock();
2246 double multiplier = fromString<double>(smultiplier);
2247 lowerBoundCheck(multiplier, 0.0);
2248 Config.visualizer_sample_multiplier = multiplier;
2249 # endif // ENABLE_VISUALIZER
2252 bool FilterPlaylistOnPriorities::canBeRun() const
2254 return myScreen == myPlaylist;
2257 void FilterPlaylistOnPriorities::run()
2259 using Global::wFooter;
2261 Statusbar::lock();
2262 Statusbar::put() << "Show songs with priority higher than: ";
2263 std::string strprio = wFooter->getString();
2264 Statusbar::unlock();
2265 unsigned prio = fromString<unsigned>(strprio);
2266 boundsCheck(prio, 0u, 255u);
2267 myPlaylist->main().filter(myPlaylist->main().begin(), myPlaylist->main().end(),
2268 [prio](const NC::Menu<MPD::Song>::Item &s) {
2269 return s.value().getPrio() > prio;
2271 Statusbar::printf("Playlist filtered (songs with priority higher than %1%)", prio);
2274 void ShowSongInfo::run()
2276 mySongInfo->switchTo();
2279 bool ShowArtistInfo::canBeRun() const
2281 #ifdef HAVE_CURL_CURL_H
2282 return myScreen == myLastfm
2283 || (myScreen->isActiveWindow(myLibrary->Tags)
2284 && !myLibrary->Tags.empty()
2285 && Config.media_lib_primary_tag == MPD_TAG_ARTIST)
2286 || currentSong(myScreen);
2287 # else
2288 return false;
2289 # endif // NOT HAVE_CURL_CURL_H
2292 void ShowArtistInfo::run()
2294 # ifdef HAVE_CURL_CURL_H
2295 if (myScreen == myLastfm)
2297 myLastfm->switchTo();
2298 return;
2301 std::string artist;
2302 if (myScreen->isActiveWindow(myLibrary->Tags))
2304 assert(!myLibrary->Tags.empty());
2305 assert(Config.media_lib_primary_tag == MPD_TAG_ARTIST);
2306 artist = myLibrary->Tags.current().value().tag();
2308 else
2310 auto s = currentSong(myScreen);
2311 assert(s);
2312 artist = s->getArtist();
2315 if (!artist.empty())
2317 myLastfm->queueJob(LastFm::ArtistInfo(artist, Config.lastfm_preferred_language));
2318 myLastfm->switchTo();
2320 # endif // HAVE_CURL_CURL_H
2323 void ShowLyrics::run()
2325 myLyrics->switchTo();
2328 void Quit::run()
2330 ExitMainLoop = true;
2333 void NextScreen::run()
2335 if (Config.screen_switcher_previous)
2337 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2338 tababble->switchToPreviousScreen();
2340 else if (!Config.screen_sequence.empty())
2342 const auto &seq = Config.screen_sequence;
2343 auto screen_type = std::find(seq.begin(), seq.end(), myScreen->type());
2344 if (++screen_type == seq.end())
2345 toScreen(seq.front())->switchTo();
2346 else
2347 toScreen(*screen_type)->switchTo();
2351 void PreviousScreen::run()
2353 if (Config.screen_switcher_previous)
2355 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2356 tababble->switchToPreviousScreen();
2358 else if (!Config.screen_sequence.empty())
2360 const auto &seq = Config.screen_sequence;
2361 auto screen_type = std::find(seq.begin(), seq.end(), myScreen->type());
2362 if (screen_type == seq.begin())
2363 toScreen(seq.back())->switchTo();
2364 else
2365 toScreen(*--screen_type)->switchTo();
2369 bool ShowHelp::canBeRun() const
2371 return myScreen != myHelp
2372 # ifdef HAVE_TAGLIB_H
2373 && myScreen != myTinyTagEditor
2374 # endif // HAVE_TAGLIB_H
2378 void ShowHelp::run()
2380 myHelp->switchTo();
2383 bool ShowPlaylist::canBeRun() const
2385 return myScreen != myPlaylist
2386 # ifdef HAVE_TAGLIB_H
2387 && myScreen != myTinyTagEditor
2388 # endif // HAVE_TAGLIB_H
2392 void ShowPlaylist::run()
2394 myPlaylist->switchTo();
2397 bool ShowBrowser::canBeRun() const
2399 return myScreen != myBrowser
2400 # ifdef HAVE_TAGLIB_H
2401 && myScreen != myTinyTagEditor
2402 # endif // HAVE_TAGLIB_H
2406 void ShowBrowser::run()
2408 myBrowser->switchTo();
2411 bool ChangeBrowseMode::canBeRun() const
2413 return myScreen == myBrowser;
2416 void ChangeBrowseMode::run()
2418 myBrowser->ChangeBrowseMode();
2421 bool ShowSearchEngine::canBeRun() const
2423 return myScreen != mySearcher
2424 # ifdef HAVE_TAGLIB_H
2425 && myScreen != myTinyTagEditor
2426 # endif // HAVE_TAGLIB_H
2430 void ShowSearchEngine::run()
2432 mySearcher->switchTo();
2435 bool ResetSearchEngine::canBeRun() const
2437 return myScreen == mySearcher;
2440 void ResetSearchEngine::run()
2442 mySearcher->reset();
2445 bool ShowMediaLibrary::canBeRun() const
2447 return myScreen != myLibrary
2448 # ifdef HAVE_TAGLIB_H
2449 && myScreen != myTinyTagEditor
2450 # endif // HAVE_TAGLIB_H
2454 void ShowMediaLibrary::run()
2456 myLibrary->switchTo();
2459 bool ToggleMediaLibraryColumnsMode::canBeRun() const
2461 return myScreen == myLibrary;
2464 void ToggleMediaLibraryColumnsMode::run()
2466 myLibrary->toggleColumnsMode();
2467 myLibrary->refresh();
2470 bool ShowPlaylistEditor::canBeRun() const
2472 return myScreen != myPlaylistEditor
2473 # ifdef HAVE_TAGLIB_H
2474 && myScreen != myTinyTagEditor
2475 # endif // HAVE_TAGLIB_H
2479 void ShowPlaylistEditor::run()
2481 myPlaylistEditor->switchTo();
2484 bool ShowTagEditor::canBeRun() const
2486 # ifdef HAVE_TAGLIB_H
2487 return myScreen != myTagEditor
2488 && myScreen != myTinyTagEditor;
2489 # else
2490 return false;
2491 # endif // HAVE_TAGLIB_H
2494 void ShowTagEditor::run()
2496 # ifdef HAVE_TAGLIB_H
2497 if (isMPDMusicDirSet())
2498 myTagEditor->switchTo();
2499 # endif // HAVE_TAGLIB_H
2502 bool ShowOutputs::canBeRun() const
2504 # ifdef ENABLE_OUTPUTS
2505 return myScreen != myOutputs
2506 # ifdef HAVE_TAGLIB_H
2507 && myScreen != myTinyTagEditor
2508 # endif // HAVE_TAGLIB_H
2510 # else
2511 return false;
2512 # endif // ENABLE_OUTPUTS
2515 void ShowOutputs::run()
2517 # ifdef ENABLE_OUTPUTS
2518 myOutputs->switchTo();
2519 # endif // ENABLE_OUTPUTS
2522 bool ShowVisualizer::canBeRun() const
2524 # ifdef ENABLE_VISUALIZER
2525 return myScreen != myVisualizer
2526 # ifdef HAVE_TAGLIB_H
2527 && myScreen != myTinyTagEditor
2528 # endif // HAVE_TAGLIB_H
2530 # else
2531 return false;
2532 # endif // ENABLE_VISUALIZER
2535 void ShowVisualizer::run()
2537 # ifdef ENABLE_VISUALIZER
2538 myVisualizer->switchTo();
2539 # endif // ENABLE_VISUALIZER
2542 bool ShowClock::canBeRun() const
2544 # ifdef ENABLE_CLOCK
2545 return myScreen != myClock
2546 # ifdef HAVE_TAGLIB_H
2547 && myScreen != myTinyTagEditor
2548 # endif // HAVE_TAGLIB_H
2550 # else
2551 return false;
2552 # endif // ENABLE_CLOCK
2555 void ShowClock::run()
2557 # ifdef ENABLE_CLOCK
2558 myClock->switchTo();
2559 # endif // ENABLE_CLOCK
2562 #ifdef HAVE_TAGLIB_H
2563 bool ShowServerInfo::canBeRun() const
2565 return myScreen != myTinyTagEditor;
2567 #endif // HAVE_TAGLIB_H
2569 void ShowServerInfo::run()
2571 myServerInfo->switchTo();
2576 namespace {//
2578 void populateActions()
2580 auto insert_action = [](Actions::BaseAction *a) {
2581 AvailableActions[static_cast<size_t>(a->type())] = a;
2583 insert_action(new Actions::Dummy());
2584 insert_action(new Actions::MouseEvent());
2585 insert_action(new Actions::ScrollUp());
2586 insert_action(new Actions::ScrollDown());
2587 insert_action(new Actions::ScrollUpArtist());
2588 insert_action(new Actions::ScrollUpAlbum());
2589 insert_action(new Actions::ScrollDownArtist());
2590 insert_action(new Actions::ScrollDownAlbum());
2591 insert_action(new Actions::PageUp());
2592 insert_action(new Actions::PageDown());
2593 insert_action(new Actions::MoveHome());
2594 insert_action(new Actions::MoveEnd());
2595 insert_action(new Actions::ToggleInterface());
2596 insert_action(new Actions::JumpToParentDirectory());
2597 insert_action(new Actions::PressEnter());
2598 insert_action(new Actions::PressSpace());
2599 insert_action(new Actions::PreviousColumn());
2600 insert_action(new Actions::NextColumn());
2601 insert_action(new Actions::MasterScreen());
2602 insert_action(new Actions::SlaveScreen());
2603 insert_action(new Actions::VolumeUp());
2604 insert_action(new Actions::VolumeDown());
2605 insert_action(new Actions::DeletePlaylistItems());
2606 insert_action(new Actions::DeleteStoredPlaylist());
2607 insert_action(new Actions::DeleteBrowserItems());
2608 insert_action(new Actions::ReplaySong());
2609 insert_action(new Actions::PreviousSong());
2610 insert_action(new Actions::NextSong());
2611 insert_action(new Actions::Pause());
2612 insert_action(new Actions::Stop());
2613 insert_action(new Actions::ExecuteCommand());
2614 insert_action(new Actions::SavePlaylist());
2615 insert_action(new Actions::MoveSortOrderUp());
2616 insert_action(new Actions::MoveSortOrderDown());
2617 insert_action(new Actions::MoveSelectedItemsUp());
2618 insert_action(new Actions::MoveSelectedItemsDown());
2619 insert_action(new Actions::MoveSelectedItemsTo());
2620 insert_action(new Actions::Add());
2621 insert_action(new Actions::SeekForward());
2622 insert_action(new Actions::SeekBackward());
2623 insert_action(new Actions::ToggleDisplayMode());
2624 insert_action(new Actions::ToggleSeparatorsBetweenAlbums());
2625 insert_action(new Actions::ToggleLyricsFetcher());
2626 insert_action(new Actions::ToggleFetchingLyricsInBackground());
2627 insert_action(new Actions::TogglePlayingSongCentering());
2628 insert_action(new Actions::UpdateDatabase());
2629 insert_action(new Actions::JumpToPlayingSong());
2630 insert_action(new Actions::ToggleRepeat());
2631 insert_action(new Actions::Shuffle());
2632 insert_action(new Actions::ToggleRandom());
2633 insert_action(new Actions::StartSearching());
2634 insert_action(new Actions::SaveTagChanges());
2635 insert_action(new Actions::ToggleSingle());
2636 insert_action(new Actions::ToggleConsume());
2637 insert_action(new Actions::ToggleCrossfade());
2638 insert_action(new Actions::SetCrossfade());
2639 insert_action(new Actions::SetVolume());
2640 insert_action(new Actions::EditSong());
2641 insert_action(new Actions::EditLibraryTag());
2642 insert_action(new Actions::EditLibraryAlbum());
2643 insert_action(new Actions::EditDirectoryName());
2644 insert_action(new Actions::EditPlaylistName());
2645 insert_action(new Actions::EditLyrics());
2646 insert_action(new Actions::JumpToBrowser());
2647 insert_action(new Actions::JumpToMediaLibrary());
2648 insert_action(new Actions::JumpToPlaylistEditor());
2649 insert_action(new Actions::ToggleScreenLock());
2650 insert_action(new Actions::JumpToTagEditor());
2651 insert_action(new Actions::JumpToPositionInSong());
2652 insert_action(new Actions::ReverseSelection());
2653 insert_action(new Actions::RemoveSelection());
2654 insert_action(new Actions::SelectAlbum());
2655 insert_action(new Actions::AddSelectedItems());
2656 insert_action(new Actions::CropMainPlaylist());
2657 insert_action(new Actions::CropPlaylist());
2658 insert_action(new Actions::ClearMainPlaylist());
2659 insert_action(new Actions::ClearPlaylist());
2660 insert_action(new Actions::SortPlaylist());
2661 insert_action(new Actions::ReversePlaylist());
2662 insert_action(new Actions::ApplyFilter());
2663 insert_action(new Actions::Find());
2664 insert_action(new Actions::FindItemForward());
2665 insert_action(new Actions::FindItemBackward());
2666 insert_action(new Actions::NextFoundItem());
2667 insert_action(new Actions::PreviousFoundItem());
2668 insert_action(new Actions::ToggleFindMode());
2669 insert_action(new Actions::ToggleReplayGainMode());
2670 insert_action(new Actions::ToggleSpaceMode());
2671 insert_action(new Actions::ToggleAddMode());
2672 insert_action(new Actions::ToggleMouse());
2673 insert_action(new Actions::ToggleBitrateVisibility());
2674 insert_action(new Actions::AddRandomItems());
2675 insert_action(new Actions::ToggleBrowserSortMode());
2676 insert_action(new Actions::ToggleLibraryTagType());
2677 insert_action(new Actions::ToggleMediaLibrarySortMode());
2678 insert_action(new Actions::RefetchLyrics());
2679 insert_action(new Actions::SetSelectedItemsPriority());
2680 insert_action(new Actions::SetVisualizerSampleMultiplier());
2681 insert_action(new Actions::FilterPlaylistOnPriorities());
2682 insert_action(new Actions::ShowSongInfo());
2683 insert_action(new Actions::ShowArtistInfo());
2684 insert_action(new Actions::ShowLyrics());
2685 insert_action(new Actions::Quit());
2686 insert_action(new Actions::NextScreen());
2687 insert_action(new Actions::PreviousScreen());
2688 insert_action(new Actions::ShowHelp());
2689 insert_action(new Actions::ShowPlaylist());
2690 insert_action(new Actions::ShowBrowser());
2691 insert_action(new Actions::ChangeBrowseMode());
2692 insert_action(new Actions::ShowSearchEngine());
2693 insert_action(new Actions::ResetSearchEngine());
2694 insert_action(new Actions::ShowMediaLibrary());
2695 insert_action(new Actions::ToggleMediaLibraryColumnsMode());
2696 insert_action(new Actions::ShowPlaylistEditor());
2697 insert_action(new Actions::ShowTagEditor());
2698 insert_action(new Actions::ShowOutputs());
2699 insert_action(new Actions::ShowVisualizer());
2700 insert_action(new Actions::ShowClock());
2701 insert_action(new Actions::ShowServerInfo());
2704 void seek()
2706 using Global::wHeader;
2707 using Global::wFooter;
2708 using Global::Timer;
2709 using Global::SeekingInProgress;
2711 if (!Status::State::totalTime())
2713 Statusbar::print("Unknown item length");
2714 return;
2717 Progressbar::lock();
2718 Statusbar::lock();
2720 unsigned songpos = Status::State::elapsedTime();
2721 auto t = Timer;
2723 int old_timeout = wFooter->getTimeout();
2724 wFooter->setTimeout(500);
2726 auto seekForward = &Actions::get(Actions::Type::SeekForward);
2727 auto seekBackward = &Actions::get(Actions::Type::SeekBackward);
2729 SeekingInProgress = true;
2730 while (true)
2732 Status::trace();
2734 unsigned howmuch = Config.incremental_seeking
2735 ? (Timer-t).total_seconds()/2+Config.seek_time
2736 : Config.seek_time;
2738 Key input = Key::read(*wFooter);
2739 auto k = Bindings.get(input);
2740 if (k.first == k.second || !k.first->isSingle()) // no single action?
2741 break;
2742 auto a = k.first->action();
2743 if (a == seekForward)
2745 if (songpos < Status::State::totalTime())
2746 songpos = std::min(songpos + howmuch, Status::State::totalTime());
2748 else if (a == seekBackward)
2750 if (songpos > 0)
2752 if (songpos < howmuch)
2753 songpos = 0;
2754 else
2755 songpos -= howmuch;
2758 else
2759 break;
2761 *wFooter << NC::Format::Bold;
2762 std::string tracklength;
2763 switch (Config.design)
2765 case Design::Classic:
2766 tracklength = " [";
2767 if (Config.display_remaining_time)
2769 tracklength += "-";
2770 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
2772 else
2773 tracklength += MPD::Song::ShowTime(songpos);
2774 tracklength += "/";
2775 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
2776 tracklength += "]";
2777 *wFooter << NC::XY(wFooter->getWidth()-tracklength.length(), 1) << tracklength;
2778 break;
2779 case Design::Alternative:
2780 if (Config.display_remaining_time)
2782 tracklength = "-";
2783 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
2785 else
2786 tracklength = MPD::Song::ShowTime(songpos);
2787 tracklength += "/";
2788 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
2789 *wHeader << NC::XY(0, 0) << tracklength << " ";
2790 wHeader->refresh();
2791 break;
2793 *wFooter << NC::Format::NoBold;
2794 Progressbar::draw(songpos, Status::State::totalTime());
2795 wFooter->refresh();
2797 SeekingInProgress = false;
2798 Mpd.Seek(Status::State::currentSongPosition(), songpos);
2800 wFooter->setTimeout(old_timeout);
2802 Progressbar::unlock();
2803 Statusbar::unlock();
2806 void findItem(const Find direction)
2808 using Global::wFooter;
2810 Searchable *w = dynamic_cast<Searchable *>(myScreen);
2811 assert(w);
2812 assert(w->allowsSearching());
2814 Statusbar::lock();
2815 Statusbar::put() << "Find " << (direction == Find::Forward ? "forward" : "backward") << ": ";
2816 std::string findme = wFooter->getString();
2817 Statusbar::unlock();
2819 if (!findme.empty())
2820 Statusbar::print("Searching...");
2822 bool success = w->search(findme);
2824 if (findme.empty())
2825 return;
2827 if (success)
2828 Statusbar::print("Searching finished");
2829 else
2830 Statusbar::printf("Unable to find \"%1%\"", findme);
2832 if (direction == ::Find::Forward)
2833 w->nextFound(Config.wrapped_search);
2834 else
2835 w->prevFound(Config.wrapped_search);
2837 if (myScreen == myPlaylist)
2838 myPlaylist->EnableHighlighting();
2841 void listsChangeFinisher()
2843 if (myScreen == myLibrary
2844 || myScreen == myPlaylistEditor
2845 # ifdef HAVE_TAGLIB_H
2846 || myScreen == myTagEditor
2847 # endif // HAVE_TAGLIB_H
2850 if (myScreen->activeWindow() == &myLibrary->Tags)
2852 myLibrary->Albums.clear();
2853 myLibrary->Albums.refresh();
2854 myLibrary->Songs.clear();
2855 myLibrary->Songs.refresh();
2856 myLibrary->updateTimer();
2858 else if (myScreen->activeWindow() == &myLibrary->Albums)
2860 myLibrary->Songs.clear();
2861 myLibrary->Songs.refresh();
2862 myLibrary->updateTimer();
2864 else if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
2866 myPlaylistEditor->Content.clear();
2867 myPlaylistEditor->Content.refresh();
2868 myPlaylistEditor->updateTimer();
2870 # ifdef HAVE_TAGLIB_H
2871 else if (myScreen->activeWindow() == myTagEditor->Dirs)
2873 myTagEditor->Tags->clear();
2875 # endif // HAVE_TAGLIB_H