actions: add support for range selection and make a few actions work on ranges
[ncmpcpp.git] / src / actions.cpp
blob6c26f2267b2cc57b6059571e83d1589997f7cd83
1 /***************************************************************************
2 * Copyright (C) 2008-2014 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include <cassert>
22 #include <cerrno>
23 #include <cstring>
24 #include <boost/array.hpp>
25 #include <boost/filesystem/operations.hpp>
26 #include <boost/locale/conversion.hpp>
27 #include <boost/lexical_cast.hpp>
28 #include <algorithm>
29 #include <iostream>
30 #include <readline/readline.h>
32 #include "actions.h"
33 #include "charset.h"
34 #include "config.h"
35 #include "display.h"
36 #include "global.h"
37 #include "mpdpp.h"
38 #include "helpers.h"
39 #include "statusbar.h"
40 #include "utility/comparators.h"
41 #include "utility/conversion.h"
43 #include "bindings.h"
44 #include "browser.h"
45 #include "clock.h"
46 #include "help.h"
47 #include "media_library.h"
48 #include "menu_impl.h"
49 #include "lastfm.h"
50 #include "lyrics.h"
51 #include "playlist.h"
52 #include "playlist_editor.h"
53 #include "sort_playlist.h"
54 #include "search_engine.h"
55 #include "sel_items_adder.h"
56 #include "server_info.h"
57 #include "song_info.h"
58 #include "outputs.h"
59 #include "utility/string.h"
60 #include "utility/type_conversions.h"
61 #include "tag_editor.h"
62 #include "tiny_tag_editor.h"
63 #include "visualizer.h"
64 #include "title.h"
65 #include "tags.h"
67 #ifdef HAVE_TAGLIB_H
68 # include "fileref.h"
69 # include "tag.h"
70 #endif // HAVE_TAGLIB_H
72 using Global::myScreen;
74 namespace ph = std::placeholders;
76 namespace {
78 boost::array<
79 Actions::BaseAction *, static_cast<size_t>(Actions::Type::_numberOfActions)
80 > AvailableActions;
82 void populateActions();
84 bool scrollTagCanBeRun(NC::List *&list, SongList *&songs);
85 void scrollTagUpRun(NC::List *list, SongList *songs, MPD::Song::GetFunction get);
86 void scrollTagDownRun(NC::List *list, SongList *songs, MPD::Song::GetFunction get);
88 void seek();
89 void findItem(const SearchDirection direction);
90 void listsChangeFinisher();
92 template <typename Iterator>
93 bool findSelectedRangeAndPrintInfoIfNot(Iterator &first, Iterator &last)
95 bool success = findSelectedRange(first, last);
96 if (!success)
97 Statusbar::print("No range selected");
98 return success;
103 namespace Actions {
105 bool OriginalStatusbarVisibility;
106 bool ExitMainLoop = false;
108 size_t HeaderHeight;
109 size_t FooterHeight;
110 size_t FooterStartY;
112 void validateScreenSize()
114 using Global::MainHeight;
116 if (COLS < 30 || MainHeight < 5)
118 NC::destroyScreen();
119 std::cout << "Screen is too small to handle ncmpcpp correctly\n";
120 exit(1);
124 void initializeScreens()
126 myHelp = new Help;
127 myPlaylist = new Playlist;
128 myBrowser = new Browser;
129 mySearcher = new SearchEngine;
130 myLibrary = new MediaLibrary;
131 myPlaylistEditor = new PlaylistEditor;
132 myLyrics = new Lyrics;
133 mySelectedItemsAdder = new SelectedItemsAdder;
134 mySongInfo = new SongInfo;
135 myServerInfo = new ServerInfo;
136 mySortPlaylistDialog = new SortPlaylistDialog;
138 # ifdef HAVE_CURL_CURL_H
139 myLastfm = new Lastfm;
140 # endif // HAVE_CURL_CURL_H
142 # ifdef HAVE_TAGLIB_H
143 myTinyTagEditor = new TinyTagEditor;
144 myTagEditor = new TagEditor;
145 # endif // HAVE_TAGLIB_H
147 # ifdef ENABLE_VISUALIZER
148 myVisualizer = new Visualizer;
149 # endif // ENABLE_VISUALIZER
151 # ifdef ENABLE_OUTPUTS
152 myOutputs = new Outputs;
153 # endif // ENABLE_OUTPUTS
155 # ifdef ENABLE_CLOCK
156 myClock = new Clock;
157 # endif // ENABLE_CLOCK
161 void setResizeFlags()
163 myHelp->hasToBeResized = 1;
164 myPlaylist->hasToBeResized = 1;
165 myBrowser->hasToBeResized = 1;
166 mySearcher->hasToBeResized = 1;
167 myLibrary->hasToBeResized = 1;
168 myPlaylistEditor->hasToBeResized = 1;
169 myLyrics->hasToBeResized = 1;
170 mySelectedItemsAdder->hasToBeResized = 1;
171 mySongInfo->hasToBeResized = 1;
172 myServerInfo->hasToBeResized = 1;
173 mySortPlaylistDialog->hasToBeResized = 1;
175 # ifdef HAVE_CURL_CURL_H
176 myLastfm->hasToBeResized = 1;
177 # endif // HAVE_CURL_CURL_H
179 # ifdef HAVE_TAGLIB_H
180 myTinyTagEditor->hasToBeResized = 1;
181 myTagEditor->hasToBeResized = 1;
182 # endif // HAVE_TAGLIB_H
184 # ifdef ENABLE_VISUALIZER
185 myVisualizer->hasToBeResized = 1;
186 # endif // ENABLE_VISUALIZER
188 # ifdef ENABLE_OUTPUTS
189 myOutputs->hasToBeResized = 1;
190 # endif // ENABLE_OUTPUTS
192 # ifdef ENABLE_CLOCK
193 myClock->hasToBeResized = 1;
194 # endif // ENABLE_CLOCK
197 void resizeScreen(bool reload_main_window)
199 using Global::MainHeight;
200 using Global::wHeader;
201 using Global::wFooter;
203 // update internal screen dimensions
204 if (reload_main_window)
206 rl_resize_terminal();
207 endwin();
208 refresh();
211 MainHeight = LINES-(Config.design == Design::Alternative ? 7 : 4);
213 validateScreenSize();
215 if (!Config.header_visibility)
216 MainHeight += 2;
217 if (!Config.statusbar_visibility)
218 ++MainHeight;
220 setResizeFlags();
222 applyToVisibleWindows(&BaseScreen::resize);
224 if (Config.header_visibility || Config.design == Design::Alternative)
225 wHeader->resize(COLS, HeaderHeight);
227 FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1);
228 wFooter->moveTo(0, FooterStartY);
229 wFooter->resize(COLS, Config.statusbar_visibility ? 2 : 1);
231 applyToVisibleWindows(&BaseScreen::refresh);
233 Status::Changes::elapsedTime(false);
234 Status::Changes::playerState();
235 // Note: routines for drawing separator if alternative user
236 // interface is active and header is hidden are placed in
237 // NcmpcppStatusChanges.StatusFlags
238 Status::Changes::flags();
239 drawHeader();
240 wFooter->refresh();
241 refresh();
244 void setWindowsDimensions()
246 using Global::MainStartY;
247 using Global::MainHeight;
249 MainStartY = Config.design == Design::Alternative ? 5 : 2;
250 MainHeight = LINES-(Config.design == Design::Alternative ? 7 : 4);
252 if (!Config.header_visibility)
254 MainStartY -= 2;
255 MainHeight += 2;
257 if (!Config.statusbar_visibility)
258 ++MainHeight;
260 HeaderHeight = Config.design == Design::Alternative ? (Config.header_visibility ? 5 : 3) : 1;
261 FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1);
262 FooterHeight = Config.statusbar_visibility ? 2 : 1;
265 void confirmAction(const boost::format &description)
267 Statusbar::ScopedLock slock;
268 Statusbar::put() << description.str()
269 << " [" << NC::Format::Bold << 'y' << NC::Format::NoBold
270 << '/' << NC::Format::Bold << 'n' << NC::Format::NoBold
271 << "] ";
272 auto answer = Statusbar::Helpers::promptReturnOneOf({"y", "n"});
273 if (answer == "n")
274 throw NC::PromptAborted(std::move(answer));
277 bool isMPDMusicDirSet()
279 if (Config.mpd_music_dir.empty())
281 Statusbar::print("Proper mpd_music_dir variable has to be set in configuration file");
282 return false;
284 return true;
287 BaseAction &get(Actions::Type at)
289 if (AvailableActions[1] == nullptr)
290 populateActions();
291 BaseAction *action = AvailableActions[static_cast<size_t>(at)];
292 // action should be always present if action type in queried
293 assert(action != nullptr);
294 return *action;
297 BaseAction *get(const std::string &name)
299 BaseAction *result = 0;
300 if (AvailableActions[1] == nullptr)
301 populateActions();
302 for (auto it = AvailableActions.begin(); it != AvailableActions.end(); ++it)
304 if (*it != nullptr && (*it)->name() == name)
306 result = *it;
307 break;
310 return result;
313 UpdateEnvironment::UpdateEnvironment()
314 : BaseAction(Type::UpdateEnvironment, "update_environment")
315 , m_past(boost::posix_time::from_time_t(0))
318 void UpdateEnvironment::run(bool update_timer, bool refresh_window)
320 using Global::Timer;
322 // update timer, status if necessary etc.
323 Status::trace(update_timer, true);
325 // header stuff
326 if ((myScreen == myPlaylist || myScreen == myBrowser || myScreen == myLyrics)
327 && (Timer - m_past > boost::posix_time::milliseconds(500))
330 drawHeader();
331 m_past = Timer;
334 if (refresh_window)
335 myScreen->refreshWindow();
338 void UpdateEnvironment::run()
340 run(true, true);
343 bool MouseEvent::canBeRun()
345 return Config.mouse_support;
348 void MouseEvent::run()
350 using Global::VolumeState;
351 using Global::wFooter;
353 m_old_mouse_event = m_mouse_event;
354 m_mouse_event = wFooter->getMouseEvent();
356 //Statusbar::printf("(%1%, %2%, %3%)", m_mouse_event.bstate, m_mouse_event.x, m_mouse_event.y);
358 if (m_mouse_event.bstate & BUTTON1_PRESSED
359 && m_mouse_event.y == LINES-(Config.statusbar_visibility ? 2 : 1)
360 ) // progressbar
362 if (Status::State::player() == MPD::psStop)
363 return;
364 Mpd.Seek(Status::State::currentSongPosition(),
365 Status::State::totalTime()*m_mouse_event.x/double(COLS));
367 else if (m_mouse_event.bstate & BUTTON1_PRESSED
368 && (Config.statusbar_visibility || Config.design == Design::Alternative)
369 && Status::State::player() != MPD::psStop
370 && m_mouse_event.y == (Config.design == Design::Alternative ? 1 : LINES-1)
371 && m_mouse_event.x < 9
372 ) // playing/paused
374 Mpd.Toggle();
376 else if ((m_mouse_event.bstate & BUTTON5_PRESSED || m_mouse_event.bstate & BUTTON4_PRESSED)
377 && (Config.header_visibility || Config.design == Design::Alternative)
378 && m_mouse_event.y == 0 && size_t(m_mouse_event.x) > COLS-VolumeState.length()
379 ) // volume
381 if (m_mouse_event.bstate & BUTTON5_PRESSED)
382 get(Type::VolumeDown).execute();
383 else
384 get(Type::VolumeUp).execute();
386 else if (m_mouse_event.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED | BUTTON4_PRESSED | BUTTON5_PRESSED))
387 myScreen->mouseButtonPressed(m_mouse_event);
390 void ScrollUp::run()
392 myScreen->scroll(NC::Scroll::Up);
393 listsChangeFinisher();
396 void ScrollDown::run()
398 myScreen->scroll(NC::Scroll::Down);
399 listsChangeFinisher();
402 bool ScrollUpArtist::canBeRun()
404 return scrollTagCanBeRun(m_list, m_songs);
407 void ScrollUpArtist::run()
409 scrollTagUpRun(m_list, m_songs, &MPD::Song::getArtist);
412 bool ScrollUpAlbum::canBeRun()
414 return scrollTagCanBeRun(m_list, m_songs);
417 void ScrollUpAlbum::run()
419 scrollTagUpRun(m_list, m_songs, &MPD::Song::getAlbum);
422 bool ScrollDownArtist::canBeRun()
424 return scrollTagCanBeRun(m_list, m_songs);
427 void ScrollDownArtist::run()
429 scrollTagDownRun(m_list, m_songs, &MPD::Song::getArtist);
432 bool ScrollDownAlbum::canBeRun()
434 return scrollTagCanBeRun(m_list, m_songs);
437 void ScrollDownAlbum::run()
439 scrollTagDownRun(m_list, m_songs, &MPD::Song::getAlbum);
442 void PageUp::run()
444 myScreen->scroll(NC::Scroll::PageUp);
445 listsChangeFinisher();
448 void PageDown::run()
450 myScreen->scroll(NC::Scroll::PageDown);
451 listsChangeFinisher();
454 void MoveHome::run()
456 myScreen->scroll(NC::Scroll::Home);
457 listsChangeFinisher();
460 void MoveEnd::run()
462 myScreen->scroll(NC::Scroll::End);
463 listsChangeFinisher();
466 void ToggleInterface::run()
468 switch (Config.design)
470 case Design::Classic:
471 Config.design = Design::Alternative;
472 Config.statusbar_visibility = false;
473 break;
474 case Design::Alternative:
475 Config.design = Design::Classic;
476 Config.statusbar_visibility = OriginalStatusbarVisibility;
477 break;
479 setWindowsDimensions();
480 resizeScreen(false);
481 // unlock progressbar
482 Progressbar::ScopedLock();
483 Status::Changes::mixer();
484 Status::Changes::elapsedTime(false);
485 Statusbar::printf("User interface: %1%", Config.design);
488 bool JumpToParentDirectory::canBeRun()
490 return (myScreen == myBrowser)
491 # ifdef HAVE_TAGLIB_H
492 || (myScreen->activeWindow() == myTagEditor->Dirs)
493 # endif // HAVE_TAGLIB_H
497 void JumpToParentDirectory::run()
499 if (myScreen == myBrowser)
501 if (!myBrowser->inRootDirectory())
503 myBrowser->main().reset();
504 myBrowser->enterPressed();
507 # ifdef HAVE_TAGLIB_H
508 else if (myScreen == myTagEditor)
510 if (myTagEditor->CurrentDir() != "/")
512 myTagEditor->Dirs->reset();
513 myTagEditor->enterPressed();
516 # endif // HAVE_TAGLIB_H
519 void PressEnter::run()
521 myScreen->enterPressed();
524 bool PreviousColumn::canBeRun()
526 auto hc = hasColumns(myScreen);
527 return hc && hc->previousColumnAvailable();
530 void PreviousColumn::run()
532 hasColumns(myScreen)->previousColumn();
535 bool NextColumn::canBeRun()
537 auto hc = hasColumns(myScreen);
538 return hc && hc->nextColumnAvailable();
541 void NextColumn::run()
543 hasColumns(myScreen)->nextColumn();
546 bool MasterScreen::canBeRun()
548 using Global::myLockedScreen;
549 using Global::myInactiveScreen;
551 return myLockedScreen
552 && myInactiveScreen
553 && myLockedScreen != myScreen
554 && myScreen->isMergable();
557 void MasterScreen::run()
559 using Global::myInactiveScreen;
560 using Global::myLockedScreen;
562 myInactiveScreen = myScreen;
563 myScreen = myLockedScreen;
564 drawHeader();
567 bool SlaveScreen::canBeRun()
569 using Global::myLockedScreen;
570 using Global::myInactiveScreen;
572 return myLockedScreen
573 && myInactiveScreen
574 && myLockedScreen == myScreen
575 && myScreen->isMergable();
578 void SlaveScreen::run()
580 using Global::myInactiveScreen;
581 using Global::myLockedScreen;
583 myScreen = myInactiveScreen;
584 myInactiveScreen = myLockedScreen;
585 drawHeader();
588 void VolumeUp::run()
590 int volume = std::min(Status::State::volume()+Config.volume_change_step, 100u);
591 Mpd.SetVolume(volume);
594 void VolumeDown::run()
596 int volume = std::max(int(Status::State::volume()-Config.volume_change_step), 0);
597 Mpd.SetVolume(volume);
600 bool AddItemToPlaylist::canBeRun()
602 if (m_hs != static_cast<void *>(myScreen))
603 m_hs = dynamic_cast<HasSongs *>(myScreen);
604 return m_hs != nullptr;
607 void AddItemToPlaylist::run()
609 bool success = m_hs->addItemToPlaylist();
610 if (success)
612 myScreen->scroll(NC::Scroll::Down);
613 listsChangeFinisher();
617 bool DeletePlaylistItems::canBeRun()
619 return (myScreen == myPlaylist && !myPlaylist->main().empty())
620 || (myScreen->isActiveWindow(myPlaylistEditor->Content) && !myPlaylistEditor->Content.empty());
623 void DeletePlaylistItems::run()
625 if (myScreen == myPlaylist)
627 Statusbar::print("Deleting items...");
628 auto delete_fun = std::bind(&MPD::Connection::Delete, ph::_1, ph::_2);
629 deleteSelectedSongs(myPlaylist->main(), delete_fun);
630 Statusbar::print("Item(s) deleted");
632 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
634 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
635 auto delete_fun = std::bind(&MPD::Connection::PlaylistDelete, ph::_1, playlist, ph::_2);
636 Statusbar::print("Deleting items...");
637 deleteSelectedSongs(myPlaylistEditor->Content, delete_fun);
638 Statusbar::print("Item(s) deleted");
642 bool DeleteBrowserItems::canBeRun()
644 auto check_if_deletion_allowed = []() {
645 if (Config.allow_for_physical_item_deletion)
646 return true;
647 else
649 Statusbar::print("Flag \"allow_for_physical_item_deletion\" needs to be enabled in configuration file");
650 return false;
653 return myScreen == myBrowser
654 && !myBrowser->main().empty()
655 && isMPDMusicDirSet()
656 && check_if_deletion_allowed();
659 void DeleteBrowserItems::run()
661 auto get_name = [](const MPD::Item &item) -> std::string {
662 std::string iname;
663 switch (item.type())
665 case MPD::Item::Type::Directory:
666 iname = getBasename(item.directory().path());
667 break;
668 case MPD::Item::Type::Song:
669 iname = item.song().getName();
670 break;
671 case MPD::Item::Type::Playlist:
672 iname = getBasename(item.playlist().path());
673 break;
675 return iname;
678 boost::format question;
679 if (hasSelected(myBrowser->main().begin(), myBrowser->main().end()))
680 question = boost::format("Delete selected items?");
681 else
683 const auto &item = myBrowser->main().current()->value();
684 // parent directories are not accepted (and they
685 // can't be selected, so in other cases it's fine).
686 if (myBrowser->isParentDirectory(item))
687 return;
688 const char msg[] = "Delete \"%1%\"?";
689 question = boost::format(msg) % wideShorten(
690 get_name(item), COLS-const_strlen(msg)-5
693 confirmAction(question);
695 auto items = getSelectedOrCurrent(
696 myBrowser->main().begin(),
697 myBrowser->main().end(),
698 myBrowser->main().current()
700 for (const auto &item : items)
702 myBrowser->remove(item->value());
703 const char msg[] = "Deleted %1% \"%2%\"";
704 Statusbar::printf(msg,
705 itemTypeToString(item->value().type()),
706 wideShorten(get_name(item->value()), COLS-const_strlen(msg))
710 if (!myBrowser->isLocal())
711 Mpd.UpdateDirectory(myBrowser->currentDirectory());
712 myBrowser->requestUpdate();
715 bool DeleteStoredPlaylist::canBeRun()
717 return myScreen->isActiveWindow(myPlaylistEditor->Playlists);
720 void DeleteStoredPlaylist::run()
722 if (myPlaylistEditor->Playlists.empty())
723 return;
724 boost::format question;
725 if (hasSelected(myPlaylistEditor->Playlists.begin(), myPlaylistEditor->Playlists.end()))
726 question = boost::format("Delete selected playlists?");
727 else
728 question = boost::format("Delete playlist \"%1%\"?")
729 % wideShorten(myPlaylistEditor->Playlists.current()->value().path(), COLS-question.size()-10);
730 confirmAction(question);
731 auto list = getSelectedOrCurrent(
732 myPlaylistEditor->Playlists.begin(),
733 myPlaylistEditor->Playlists.end(),
734 myPlaylistEditor->Playlists.current()
736 for (const auto &item : list)
737 Mpd.DeletePlaylist(item->value().path());
738 Statusbar::printf("%1% deleted", list.size() == 1 ? "Playlist" : "Playlists");
739 // force playlists update. this happens automatically, but only after call
740 // to Key::read, therefore when we call PlaylistEditor::Update, it won't
741 // yet see it, so let's point that it needs to update it.
742 myPlaylistEditor->requestPlaylistsUpdate();
745 void ReplaySong::run()
747 if (Status::State::player() != MPD::psStop)
748 Mpd.Seek(Status::State::currentSongPosition(), 0);
751 void PreviousSong::run()
753 Mpd.Prev();
756 void NextSong::run()
758 Mpd.Next();
761 void Pause::run()
763 Mpd.Toggle();
766 void SavePlaylist::run()
768 using Global::wFooter;
770 std::string playlist_name;
772 Statusbar::ScopedLock slock;
773 Statusbar::put() << "Save playlist as: ";
774 playlist_name = wFooter->prompt();
778 Mpd.SavePlaylist(playlist_name);
779 Statusbar::printf("Playlist saved as \"%1%\"", playlist_name);
781 catch (MPD::ServerError &e)
783 if (e.code() == MPD_SERVER_ERROR_EXIST)
785 confirmAction(
786 boost::format("Playlist \"%1%\" already exists, overwrite?") % playlist_name
788 Mpd.DeletePlaylist(playlist_name);
789 Mpd.SavePlaylist(playlist_name);
790 Statusbar::print("Playlist overwritten");
792 else
793 throw;
797 void Stop::run()
799 Mpd.Stop();
802 void ExecuteCommand::run()
804 using Global::wFooter;
806 std::string cmd_name;
808 Statusbar::ScopedLock slock;
809 NC::Window::ScopedPromptHook helper(*wFooter,
810 Statusbar::Helpers::TryExecuteImmediateCommand()
812 Statusbar::put() << NC::Format::Bold << ":" << NC::Format::NoBold;
813 cmd_name = wFooter->prompt();
816 auto cmd = Bindings.findCommand(cmd_name);
817 if (cmd)
819 Statusbar::printf(1, "Executing %1%...", cmd_name);
820 bool res = cmd->binding().execute();
821 Statusbar::printf("Execution of command \"%1%\" %2%.",
822 cmd_name, res ? "successful" : "unsuccessful"
825 else
826 Statusbar::printf("No command named \"%1%\"", cmd_name);
829 bool MoveSortOrderUp::canBeRun()
831 return myScreen == mySortPlaylistDialog;
834 void MoveSortOrderUp::run()
836 mySortPlaylistDialog->moveSortOrderUp();
839 bool MoveSortOrderDown::canBeRun()
841 return myScreen == mySortPlaylistDialog;
844 void MoveSortOrderDown::run()
846 mySortPlaylistDialog->moveSortOrderDown();
849 bool MoveSelectedItemsUp::canBeRun()
851 return ((myScreen == myPlaylist
852 && !myPlaylist->main().empty())
853 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
854 && !myPlaylistEditor->Content.empty()));
857 void MoveSelectedItemsUp::run()
859 if (myScreen == myPlaylist)
861 moveSelectedItemsUp(myPlaylist->main(), std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
863 else if (myScreen == myPlaylistEditor)
865 assert(!myPlaylistEditor->Playlists.empty());
866 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
867 auto move_fun = std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3);
868 moveSelectedItemsUp(myPlaylistEditor->Content, move_fun);
872 bool MoveSelectedItemsDown::canBeRun()
874 return ((myScreen == myPlaylist
875 && !myPlaylist->main().empty())
876 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
877 && !myPlaylistEditor->Content.empty()));
880 void MoveSelectedItemsDown::run()
882 if (myScreen == myPlaylist)
884 moveSelectedItemsDown(myPlaylist->main(), std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
886 else if (myScreen == myPlaylistEditor)
888 assert(!myPlaylistEditor->Playlists.empty());
889 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
890 auto move_fun = std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3);
891 moveSelectedItemsDown(myPlaylistEditor->Content, move_fun);
895 bool MoveSelectedItemsTo::canBeRun()
897 return myScreen == myPlaylist
898 || myScreen->isActiveWindow(myPlaylistEditor->Content);
901 void MoveSelectedItemsTo::run()
903 if (myScreen == myPlaylist)
905 if (!myPlaylist->main().empty())
906 moveSelectedItemsTo(myPlaylist->main(), std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
908 else
910 assert(!myPlaylistEditor->Playlists.empty());
911 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
912 auto move_fun = std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3);
913 moveSelectedItemsTo(myPlaylistEditor->Content, move_fun);
917 bool Add::canBeRun()
919 return myScreen != myPlaylistEditor
920 || !myPlaylistEditor->Playlists.empty();
923 void Add::run()
925 using Global::wFooter;
927 std::string path;
929 Statusbar::ScopedLock slock;
930 Statusbar::put() << (myScreen == myPlaylistEditor ? "Add to playlist: " : "Add: ");
931 path = wFooter->prompt();
934 // confirm when one wants to add the whole database
935 if (path.empty())
936 confirmAction("Are you sure you want to add the whole database?");
938 Statusbar::put() << "Adding...";
939 wFooter->refresh();
940 if (myScreen == myPlaylistEditor)
941 Mpd.AddToPlaylist(myPlaylistEditor->Playlists.current()->value().path(), path);
942 else
946 Mpd.Add(path);
948 catch (MPD::ServerError &err)
950 // If a path is not a file or directory, assume it is a playlist.
951 if (err.code() == MPD_SERVER_ERROR_NO_EXIST)
952 Mpd.LoadPlaylist(path);
953 else
954 throw;
959 bool SeekForward::canBeRun()
961 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
964 void SeekForward::run()
966 seek();
969 bool SeekBackward::canBeRun()
971 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
974 void SeekBackward::run()
976 seek();
979 bool ToggleDisplayMode::canBeRun()
981 return myScreen == myPlaylist
982 || myScreen == myBrowser
983 || myScreen == mySearcher
984 || myScreen->isActiveWindow(myPlaylistEditor->Content);
987 void ToggleDisplayMode::run()
989 if (myScreen == myPlaylist)
991 switch (Config.playlist_display_mode)
993 case DisplayMode::Classic:
994 Config.playlist_display_mode = DisplayMode::Columns;
995 myPlaylist->main().setItemDisplayer(std::bind(
996 Display::SongsInColumns, ph::_1, std::cref(myPlaylist->main())
998 if (Config.titles_visibility)
999 myPlaylist->main().setTitle(Display::Columns(myPlaylist->main().getWidth()));
1000 else
1001 myPlaylist->main().setTitle("");
1002 break;
1003 case DisplayMode::Columns:
1004 Config.playlist_display_mode = DisplayMode::Classic;
1005 myPlaylist->main().setItemDisplayer(std::bind(
1006 Display::Songs, ph::_1, std::cref(myPlaylist->main()), std::cref(Config.song_list_format)
1008 myPlaylist->main().setTitle("");
1010 Statusbar::printf("Playlist display mode: %1%", Config.playlist_display_mode);
1012 else if (myScreen == myBrowser)
1014 switch (Config.browser_display_mode)
1016 case DisplayMode::Classic:
1017 Config.browser_display_mode = DisplayMode::Columns;
1018 if (Config.titles_visibility)
1019 myBrowser->main().setTitle(Display::Columns(myBrowser->main().getWidth()));
1020 else
1021 myBrowser->main().setTitle("");
1022 break;
1023 case DisplayMode::Columns:
1024 Config.browser_display_mode = DisplayMode::Classic;
1025 myBrowser->main().setTitle("");
1026 break;
1028 Statusbar::printf("Browser display mode: %1%", Config.browser_display_mode);
1030 else if (myScreen == mySearcher)
1032 switch (Config.search_engine_display_mode)
1034 case DisplayMode::Classic:
1035 Config.search_engine_display_mode = DisplayMode::Columns;
1036 break;
1037 case DisplayMode::Columns:
1038 Config.search_engine_display_mode = DisplayMode::Classic;
1039 break;
1041 Statusbar::printf("Search engine display mode: %1%", Config.search_engine_display_mode);
1042 if (mySearcher->main().size() > SearchEngine::StaticOptions)
1043 mySearcher->main().setTitle(
1044 Config.search_engine_display_mode == DisplayMode::Columns
1045 && Config.titles_visibility
1046 ? Display::Columns(mySearcher->main().getWidth())
1047 : ""
1050 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
1052 switch (Config.playlist_editor_display_mode)
1054 case DisplayMode::Classic:
1055 Config.playlist_editor_display_mode = DisplayMode::Columns;
1056 myPlaylistEditor->Content.setItemDisplayer(std::bind(
1057 Display::SongsInColumns, ph::_1, std::cref(myPlaylistEditor->Content)
1059 break;
1060 case DisplayMode::Columns:
1061 Config.playlist_editor_display_mode = DisplayMode::Classic;
1062 myPlaylistEditor->Content.setItemDisplayer(std::bind(
1063 Display::Songs, ph::_1, std::cref(myPlaylistEditor->Content), std::cref(Config.song_list_format)
1065 break;
1067 Statusbar::printf("Playlist editor display mode: %1%", Config.playlist_editor_display_mode);
1071 bool ToggleSeparatorsBetweenAlbums::canBeRun()
1073 return true;
1076 void ToggleSeparatorsBetweenAlbums::run()
1078 Config.playlist_separate_albums = !Config.playlist_separate_albums;
1079 Statusbar::printf("Separators between albums: %1%",
1080 Config.playlist_separate_albums ? "on" : "off"
1084 bool ToggleLyricsUpdateOnSongChange::canBeRun()
1086 return myScreen == myLyrics;
1089 void ToggleLyricsUpdateOnSongChange::run()
1091 Config.now_playing_lyrics = !Config.now_playing_lyrics;
1092 Statusbar::printf("Update lyrics if song changes: %1%",
1093 Config.now_playing_lyrics ? "on" : "off"
1097 #ifndef HAVE_CURL_CURL_H
1098 bool ToggleLyricsFetcher::canBeRun()
1100 return false;
1102 #endif // NOT HAVE_CURL_CURL_H
1104 void ToggleLyricsFetcher::run()
1106 # ifdef HAVE_CURL_CURL_H
1107 myLyrics->ToggleFetcher();
1108 # endif // HAVE_CURL_CURL_H
1111 #ifndef HAVE_CURL_CURL_H
1112 bool ToggleFetchingLyricsInBackground::canBeRun()
1114 return false;
1116 #endif // NOT HAVE_CURL_CURL_H
1118 void ToggleFetchingLyricsInBackground::run()
1120 # ifdef HAVE_CURL_CURL_H
1121 Config.fetch_lyrics_in_background = !Config.fetch_lyrics_in_background;
1122 Statusbar::printf("Fetching lyrics for playing songs in background: %1%",
1123 Config.fetch_lyrics_in_background ? "on" : "off"
1125 # endif // HAVE_CURL_CURL_H
1128 void TogglePlayingSongCentering::run()
1130 Config.autocenter_mode = !Config.autocenter_mode;
1131 Statusbar::printf("Centering playing song: %1%",
1132 Config.autocenter_mode ? "on" : "off"
1134 if (Config.autocenter_mode)
1136 auto s = myPlaylist->nowPlayingSong();
1137 if (!s.empty())
1138 myPlaylist->main().highlight(s.getPosition());
1142 void UpdateDatabase::run()
1144 if (myScreen == myBrowser)
1145 Mpd.UpdateDirectory(myBrowser->currentDirectory());
1146 # ifdef HAVE_TAGLIB_H
1147 else if (myScreen == myTagEditor)
1148 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1149 # endif // HAVE_TAGLIB_H
1150 else
1151 Mpd.UpdateDirectory("/");
1154 bool JumpToPlayingSong::canBeRun()
1156 return myScreen == myPlaylist
1157 || myScreen == myBrowser
1158 || myScreen == myLibrary;
1161 void JumpToPlayingSong::run()
1163 auto s = myPlaylist->nowPlayingSong();
1164 if (s.empty())
1165 return;
1166 if (myScreen == myPlaylist)
1168 myPlaylist->main().highlight(s.getPosition());
1170 else if (myScreen == myBrowser)
1172 myBrowser->locateSong(s);
1174 else if (myScreen == myLibrary)
1176 myLibrary->LocateSong(s);
1180 void ToggleRepeat::run()
1182 Mpd.SetRepeat(!Status::State::repeat());
1185 bool Shuffle::canBeRun()
1187 if (myScreen != myPlaylist)
1188 return false;
1189 m_begin = myPlaylist->main().begin();
1190 m_end = myPlaylist->main().end();
1191 return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end);
1194 void Shuffle::run()
1196 auto begin = myPlaylist->main().begin();
1197 Mpd.ShuffleRange(m_begin-begin, m_end-begin);
1198 Statusbar::print("Range shuffled");
1201 void ToggleRandom::run()
1203 Mpd.SetRandom(!Status::State::random());
1206 bool StartSearching::canBeRun()
1208 return myScreen == mySearcher && !mySearcher->main()[0].isInactive();
1211 void StartSearching::run()
1213 mySearcher->main().highlight(SearchEngine::SearchButton);
1214 mySearcher->main().setHighlighting(0);
1215 mySearcher->main().refresh();
1216 mySearcher->main().setHighlighting(1);
1217 mySearcher->enterPressed();
1220 bool SaveTagChanges::canBeRun()
1222 # ifdef HAVE_TAGLIB_H
1223 return myScreen == myTinyTagEditor
1224 || myScreen->activeWindow() == myTagEditor->TagTypes;
1225 # else
1226 return false;
1227 # endif // HAVE_TAGLIB_H
1230 void SaveTagChanges::run()
1232 # ifdef HAVE_TAGLIB_H
1233 if (myScreen == myTinyTagEditor)
1235 myTinyTagEditor->main().highlight(myTinyTagEditor->main().size()-2); // Save
1236 myTinyTagEditor->enterPressed();
1238 else if (myScreen->activeWindow() == myTagEditor->TagTypes)
1240 myTagEditor->TagTypes->highlight(myTagEditor->TagTypes->size()-1); // Save
1241 myTagEditor->enterPressed();
1243 # endif // HAVE_TAGLIB_H
1246 void ToggleSingle::run()
1248 Mpd.SetSingle(!Status::State::single());
1251 void ToggleConsume::run()
1253 Mpd.SetConsume(!Status::State::consume());
1256 void ToggleCrossfade::run()
1258 Mpd.SetCrossfade(Status::State::crossfade() ? 0 : Config.crossfade_time);
1261 void SetCrossfade::run()
1263 using Global::wFooter;
1265 Statusbar::ScopedLock slock;
1266 Statusbar::put() << "Set crossfade to: ";
1267 auto crossfade = fromString<unsigned>(wFooter->prompt());
1268 lowerBoundCheck(crossfade, 0u);
1269 Config.crossfade_time = crossfade;
1270 Mpd.SetCrossfade(crossfade);
1273 void SetVolume::run()
1275 using Global::wFooter;
1277 unsigned volume;
1279 Statusbar::ScopedLock slock;
1280 Statusbar::put() << "Set volume to: ";
1281 volume = fromString<unsigned>(wFooter->prompt());
1282 boundsCheck(volume, 0u, 100u);
1283 Mpd.SetVolume(volume);
1285 Statusbar::printf("Volume set to %1%%%", volume);
1288 bool EditSong::canBeRun()
1290 # ifdef HAVE_TAGLIB_H
1291 m_song = currentSong(myScreen);
1292 return m_song != nullptr && isMPDMusicDirSet();
1293 # else
1294 return false;
1295 # endif // HAVE_TAGLIB_H
1298 void EditSong::run()
1300 # ifdef HAVE_TAGLIB_H
1301 myTinyTagEditor->SetEdited(*m_song);
1302 myTinyTagEditor->switchTo();
1303 # endif // HAVE_TAGLIB_H
1306 bool EditLibraryTag::canBeRun()
1308 # ifdef HAVE_TAGLIB_H
1309 return myScreen->isActiveWindow(myLibrary->Tags)
1310 && !myLibrary->Tags.empty()
1311 && isMPDMusicDirSet();
1312 # else
1313 return false;
1314 # endif // HAVE_TAGLIB_H
1317 void EditLibraryTag::run()
1319 # ifdef HAVE_TAGLIB_H
1320 using Global::wFooter;
1322 std::string new_tag;
1324 Statusbar::ScopedLock slock;
1325 Statusbar::put() << NC::Format::Bold << tagTypeToString(Config.media_lib_primary_tag) << NC::Format::NoBold << ": ";
1326 new_tag = wFooter->prompt(myLibrary->Tags.current()->value().tag());
1328 if (!new_tag.empty() && new_tag != myLibrary->Tags.current()->value().tag())
1330 Statusbar::print("Updating tags...");
1331 Mpd.StartSearch(true);
1332 Mpd.AddSearch(Config.media_lib_primary_tag, myLibrary->Tags.current()->value().tag());
1333 MPD::MutableSong::SetFunction set = tagTypeToSetFunction(Config.media_lib_primary_tag);
1334 assert(set);
1335 bool success = true;
1336 std::string dir_to_update;
1337 for (MPD::SongIterator s = Mpd.CommitSearchSongs(), end; s != end; ++s)
1339 MPD::MutableSong ms = std::move(*s);
1340 ms.setTags(set, new_tag);
1341 Statusbar::printf("Updating tags in \"%1%\"...", ms.getName());
1342 std::string path = Config.mpd_music_dir + ms.getURI();
1343 if (!Tags::write(ms))
1345 success = false;
1346 const char msg[] = "Error while updating tags in \"%1%\"";
1347 Statusbar::printf(msg, wideShorten(ms.getURI(), COLS-const_strlen(msg)));
1348 s.finish();
1349 break;
1351 if (dir_to_update.empty())
1352 dir_to_update = ms.getURI();
1353 else
1354 dir_to_update = getSharedDirectory(dir_to_update, ms.getURI());
1356 if (success)
1358 Mpd.UpdateDirectory(dir_to_update);
1359 Statusbar::print("Tags updated successfully");
1362 # endif // HAVE_TAGLIB_H
1365 bool EditLibraryAlbum::canBeRun()
1367 # ifdef HAVE_TAGLIB_H
1368 return myScreen->isActiveWindow(myLibrary->Albums)
1369 && !myLibrary->Albums.empty()
1370 && isMPDMusicDirSet();
1371 # else
1372 return false;
1373 # endif // HAVE_TAGLIB_H
1376 void EditLibraryAlbum::run()
1378 # ifdef HAVE_TAGLIB_H
1379 using Global::wFooter;
1380 // FIXME: merge this and EditLibraryTag. also, prompt on failure if user wants to continue
1381 std::string new_album;
1383 Statusbar::ScopedLock slock;
1384 Statusbar::put() << NC::Format::Bold << "Album: " << NC::Format::NoBold;
1385 new_album = wFooter->prompt(myLibrary->Albums.current()->value().entry().album());
1387 if (!new_album.empty() && new_album != myLibrary->Albums.current()->value().entry().album())
1389 bool success = 1;
1390 Statusbar::print("Updating tags...");
1391 for (size_t i = 0; i < myLibrary->Songs.size(); ++i)
1393 Statusbar::printf("Updating tags in \"%1%\"...", myLibrary->Songs[i].value().getName());
1394 std::string path = Config.mpd_music_dir + myLibrary->Songs[i].value().getURI();
1395 TagLib::FileRef f(path.c_str());
1396 if (f.isNull())
1398 const char msg[] = "Error while opening file \"%1%\"";
1399 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1400 success = 0;
1401 break;
1403 f.tag()->setAlbum(ToWString(new_album));
1404 if (!f.save())
1406 const char msg[] = "Error while writing tags in \"%1%\"";
1407 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1408 success = 0;
1409 break;
1412 if (success)
1414 Mpd.UpdateDirectory(getSharedDirectory(myLibrary->Songs.beginV(), myLibrary->Songs.endV()));
1415 Statusbar::print("Tags updated successfully");
1418 # endif // HAVE_TAGLIB_H
1421 bool EditDirectoryName::canBeRun()
1423 return ((myScreen == myBrowser
1424 && !myBrowser->main().empty()
1425 && myBrowser->main().current()->value().type() == MPD::Item::Type::Directory)
1426 # ifdef HAVE_TAGLIB_H
1427 || (myScreen->activeWindow() == myTagEditor->Dirs
1428 && !myTagEditor->Dirs->empty()
1429 && myTagEditor->Dirs->choice() > 0)
1430 # endif // HAVE_TAGLIB_H
1431 ) && isMPDMusicDirSet();
1434 void EditDirectoryName::run()
1436 using Global::wFooter;
1437 if (myScreen == myBrowser)
1439 std::string old_dir = myBrowser->main().current()->value().directory().path();
1440 std::string new_dir;
1442 Statusbar::ScopedLock slock;
1443 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1444 new_dir = wFooter->prompt(old_dir);
1446 if (!new_dir.empty() && new_dir != old_dir)
1448 std::string full_old_dir;
1449 if (!myBrowser->isLocal())
1450 full_old_dir += Config.mpd_music_dir;
1451 full_old_dir += old_dir;
1452 std::string full_new_dir;
1453 if (!myBrowser->isLocal())
1454 full_new_dir += Config.mpd_music_dir;
1455 full_new_dir += new_dir;
1456 boost::filesystem::rename(full_old_dir, full_new_dir);
1457 const char msg[] = "Directory renamed to \"%1%\"";
1458 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1459 if (!myBrowser->isLocal())
1460 Mpd.UpdateDirectory(getSharedDirectory(old_dir, new_dir));
1461 myBrowser->requestUpdate();
1464 # ifdef HAVE_TAGLIB_H
1465 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1467 std::string old_dir = myTagEditor->Dirs->current()->value().first, new_dir;
1469 Statusbar::ScopedLock slock;
1470 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1471 new_dir = wFooter->prompt(old_dir);
1473 if (!new_dir.empty() && new_dir != old_dir)
1475 std::string full_old_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + old_dir;
1476 std::string full_new_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + new_dir;
1477 if (rename(full_old_dir.c_str(), full_new_dir.c_str()) == 0)
1479 const char msg[] = "Directory renamed to \"%1%\"";
1480 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1481 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1483 else
1485 const char msg[] = "Couldn't rename \"%1%\": %2%";
1486 Statusbar::printf(msg, wideShorten(old_dir, COLS-const_strlen(msg)-25), strerror(errno));
1490 # endif // HAVE_TAGLIB_H
1493 bool EditPlaylistName::canBeRun()
1495 return (myScreen->isActiveWindow(myPlaylistEditor->Playlists)
1496 && !myPlaylistEditor->Playlists.empty())
1497 || (myScreen == myBrowser
1498 && !myBrowser->main().empty()
1499 && myBrowser->main().current()->value().type() == MPD::Item::Type::Playlist);
1502 void EditPlaylistName::run()
1504 using Global::wFooter;
1505 std::string old_name, new_name;
1506 if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
1507 old_name = myPlaylistEditor->Playlists.current()->value().path();
1508 else
1509 old_name = myBrowser->main().current()->value().playlist().path();
1511 Statusbar::ScopedLock slock;
1512 Statusbar::put() << NC::Format::Bold << "Playlist: " << NC::Format::NoBold;
1513 new_name = wFooter->prompt(old_name);
1515 if (!new_name.empty() && new_name != old_name)
1517 Mpd.Rename(old_name, new_name);
1518 const char msg[] = "Playlist renamed to \"%1%\"";
1519 Statusbar::printf(msg, wideShorten(new_name, COLS-const_strlen(msg)));
1523 bool EditLyrics::canBeRun()
1525 return myScreen == myLyrics;
1528 void EditLyrics::run()
1530 myLyrics->Edit();
1533 bool JumpToBrowser::canBeRun()
1535 m_song = currentSong(myScreen);
1536 return m_song != nullptr;
1539 void JumpToBrowser::run()
1541 myBrowser->locateSong(*m_song);
1544 bool JumpToMediaLibrary::canBeRun()
1546 m_song = currentSong(myScreen);
1547 return m_song != nullptr;
1550 void JumpToMediaLibrary::run()
1552 myLibrary->LocateSong(*m_song);
1555 bool JumpToPlaylistEditor::canBeRun()
1557 return myScreen == myBrowser
1558 && myBrowser->main().current()->value().type() == MPD::Item::Type::Playlist;
1561 void JumpToPlaylistEditor::run()
1563 myPlaylistEditor->Locate(myBrowser->main().current()->value().playlist());
1566 void ToggleScreenLock::run()
1568 using Global::wFooter;
1569 using Global::myLockedScreen;
1570 const char *msg_unlockable_screen = "Current screen can't be locked";
1571 if (myLockedScreen != nullptr)
1573 BaseScreen::unlock();
1574 Actions::setResizeFlags();
1575 myScreen->resize();
1576 Statusbar::print("Screen unlocked");
1578 else if (!myScreen->isLockable())
1580 Statusbar::print(msg_unlockable_screen);
1582 else
1584 unsigned part = Config.locked_screen_width_part*100;
1585 if (Config.ask_for_locked_screen_width_part)
1587 Statusbar::ScopedLock slock;
1588 Statusbar::put() << "% of the locked screen's width to be reserved (20-80): ";
1589 part = fromString<unsigned>(wFooter->prompt(boost::lexical_cast<std::string>(part)));
1591 boundsCheck(part, 20u, 80u);
1592 Config.locked_screen_width_part = part/100.0;
1593 if (myScreen->lock())
1594 Statusbar::printf("Screen locked (with %1%%% width)", part);
1595 else
1596 Statusbar::print(msg_unlockable_screen);
1600 bool JumpToTagEditor::canBeRun()
1602 # ifdef HAVE_TAGLIB_H
1603 m_song = currentSong(myScreen);
1604 return m_song != nullptr && isMPDMusicDirSet();
1605 # else
1606 return false;
1607 # endif // HAVE_TAGLIB_H
1610 void JumpToTagEditor::run()
1612 # ifdef HAVE_TAGLIB_H
1613 myTagEditor->LocateSong(*m_song);
1614 # endif // HAVE_TAGLIB_H
1617 bool JumpToPositionInSong::canBeRun()
1619 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1622 void JumpToPositionInSong::run()
1624 using Global::wFooter;
1626 const MPD::Song s = myPlaylist->nowPlayingSong();
1628 std::string spos;
1630 Statusbar::ScopedLock slock;
1631 Statusbar::put() << "Position to go (in %/m:ss/seconds(s)): ";
1632 spos = wFooter->prompt();
1635 boost::regex rx;
1636 boost::smatch what;
1637 if (boost::regex_match(spos, what, rx.assign("([0-9]+):([0-9]{2})"))) // mm:ss
1639 auto mins = fromString<unsigned>(what[1]);
1640 auto secs = fromString<unsigned>(what[2]);
1641 boundsCheck(secs, 0u, 60u);
1642 Mpd.Seek(s.getPosition(), mins * 60 + secs);
1644 else if (boost::regex_match(spos, what, rx.assign("([0-9]+)s"))) // position in seconds
1646 auto secs = fromString<unsigned>(what[1]);
1647 Mpd.Seek(s.getPosition(), secs);
1649 else if (boost::regex_match(spos, what, rx.assign("([0-9]+)[%]{0,1}"))) // position in %
1651 auto percent = fromString<unsigned>(what[1]);
1652 boundsCheck(percent, 0u, 100u);
1653 int secs = (percent * s.getDuration()) / 100.0;
1654 Mpd.Seek(s.getPosition(), secs);
1656 else
1657 Statusbar::print("Invalid format ([m]:[ss], [s]s, [%]%, [%] accepted)");
1660 bool SelectItem::canBeRun()
1662 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1663 return m_list != nullptr
1664 && !m_list->empty()
1665 && m_list->currentP()->isSelectable();
1668 void SelectItem::run()
1670 auto current = m_list->currentP();
1671 current->setSelected(!current->isSelected());
1674 bool SelectRange::canBeRun()
1676 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1677 if (m_list == nullptr)
1678 return false;
1679 m_begin = m_list->beginP();
1680 m_end = m_list->endP();
1681 return findRange(m_begin, m_end);
1684 void SelectRange::run()
1686 for (; m_begin != m_end; ++m_begin)
1687 m_begin->setSelected(true);
1688 Statusbar::print("Range selected");
1691 bool ReverseSelection::canBeRun()
1693 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1694 return m_list != nullptr;
1697 void ReverseSelection::run()
1699 for (auto &p : *m_list)
1700 p.setSelected(!p.isSelected());
1701 Statusbar::print("Selection reversed");
1704 bool RemoveSelection::canBeRun()
1706 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1707 return m_list != nullptr;
1710 void RemoveSelection::run()
1712 for (auto &p : *m_list)
1713 p.setSelected(false);
1714 Statusbar::print("Selection removed");
1717 bool SelectAlbum::canBeRun()
1719 auto *w = myScreen->activeWindow();
1720 if (m_list != static_cast<void *>(w))
1721 m_list = dynamic_cast<NC::List *>(w);
1722 if (m_songs != static_cast<void *>(w))
1723 m_songs = dynamic_cast<SongList *>(w);
1724 return m_list != nullptr && !m_list->empty()
1725 && m_songs != nullptr;
1728 void SelectAlbum::run()
1730 const auto front = m_songs->beginS(), current = m_songs->currentS(), end = m_songs->endS();
1731 auto *s = current->get<Bit::Song>();
1732 if (s == nullptr)
1733 return;
1734 auto get = &MPD::Song::getAlbum;
1735 const std::string tag = s->getTags(get);
1736 // go up
1737 for (auto it = current; it != front;)
1739 --it;
1740 s = it->get<Bit::Song>();
1741 if (s == nullptr || s->getTags(get) != tag)
1742 break;
1743 it->get<Bit::Properties>().setSelected(true);
1745 // go down
1746 for (auto it = current;;)
1748 it->get<Bit::Properties>().setSelected(true);
1749 if (++it == end)
1750 break;
1751 s = it->get<Bit::Song>();
1752 if (s == nullptr || s->getTags(get) != tag)
1753 break;
1755 Statusbar::print("Album around cursor position selected");
1758 bool AddSelectedItems::canBeRun()
1760 return myScreen != mySelectedItemsAdder;
1763 void AddSelectedItems::run()
1765 mySelectedItemsAdder->switchTo();
1768 void CropMainPlaylist::run()
1770 auto &w = myPlaylist->main();
1771 // cropping doesn't make sense in this case
1772 if (w.size() <= 1)
1773 return;
1774 if (Config.ask_before_clearing_playlists)
1775 confirmAction("Do you really want to crop main playlist?");
1776 Statusbar::print("Cropping playlist...");
1777 selectCurrentIfNoneSelected(w);
1778 cropPlaylist(w, std::bind(&MPD::Connection::Delete, ph::_1, ph::_2));
1779 Statusbar::print("Playlist cropped");
1782 bool CropPlaylist::canBeRun()
1784 return myScreen == myPlaylistEditor;
1787 void CropPlaylist::run()
1789 auto &w = myPlaylistEditor->Content;
1790 // cropping doesn't make sense in this case
1791 if (w.size() <= 1)
1792 return;
1793 assert(!myPlaylistEditor->Playlists.empty());
1794 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
1795 if (Config.ask_before_clearing_playlists)
1796 confirmAction(boost::format("Do you really want to crop playlist \"%1%\"?") % playlist);
1797 selectCurrentIfNoneSelected(w);
1798 Statusbar::printf("Cropping playlist \"%1%\"...", playlist);
1799 cropPlaylist(w, std::bind(&MPD::Connection::PlaylistDelete, ph::_1, playlist, ph::_2));
1800 Statusbar::printf("Playlist \"%1%\" cropped", playlist);
1803 void ClearMainPlaylist::run()
1805 if (!myPlaylist->main().empty() && Config.ask_before_clearing_playlists)
1806 confirmAction("Do you really want to clear main playlist?");
1807 Mpd.ClearMainPlaylist();
1808 Statusbar::print("Playlist cleared");
1809 myPlaylist->main().reset();
1812 bool ClearPlaylist::canBeRun()
1814 return myScreen == myPlaylistEditor;
1817 void ClearPlaylist::run()
1819 if (myPlaylistEditor->Playlists.empty())
1820 return;
1821 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
1822 if (Config.ask_before_clearing_playlists)
1823 confirmAction(boost::format("Do you really want to clear playlist \"%1%\"?") % playlist);
1824 Mpd.ClearPlaylist(playlist);
1825 Statusbar::printf("Playlist \"%1%\" cleared", playlist);
1828 bool SortPlaylist::canBeRun()
1830 if (myScreen != myPlaylist)
1831 return false;
1832 auto first = myPlaylist->main().begin(), last = myPlaylist->main().end();
1833 return findSelectedRangeAndPrintInfoIfNot(first, last);
1836 void SortPlaylist::run()
1838 mySortPlaylistDialog->switchTo();
1841 bool ReversePlaylist::canBeRun()
1843 if (myScreen != myPlaylist)
1844 return false;
1845 m_begin = myPlaylist->main().begin();
1846 m_end = myPlaylist->main().end();
1847 return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end);
1850 void ReversePlaylist::run()
1852 Statusbar::print("Reversing range...");
1853 Mpd.StartCommandsList();
1854 for (--m_end; m_begin < m_end; ++m_begin, --m_end)
1855 Mpd.Swap(m_begin->value().getPosition(), m_end->value().getPosition());
1856 Mpd.CommitCommandsList();
1857 Statusbar::print("Range reversed");
1860 bool Find::canBeRun()
1862 return myScreen == myHelp
1863 || myScreen == myLyrics
1864 # ifdef HAVE_CURL_CURL_H
1865 || myScreen == myLastfm
1866 # endif // HAVE_CURL_CURL_H
1870 void Find::run()
1872 using Global::wFooter;
1874 std::string token;
1876 Statusbar::ScopedLock slock;
1877 Statusbar::put() << "Find: ";
1878 token = wFooter->prompt();
1881 Statusbar::print("Searching...");
1882 auto s = static_cast<Screen<NC::Scrollpad> *>(myScreen);
1883 s->main().removeProperties();
1884 if (token.empty() || s->main().setProperties(NC::Format::Reverse, token, NC::Format::NoReverse, Config.regex_type))
1885 Statusbar::print("Done");
1886 else
1887 Statusbar::print("No matching patterns found");
1888 s->main().flush();
1891 bool FindItemBackward::canBeRun()
1893 auto w = dynamic_cast<Searchable *>(myScreen);
1894 return w && w->allowsSearching();
1897 void FindItemForward::run()
1899 findItem(SearchDirection::Forward);
1900 listsChangeFinisher();
1903 bool FindItemForward::canBeRun()
1905 auto w = dynamic_cast<Searchable *>(myScreen);
1906 return w && w->allowsSearching();
1909 void FindItemBackward::run()
1911 findItem(SearchDirection::Backward);
1912 listsChangeFinisher();
1915 bool NextFoundItem::canBeRun()
1917 return dynamic_cast<Searchable *>(myScreen);
1920 void NextFoundItem::run()
1922 Searchable *w = dynamic_cast<Searchable *>(myScreen);
1923 assert(w != nullptr);
1924 w->find(SearchDirection::Forward, Config.wrapped_search, true);
1925 listsChangeFinisher();
1928 bool PreviousFoundItem::canBeRun()
1930 return dynamic_cast<Searchable *>(myScreen);
1933 void PreviousFoundItem::run()
1935 Searchable *w = dynamic_cast<Searchable *>(myScreen);
1936 assert(w != nullptr);
1937 w->find(SearchDirection::Backward, Config.wrapped_search, true);
1938 listsChangeFinisher();
1941 void ToggleFindMode::run()
1943 Config.wrapped_search = !Config.wrapped_search;
1944 Statusbar::printf("Search mode: %1%",
1945 Config.wrapped_search ? "Wrapped" : "Normal"
1949 void ToggleReplayGainMode::run()
1951 using Global::wFooter;
1953 char rgm = 0;
1955 Statusbar::ScopedLock slock;
1956 Statusbar::put() << "Replay gain mode? "
1957 << "[" << NC::Format::Bold << 'o' << NC::Format::NoBold << "ff"
1958 << "/" << NC::Format::Bold << 't' << NC::Format::NoBold << "rack"
1959 << "/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "lbum"
1960 << "] ";
1961 rgm = Statusbar::Helpers::promptReturnOneOf({"t", "a", "o"})[0];
1963 switch (rgm)
1965 case 't':
1966 Mpd.SetReplayGainMode(MPD::rgmTrack);
1967 break;
1968 case 'a':
1969 Mpd.SetReplayGainMode(MPD::rgmAlbum);
1970 break;
1971 case 'o':
1972 Mpd.SetReplayGainMode(MPD::rgmOff);
1973 break;
1974 default: // impossible
1975 throw std::runtime_error(
1976 (boost::format("ToggleReplayGainMode: impossible case reached: %1%") % rgm).str()
1979 Statusbar::printf("Replay gain mode: %1%", Mpd.GetReplayGainMode());
1982 void ToggleAddMode::run()
1984 std::string mode_desc;
1985 switch (Config.space_add_mode)
1987 case SpaceAddMode::AddRemove:
1988 Config.space_add_mode = SpaceAddMode::AlwaysAdd;
1989 mode_desc = "always add an item to playlist";
1990 break;
1991 case SpaceAddMode::AlwaysAdd:
1992 Config.space_add_mode = SpaceAddMode::AddRemove;
1993 mode_desc = "add an item to playlist or remove if already added";
1994 break;
1996 Statusbar::printf("Add mode: %1%", mode_desc);
1999 void ToggleMouse::run()
2001 Config.mouse_support = !Config.mouse_support;
2002 if (Config.mouse_support)
2003 NC::Mouse::enable();
2004 else
2005 NC::Mouse::disable();
2006 Statusbar::printf("Mouse support %1%",
2007 Config.mouse_support ? "enabled" : "disabled"
2011 void ToggleBitrateVisibility::run()
2013 Config.display_bitrate = !Config.display_bitrate;
2014 Statusbar::printf("Bitrate visibility %1%",
2015 Config.display_bitrate ? "enabled" : "disabled"
2019 void AddRandomItems::run()
2021 using Global::wFooter;
2022 char rnd_type = 0;
2024 Statusbar::ScopedLock slock;
2025 Statusbar::put() << "Add random? "
2026 << "[" << NC::Format::Bold << 's' << NC::Format::NoBold << "ongs"
2027 << "/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtists"
2028 << "/" << "album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtists"
2029 << "/" << "al" << NC::Format::Bold << 'b' << NC::Format::NoBold << "ums"
2030 << "] ";
2031 rnd_type = Statusbar::Helpers::promptReturnOneOf({"s", "a", "A", "b"})[0];
2034 mpd_tag_type tag_type = MPD_TAG_ARTIST;
2035 std::string tag_type_str ;
2036 if (rnd_type != 's')
2038 tag_type = charToTagType(rnd_type);
2039 tag_type_str = boost::locale::to_lower(tagTypeToString(tag_type));
2041 else
2042 tag_type_str = "song";
2044 unsigned number;
2046 Statusbar::ScopedLock slock;
2047 Statusbar::put() << "Number of random " << tag_type_str << "s: ";
2048 number = fromString<unsigned>(wFooter->prompt());
2050 if (number && (rnd_type == 's' ? Mpd.AddRandomSongs(number) : Mpd.AddRandomTag(tag_type, number)))
2052 Statusbar::printf("%1% random %2%%3% added to playlist",
2053 number, tag_type_str, number == 1 ? "" : "s"
2058 bool ToggleBrowserSortMode::canBeRun()
2060 return myScreen == myBrowser;
2063 void ToggleBrowserSortMode::run()
2065 switch (Config.browser_sort_mode)
2067 case SortMode::Name:
2068 Config.browser_sort_mode = SortMode::ModificationTime;
2069 Statusbar::print("Sort songs by: modification time");
2070 break;
2071 case SortMode::ModificationTime:
2072 Config.browser_sort_mode = SortMode::CustomFormat;
2073 Statusbar::print("Sort songs by: custom format");
2074 break;
2075 case SortMode::CustomFormat:
2076 Config.browser_sort_mode = SortMode::NoOp;
2077 Statusbar::print("Do not sort songs");
2078 break;
2079 case SortMode::NoOp:
2080 Config.browser_sort_mode = SortMode::Name;
2081 Statusbar::print("Sort songs by: name");
2083 if (Config.browser_sort_mode != SortMode::NoOp)
2085 size_t sort_offset = myBrowser->inRootDirectory() ? 0 : 1;
2086 std::sort(myBrowser->main().begin()+sort_offset, myBrowser->main().end(),
2087 LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode)
2092 bool ToggleLibraryTagType::canBeRun()
2094 return (myScreen->isActiveWindow(myLibrary->Tags))
2095 || (myLibrary->Columns() == 2 && myScreen->isActiveWindow(myLibrary->Albums));
2098 void ToggleLibraryTagType::run()
2100 using Global::wFooter;
2102 char tag_type = 0;
2104 Statusbar::ScopedLock slock;
2105 Statusbar::put() << "Tag type? "
2106 << "[" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtist"
2107 << "/" << "album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtist"
2108 << "/" << NC::Format::Bold << 'y' << NC::Format::NoBold << "ear"
2109 << "/" << NC::Format::Bold << 'g' << NC::Format::NoBold << "enre"
2110 << "/" << NC::Format::Bold << 'c' << NC::Format::NoBold << "omposer"
2111 << "/" << NC::Format::Bold << 'p' << NC::Format::NoBold << "erformer"
2112 << "] ";
2113 tag_type = Statusbar::Helpers::promptReturnOneOf({"a", "A", "y", "g", "c", "p"})[0];
2115 mpd_tag_type new_tagitem = charToTagType(tag_type);
2116 if (new_tagitem != Config.media_lib_primary_tag)
2118 Config.media_lib_primary_tag = new_tagitem;
2119 std::string item_type = tagTypeToString(Config.media_lib_primary_tag);
2120 myLibrary->Tags.setTitle(Config.titles_visibility ? item_type + "s" : "");
2121 myLibrary->Tags.reset();
2122 item_type = boost::locale::to_lower(item_type);
2123 std::string and_mtime = Config.media_library_sort_by_mtime ?
2124 " and mtime" :
2126 if (myLibrary->Columns() == 2)
2128 myLibrary->Songs.clear();
2129 myLibrary->Albums.reset();
2130 myLibrary->Albums.clear();
2131 myLibrary->Albums.setTitle(Config.titles_visibility ? "Albums (sorted by " + item_type + and_mtime + ")" : "");
2132 myLibrary->Albums.display();
2134 else
2136 myLibrary->Tags.clear();
2137 myLibrary->Tags.display();
2139 Statusbar::printf("Switched to the list of %1%s", item_type);
2143 bool ToggleMediaLibrarySortMode::canBeRun()
2145 return myScreen == myLibrary;
2148 void ToggleMediaLibrarySortMode::run()
2150 myLibrary->toggleSortMode();
2153 bool RefetchLyrics::canBeRun()
2155 # ifdef HAVE_CURL_CURL_H
2156 return myScreen == myLyrics;
2157 # else
2158 return false;
2159 # endif // HAVE_CURL_CURL_H
2162 void RefetchLyrics::run()
2164 # ifdef HAVE_CURL_CURL_H
2165 myLyrics->Refetch();
2166 # endif // HAVE_CURL_CURL_H
2169 bool SetSelectedItemsPriority::canBeRun()
2171 if (Mpd.Version() < 17)
2173 Statusbar::print("Priorities are supported in MPD >= 0.17.0");
2174 return false;
2176 return myScreen == myPlaylist && !myPlaylist->main().empty();
2179 void SetSelectedItemsPriority::run()
2181 using Global::wFooter;
2183 unsigned prio;
2185 Statusbar::ScopedLock slock;
2186 Statusbar::put() << "Set priority [0-255]: ";
2187 prio = fromString<unsigned>(wFooter->prompt());
2188 boundsCheck(prio, 0u, 255u);
2190 myPlaylist->SetSelectedItemsPriority(prio);
2193 bool ToggleVisualizationType::canBeRun()
2195 # ifdef ENABLE_VISUALIZER
2196 return myScreen == myVisualizer;
2197 # else
2198 return false;
2199 # endif // ENABLE_VISUALIZER
2202 void ToggleVisualizationType::run()
2204 # ifdef ENABLE_VISUALIZER
2205 myVisualizer->ToggleVisualizationType();
2206 # endif // ENABLE_VISUALIZER
2209 bool SetVisualizerSampleMultiplier::canBeRun()
2211 # ifdef ENABLE_VISUALIZER
2212 return myScreen == myVisualizer;
2213 # else
2214 return false;
2215 # endif // ENABLE_VISUALIZER
2218 void SetVisualizerSampleMultiplier::run()
2220 # ifdef ENABLE_VISUALIZER
2221 using Global::wFooter;
2223 double multiplier;
2225 Statusbar::ScopedLock slock;
2226 Statusbar::put() << "Set visualizer sample multiplier: ";
2227 multiplier = fromString<double>(wFooter->prompt());
2228 lowerBoundCheck(multiplier, 0.0);
2229 Config.visualizer_sample_multiplier = multiplier;
2231 Statusbar::printf("Visualizer sample multiplier set to %1%", multiplier);
2232 # endif // ENABLE_VISUALIZER
2235 void ShowSongInfo::run()
2237 mySongInfo->switchTo();
2240 bool ShowArtistInfo::canBeRun()
2242 #ifdef HAVE_CURL_CURL_H
2243 return myScreen == myLastfm
2244 || (myScreen->isActiveWindow(myLibrary->Tags)
2245 && !myLibrary->Tags.empty()
2246 && Config.media_lib_primary_tag == MPD_TAG_ARTIST)
2247 || currentSong(myScreen);
2248 # else
2249 return false;
2250 # endif // NOT HAVE_CURL_CURL_H
2253 void ShowArtistInfo::run()
2255 # ifdef HAVE_CURL_CURL_H
2256 if (myScreen == myLastfm)
2258 myLastfm->switchTo();
2259 return;
2262 std::string artist;
2263 if (myScreen->isActiveWindow(myLibrary->Tags))
2265 assert(!myLibrary->Tags.empty());
2266 assert(Config.media_lib_primary_tag == MPD_TAG_ARTIST);
2267 artist = myLibrary->Tags.current()->value().tag();
2269 else
2271 auto s = currentSong(myScreen);
2272 assert(s);
2273 artist = s->getArtist();
2276 if (!artist.empty())
2278 myLastfm->queueJob(new LastFm::ArtistInfo(artist, Config.lastfm_preferred_language));
2279 myLastfm->switchTo();
2281 # endif // HAVE_CURL_CURL_H
2284 void ShowLyrics::run()
2286 myLyrics->switchTo();
2289 void Quit::run()
2291 ExitMainLoop = true;
2294 void NextScreen::run()
2296 if (Config.screen_switcher_previous)
2298 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2299 tababble->switchToPreviousScreen();
2301 else if (!Config.screen_sequence.empty())
2303 const auto &seq = Config.screen_sequence;
2304 auto screen_type = std::find(seq.begin(), seq.end(), myScreen->type());
2305 if (++screen_type == seq.end())
2306 toScreen(seq.front())->switchTo();
2307 else
2308 toScreen(*screen_type)->switchTo();
2312 void PreviousScreen::run()
2314 if (Config.screen_switcher_previous)
2316 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2317 tababble->switchToPreviousScreen();
2319 else if (!Config.screen_sequence.empty())
2321 const auto &seq = Config.screen_sequence;
2322 auto screen_type = std::find(seq.begin(), seq.end(), myScreen->type());
2323 if (screen_type == seq.begin())
2324 toScreen(seq.back())->switchTo();
2325 else
2326 toScreen(*--screen_type)->switchTo();
2330 bool ShowHelp::canBeRun()
2332 return myScreen != myHelp
2333 # ifdef HAVE_TAGLIB_H
2334 && myScreen != myTinyTagEditor
2335 # endif // HAVE_TAGLIB_H
2339 void ShowHelp::run()
2341 myHelp->switchTo();
2344 bool ShowPlaylist::canBeRun()
2346 return myScreen != myPlaylist
2347 # ifdef HAVE_TAGLIB_H
2348 && myScreen != myTinyTagEditor
2349 # endif // HAVE_TAGLIB_H
2353 void ShowPlaylist::run()
2355 myPlaylist->switchTo();
2358 bool ShowBrowser::canBeRun()
2360 return myScreen != myBrowser
2361 # ifdef HAVE_TAGLIB_H
2362 && myScreen != myTinyTagEditor
2363 # endif // HAVE_TAGLIB_H
2367 void ShowBrowser::run()
2369 myBrowser->switchTo();
2372 bool ChangeBrowseMode::canBeRun()
2374 return myScreen == myBrowser;
2377 void ChangeBrowseMode::run()
2379 myBrowser->changeBrowseMode();
2382 bool ShowSearchEngine::canBeRun()
2384 return myScreen != mySearcher
2385 # ifdef HAVE_TAGLIB_H
2386 && myScreen != myTinyTagEditor
2387 # endif // HAVE_TAGLIB_H
2391 void ShowSearchEngine::run()
2393 mySearcher->switchTo();
2396 bool ResetSearchEngine::canBeRun()
2398 return myScreen == mySearcher;
2401 void ResetSearchEngine::run()
2403 mySearcher->reset();
2406 bool ShowMediaLibrary::canBeRun()
2408 return myScreen != myLibrary
2409 # ifdef HAVE_TAGLIB_H
2410 && myScreen != myTinyTagEditor
2411 # endif // HAVE_TAGLIB_H
2415 void ShowMediaLibrary::run()
2417 myLibrary->switchTo();
2420 bool ToggleMediaLibraryColumnsMode::canBeRun()
2422 return myScreen == myLibrary;
2425 void ToggleMediaLibraryColumnsMode::run()
2427 myLibrary->toggleColumnsMode();
2428 myLibrary->refresh();
2431 bool ShowPlaylistEditor::canBeRun()
2433 return myScreen != myPlaylistEditor
2434 # ifdef HAVE_TAGLIB_H
2435 && myScreen != myTinyTagEditor
2436 # endif // HAVE_TAGLIB_H
2440 void ShowPlaylistEditor::run()
2442 myPlaylistEditor->switchTo();
2445 bool ShowTagEditor::canBeRun()
2447 # ifdef HAVE_TAGLIB_H
2448 return myScreen != myTagEditor
2449 && myScreen != myTinyTagEditor;
2450 # else
2451 return false;
2452 # endif // HAVE_TAGLIB_H
2455 void ShowTagEditor::run()
2457 # ifdef HAVE_TAGLIB_H
2458 if (isMPDMusicDirSet())
2459 myTagEditor->switchTo();
2460 # endif // HAVE_TAGLIB_H
2463 bool ShowOutputs::canBeRun()
2465 # ifdef ENABLE_OUTPUTS
2466 return myScreen != myOutputs
2467 # ifdef HAVE_TAGLIB_H
2468 && myScreen != myTinyTagEditor
2469 # endif // HAVE_TAGLIB_H
2471 # else
2472 return false;
2473 # endif // ENABLE_OUTPUTS
2476 void ShowOutputs::run()
2478 # ifdef ENABLE_OUTPUTS
2479 myOutputs->switchTo();
2480 # endif // ENABLE_OUTPUTS
2483 bool ShowVisualizer::canBeRun()
2485 # ifdef ENABLE_VISUALIZER
2486 return myScreen != myVisualizer
2487 # ifdef HAVE_TAGLIB_H
2488 && myScreen != myTinyTagEditor
2489 # endif // HAVE_TAGLIB_H
2491 # else
2492 return false;
2493 # endif // ENABLE_VISUALIZER
2496 void ShowVisualizer::run()
2498 # ifdef ENABLE_VISUALIZER
2499 myVisualizer->switchTo();
2500 # endif // ENABLE_VISUALIZER
2503 bool ShowClock::canBeRun()
2505 # ifdef ENABLE_CLOCK
2506 return myScreen != myClock
2507 # ifdef HAVE_TAGLIB_H
2508 && myScreen != myTinyTagEditor
2509 # endif // HAVE_TAGLIB_H
2511 # else
2512 return false;
2513 # endif // ENABLE_CLOCK
2516 void ShowClock::run()
2518 # ifdef ENABLE_CLOCK
2519 myClock->switchTo();
2520 # endif // ENABLE_CLOCK
2523 #ifdef HAVE_TAGLIB_H
2524 bool ShowServerInfo::canBeRun()
2526 return myScreen != myTinyTagEditor;
2528 #endif // HAVE_TAGLIB_H
2530 void ShowServerInfo::run()
2532 myServerInfo->switchTo();
2537 namespace {
2539 void populateActions()
2541 auto insert_action = [](Actions::BaseAction *a) {
2542 AvailableActions[static_cast<size_t>(a->type())] = a;
2544 insert_action(new Actions::Dummy());
2545 insert_action(new Actions::UpdateEnvironment());
2546 insert_action(new Actions::MouseEvent());
2547 insert_action(new Actions::ScrollUp());
2548 insert_action(new Actions::ScrollDown());
2549 insert_action(new Actions::ScrollUpArtist());
2550 insert_action(new Actions::ScrollUpAlbum());
2551 insert_action(new Actions::ScrollDownArtist());
2552 insert_action(new Actions::ScrollDownAlbum());
2553 insert_action(new Actions::PageUp());
2554 insert_action(new Actions::PageDown());
2555 insert_action(new Actions::MoveHome());
2556 insert_action(new Actions::MoveEnd());
2557 insert_action(new Actions::ToggleInterface());
2558 insert_action(new Actions::JumpToParentDirectory());
2559 insert_action(new Actions::PressEnter());
2560 insert_action(new Actions::SelectItem());
2561 insert_action(new Actions::SelectRange());
2562 insert_action(new Actions::PreviousColumn());
2563 insert_action(new Actions::NextColumn());
2564 insert_action(new Actions::MasterScreen());
2565 insert_action(new Actions::SlaveScreen());
2566 insert_action(new Actions::VolumeUp());
2567 insert_action(new Actions::VolumeDown());
2568 insert_action(new Actions::AddItemToPlaylist());
2569 insert_action(new Actions::DeletePlaylistItems());
2570 insert_action(new Actions::DeleteStoredPlaylist());
2571 insert_action(new Actions::DeleteBrowserItems());
2572 insert_action(new Actions::ReplaySong());
2573 insert_action(new Actions::PreviousSong());
2574 insert_action(new Actions::NextSong());
2575 insert_action(new Actions::Pause());
2576 insert_action(new Actions::Stop());
2577 insert_action(new Actions::ExecuteCommand());
2578 insert_action(new Actions::SavePlaylist());
2579 insert_action(new Actions::MoveSortOrderUp());
2580 insert_action(new Actions::MoveSortOrderDown());
2581 insert_action(new Actions::MoveSelectedItemsUp());
2582 insert_action(new Actions::MoveSelectedItemsDown());
2583 insert_action(new Actions::MoveSelectedItemsTo());
2584 insert_action(new Actions::Add());
2585 insert_action(new Actions::SeekForward());
2586 insert_action(new Actions::SeekBackward());
2587 insert_action(new Actions::ToggleDisplayMode());
2588 insert_action(new Actions::ToggleSeparatorsBetweenAlbums());
2589 insert_action(new Actions::ToggleLyricsUpdateOnSongChange());
2590 insert_action(new Actions::ToggleLyricsFetcher());
2591 insert_action(new Actions::ToggleFetchingLyricsInBackground());
2592 insert_action(new Actions::TogglePlayingSongCentering());
2593 insert_action(new Actions::UpdateDatabase());
2594 insert_action(new Actions::JumpToPlayingSong());
2595 insert_action(new Actions::ToggleRepeat());
2596 insert_action(new Actions::Shuffle());
2597 insert_action(new Actions::ToggleRandom());
2598 insert_action(new Actions::StartSearching());
2599 insert_action(new Actions::SaveTagChanges());
2600 insert_action(new Actions::ToggleSingle());
2601 insert_action(new Actions::ToggleConsume());
2602 insert_action(new Actions::ToggleCrossfade());
2603 insert_action(new Actions::SetCrossfade());
2604 insert_action(new Actions::SetVolume());
2605 insert_action(new Actions::EditSong());
2606 insert_action(new Actions::EditLibraryTag());
2607 insert_action(new Actions::EditLibraryAlbum());
2608 insert_action(new Actions::EditDirectoryName());
2609 insert_action(new Actions::EditPlaylistName());
2610 insert_action(new Actions::EditLyrics());
2611 insert_action(new Actions::JumpToBrowser());
2612 insert_action(new Actions::JumpToMediaLibrary());
2613 insert_action(new Actions::JumpToPlaylistEditor());
2614 insert_action(new Actions::ToggleScreenLock());
2615 insert_action(new Actions::JumpToTagEditor());
2616 insert_action(new Actions::JumpToPositionInSong());
2617 insert_action(new Actions::ReverseSelection());
2618 insert_action(new Actions::RemoveSelection());
2619 insert_action(new Actions::SelectAlbum());
2620 insert_action(new Actions::AddSelectedItems());
2621 insert_action(new Actions::CropMainPlaylist());
2622 insert_action(new Actions::CropPlaylist());
2623 insert_action(new Actions::ClearMainPlaylist());
2624 insert_action(new Actions::ClearPlaylist());
2625 insert_action(new Actions::SortPlaylist());
2626 insert_action(new Actions::ReversePlaylist());
2627 insert_action(new Actions::Find());
2628 insert_action(new Actions::FindItemForward());
2629 insert_action(new Actions::FindItemBackward());
2630 insert_action(new Actions::NextFoundItem());
2631 insert_action(new Actions::PreviousFoundItem());
2632 insert_action(new Actions::ToggleFindMode());
2633 insert_action(new Actions::ToggleReplayGainMode());
2634 insert_action(new Actions::ToggleAddMode());
2635 insert_action(new Actions::ToggleMouse());
2636 insert_action(new Actions::ToggleBitrateVisibility());
2637 insert_action(new Actions::AddRandomItems());
2638 insert_action(new Actions::ToggleBrowserSortMode());
2639 insert_action(new Actions::ToggleLibraryTagType());
2640 insert_action(new Actions::ToggleMediaLibrarySortMode());
2641 insert_action(new Actions::RefetchLyrics());
2642 insert_action(new Actions::SetSelectedItemsPriority());
2643 insert_action(new Actions::ToggleVisualizationType());
2644 insert_action(new Actions::SetVisualizerSampleMultiplier());
2645 insert_action(new Actions::ShowSongInfo());
2646 insert_action(new Actions::ShowArtistInfo());
2647 insert_action(new Actions::ShowLyrics());
2648 insert_action(new Actions::Quit());
2649 insert_action(new Actions::NextScreen());
2650 insert_action(new Actions::PreviousScreen());
2651 insert_action(new Actions::ShowHelp());
2652 insert_action(new Actions::ShowPlaylist());
2653 insert_action(new Actions::ShowBrowser());
2654 insert_action(new Actions::ChangeBrowseMode());
2655 insert_action(new Actions::ShowSearchEngine());
2656 insert_action(new Actions::ResetSearchEngine());
2657 insert_action(new Actions::ShowMediaLibrary());
2658 insert_action(new Actions::ToggleMediaLibraryColumnsMode());
2659 insert_action(new Actions::ShowPlaylistEditor());
2660 insert_action(new Actions::ShowTagEditor());
2661 insert_action(new Actions::ShowOutputs());
2662 insert_action(new Actions::ShowVisualizer());
2663 insert_action(new Actions::ShowClock());
2664 insert_action(new Actions::ShowServerInfo());
2667 bool scrollTagCanBeRun(NC::List *&list, SongList *&songs)
2669 auto w = myScreen->activeWindow();
2670 if (list != static_cast<void *>(w))
2671 list = dynamic_cast<NC::List *>(w);
2672 if (songs != static_cast<void *>(w))
2673 songs = dynamic_cast<SongList *>(w);
2674 return list != nullptr && !list->empty()
2675 && songs != nullptr;
2678 void scrollTagUpRun(NC::List *list, SongList *songs, MPD::Song::GetFunction get)
2680 const auto front = songs->beginS();
2681 auto it = songs->currentS();
2682 if (auto *s = it->get<Bit::Song>())
2684 const std::string tag = s->getTags(get);
2685 while (it != front)
2687 --it;
2688 s = it->get<Bit::Song>();
2689 if (s == nullptr || s->getTags(get) != tag)
2690 break;
2692 list->highlight(it-front);
2696 void scrollTagDownRun(NC::List *list, SongList *songs, MPD::Song::GetFunction get)
2698 const auto front = songs->beginS(), back = --songs->endS();
2699 auto it = songs->currentS();
2700 if (auto *s = it->get<Bit::Song>())
2702 const std::string tag = s->getTags(get);
2703 while (it != back)
2705 ++it;
2706 s = it->get<Bit::Song>();
2707 if (s == nullptr || s->getTags(get) != tag)
2708 break;
2710 list->highlight(it-front);
2714 void seek()
2716 using Global::wHeader;
2717 using Global::wFooter;
2718 using Global::Timer;
2719 using Global::SeekingInProgress;
2721 if (!Status::State::totalTime())
2723 Statusbar::print("Unknown item length");
2724 return;
2727 Progressbar::ScopedLock progressbar_lock;
2728 Statusbar::ScopedLock statusbar_lock;
2730 unsigned songpos = Status::State::elapsedTime();
2731 auto t = Timer;
2733 int old_timeout = wFooter->getTimeout();
2734 wFooter->setTimeout(BaseScreen::defaultWindowTimeout);
2736 auto seekForward = &Actions::get(Actions::Type::SeekForward);
2737 auto seekBackward = &Actions::get(Actions::Type::SeekBackward);
2739 SeekingInProgress = true;
2740 while (true)
2742 Status::trace();
2744 unsigned howmuch = Config.incremental_seeking
2745 ? (Timer-t).total_seconds()/2+Config.seek_time
2746 : Config.seek_time;
2748 NC::Key::Type input = readKey(*wFooter);
2749 auto k = Bindings.get(input);
2750 if (k.first == k.second || !k.first->isSingle()) // no single action?
2751 break;
2752 auto a = k.first->action();
2753 if (a == seekForward)
2755 if (songpos < Status::State::totalTime())
2756 songpos = std::min(songpos + howmuch, Status::State::totalTime());
2758 else if (a == seekBackward)
2760 if (songpos > 0)
2762 if (songpos < howmuch)
2763 songpos = 0;
2764 else
2765 songpos -= howmuch;
2768 else
2769 break;
2771 *wFooter << NC::Format::Bold;
2772 std::string tracklength;
2773 // FIXME: merge this with the code in status.cpp
2774 switch (Config.design)
2776 case Design::Classic:
2777 tracklength = " [";
2778 if (Config.display_remaining_time)
2780 tracklength += "-";
2781 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
2783 else
2784 tracklength += MPD::Song::ShowTime(songpos);
2785 tracklength += "/";
2786 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
2787 tracklength += "]";
2788 *wFooter << NC::XY(wFooter->getWidth()-tracklength.length(), 1) << tracklength;
2789 break;
2790 case Design::Alternative:
2791 if (Config.display_remaining_time)
2793 tracklength = "-";
2794 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
2796 else
2797 tracklength = MPD::Song::ShowTime(songpos);
2798 tracklength += "/";
2799 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
2800 *wHeader << NC::XY(0, 0) << tracklength << " ";
2801 wHeader->refresh();
2802 break;
2804 *wFooter << NC::Format::NoBold;
2805 Progressbar::draw(songpos, Status::State::totalTime());
2806 wFooter->refresh();
2808 SeekingInProgress = false;
2809 Mpd.Seek(Status::State::currentSongPosition(), songpos);
2811 wFooter->setTimeout(old_timeout);
2814 void findItem(const SearchDirection direction)
2816 using Global::wFooter;
2818 Searchable *w = dynamic_cast<Searchable *>(myScreen);
2819 assert(w != nullptr);
2820 assert(w->allowsSearching());
2822 std::string constraint;
2824 Statusbar::ScopedLock slock;
2825 NC::Window::ScopedPromptHook prompt_hook(*wFooter,
2826 Statusbar::Helpers::FindImmediately(w, direction)
2828 Statusbar::put() << (boost::format("Find %1%: ") % direction).str();
2829 constraint = wFooter->prompt();
2834 if (constraint.empty())
2836 Statusbar::printf("Constraint unset");
2837 w->clearConstraint();
2839 else
2841 w->setSearchConstraint(constraint);
2842 Statusbar::printf("Using constraint \"%1%\"", constraint);
2845 catch (boost::bad_expression &e)
2847 Statusbar::printf("%1%", e.what());
2851 void listsChangeFinisher()
2853 if (myScreen == myLibrary
2854 || myScreen == myPlaylistEditor
2855 # ifdef HAVE_TAGLIB_H
2856 || myScreen == myTagEditor
2857 # endif // HAVE_TAGLIB_H
2860 if (myScreen->activeWindow() == &myLibrary->Tags)
2862 myLibrary->Albums.clear();
2863 myLibrary->Albums.refresh();
2864 myLibrary->Songs.clear();
2865 myLibrary->Songs.refresh();
2866 myLibrary->updateTimer();
2868 else if (myScreen->activeWindow() == &myLibrary->Albums)
2870 myLibrary->Songs.clear();
2871 myLibrary->Songs.refresh();
2872 myLibrary->updateTimer();
2874 else if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
2876 myPlaylistEditor->Content.clear();
2877 myPlaylistEditor->Content.refresh();
2878 myPlaylistEditor->updateTimer();
2880 # ifdef HAVE_TAGLIB_H
2881 else if (myScreen->activeWindow() == myTagEditor->Dirs)
2883 myTagEditor->Tags->clear();
2885 # endif // HAVE_TAGLIB_H