1 /***************************************************************************
2 * Copyright (C) 2008-2016 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
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. *
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. *
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 ***************************************************************************/
24 #include <boost/array.hpp>
25 #include <boost/date_time/posix_time/posix_time.hpp>
26 #include <boost/filesystem/operations.hpp>
27 #include <boost/locale/conversion.hpp>
28 #include <boost/lexical_cast.hpp>
39 #include "statusbar.h"
40 #include "utility/comparators.h"
41 #include "utility/conversion.h"
47 #include "media_library.h"
48 #include "menu_impl.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"
59 #include "utility/readline.h"
60 #include "utility/string.h"
61 #include "utility/type_conversions.h"
62 #include "tag_editor.h"
63 #include "tiny_tag_editor.h"
64 #include "visualizer.h"
71 #endif // HAVE_TAGLIB_H
73 using Global::myScreen
;
75 namespace ph
= std::placeholders
;
80 Actions::BaseAction
*, static_cast<size_t>(Actions::Type::_numberOfActions
)
83 void populateActions();
85 bool scrollTagCanBeRun(NC::List
*&list
, SongList
*&songs
);
86 void scrollTagUpRun(NC::List
*list
, SongList
*songs
, MPD::Song::GetFunction get
);
87 void scrollTagDownRun(NC::List
*list
, SongList
*songs
, MPD::Song::GetFunction get
);
90 void findItem(const SearchDirection direction
);
91 void listsChangeFinisher();
93 template <typename Iterator
>
94 bool findSelectedRangeAndPrintInfoIfNot(Iterator
&first
, Iterator
&last
)
96 bool success
= findSelectedRange(first
, last
);
98 Statusbar::print("No range selected");
106 bool OriginalStatusbarVisibility
;
107 bool ExitMainLoop
= false;
113 void validateScreenSize()
115 using Global::MainHeight
;
117 if (COLS
< 30 || MainHeight
< 5)
120 std::cout
<< "Screen is too small to handle ncmpcpp correctly\n";
125 void initializeScreens()
128 myPlaylist
= new Playlist
;
129 myBrowser
= new Browser
;
130 mySearcher
= new SearchEngine
;
131 myLibrary
= new MediaLibrary
;
132 myPlaylistEditor
= new PlaylistEditor
;
133 myLyrics
= new Lyrics
;
134 mySelectedItemsAdder
= new SelectedItemsAdder
;
135 mySongInfo
= new SongInfo
;
136 myServerInfo
= new ServerInfo
;
137 mySortPlaylistDialog
= new SortPlaylistDialog
;
138 myLastfm
= new Lastfm
;
140 # ifdef HAVE_TAGLIB_H
141 myTinyTagEditor
= new TinyTagEditor
;
142 myTagEditor
= new TagEditor
;
143 # endif // HAVE_TAGLIB_H
145 # ifdef ENABLE_VISUALIZER
146 myVisualizer
= new Visualizer
;
147 # endif // ENABLE_VISUALIZER
149 # ifdef ENABLE_OUTPUTS
150 myOutputs
= new Outputs
;
151 # endif // ENABLE_OUTPUTS
155 # endif // ENABLE_CLOCK
159 void setResizeFlags()
161 myHelp
->hasToBeResized
= 1;
162 myPlaylist
->hasToBeResized
= 1;
163 myBrowser
->hasToBeResized
= 1;
164 mySearcher
->hasToBeResized
= 1;
165 myLibrary
->hasToBeResized
= 1;
166 myPlaylistEditor
->hasToBeResized
= 1;
167 myLyrics
->hasToBeResized
= 1;
168 mySelectedItemsAdder
->hasToBeResized
= 1;
169 mySongInfo
->hasToBeResized
= 1;
170 myServerInfo
->hasToBeResized
= 1;
171 mySortPlaylistDialog
->hasToBeResized
= 1;
172 myLastfm
->hasToBeResized
= 1;
174 # ifdef HAVE_TAGLIB_H
175 myTinyTagEditor
->hasToBeResized
= 1;
176 myTagEditor
->hasToBeResized
= 1;
177 # endif // HAVE_TAGLIB_H
179 # ifdef ENABLE_VISUALIZER
180 myVisualizer
->hasToBeResized
= 1;
181 # endif // ENABLE_VISUALIZER
183 # ifdef ENABLE_OUTPUTS
184 myOutputs
->hasToBeResized
= 1;
185 # endif // ENABLE_OUTPUTS
188 myClock
->hasToBeResized
= 1;
189 # endif // ENABLE_CLOCK
192 void resizeScreen(bool reload_main_window
)
194 using Global::MainHeight
;
195 using Global::wHeader
;
196 using Global::wFooter
;
198 // update internal screen dimensions
199 if (reload_main_window
)
201 rl_resize_terminal();
206 MainHeight
= LINES
-(Config
.design
== Design::Alternative
? 7 : 4);
208 validateScreenSize();
210 if (!Config
.header_visibility
)
212 if (!Config
.statusbar_visibility
)
217 applyToVisibleWindows(&BaseScreen::resize
);
219 if (Config
.header_visibility
|| Config
.design
== Design::Alternative
)
220 wHeader
->resize(COLS
, HeaderHeight
);
222 FooterStartY
= LINES
-(Config
.statusbar_visibility
? 2 : 1);
223 wFooter
->moveTo(0, FooterStartY
);
224 wFooter
->resize(COLS
, Config
.statusbar_visibility
? 2 : 1);
226 applyToVisibleWindows(&BaseScreen::refresh
);
228 Status::Changes::elapsedTime(false);
229 Status::Changes::playerState();
230 // Note: routines for drawing separator if alternative user
231 // interface is active and header is hidden are placed in
232 // NcmpcppStatusChanges.StatusFlags
233 Status::Changes::flags();
239 void setWindowsDimensions()
241 using Global::MainStartY
;
242 using Global::MainHeight
;
244 MainStartY
= Config
.design
== Design::Alternative
? 5 : 2;
245 MainHeight
= LINES
-(Config
.design
== Design::Alternative
? 7 : 4);
247 if (!Config
.header_visibility
)
252 if (!Config
.statusbar_visibility
)
255 HeaderHeight
= Config
.design
== Design::Alternative
? (Config
.header_visibility
? 5 : 3) : 2;
256 FooterStartY
= LINES
-(Config
.statusbar_visibility
? 2 : 1);
257 FooterHeight
= Config
.statusbar_visibility
? 2 : 1;
260 void confirmAction(const boost::format
&description
)
262 Statusbar::ScopedLock slock
;
263 Statusbar::put() << description
.str()
264 << " [" << NC::Format::Bold
<< 'y' << NC::Format::NoBold
265 << '/' << NC::Format::Bold
<< 'n' << NC::Format::NoBold
267 auto answer
= Statusbar::Helpers::promptReturnOneOf({"y", "n"});
269 throw NC::PromptAborted(std::move(answer
));
272 bool isMPDMusicDirSet()
274 if (Config
.mpd_music_dir
.empty())
276 Statusbar::print("Proper mpd_music_dir variable has to be set in configuration file");
282 BaseAction
&get(Actions::Type at
)
284 if (AvailableActions
[1] == nullptr)
286 BaseAction
*action
= AvailableActions
[static_cast<size_t>(at
)];
287 // action should be always present if action type in queried
288 assert(action
!= nullptr);
292 BaseAction
*get(const std::string
&name
)
294 BaseAction
*result
= 0;
295 if (AvailableActions
[1] == nullptr)
297 for (auto it
= AvailableActions
.begin(); it
!= AvailableActions
.end(); ++it
)
299 if (*it
!= nullptr && (*it
)->name() == name
)
308 UpdateEnvironment::UpdateEnvironment()
309 : BaseAction(Type::UpdateEnvironment
, "update_environment")
310 , m_past(boost::posix_time::from_time_t(0))
313 void UpdateEnvironment::run(bool update_timer
, bool refresh_window
)
317 // update timer, status if necessary etc.
318 Status::trace(update_timer
, true);
320 // show lyrics consumer notification if appropriate
321 if (auto message
= myLyrics
->tryTakeConsumerMessage())
322 Statusbar::print(*message
);
325 if ((myScreen
== myPlaylist
|| myScreen
== myBrowser
|| myScreen
== myLyrics
)
326 && (Timer
- m_past
> boost::posix_time::milliseconds(500))
334 myScreen
->refreshWindow();
337 void UpdateEnvironment::run()
342 bool MouseEvent::canBeRun()
344 return Config
.mouse_support
;
347 void MouseEvent::run()
349 using Global::VolumeState
;
350 using Global::wFooter
;
352 m_old_mouse_event
= m_mouse_event
;
353 m_mouse_event
= wFooter
->getMouseEvent();
355 //Statusbar::printf("(%1%, %2%, %3%)", m_mouse_event.bstate, m_mouse_event.x, m_mouse_event.y);
357 if (m_mouse_event
.bstate
& BUTTON1_PRESSED
358 && m_mouse_event
.y
== LINES
-(Config
.statusbar_visibility
? 2 : 1)
361 if (Status::State::player() == MPD::psStop
)
363 Mpd
.Seek(Status::State::currentSongPosition(),
364 Status::State::totalTime()*m_mouse_event
.x
/double(COLS
));
366 else if (m_mouse_event
.bstate
& BUTTON1_PRESSED
367 && (Config
.statusbar_visibility
|| Config
.design
== Design::Alternative
)
368 && Status::State::player() != MPD::psStop
369 && m_mouse_event
.y
== (Config
.design
== Design::Alternative
? 1 : LINES
-1)
370 && m_mouse_event
.x
< 9
375 else if ((m_mouse_event
.bstate
& BUTTON5_PRESSED
|| m_mouse_event
.bstate
& BUTTON4_PRESSED
)
376 && (Config
.header_visibility
|| Config
.design
== Design::Alternative
)
377 && m_mouse_event
.y
== 0 && size_t(m_mouse_event
.x
) > COLS
-VolumeState
.length()
380 if (m_mouse_event
.bstate
& BUTTON5_PRESSED
)
381 get(Type::VolumeDown
).execute();
383 get(Type::VolumeUp
).execute();
385 else if (m_mouse_event
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
| BUTTON4_PRESSED
| BUTTON5_PRESSED
))
386 myScreen
->mouseButtonPressed(m_mouse_event
);
391 myScreen
->scroll(NC::Scroll::Up
);
392 listsChangeFinisher();
395 void ScrollDown::run()
397 myScreen
->scroll(NC::Scroll::Down
);
398 listsChangeFinisher();
401 bool ScrollUpArtist::canBeRun()
403 return scrollTagCanBeRun(m_list
, m_songs
);
406 void ScrollUpArtist::run()
408 scrollTagUpRun(m_list
, m_songs
, &MPD::Song::getArtist
);
411 bool ScrollUpAlbum::canBeRun()
413 return scrollTagCanBeRun(m_list
, m_songs
);
416 void ScrollUpAlbum::run()
418 scrollTagUpRun(m_list
, m_songs
, &MPD::Song::getAlbum
);
421 bool ScrollDownArtist::canBeRun()
423 return scrollTagCanBeRun(m_list
, m_songs
);
426 void ScrollDownArtist::run()
428 scrollTagDownRun(m_list
, m_songs
, &MPD::Song::getArtist
);
431 bool ScrollDownAlbum::canBeRun()
433 return scrollTagCanBeRun(m_list
, m_songs
);
436 void ScrollDownAlbum::run()
438 scrollTagDownRun(m_list
, m_songs
, &MPD::Song::getAlbum
);
443 myScreen
->scroll(NC::Scroll::PageUp
);
444 listsChangeFinisher();
449 myScreen
->scroll(NC::Scroll::PageDown
);
450 listsChangeFinisher();
455 myScreen
->scroll(NC::Scroll::Home
);
456 listsChangeFinisher();
461 myScreen
->scroll(NC::Scroll::End
);
462 listsChangeFinisher();
465 void ToggleInterface::run()
467 switch (Config
.design
)
469 case Design::Classic
:
470 Config
.design
= Design::Alternative
;
471 Config
.statusbar_visibility
= false;
473 case Design::Alternative
:
474 Config
.design
= Design::Classic
;
475 Config
.statusbar_visibility
= OriginalStatusbarVisibility
;
478 setWindowsDimensions();
480 // unlock progressbar
481 Progressbar::ScopedLock();
482 Status::Changes::mixer();
483 Status::Changes::elapsedTime(false);
484 Statusbar::printf("User interface: %1%", Config
.design
);
487 bool JumpToParentDirectory::canBeRun()
489 return (myScreen
== myBrowser
)
490 # ifdef HAVE_TAGLIB_H
491 || (myScreen
->activeWindow() == myTagEditor
->Dirs
)
492 # endif // HAVE_TAGLIB_H
496 void JumpToParentDirectory::run()
498 if (myScreen
== myBrowser
)
500 if (!myBrowser
->inRootDirectory())
502 myBrowser
->main().reset();
503 myBrowser
->enterDirectory();
506 # ifdef HAVE_TAGLIB_H
507 else if (myScreen
== myTagEditor
)
509 if (myTagEditor
->CurrentDir() != "/")
511 myTagEditor
->Dirs
->reset();
512 myTagEditor
->enterDirectory();
515 # endif // HAVE_TAGLIB_H
518 bool RunAction::canBeRun()
520 m_ha
= dynamic_cast<HasActions
*>(myScreen
);
521 return m_ha
!= nullptr
522 && m_ha
->actionRunnable();
525 void RunAction::run()
530 bool PreviousColumn::canBeRun()
532 m_hc
= dynamic_cast<HasColumns
*>(myScreen
);
533 return m_hc
!= nullptr
534 && m_hc
->previousColumnAvailable();
537 void PreviousColumn::run()
539 m_hc
->previousColumn();
542 bool NextColumn::canBeRun()
544 m_hc
= dynamic_cast<HasColumns
*>(myScreen
);
545 return m_hc
!= nullptr
546 && m_hc
->nextColumnAvailable();
549 void NextColumn::run()
554 bool MasterScreen::canBeRun()
556 using Global::myLockedScreen
;
557 using Global::myInactiveScreen
;
559 return myLockedScreen
561 && myLockedScreen
!= myScreen
562 && myScreen
->isMergable();
565 void MasterScreen::run()
567 using Global::myInactiveScreen
;
568 using Global::myLockedScreen
;
570 myInactiveScreen
= myScreen
;
571 myScreen
= myLockedScreen
;
575 bool SlaveScreen::canBeRun()
577 using Global::myLockedScreen
;
578 using Global::myInactiveScreen
;
580 return myLockedScreen
582 && myLockedScreen
== myScreen
583 && myScreen
->isMergable();
586 void SlaveScreen::run()
588 using Global::myInactiveScreen
;
589 using Global::myLockedScreen
;
591 myScreen
= myInactiveScreen
;
592 myInactiveScreen
= myLockedScreen
;
598 int volume
= std::min(Status::State::volume()+Config
.volume_change_step
, 100u);
599 Mpd
.SetVolume(volume
);
602 void VolumeDown::run()
604 int volume
= std::max(int(Status::State::volume()-Config
.volume_change_step
), 0);
605 Mpd
.SetVolume(volume
);
608 bool AddItemToPlaylist::canBeRun()
610 m_hs
= dynamic_cast<HasSongs
*>(myScreen
);
611 return m_hs
!= nullptr && m_hs
->itemAvailable();
614 void AddItemToPlaylist::run()
616 bool success
= m_hs
->addItemToPlaylist(false);
619 myScreen
->scroll(NC::Scroll::Down
);
620 listsChangeFinisher();
624 bool PlayItem::canBeRun()
626 m_hs
= dynamic_cast<HasSongs
*>(myScreen
);
627 return m_hs
!= nullptr && m_hs
->itemAvailable();
632 bool success
= m_hs
->addItemToPlaylist(true);
634 listsChangeFinisher();
637 bool DeletePlaylistItems::canBeRun()
639 return (myScreen
== myPlaylist
&& !myPlaylist
->main().empty())
640 || (myScreen
->isActiveWindow(myPlaylistEditor
->Content
) && !myPlaylistEditor
->Content
.empty());
643 void DeletePlaylistItems::run()
645 if (myScreen
== myPlaylist
)
647 Statusbar::print("Deleting items...");
648 auto delete_fun
= std::bind(&MPD::Connection::Delete
, ph::_1
, ph::_2
);
649 deleteSelectedSongs(myPlaylist
->main(), delete_fun
);
650 Statusbar::print("Item(s) deleted");
652 else if (myScreen
->isActiveWindow(myPlaylistEditor
->Content
))
654 std::string playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
655 auto delete_fun
= std::bind(&MPD::Connection::PlaylistDelete
, ph::_1
, playlist
, ph::_2
);
656 Statusbar::print("Deleting items...");
657 deleteSelectedSongs(myPlaylistEditor
->Content
, delete_fun
);
658 Statusbar::print("Item(s) deleted");
662 bool DeleteBrowserItems::canBeRun()
664 auto check_if_deletion_allowed
= []() {
665 if (Config
.allow_for_physical_item_deletion
)
669 Statusbar::print("Flag \"allow_for_physical_item_deletion\" needs to be enabled in configuration file");
673 return myScreen
== myBrowser
674 && !myBrowser
->main().empty()
675 && isMPDMusicDirSet()
676 && check_if_deletion_allowed();
679 void DeleteBrowserItems::run()
681 auto get_name
= [](const MPD::Item
&item
) -> std::string
{
685 case MPD::Item::Type::Directory
:
686 iname
= getBasename(item
.directory().path());
688 case MPD::Item::Type::Song
:
689 iname
= item
.song().getName();
691 case MPD::Item::Type::Playlist
:
692 iname
= getBasename(item
.playlist().path());
698 boost::format question
;
699 if (hasSelected(myBrowser
->main().begin(), myBrowser
->main().end()))
700 question
= boost::format("Delete selected items?");
703 const auto &item
= myBrowser
->main().current()->value();
704 // parent directories are not accepted (and they
705 // can't be selected, so in other cases it's fine).
706 if (myBrowser
->isParentDirectory(item
))
708 const char msg
[] = "Delete \"%1%\"?";
709 question
= boost::format(msg
) % wideShorten(
710 get_name(item
), COLS
-const_strlen(msg
)-5
713 confirmAction(question
);
715 auto items
= getSelectedOrCurrent(
716 myBrowser
->main().begin(),
717 myBrowser
->main().end(),
718 myBrowser
->main().current()
720 for (const auto &item
: items
)
722 myBrowser
->remove(item
->value());
723 const char msg
[] = "Deleted %1% \"%2%\"";
724 Statusbar::printf(msg
,
725 itemTypeToString(item
->value().type()),
726 wideShorten(get_name(item
->value()), COLS
-const_strlen(msg
))
730 if (!myBrowser
->isLocal())
731 Mpd
.UpdateDirectory(myBrowser
->currentDirectory());
732 myBrowser
->requestUpdate();
735 bool DeleteStoredPlaylist::canBeRun()
737 return myScreen
->isActiveWindow(myPlaylistEditor
->Playlists
);
740 void DeleteStoredPlaylist::run()
742 if (myPlaylistEditor
->Playlists
.empty())
744 boost::format question
;
745 if (hasSelected(myPlaylistEditor
->Playlists
.begin(), myPlaylistEditor
->Playlists
.end()))
746 question
= boost::format("Delete selected playlists?");
748 question
= boost::format("Delete playlist \"%1%\"?")
749 % wideShorten(myPlaylistEditor
->Playlists
.current()->value().path(), COLS
-question
.size()-10);
750 confirmAction(question
);
751 auto list
= getSelectedOrCurrent(
752 myPlaylistEditor
->Playlists
.begin(),
753 myPlaylistEditor
->Playlists
.end(),
754 myPlaylistEditor
->Playlists
.current()
756 for (const auto &item
: list
)
757 Mpd
.DeletePlaylist(item
->value().path());
758 Statusbar::printf("%1% deleted", list
.size() == 1 ? "Playlist" : "Playlists");
759 // force playlists update. this happens automatically, but only after call
760 // to Key::read, therefore when we call PlaylistEditor::Update, it won't
761 // yet see it, so let's point that it needs to update it.
762 myPlaylistEditor
->requestPlaylistsUpdate();
765 void ReplaySong::run()
767 if (Status::State::player() != MPD::psStop
)
768 Mpd
.Seek(Status::State::currentSongPosition(), 0);
771 void PreviousSong::run()
786 void SavePlaylist::run()
788 using Global::wFooter
;
790 std::string playlist_name
;
792 Statusbar::ScopedLock slock
;
793 Statusbar::put() << "Save playlist as: ";
794 playlist_name
= wFooter
->prompt();
798 Mpd
.SavePlaylist(playlist_name
);
799 Statusbar::printf("Playlist saved as \"%1%\"", playlist_name
);
801 catch (MPD::ServerError
&e
)
803 if (e
.code() == MPD_SERVER_ERROR_EXIST
)
806 boost::format("Playlist \"%1%\" already exists, overwrite?") % playlist_name
808 Mpd
.DeletePlaylist(playlist_name
);
809 Mpd
.SavePlaylist(playlist_name
);
810 Statusbar::print("Playlist overwritten");
822 void ExecuteCommand::run()
824 using Global::wFooter
;
826 std::string cmd_name
;
828 Statusbar::ScopedLock slock
;
829 NC::Window::ScopedPromptHook
helper(*wFooter
,
830 Statusbar::Helpers::TryExecuteImmediateCommand()
832 Statusbar::put() << NC::Format::Bold
<< ":" << NC::Format::NoBold
;
833 cmd_name
= wFooter
->prompt();
836 auto cmd
= Bindings
.findCommand(cmd_name
);
839 Statusbar::printf(1, "Executing %1%...", cmd_name
);
840 bool res
= cmd
->binding().execute();
841 Statusbar::printf("Execution of command \"%1%\" %2%.",
842 cmd_name
, res
? "successful" : "unsuccessful"
846 Statusbar::printf("No command named \"%1%\"", cmd_name
);
849 bool MoveSortOrderUp::canBeRun()
851 return myScreen
== mySortPlaylistDialog
;
854 void MoveSortOrderUp::run()
856 mySortPlaylistDialog
->moveSortOrderUp();
859 bool MoveSortOrderDown::canBeRun()
861 return myScreen
== mySortPlaylistDialog
;
864 void MoveSortOrderDown::run()
866 mySortPlaylistDialog
->moveSortOrderDown();
869 bool MoveSelectedItemsUp::canBeRun()
871 return ((myScreen
== myPlaylist
872 && !myPlaylist
->main().empty())
873 || (myScreen
->isActiveWindow(myPlaylistEditor
->Content
)
874 && !myPlaylistEditor
->Content
.empty()));
877 void MoveSelectedItemsUp::run()
879 const char *filteredMsg
= "Moving items up is disabled in filtered playlist";
880 if (myScreen
== myPlaylist
)
882 if (myPlaylist
->main().isFiltered())
883 Statusbar::print(filteredMsg
);
887 std::bind(&MPD::Connection::Move
, ph::_1
, ph::_2
, ph::_3
));
889 else if (myScreen
== myPlaylistEditor
)
891 if (myPlaylistEditor
->Content
.isFiltered())
892 Statusbar::print(filteredMsg
);
895 auto playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
897 myPlaylistEditor
->Content
,
898 std::bind(&MPD::Connection::PlaylistMove
, ph::_1
, playlist
, ph::_2
, ph::_3
));
903 bool MoveSelectedItemsDown::canBeRun()
905 return ((myScreen
== myPlaylist
906 && !myPlaylist
->main().empty())
907 || (myScreen
->isActiveWindow(myPlaylistEditor
->Content
)
908 && !myPlaylistEditor
->Content
.empty()));
911 void MoveSelectedItemsDown::run()
913 const char *filteredMsg
= "Moving items down is disabled in filtered playlist";
914 if (myScreen
== myPlaylist
)
916 if (myPlaylist
->main().isFiltered())
917 Statusbar::print(filteredMsg
);
919 moveSelectedItemsDown(
921 std::bind(&MPD::Connection::Move
, ph::_1
, ph::_2
, ph::_3
));
923 else if (myScreen
== myPlaylistEditor
)
925 if (myPlaylistEditor
->Content
.isFiltered())
926 Statusbar::print(filteredMsg
);
929 auto playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
930 moveSelectedItemsDown(
931 myPlaylistEditor
->Content
,
932 std::bind(&MPD::Connection::PlaylistMove
, ph::_1
, playlist
, ph::_2
, ph::_3
));
937 bool MoveSelectedItemsTo::canBeRun()
939 return myScreen
== myPlaylist
940 || myScreen
->isActiveWindow(myPlaylistEditor
->Content
);
943 void MoveSelectedItemsTo::run()
945 if (myScreen
== myPlaylist
)
947 if (!myPlaylist
->main().empty())
948 moveSelectedItemsTo(myPlaylist
->main(), std::bind(&MPD::Connection::Move
, ph::_1
, ph::_2
, ph::_3
));
952 assert(!myPlaylistEditor
->Playlists
.empty());
953 std::string playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
954 auto move_fun
= std::bind(&MPD::Connection::PlaylistMove
, ph::_1
, playlist
, ph::_2
, ph::_3
);
955 moveSelectedItemsTo(myPlaylistEditor
->Content
, move_fun
);
961 return myScreen
!= myPlaylistEditor
962 || !myPlaylistEditor
->Playlists
.empty();
967 using Global::wFooter
;
971 Statusbar::ScopedLock slock
;
972 Statusbar::put() << (myScreen
== myPlaylistEditor
? "Add to playlist: " : "Add: ");
973 path
= wFooter
->prompt();
976 // confirm when one wants to add the whole database
978 confirmAction("Are you sure you want to add the whole database?");
980 Statusbar::put() << "Adding...";
982 if (myScreen
== myPlaylistEditor
)
983 Mpd
.AddToPlaylist(myPlaylistEditor
->Playlists
.current()->value().path(), path
);
990 catch (MPD::ServerError
&err
)
992 // If a path is not a file or directory, assume it is a playlist.
993 if (err
.code() == MPD_SERVER_ERROR_NO_EXIST
)
994 Mpd
.LoadPlaylist(path
);
1001 bool SeekForward::canBeRun()
1003 return Status::State::player() != MPD::psStop
&& Status::State::totalTime() > 0;
1006 void SeekForward::run()
1011 bool SeekBackward::canBeRun()
1013 return Status::State::player() != MPD::psStop
&& Status::State::totalTime() > 0;
1016 void SeekBackward::run()
1021 bool ToggleDisplayMode::canBeRun()
1023 return myScreen
== myPlaylist
1024 || myScreen
== myBrowser
1025 || myScreen
== mySearcher
1026 || myScreen
->isActiveWindow(myPlaylistEditor
->Content
);
1029 void ToggleDisplayMode::run()
1031 if (myScreen
== myPlaylist
)
1033 switch (Config
.playlist_display_mode
)
1035 case DisplayMode::Classic
:
1036 Config
.playlist_display_mode
= DisplayMode::Columns
;
1037 myPlaylist
->main().setItemDisplayer(std::bind(
1038 Display::SongsInColumns
, ph::_1
, std::cref(myPlaylist
->main())
1040 if (Config
.titles_visibility
)
1041 myPlaylist
->main().setTitle(Display::Columns(myPlaylist
->main().getWidth()));
1043 myPlaylist
->main().setTitle("");
1045 case DisplayMode::Columns
:
1046 Config
.playlist_display_mode
= DisplayMode::Classic
;
1047 myPlaylist
->main().setItemDisplayer(std::bind(
1048 Display::Songs
, ph::_1
, std::cref(myPlaylist
->main()), std::cref(Config
.song_list_format
)
1050 myPlaylist
->main().setTitle("");
1052 Statusbar::printf("Playlist display mode: %1%", Config
.playlist_display_mode
);
1054 else if (myScreen
== myBrowser
)
1056 switch (Config
.browser_display_mode
)
1058 case DisplayMode::Classic
:
1059 Config
.browser_display_mode
= DisplayMode::Columns
;
1060 if (Config
.titles_visibility
)
1061 myBrowser
->main().setTitle(Display::Columns(myBrowser
->main().getWidth()));
1063 myBrowser
->main().setTitle("");
1065 case DisplayMode::Columns
:
1066 Config
.browser_display_mode
= DisplayMode::Classic
;
1067 myBrowser
->main().setTitle("");
1070 Statusbar::printf("Browser display mode: %1%", Config
.browser_display_mode
);
1072 else if (myScreen
== mySearcher
)
1074 switch (Config
.search_engine_display_mode
)
1076 case DisplayMode::Classic
:
1077 Config
.search_engine_display_mode
= DisplayMode::Columns
;
1079 case DisplayMode::Columns
:
1080 Config
.search_engine_display_mode
= DisplayMode::Classic
;
1083 Statusbar::printf("Search engine display mode: %1%", Config
.search_engine_display_mode
);
1084 if (mySearcher
->main().size() > SearchEngine::StaticOptions
)
1085 mySearcher
->main().setTitle(
1086 Config
.search_engine_display_mode
== DisplayMode::Columns
1087 && Config
.titles_visibility
1088 ? Display::Columns(mySearcher
->main().getWidth())
1092 else if (myScreen
->isActiveWindow(myPlaylistEditor
->Content
))
1094 switch (Config
.playlist_editor_display_mode
)
1096 case DisplayMode::Classic
:
1097 Config
.playlist_editor_display_mode
= DisplayMode::Columns
;
1098 myPlaylistEditor
->Content
.setItemDisplayer(std::bind(
1099 Display::SongsInColumns
, ph::_1
, std::cref(myPlaylistEditor
->Content
)
1102 case DisplayMode::Columns
:
1103 Config
.playlist_editor_display_mode
= DisplayMode::Classic
;
1104 myPlaylistEditor
->Content
.setItemDisplayer(std::bind(
1105 Display::Songs
, ph::_1
, std::cref(myPlaylistEditor
->Content
), std::cref(Config
.song_list_format
)
1109 Statusbar::printf("Playlist editor display mode: %1%", Config
.playlist_editor_display_mode
);
1113 bool ToggleSeparatorsBetweenAlbums::canBeRun()
1118 void ToggleSeparatorsBetweenAlbums::run()
1120 Config
.playlist_separate_albums
= !Config
.playlist_separate_albums
;
1121 Statusbar::printf("Separators between albums: %1%",
1122 Config
.playlist_separate_albums
? "on" : "off"
1126 bool ToggleLyricsUpdateOnSongChange::canBeRun()
1128 return myScreen
== myLyrics
;
1131 void ToggleLyricsUpdateOnSongChange::run()
1133 Config
.now_playing_lyrics
= !Config
.now_playing_lyrics
;
1134 Statusbar::printf("Update lyrics if song changes: %1%",
1135 Config
.now_playing_lyrics
? "on" : "off"
1139 void ToggleLyricsFetcher::run()
1141 myLyrics
->toggleFetcher();
1144 void ToggleFetchingLyricsInBackground::run()
1146 Config
.fetch_lyrics_in_background
= !Config
.fetch_lyrics_in_background
;
1147 Statusbar::printf("Fetching lyrics for playing songs in background: %1%",
1148 Config
.fetch_lyrics_in_background
? "on" : "off");
1151 void TogglePlayingSongCentering::run()
1153 Config
.autocenter_mode
= !Config
.autocenter_mode
;
1154 Statusbar::printf("Centering playing song: %1%",
1155 Config
.autocenter_mode
? "on" : "off"
1157 if (Config
.autocenter_mode
)
1159 auto s
= myPlaylist
->nowPlayingSong();
1161 myPlaylist
->locateSong(s
);
1165 void UpdateDatabase::run()
1167 if (myScreen
== myBrowser
)
1168 Mpd
.UpdateDirectory(myBrowser
->currentDirectory());
1169 # ifdef HAVE_TAGLIB_H
1170 else if (myScreen
== myTagEditor
)
1171 Mpd
.UpdateDirectory(myTagEditor
->CurrentDir());
1172 # endif // HAVE_TAGLIB_H
1174 Mpd
.UpdateDirectory("/");
1177 bool JumpToPlayingSong::canBeRun()
1179 return myScreen
== myPlaylist
1180 || myScreen
== myBrowser
1181 || myScreen
== myLibrary
;
1184 void JumpToPlayingSong::run()
1186 auto s
= myPlaylist
->nowPlayingSong();
1189 if (myScreen
== myPlaylist
)
1191 myPlaylist
->locateSong(s
);
1193 else if (myScreen
== myBrowser
)
1195 myBrowser
->locateSong(s
);
1197 else if (myScreen
== myLibrary
)
1199 myLibrary
->locateSong(s
);
1203 void ToggleRepeat::run()
1205 Mpd
.SetRepeat(!Status::State::repeat());
1208 bool Shuffle::canBeRun()
1210 if (myScreen
!= myPlaylist
)
1212 m_begin
= myPlaylist
->main().begin();
1213 m_end
= myPlaylist
->main().end();
1214 return findSelectedRangeAndPrintInfoIfNot(m_begin
, m_end
);
1219 if (Config
.ask_before_shuffling_playlists
)
1220 confirmAction("Do you really want to shuffle selected range?");
1221 auto begin
= myPlaylist
->main().begin();
1222 Mpd
.ShuffleRange(m_begin
-begin
, m_end
-begin
);
1223 Statusbar::print("Range shuffled");
1226 void ToggleRandom::run()
1228 Mpd
.SetRandom(!Status::State::random());
1231 bool StartSearching::canBeRun()
1233 return myScreen
== mySearcher
&& !mySearcher
->main()[0].isInactive();
1236 void StartSearching::run()
1238 mySearcher
->main().highlight(SearchEngine::SearchButton
);
1239 mySearcher
->main().setHighlighting(0);
1240 mySearcher
->main().refresh();
1241 mySearcher
->main().setHighlighting(1);
1242 mySearcher
->runAction();
1245 bool SaveTagChanges::canBeRun()
1247 # ifdef HAVE_TAGLIB_H
1248 return myScreen
== myTinyTagEditor
1249 || myScreen
->activeWindow() == myTagEditor
->TagTypes
;
1252 # endif // HAVE_TAGLIB_H
1255 void SaveTagChanges::run()
1257 # ifdef HAVE_TAGLIB_H
1258 if (myScreen
== myTinyTagEditor
)
1260 myTinyTagEditor
->main().highlight(myTinyTagEditor
->main().size()-2); // Save
1261 myTinyTagEditor
->runAction();
1263 else if (myScreen
->activeWindow() == myTagEditor
->TagTypes
)
1265 myTagEditor
->TagTypes
->highlight(myTagEditor
->TagTypes
->size()-1); // Save
1266 myTagEditor
->runAction();
1268 # endif // HAVE_TAGLIB_H
1271 void ToggleSingle::run()
1273 Mpd
.SetSingle(!Status::State::single());
1276 void ToggleConsume::run()
1278 Mpd
.SetConsume(!Status::State::consume());
1281 void ToggleCrossfade::run()
1283 Mpd
.SetCrossfade(Status::State::crossfade() ? 0 : Config
.crossfade_time
);
1286 void SetCrossfade::run()
1288 using Global::wFooter
;
1290 Statusbar::ScopedLock slock
;
1291 Statusbar::put() << "Set crossfade to: ";
1292 auto crossfade
= fromString
<unsigned>(wFooter
->prompt());
1293 lowerBoundCheck(crossfade
, 0u);
1294 Config
.crossfade_time
= crossfade
;
1295 Mpd
.SetCrossfade(crossfade
);
1298 void SetVolume::run()
1300 using Global::wFooter
;
1304 Statusbar::ScopedLock slock
;
1305 Statusbar::put() << "Set volume to: ";
1306 volume
= fromString
<unsigned>(wFooter
->prompt());
1307 boundsCheck(volume
, 0u, 100u);
1308 Mpd
.SetVolume(volume
);
1310 Statusbar::printf("Volume set to %1%%%", volume
);
1313 bool EnterDirectory::canBeRun()
1315 bool result
= false;
1316 if (myScreen
== myBrowser
&& !myBrowser
->main().empty())
1318 result
= myBrowser
->main().current()->value().type()
1319 == MPD::Item::Type::Directory
;
1321 #ifdef HAVE_TAGLIB_H
1322 else if (myScreen
->activeWindow() == myTagEditor
->Dirs
)
1324 #endif // HAVE_TAGLIB_H
1328 void EnterDirectory::run()
1330 if (myScreen
== myBrowser
)
1331 myBrowser
->enterDirectory();
1332 #ifdef HAVE_TAGLIB_H
1333 else if (myScreen
->activeWindow() == myTagEditor
->Dirs
)
1335 if (!myTagEditor
->enterDirectory())
1336 Statusbar::print("No subdirectories found");
1338 #endif // HAVE_TAGLIB_H
1341 bool EditSong::canBeRun()
1343 # ifdef HAVE_TAGLIB_H
1344 m_song
= currentSong(myScreen
);
1345 return m_song
!= nullptr && isMPDMusicDirSet();
1348 # endif // HAVE_TAGLIB_H
1351 void EditSong::run()
1353 # ifdef HAVE_TAGLIB_H
1354 myTinyTagEditor
->SetEdited(*m_song
);
1355 myTinyTagEditor
->switchTo();
1356 # endif // HAVE_TAGLIB_H
1359 bool EditLibraryTag::canBeRun()
1361 # ifdef HAVE_TAGLIB_H
1362 return myScreen
->isActiveWindow(myLibrary
->Tags
)
1363 && !myLibrary
->Tags
.empty()
1364 && isMPDMusicDirSet();
1367 # endif // HAVE_TAGLIB_H
1370 void EditLibraryTag::run()
1372 # ifdef HAVE_TAGLIB_H
1373 using Global::wFooter
;
1375 std::string new_tag
;
1377 Statusbar::ScopedLock slock
;
1378 Statusbar::put() << NC::Format::Bold
<< tagTypeToString(Config
.media_lib_primary_tag
) << NC::Format::NoBold
<< ": ";
1379 new_tag
= wFooter
->prompt(myLibrary
->Tags
.current()->value().tag());
1381 if (!new_tag
.empty() && new_tag
!= myLibrary
->Tags
.current()->value().tag())
1383 Statusbar::print("Updating tags...");
1384 Mpd
.StartSearch(true);
1385 Mpd
.AddSearch(Config
.media_lib_primary_tag
, myLibrary
->Tags
.current()->value().tag());
1386 MPD::MutableSong::SetFunction set
= tagTypeToSetFunction(Config
.media_lib_primary_tag
);
1388 bool success
= true;
1389 std::string dir_to_update
;
1390 for (MPD::SongIterator s
= Mpd
.CommitSearchSongs(), end
; s
!= end
; ++s
)
1392 MPD::MutableSong ms
= std::move(*s
);
1393 ms
.setTags(set
, new_tag
);
1394 Statusbar::printf("Updating tags in \"%1%\"...", ms
.getName());
1395 std::string path
= Config
.mpd_music_dir
+ ms
.getURI();
1396 if (!Tags::write(ms
))
1399 Statusbar::printf("Error while writing tags to \"%1%\": %2%",
1400 ms
.getName(), strerror(errno
));
1404 if (dir_to_update
.empty())
1405 dir_to_update
= ms
.getURI();
1407 dir_to_update
= getSharedDirectory(dir_to_update
, ms
.getURI());
1411 Mpd
.UpdateDirectory(dir_to_update
);
1412 Statusbar::print("Tags updated successfully");
1415 # endif // HAVE_TAGLIB_H
1418 bool EditLibraryAlbum::canBeRun()
1420 # ifdef HAVE_TAGLIB_H
1421 return myScreen
->isActiveWindow(myLibrary
->Albums
)
1422 && !myLibrary
->Albums
.empty()
1423 && isMPDMusicDirSet();
1426 # endif // HAVE_TAGLIB_H
1429 void EditLibraryAlbum::run()
1431 # ifdef HAVE_TAGLIB_H
1432 using Global::wFooter
;
1433 // FIXME: merge this and EditLibraryTag. also, prompt on failure if user wants to continue
1434 std::string new_album
;
1436 Statusbar::ScopedLock slock
;
1437 Statusbar::put() << NC::Format::Bold
<< "Album: " << NC::Format::NoBold
;
1438 new_album
= wFooter
->prompt(myLibrary
->Albums
.current()->value().entry().album());
1440 if (!new_album
.empty() && new_album
!= myLibrary
->Albums
.current()->value().entry().album())
1443 Statusbar::print("Updating tags...");
1444 for (size_t i
= 0; i
< myLibrary
->Songs
.size(); ++i
)
1446 Statusbar::printf("Updating tags in \"%1%\"...", myLibrary
->Songs
[i
].value().getName());
1447 std::string path
= Config
.mpd_music_dir
+ myLibrary
->Songs
[i
].value().getURI();
1448 TagLib::FileRef
f(path
.c_str());
1451 const char msg
[] = "Error while opening file \"%1%\"";
1452 Statusbar::printf(msg
, wideShorten(myLibrary
->Songs
[i
].value().getURI(), COLS
-const_strlen(msg
)));
1456 f
.tag()->setAlbum(ToWString(new_album
));
1459 const char msg
[] = "Error while writing tags in \"%1%\"";
1460 Statusbar::printf(msg
, wideShorten(myLibrary
->Songs
[i
].value().getURI(), COLS
-const_strlen(msg
)));
1467 Mpd
.UpdateDirectory(getSharedDirectory(myLibrary
->Songs
.beginV(), myLibrary
->Songs
.endV()));
1468 Statusbar::print("Tags updated successfully");
1471 # endif // HAVE_TAGLIB_H
1474 bool EditDirectoryName::canBeRun()
1476 return ((myScreen
== myBrowser
1477 && !myBrowser
->main().empty()
1478 && myBrowser
->main().current()->value().type() == MPD::Item::Type::Directory
)
1479 # ifdef HAVE_TAGLIB_H
1480 || (myScreen
->activeWindow() == myTagEditor
->Dirs
1481 && !myTagEditor
->Dirs
->empty()
1482 && myTagEditor
->Dirs
->choice() > 0)
1483 # endif // HAVE_TAGLIB_H
1484 ) && isMPDMusicDirSet();
1487 void EditDirectoryName::run()
1489 using Global::wFooter
;
1490 if (myScreen
== myBrowser
)
1492 std::string old_dir
= myBrowser
->main().current()->value().directory().path();
1493 std::string new_dir
;
1495 Statusbar::ScopedLock slock
;
1496 Statusbar::put() << NC::Format::Bold
<< "Directory: " << NC::Format::NoBold
;
1497 new_dir
= wFooter
->prompt(old_dir
);
1499 if (!new_dir
.empty() && new_dir
!= old_dir
)
1501 std::string full_old_dir
;
1502 if (!myBrowser
->isLocal())
1503 full_old_dir
+= Config
.mpd_music_dir
;
1504 full_old_dir
+= old_dir
;
1505 std::string full_new_dir
;
1506 if (!myBrowser
->isLocal())
1507 full_new_dir
+= Config
.mpd_music_dir
;
1508 full_new_dir
+= new_dir
;
1509 boost::filesystem::rename(full_old_dir
, full_new_dir
);
1510 const char msg
[] = "Directory renamed to \"%1%\"";
1511 Statusbar::printf(msg
, wideShorten(new_dir
, COLS
-const_strlen(msg
)));
1512 if (!myBrowser
->isLocal())
1513 Mpd
.UpdateDirectory(getSharedDirectory(old_dir
, new_dir
));
1514 myBrowser
->requestUpdate();
1517 # ifdef HAVE_TAGLIB_H
1518 else if (myScreen
->activeWindow() == myTagEditor
->Dirs
)
1520 std::string old_dir
= myTagEditor
->Dirs
->current()->value().first
, new_dir
;
1522 Statusbar::ScopedLock slock
;
1523 Statusbar::put() << NC::Format::Bold
<< "Directory: " << NC::Format::NoBold
;
1524 new_dir
= wFooter
->prompt(old_dir
);
1526 if (!new_dir
.empty() && new_dir
!= old_dir
)
1528 std::string full_old_dir
= Config
.mpd_music_dir
+ myTagEditor
->CurrentDir() + "/" + old_dir
;
1529 std::string full_new_dir
= Config
.mpd_music_dir
+ myTagEditor
->CurrentDir() + "/" + new_dir
;
1530 if (rename(full_old_dir
.c_str(), full_new_dir
.c_str()) == 0)
1532 const char msg
[] = "Directory renamed to \"%1%\"";
1533 Statusbar::printf(msg
, wideShorten(new_dir
, COLS
-const_strlen(msg
)));
1534 Mpd
.UpdateDirectory(myTagEditor
->CurrentDir());
1538 const char msg
[] = "Couldn't rename \"%1%\": %2%";
1539 Statusbar::printf(msg
, wideShorten(old_dir
, COLS
-const_strlen(msg
)-25), strerror(errno
));
1543 # endif // HAVE_TAGLIB_H
1546 bool EditPlaylistName::canBeRun()
1548 return (myScreen
->isActiveWindow(myPlaylistEditor
->Playlists
)
1549 && !myPlaylistEditor
->Playlists
.empty())
1550 || (myScreen
== myBrowser
1551 && !myBrowser
->main().empty()
1552 && myBrowser
->main().current()->value().type() == MPD::Item::Type::Playlist
);
1555 void EditPlaylistName::run()
1557 using Global::wFooter
;
1558 std::string old_name
, new_name
;
1559 if (myScreen
->isActiveWindow(myPlaylistEditor
->Playlists
))
1560 old_name
= myPlaylistEditor
->Playlists
.current()->value().path();
1562 old_name
= myBrowser
->main().current()->value().playlist().path();
1564 Statusbar::ScopedLock slock
;
1565 Statusbar::put() << NC::Format::Bold
<< "Playlist: " << NC::Format::NoBold
;
1566 new_name
= wFooter
->prompt(old_name
);
1568 if (!new_name
.empty() && new_name
!= old_name
)
1570 Mpd
.Rename(old_name
, new_name
);
1571 const char msg
[] = "Playlist renamed to \"%1%\"";
1572 Statusbar::printf(msg
, wideShorten(new_name
, COLS
-const_strlen(msg
)));
1576 bool EditLyrics::canBeRun()
1578 return myScreen
== myLyrics
;
1581 void EditLyrics::run()
1586 bool JumpToBrowser::canBeRun()
1588 m_song
= currentSong(myScreen
);
1589 return m_song
!= nullptr;
1592 void JumpToBrowser::run()
1594 myBrowser
->locateSong(*m_song
);
1597 bool JumpToMediaLibrary::canBeRun()
1599 m_song
= currentSong(myScreen
);
1600 return m_song
!= nullptr;
1603 void JumpToMediaLibrary::run()
1605 myLibrary
->locateSong(*m_song
);
1608 bool JumpToPlaylistEditor::canBeRun()
1610 return myScreen
== myBrowser
1611 && myBrowser
->main().current()->value().type() == MPD::Item::Type::Playlist
;
1614 void JumpToPlaylistEditor::run()
1616 myPlaylistEditor
->locatePlaylist(myBrowser
->main().current()->value().playlist());
1619 void ToggleScreenLock::run()
1621 using Global::wFooter
;
1622 using Global::myLockedScreen
;
1623 const char *msg_unlockable_screen
= "Current screen can't be locked";
1624 if (myLockedScreen
!= nullptr)
1626 BaseScreen::unlock();
1627 Actions::setResizeFlags();
1629 Statusbar::print("Screen unlocked");
1631 else if (!myScreen
->isLockable())
1633 Statusbar::print(msg_unlockable_screen
);
1637 unsigned part
= Config
.locked_screen_width_part
*100;
1638 if (Config
.ask_for_locked_screen_width_part
)
1640 Statusbar::ScopedLock slock
;
1641 Statusbar::put() << "% of the locked screen's width to be reserved (20-80): ";
1642 part
= fromString
<unsigned>(wFooter
->prompt(boost::lexical_cast
<std::string
>(part
)));
1644 boundsCheck(part
, 20u, 80u);
1645 Config
.locked_screen_width_part
= part
/100.0;
1646 if (myScreen
->lock())
1647 Statusbar::printf("Screen locked (with %1%%% width)", part
);
1649 Statusbar::print(msg_unlockable_screen
);
1653 bool JumpToTagEditor::canBeRun()
1655 # ifdef HAVE_TAGLIB_H
1656 m_song
= currentSong(myScreen
);
1657 return m_song
!= nullptr && isMPDMusicDirSet();
1660 # endif // HAVE_TAGLIB_H
1663 void JumpToTagEditor::run()
1665 # ifdef HAVE_TAGLIB_H
1666 myTagEditor
->LocateSong(*m_song
);
1667 # endif // HAVE_TAGLIB_H
1670 bool JumpToPositionInSong::canBeRun()
1672 return Status::State::player() != MPD::psStop
&& Status::State::totalTime() > 0;
1675 void JumpToPositionInSong::run()
1677 using Global::wFooter
;
1679 const MPD::Song s
= myPlaylist
->nowPlayingSong();
1683 Statusbar::ScopedLock slock
;
1684 Statusbar::put() << "Position to go (in %/m:ss/seconds(s)): ";
1685 spos
= wFooter
->prompt();
1690 if (boost::regex_match(spos
, what
, rx
.assign("([0-9]+):([0-9]{2})"))) // mm:ss
1692 auto mins
= fromString
<unsigned>(what
[1]);
1693 auto secs
= fromString
<unsigned>(what
[2]);
1694 boundsCheck(secs
, 0u, 60u);
1695 Mpd
.Seek(s
.getPosition(), mins
* 60 + secs
);
1697 else if (boost::regex_match(spos
, what
, rx
.assign("([0-9]+)s"))) // position in seconds
1699 auto secs
= fromString
<unsigned>(what
[1]);
1700 Mpd
.Seek(s
.getPosition(), secs
);
1702 else if (boost::regex_match(spos
, what
, rx
.assign("([0-9]+)[%]{0,1}"))) // position in %
1704 auto percent
= fromString
<unsigned>(what
[1]);
1705 boundsCheck(percent
, 0u, 100u);
1706 int secs
= (percent
* s
.getDuration()) / 100.0;
1707 Mpd
.Seek(s
.getPosition(), secs
);
1710 Statusbar::print("Invalid format ([m]:[ss], [s]s, [%]%, [%] accepted)");
1713 bool SelectItem::canBeRun()
1715 m_list
= dynamic_cast<NC::List
*>(myScreen
->activeWindow());
1716 return m_list
!= nullptr
1718 && m_list
->currentP()->isSelectable();
1721 void SelectItem::run()
1723 auto current
= m_list
->currentP();
1724 current
->setSelected(!current
->isSelected());
1727 bool SelectRange::canBeRun()
1729 m_list
= dynamic_cast<NC::List
*>(myScreen
->activeWindow());
1730 if (m_list
== nullptr)
1732 m_begin
= m_list
->beginP();
1733 m_end
= m_list
->endP();
1734 return findRange(m_begin
, m_end
);
1737 void SelectRange::run()
1739 for (; m_begin
!= m_end
; ++m_begin
)
1740 m_begin
->setSelected(true);
1741 Statusbar::print("Range selected");
1744 bool ReverseSelection::canBeRun()
1746 m_list
= dynamic_cast<NC::List
*>(myScreen
->activeWindow());
1747 return m_list
!= nullptr;
1750 void ReverseSelection::run()
1752 for (auto &p
: *m_list
)
1753 p
.setSelected(!p
.isSelected());
1754 Statusbar::print("Selection reversed");
1757 bool RemoveSelection::canBeRun()
1759 m_list
= dynamic_cast<NC::List
*>(myScreen
->activeWindow());
1760 return m_list
!= nullptr;
1763 void RemoveSelection::run()
1765 for (auto &p
: *m_list
)
1766 p
.setSelected(false);
1767 Statusbar::print("Selection removed");
1770 bool SelectAlbum::canBeRun()
1772 auto *w
= myScreen
->activeWindow();
1773 if (m_list
!= static_cast<void *>(w
))
1774 m_list
= dynamic_cast<NC::List
*>(w
);
1775 if (m_songs
!= static_cast<void *>(w
))
1776 m_songs
= dynamic_cast<SongList
*>(w
);
1777 return m_list
!= nullptr && !m_list
->empty()
1778 && m_songs
!= nullptr;
1781 void SelectAlbum::run()
1783 const auto front
= m_songs
->beginS(), current
= m_songs
->currentS(), end
= m_songs
->endS();
1784 auto *s
= current
->get
<Bit::Song
>();
1787 auto get
= &MPD::Song::getAlbum
;
1788 const std::string tag
= s
->getTags(get
);
1790 for (auto it
= current
; it
!= front
;)
1793 s
= it
->get
<Bit::Song
>();
1794 if (s
== nullptr || s
->getTags(get
) != tag
)
1796 it
->get
<Bit::Properties
>().setSelected(true);
1799 for (auto it
= current
;;)
1801 it
->get
<Bit::Properties
>().setSelected(true);
1804 s
= it
->get
<Bit::Song
>();
1805 if (s
== nullptr || s
->getTags(get
) != tag
)
1808 Statusbar::print("Album around cursor position selected");
1811 bool SelectFoundItems::canBeRun()
1813 m_list
= dynamic_cast<NC::List
*>(myScreen
->activeWindow());
1814 if (m_list
== nullptr || m_list
->empty())
1816 m_searchable
= dynamic_cast<Searchable
*>(myScreen
);
1817 return m_searchable
!= nullptr && m_searchable
->allowsSearching();
1820 void SelectFoundItems::run()
1822 auto current_pos
= m_list
->choice();
1823 myScreen
->activeWindow()->scroll(NC::Scroll::Home
);
1824 bool found
= m_searchable
->search(SearchDirection::Forward
, false, false);
1827 Statusbar::print("Searching for items...");
1828 m_list
->currentP()->setSelected(true);
1829 while (m_searchable
->search(SearchDirection::Forward
, false, true))
1830 m_list
->currentP()->setSelected(true);
1831 Statusbar::print("Found items selected");
1833 m_list
->highlight(current_pos
);
1836 bool AddSelectedItems::canBeRun()
1838 return myScreen
!= mySelectedItemsAdder
;
1841 void AddSelectedItems::run()
1843 mySelectedItemsAdder
->switchTo();
1846 void CropMainPlaylist::run()
1848 auto &w
= myPlaylist
->main();
1849 // cropping doesn't make sense in this case
1852 if (Config
.ask_before_clearing_playlists
)
1853 confirmAction("Do you really want to crop main playlist?");
1854 Statusbar::print("Cropping playlist...");
1855 selectCurrentIfNoneSelected(w
);
1856 cropPlaylist(w
, std::bind(&MPD::Connection::Delete
, ph::_1
, ph::_2
));
1857 Statusbar::print("Playlist cropped");
1860 bool CropPlaylist::canBeRun()
1862 return myScreen
== myPlaylistEditor
;
1865 void CropPlaylist::run()
1867 auto &w
= myPlaylistEditor
->Content
;
1868 // cropping doesn't make sense in this case
1871 assert(!myPlaylistEditor
->Playlists
.empty());
1872 std::string playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
1873 if (Config
.ask_before_clearing_playlists
)
1874 confirmAction(boost::format("Do you really want to crop playlist \"%1%\"?") % playlist
);
1875 selectCurrentIfNoneSelected(w
);
1876 Statusbar::printf("Cropping playlist \"%1%\"...", playlist
);
1877 cropPlaylist(w
, std::bind(&MPD::Connection::PlaylistDelete
, ph::_1
, playlist
, ph::_2
));
1878 Statusbar::printf("Playlist \"%1%\" cropped", playlist
);
1881 void ClearMainPlaylist::run()
1883 if (!myPlaylist
->main().empty() && Config
.ask_before_clearing_playlists
)
1884 confirmAction("Do you really want to clear main playlist?");
1885 Mpd
.ClearMainPlaylist();
1886 Statusbar::print("Playlist cleared");
1887 myPlaylist
->main().reset();
1890 bool ClearPlaylist::canBeRun()
1892 return myScreen
== myPlaylistEditor
;
1895 void ClearPlaylist::run()
1897 if (myPlaylistEditor
->Playlists
.empty())
1899 std::string playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
1900 if (Config
.ask_before_clearing_playlists
)
1901 confirmAction(boost::format("Do you really want to clear playlist \"%1%\"?") % playlist
);
1902 Mpd
.ClearPlaylist(playlist
);
1903 Statusbar::printf("Playlist \"%1%\" cleared", playlist
);
1906 bool SortPlaylist::canBeRun()
1908 if (myScreen
!= myPlaylist
)
1910 auto first
= myPlaylist
->main().begin(), last
= myPlaylist
->main().end();
1911 return findSelectedRangeAndPrintInfoIfNot(first
, last
);
1914 void SortPlaylist::run()
1916 mySortPlaylistDialog
->switchTo();
1919 bool ReversePlaylist::canBeRun()
1921 if (myScreen
!= myPlaylist
)
1923 m_begin
= myPlaylist
->main().begin();
1924 m_end
= myPlaylist
->main().end();
1925 return findSelectedRangeAndPrintInfoIfNot(m_begin
, m_end
);
1928 void ReversePlaylist::run()
1930 Statusbar::print("Reversing range...");
1931 Mpd
.StartCommandsList();
1932 for (--m_end
; m_begin
< m_end
; ++m_begin
, --m_end
)
1933 Mpd
.Swap(m_begin
->value().getPosition(), m_end
->value().getPosition());
1934 Mpd
.CommitCommandsList();
1935 Statusbar::print("Range reversed");
1938 bool ApplyFilter::canBeRun()
1940 m_filterable
= dynamic_cast<Filterable
*>(myScreen
);
1941 return m_filterable
!= nullptr
1942 && m_filterable
->allowsFiltering();
1945 void ApplyFilter::run()
1947 using Global::wFooter
;
1949 std::string filter
= m_filterable
->currentFilter();
1950 if (!filter
.empty())
1952 m_filterable
->applyFilter(filter
);
1953 myScreen
->refreshWindow();
1958 Statusbar::ScopedLock slock
;
1959 NC::Window::ScopedPromptHook
helper(
1961 Statusbar::Helpers::ApplyFilterImmediately(m_filterable
));
1962 Statusbar::put() << "Apply filter: ";
1963 filter
= wFooter
->prompt(filter
);
1965 catch (NC::PromptAborted
&)
1967 m_filterable
->applyFilter(filter
);
1972 Statusbar::printf("Filtering disabled");
1974 Statusbar::printf("Using filter \"%1%\"", filter
);
1976 if (myScreen
== myPlaylist
)
1977 myPlaylist
->reloadTotalLength();
1979 listsChangeFinisher();
1982 bool Find::canBeRun()
1984 return myScreen
== myHelp
1985 || myScreen
== myLyrics
1986 || myScreen
== myLastfm
;
1991 using Global::wFooter
;
1995 Statusbar::ScopedLock slock
;
1996 Statusbar::put() << "Find: ";
1997 token
= wFooter
->prompt();
2000 Statusbar::print("Searching...");
2001 auto s
= static_cast<Screen
<NC::Scrollpad
> *>(myScreen
);
2002 s
->main().removeProperties();
2003 if (token
.empty() || s
->main().setProperties(NC::Format::Reverse
, token
, NC::Format::NoReverse
, Config
.regex_type
))
2004 Statusbar::print("Done");
2006 Statusbar::print("No matching patterns found");
2010 bool FindItemBackward::canBeRun()
2012 auto w
= dynamic_cast<Searchable
*>(myScreen
);
2013 return w
&& w
->allowsSearching();
2016 void FindItemForward::run()
2018 findItem(SearchDirection::Forward
);
2019 listsChangeFinisher();
2022 bool FindItemForward::canBeRun()
2024 auto w
= dynamic_cast<Searchable
*>(myScreen
);
2025 return w
&& w
->allowsSearching();
2028 void FindItemBackward::run()
2030 findItem(SearchDirection::Backward
);
2031 listsChangeFinisher();
2034 bool NextFoundItem::canBeRun()
2036 return dynamic_cast<Searchable
*>(myScreen
);
2039 void NextFoundItem::run()
2041 Searchable
*w
= dynamic_cast<Searchable
*>(myScreen
);
2042 assert(w
!= nullptr);
2043 w
->search(SearchDirection::Forward
, Config
.wrapped_search
, true);
2044 listsChangeFinisher();
2047 bool PreviousFoundItem::canBeRun()
2049 return dynamic_cast<Searchable
*>(myScreen
);
2052 void PreviousFoundItem::run()
2054 Searchable
*w
= dynamic_cast<Searchable
*>(myScreen
);
2055 assert(w
!= nullptr);
2056 w
->search(SearchDirection::Backward
, Config
.wrapped_search
, true);
2057 listsChangeFinisher();
2060 void ToggleFindMode::run()
2062 Config
.wrapped_search
= !Config
.wrapped_search
;
2063 Statusbar::printf("Search mode: %1%",
2064 Config
.wrapped_search
? "Wrapped" : "Normal"
2068 void ToggleReplayGainMode::run()
2070 using Global::wFooter
;
2074 Statusbar::ScopedLock slock
;
2075 Statusbar::put() << "Replay gain mode? "
2076 << "[" << NC::Format::Bold
<< 'o' << NC::Format::NoBold
<< "ff"
2077 << "/" << NC::Format::Bold
<< 't' << NC::Format::NoBold
<< "rack"
2078 << "/" << NC::Format::Bold
<< 'a' << NC::Format::NoBold
<< "lbum"
2080 rgm
= Statusbar::Helpers::promptReturnOneOf({"t", "a", "o"})[0];
2085 Mpd
.SetReplayGainMode(MPD::rgmTrack
);
2088 Mpd
.SetReplayGainMode(MPD::rgmAlbum
);
2091 Mpd
.SetReplayGainMode(MPD::rgmOff
);
2093 default: // impossible
2094 throw std::runtime_error(
2095 (boost::format("ToggleReplayGainMode: impossible case reached: %1%") % rgm
).str()
2098 Statusbar::printf("Replay gain mode: %1%", Mpd
.GetReplayGainMode());
2101 void ToggleAddMode::run()
2103 std::string mode_desc
;
2104 switch (Config
.space_add_mode
)
2106 case SpaceAddMode::AddRemove
:
2107 Config
.space_add_mode
= SpaceAddMode::AlwaysAdd
;
2108 mode_desc
= "always add an item to playlist";
2110 case SpaceAddMode::AlwaysAdd
:
2111 Config
.space_add_mode
= SpaceAddMode::AddRemove
;
2112 mode_desc
= "add an item to playlist or remove if already added";
2115 Statusbar::printf("Add mode: %1%", mode_desc
);
2118 void ToggleMouse::run()
2120 Config
.mouse_support
= !Config
.mouse_support
;
2121 if (Config
.mouse_support
)
2122 NC::Mouse::enable();
2124 NC::Mouse::disable();
2125 Statusbar::printf("Mouse support %1%",
2126 Config
.mouse_support
? "enabled" : "disabled"
2130 void ToggleBitrateVisibility::run()
2132 Config
.display_bitrate
= !Config
.display_bitrate
;
2133 Statusbar::printf("Bitrate visibility %1%",
2134 Config
.display_bitrate
? "enabled" : "disabled"
2138 void AddRandomItems::run()
2140 using Global::wFooter
;
2143 Statusbar::ScopedLock slock
;
2144 Statusbar::put() << "Add random? "
2145 << "[" << NC::Format::Bold
<< 's' << NC::Format::NoBold
<< "ongs"
2146 << "/" << NC::Format::Bold
<< 'a' << NC::Format::NoBold
<< "rtists"
2147 << "/" << "album" << NC::Format::Bold
<< 'A' << NC::Format::NoBold
<< "rtists"
2148 << "/" << "al" << NC::Format::Bold
<< 'b' << NC::Format::NoBold
<< "ums"
2150 rnd_type
= Statusbar::Helpers::promptReturnOneOf({"s", "a", "A", "b"})[0];
2153 mpd_tag_type tag_type
= MPD_TAG_ARTIST
;
2154 std::string tag_type_str
;
2155 if (rnd_type
!= 's')
2157 tag_type
= charToTagType(rnd_type
);
2158 tag_type_str
= boost::locale::to_lower(tagTypeToString(tag_type
));
2161 tag_type_str
= "song";
2165 Statusbar::ScopedLock slock
;
2166 Statusbar::put() << "Number of random " << tag_type_str
<< "s: ";
2167 number
= fromString
<unsigned>(wFooter
->prompt());
2172 if (rnd_type
== 's')
2173 success
= Mpd
.AddRandomSongs(number
, Global::RNG
);
2175 success
= Mpd
.AddRandomTag(tag_type
, number
, Global::RNG
);
2177 Statusbar::printf("%1% random %2%%3% added to playlist", number
, tag_type_str
, number
== 1 ? "" : "s");
2181 bool ToggleBrowserSortMode::canBeRun()
2183 return myScreen
== myBrowser
;
2186 void ToggleBrowserSortMode::run()
2188 switch (Config
.browser_sort_mode
)
2190 case SortMode::Name
:
2191 Config
.browser_sort_mode
= SortMode::ModificationTime
;
2192 Statusbar::print("Sort songs by: modification time");
2194 case SortMode::ModificationTime
:
2195 Config
.browser_sort_mode
= SortMode::CustomFormat
;
2196 Statusbar::print("Sort songs by: custom format");
2198 case SortMode::CustomFormat
:
2199 Config
.browser_sort_mode
= SortMode::NoOp
;
2200 Statusbar::print("Do not sort songs");
2202 case SortMode::NoOp
:
2203 Config
.browser_sort_mode
= SortMode::Name
;
2204 Statusbar::print("Sort songs by: name");
2206 if (Config
.browser_sort_mode
!= SortMode::NoOp
)
2208 size_t sort_offset
= myBrowser
->inRootDirectory() ? 0 : 1;
2209 std::sort(myBrowser
->main().begin()+sort_offset
, myBrowser
->main().end(),
2210 LocaleBasedItemSorting(std::locale(), Config
.ignore_leading_the
, Config
.browser_sort_mode
)
2215 bool ToggleLibraryTagType::canBeRun()
2217 return (myScreen
->isActiveWindow(myLibrary
->Tags
))
2218 || (myLibrary
->columns() == 2 && myScreen
->isActiveWindow(myLibrary
->Albums
));
2221 void ToggleLibraryTagType::run()
2223 using Global::wFooter
;
2227 Statusbar::ScopedLock slock
;
2228 Statusbar::put() << "Tag type? "
2229 << "[" << NC::Format::Bold
<< 'a' << NC::Format::NoBold
<< "rtist"
2230 << "/" << "album" << NC::Format::Bold
<< 'A' << NC::Format::NoBold
<< "rtist"
2231 << "/" << NC::Format::Bold
<< 'y' << NC::Format::NoBold
<< "ear"
2232 << "/" << NC::Format::Bold
<< 'g' << NC::Format::NoBold
<< "enre"
2233 << "/" << NC::Format::Bold
<< 'c' << NC::Format::NoBold
<< "omposer"
2234 << "/" << NC::Format::Bold
<< 'p' << NC::Format::NoBold
<< "erformer"
2236 tag_type
= Statusbar::Helpers::promptReturnOneOf({"a", "A", "y", "g", "c", "p"})[0];
2238 mpd_tag_type new_tagitem
= charToTagType(tag_type
);
2239 if (new_tagitem
!= Config
.media_lib_primary_tag
)
2241 Config
.media_lib_primary_tag
= new_tagitem
;
2242 std::string item_type
= tagTypeToString(Config
.media_lib_primary_tag
);
2243 myLibrary
->Tags
.setTitle(Config
.titles_visibility
? item_type
+ "s" : "");
2244 myLibrary
->Tags
.reset();
2245 item_type
= boost::locale::to_lower(item_type
);
2246 std::string and_mtime
= Config
.media_library_sort_by_mtime
?
2249 if (myLibrary
->columns() == 2)
2251 myLibrary
->Songs
.clear();
2252 myLibrary
->Albums
.reset();
2253 myLibrary
->Albums
.clear();
2254 myLibrary
->Albums
.setTitle(Config
.titles_visibility
? "Albums (sorted by " + item_type
+ and_mtime
+ ")" : "");
2255 myLibrary
->Albums
.display();
2259 myLibrary
->Tags
.clear();
2260 myLibrary
->Tags
.display();
2262 Statusbar::printf("Switched to the list of %1%s", item_type
);
2266 bool ToggleMediaLibrarySortMode::canBeRun()
2268 return myScreen
== myLibrary
;
2271 void ToggleMediaLibrarySortMode::run()
2273 myLibrary
->toggleSortMode();
2276 bool FetchLyricsInBackground::canBeRun()
2278 m_hs
= dynamic_cast<HasSongs
*>(myScreen
);
2279 return m_hs
!= nullptr && m_hs
->itemAvailable();
2282 void FetchLyricsInBackground::run()
2284 auto songs
= m_hs
->getSelectedSongs();
2285 for (const auto &s
: songs
)
2286 myLyrics
->fetchInBackground(s
, true);
2287 Statusbar::print("Selected songs queued for lyrics fetching");
2290 bool RefetchLyrics::canBeRun()
2292 return myScreen
== myLyrics
;
2295 void RefetchLyrics::run()
2297 myLyrics
->refetchCurrent();
2300 bool SetSelectedItemsPriority::canBeRun()
2302 if (Mpd
.Version() < 17)
2304 Statusbar::print("Priorities are supported in MPD >= 0.17.0");
2307 return myScreen
== myPlaylist
&& !myPlaylist
->main().empty();
2310 void SetSelectedItemsPriority::run()
2312 using Global::wFooter
;
2316 Statusbar::ScopedLock slock
;
2317 Statusbar::put() << "Set priority [0-255]: ";
2318 prio
= fromString
<unsigned>(wFooter
->prompt());
2319 boundsCheck(prio
, 0u, 255u);
2321 myPlaylist
->setSelectedItemsPriority(prio
);
2324 bool ToggleOutput::canBeRun()
2326 #ifdef ENABLE_OUTPUTS
2327 return myScreen
== myOutputs
;
2330 #endif // ENABLE_OUTPUTS
2333 void ToggleOutput::run()
2335 #ifdef ENABLE_OUTPUTS
2336 myOutputs
->toggleOutput();
2337 #endif // ENABLE_OUTPUTS
2340 bool ToggleVisualizationType::canBeRun()
2342 # ifdef ENABLE_VISUALIZER
2343 return myScreen
== myVisualizer
;
2346 # endif // ENABLE_VISUALIZER
2349 void ToggleVisualizationType::run()
2351 # ifdef ENABLE_VISUALIZER
2352 myVisualizer
->ToggleVisualizationType();
2353 # endif // ENABLE_VISUALIZER
2356 bool SetVisualizerSampleMultiplier::canBeRun()
2358 # ifdef ENABLE_VISUALIZER
2359 return myScreen
== myVisualizer
;
2362 # endif // ENABLE_VISUALIZER
2365 void SetVisualizerSampleMultiplier::run()
2367 # ifdef ENABLE_VISUALIZER
2368 using Global::wFooter
;
2372 Statusbar::ScopedLock slock
;
2373 Statusbar::put() << "Set visualizer sample multiplier: ";
2374 multiplier
= fromString
<double>(wFooter
->prompt());
2375 lowerBoundCheck(multiplier
, 0.0);
2376 Config
.visualizer_sample_multiplier
= multiplier
;
2378 Statusbar::printf("Visualizer sample multiplier set to %1%", multiplier
);
2379 # endif // ENABLE_VISUALIZER
2382 void ShowSongInfo::run()
2384 mySongInfo
->switchTo();
2387 bool ShowArtistInfo::canBeRun()
2389 return myScreen
== myLastfm
2390 || (myScreen
->isActiveWindow(myLibrary
->Tags
)
2391 && !myLibrary
->Tags
.empty()
2392 && Config
.media_lib_primary_tag
== MPD_TAG_ARTIST
)
2393 || currentSong(myScreen
);
2396 void ShowArtistInfo::run()
2398 if (myScreen
== myLastfm
)
2400 myLastfm
->switchTo();
2405 if (myScreen
->isActiveWindow(myLibrary
->Tags
))
2407 assert(!myLibrary
->Tags
.empty());
2408 assert(Config
.media_lib_primary_tag
== MPD_TAG_ARTIST
);
2409 artist
= myLibrary
->Tags
.current()->value().tag();
2413 auto s
= currentSong(myScreen
);
2415 artist
= s
->getArtist();
2418 if (!artist
.empty())
2420 myLastfm
->queueJob(new LastFm::ArtistInfo(artist
, Config
.lastfm_preferred_language
));
2421 myLastfm
->switchTo();
2425 bool ShowLyrics::canBeRun()
2427 if (myScreen
== myLyrics
)
2434 m_song
= currentSong(myScreen
);
2435 return m_song
!= nullptr;
2439 void ShowLyrics::run()
2441 if (m_song
!= nullptr)
2442 myLyrics
->fetch(*m_song
);
2443 myLyrics
->switchTo();
2448 ExitMainLoop
= true;
2451 void NextScreen::run()
2453 if (Config
.screen_switcher_previous
)
2455 if (auto tababble
= dynamic_cast<Tabbable
*>(myScreen
))
2456 tababble
->switchToPreviousScreen();
2458 else if (!Config
.screen_sequence
.empty())
2460 const auto &seq
= Config
.screen_sequence
;
2461 auto screen_type
= std::find(seq
.begin(), seq
.end(), myScreen
->type());
2462 if (++screen_type
== seq
.end())
2463 toScreen(seq
.front())->switchTo();
2465 toScreen(*screen_type
)->switchTo();
2469 void PreviousScreen::run()
2471 if (Config
.screen_switcher_previous
)
2473 if (auto tababble
= dynamic_cast<Tabbable
*>(myScreen
))
2474 tababble
->switchToPreviousScreen();
2476 else if (!Config
.screen_sequence
.empty())
2478 const auto &seq
= Config
.screen_sequence
;
2479 auto screen_type
= std::find(seq
.begin(), seq
.end(), myScreen
->type());
2480 if (screen_type
== seq
.begin())
2481 toScreen(seq
.back())->switchTo();
2483 toScreen(*--screen_type
)->switchTo();
2487 bool ShowHelp::canBeRun()
2489 return myScreen
!= myHelp
2490 # ifdef HAVE_TAGLIB_H
2491 && myScreen
!= myTinyTagEditor
2492 # endif // HAVE_TAGLIB_H
2496 void ShowHelp::run()
2501 bool ShowPlaylist::canBeRun()
2503 return myScreen
!= myPlaylist
2504 # ifdef HAVE_TAGLIB_H
2505 && myScreen
!= myTinyTagEditor
2506 # endif // HAVE_TAGLIB_H
2510 void ShowPlaylist::run()
2512 myPlaylist
->switchTo();
2515 bool ShowBrowser::canBeRun()
2517 return myScreen
!= myBrowser
2518 # ifdef HAVE_TAGLIB_H
2519 && myScreen
!= myTinyTagEditor
2520 # endif // HAVE_TAGLIB_H
2524 void ShowBrowser::run()
2526 myBrowser
->switchTo();
2529 bool ChangeBrowseMode::canBeRun()
2531 return myScreen
== myBrowser
;
2534 void ChangeBrowseMode::run()
2536 myBrowser
->changeBrowseMode();
2539 bool ShowSearchEngine::canBeRun()
2541 return myScreen
!= mySearcher
2542 # ifdef HAVE_TAGLIB_H
2543 && myScreen
!= myTinyTagEditor
2544 # endif // HAVE_TAGLIB_H
2548 void ShowSearchEngine::run()
2550 mySearcher
->switchTo();
2553 bool ResetSearchEngine::canBeRun()
2555 return myScreen
== mySearcher
;
2558 void ResetSearchEngine::run()
2560 mySearcher
->reset();
2563 bool ShowMediaLibrary::canBeRun()
2565 return myScreen
!= myLibrary
2566 # ifdef HAVE_TAGLIB_H
2567 && myScreen
!= myTinyTagEditor
2568 # endif // HAVE_TAGLIB_H
2572 void ShowMediaLibrary::run()
2574 myLibrary
->switchTo();
2577 bool ToggleMediaLibraryColumnsMode::canBeRun()
2579 return myScreen
== myLibrary
;
2582 void ToggleMediaLibraryColumnsMode::run()
2584 myLibrary
->toggleColumnsMode();
2585 myLibrary
->refresh();
2588 bool ShowPlaylistEditor::canBeRun()
2590 return myScreen
!= myPlaylistEditor
2591 # ifdef HAVE_TAGLIB_H
2592 && myScreen
!= myTinyTagEditor
2593 # endif // HAVE_TAGLIB_H
2597 void ShowPlaylistEditor::run()
2599 myPlaylistEditor
->switchTo();
2602 bool ShowTagEditor::canBeRun()
2604 # ifdef HAVE_TAGLIB_H
2605 return myScreen
!= myTagEditor
2606 && myScreen
!= myTinyTagEditor
;
2609 # endif // HAVE_TAGLIB_H
2612 void ShowTagEditor::run()
2614 # ifdef HAVE_TAGLIB_H
2615 if (isMPDMusicDirSet())
2616 myTagEditor
->switchTo();
2617 # endif // HAVE_TAGLIB_H
2620 bool ShowOutputs::canBeRun()
2622 # ifdef ENABLE_OUTPUTS
2623 return myScreen
!= myOutputs
2624 # ifdef HAVE_TAGLIB_H
2625 && myScreen
!= myTinyTagEditor
2626 # endif // HAVE_TAGLIB_H
2630 # endif // ENABLE_OUTPUTS
2633 void ShowOutputs::run()
2635 # ifdef ENABLE_OUTPUTS
2636 myOutputs
->switchTo();
2637 # endif // ENABLE_OUTPUTS
2640 bool ShowVisualizer::canBeRun()
2642 # ifdef ENABLE_VISUALIZER
2643 return myScreen
!= myVisualizer
2644 # ifdef HAVE_TAGLIB_H
2645 && myScreen
!= myTinyTagEditor
2646 # endif // HAVE_TAGLIB_H
2650 # endif // ENABLE_VISUALIZER
2653 void ShowVisualizer::run()
2655 # ifdef ENABLE_VISUALIZER
2656 myVisualizer
->switchTo();
2657 # endif // ENABLE_VISUALIZER
2660 bool ShowClock::canBeRun()
2662 # ifdef ENABLE_CLOCK
2663 return myScreen
!= myClock
2664 # ifdef HAVE_TAGLIB_H
2665 && myScreen
!= myTinyTagEditor
2666 # endif // HAVE_TAGLIB_H
2670 # endif // ENABLE_CLOCK
2673 void ShowClock::run()
2675 # ifdef ENABLE_CLOCK
2676 myClock
->switchTo();
2677 # endif // ENABLE_CLOCK
2680 #ifdef HAVE_TAGLIB_H
2681 bool ShowServerInfo::canBeRun()
2683 return myScreen
!= myTinyTagEditor
;
2685 #endif // HAVE_TAGLIB_H
2687 void ShowServerInfo::run()
2689 myServerInfo
->switchTo();
2696 void populateActions()
2698 auto insert_action
= [](Actions::BaseAction
*a
) {
2699 AvailableActions
[static_cast<size_t>(a
->type())] = a
;
2701 insert_action(new Actions::Dummy());
2702 insert_action(new Actions::UpdateEnvironment());
2703 insert_action(new Actions::MouseEvent());
2704 insert_action(new Actions::ScrollUp());
2705 insert_action(new Actions::ScrollDown());
2706 insert_action(new Actions::ScrollUpArtist());
2707 insert_action(new Actions::ScrollUpAlbum());
2708 insert_action(new Actions::ScrollDownArtist());
2709 insert_action(new Actions::ScrollDownAlbum());
2710 insert_action(new Actions::PageUp());
2711 insert_action(new Actions::PageDown());
2712 insert_action(new Actions::MoveHome());
2713 insert_action(new Actions::MoveEnd());
2714 insert_action(new Actions::ToggleInterface());
2715 insert_action(new Actions::JumpToParentDirectory());
2716 insert_action(new Actions::RunAction());
2717 insert_action(new Actions::SelectItem());
2718 insert_action(new Actions::SelectRange());
2719 insert_action(new Actions::PreviousColumn());
2720 insert_action(new Actions::NextColumn());
2721 insert_action(new Actions::MasterScreen());
2722 insert_action(new Actions::SlaveScreen());
2723 insert_action(new Actions::VolumeUp());
2724 insert_action(new Actions::VolumeDown());
2725 insert_action(new Actions::AddItemToPlaylist());
2726 insert_action(new Actions::DeletePlaylistItems());
2727 insert_action(new Actions::DeleteStoredPlaylist());
2728 insert_action(new Actions::DeleteBrowserItems());
2729 insert_action(new Actions::ReplaySong());
2730 insert_action(new Actions::PreviousSong());
2731 insert_action(new Actions::NextSong());
2732 insert_action(new Actions::Pause());
2733 insert_action(new Actions::Stop());
2734 insert_action(new Actions::ExecuteCommand());
2735 insert_action(new Actions::SavePlaylist());
2736 insert_action(new Actions::MoveSortOrderUp());
2737 insert_action(new Actions::MoveSortOrderDown());
2738 insert_action(new Actions::MoveSelectedItemsUp());
2739 insert_action(new Actions::MoveSelectedItemsDown());
2740 insert_action(new Actions::MoveSelectedItemsTo());
2741 insert_action(new Actions::Add());
2742 insert_action(new Actions::PlayItem());
2743 insert_action(new Actions::SeekForward());
2744 insert_action(new Actions::SeekBackward());
2745 insert_action(new Actions::ToggleDisplayMode());
2746 insert_action(new Actions::ToggleSeparatorsBetweenAlbums());
2747 insert_action(new Actions::ToggleLyricsUpdateOnSongChange());
2748 insert_action(new Actions::ToggleLyricsFetcher());
2749 insert_action(new Actions::ToggleFetchingLyricsInBackground());
2750 insert_action(new Actions::TogglePlayingSongCentering());
2751 insert_action(new Actions::UpdateDatabase());
2752 insert_action(new Actions::JumpToPlayingSong());
2753 insert_action(new Actions::ToggleRepeat());
2754 insert_action(new Actions::Shuffle());
2755 insert_action(new Actions::ToggleRandom());
2756 insert_action(new Actions::StartSearching());
2757 insert_action(new Actions::SaveTagChanges());
2758 insert_action(new Actions::ToggleSingle());
2759 insert_action(new Actions::ToggleConsume());
2760 insert_action(new Actions::ToggleCrossfade());
2761 insert_action(new Actions::SetCrossfade());
2762 insert_action(new Actions::SetVolume());
2763 insert_action(new Actions::EnterDirectory());
2764 insert_action(new Actions::EditSong());
2765 insert_action(new Actions::EditLibraryTag());
2766 insert_action(new Actions::EditLibraryAlbum());
2767 insert_action(new Actions::EditDirectoryName());
2768 insert_action(new Actions::EditPlaylistName());
2769 insert_action(new Actions::EditLyrics());
2770 insert_action(new Actions::JumpToBrowser());
2771 insert_action(new Actions::JumpToMediaLibrary());
2772 insert_action(new Actions::JumpToPlaylistEditor());
2773 insert_action(new Actions::ToggleScreenLock());
2774 insert_action(new Actions::JumpToTagEditor());
2775 insert_action(new Actions::JumpToPositionInSong());
2776 insert_action(new Actions::ReverseSelection());
2777 insert_action(new Actions::RemoveSelection());
2778 insert_action(new Actions::SelectAlbum());
2779 insert_action(new Actions::SelectFoundItems());
2780 insert_action(new Actions::AddSelectedItems());
2781 insert_action(new Actions::CropMainPlaylist());
2782 insert_action(new Actions::CropPlaylist());
2783 insert_action(new Actions::ClearMainPlaylist());
2784 insert_action(new Actions::ClearPlaylist());
2785 insert_action(new Actions::SortPlaylist());
2786 insert_action(new Actions::ReversePlaylist());
2787 insert_action(new Actions::ApplyFilter());
2788 insert_action(new Actions::Find());
2789 insert_action(new Actions::FindItemForward());
2790 insert_action(new Actions::FindItemBackward());
2791 insert_action(new Actions::NextFoundItem());
2792 insert_action(new Actions::PreviousFoundItem());
2793 insert_action(new Actions::ToggleFindMode());
2794 insert_action(new Actions::ToggleReplayGainMode());
2795 insert_action(new Actions::ToggleAddMode());
2796 insert_action(new Actions::ToggleMouse());
2797 insert_action(new Actions::ToggleBitrateVisibility());
2798 insert_action(new Actions::AddRandomItems());
2799 insert_action(new Actions::ToggleBrowserSortMode());
2800 insert_action(new Actions::ToggleLibraryTagType());
2801 insert_action(new Actions::ToggleMediaLibrarySortMode());
2802 insert_action(new Actions::FetchLyricsInBackground());
2803 insert_action(new Actions::RefetchLyrics());
2804 insert_action(new Actions::SetSelectedItemsPriority());
2805 insert_action(new Actions::ToggleOutput());
2806 insert_action(new Actions::ToggleVisualizationType());
2807 insert_action(new Actions::SetVisualizerSampleMultiplier());
2808 insert_action(new Actions::ShowSongInfo());
2809 insert_action(new Actions::ShowArtistInfo());
2810 insert_action(new Actions::ShowLyrics());
2811 insert_action(new Actions::Quit());
2812 insert_action(new Actions::NextScreen());
2813 insert_action(new Actions::PreviousScreen());
2814 insert_action(new Actions::ShowHelp());
2815 insert_action(new Actions::ShowPlaylist());
2816 insert_action(new Actions::ShowBrowser());
2817 insert_action(new Actions::ChangeBrowseMode());
2818 insert_action(new Actions::ShowSearchEngine());
2819 insert_action(new Actions::ResetSearchEngine());
2820 insert_action(new Actions::ShowMediaLibrary());
2821 insert_action(new Actions::ToggleMediaLibraryColumnsMode());
2822 insert_action(new Actions::ShowPlaylistEditor());
2823 insert_action(new Actions::ShowTagEditor());
2824 insert_action(new Actions::ShowOutputs());
2825 insert_action(new Actions::ShowVisualizer());
2826 insert_action(new Actions::ShowClock());
2827 insert_action(new Actions::ShowServerInfo());
2830 bool scrollTagCanBeRun(NC::List
*&list
, SongList
*&songs
)
2832 auto w
= myScreen
->activeWindow();
2833 if (list
!= static_cast<void *>(w
))
2834 list
= dynamic_cast<NC::List
*>(w
);
2835 if (songs
!= static_cast<void *>(w
))
2836 songs
= dynamic_cast<SongList
*>(w
);
2837 return list
!= nullptr && !list
->empty()
2838 && songs
!= nullptr;
2841 void scrollTagUpRun(NC::List
*list
, SongList
*songs
, MPD::Song::GetFunction get
)
2843 const auto front
= songs
->beginS();
2844 auto it
= songs
->currentS();
2845 if (auto *s
= it
->get
<Bit::Song
>())
2847 const std::string tag
= s
->getTags(get
);
2851 s
= it
->get
<Bit::Song
>();
2852 if (s
== nullptr || s
->getTags(get
) != tag
)
2855 list
->highlight(it
-front
);
2859 void scrollTagDownRun(NC::List
*list
, SongList
*songs
, MPD::Song::GetFunction get
)
2861 const auto front
= songs
->beginS(), back
= --songs
->endS();
2862 auto it
= songs
->currentS();
2863 if (auto *s
= it
->get
<Bit::Song
>())
2865 const std::string tag
= s
->getTags(get
);
2869 s
= it
->get
<Bit::Song
>();
2870 if (s
== nullptr || s
->getTags(get
) != tag
)
2873 list
->highlight(it
-front
);
2879 using Global::wHeader
;
2880 using Global::wFooter
;
2881 using Global::Timer
;
2882 using Global::SeekingInProgress
;
2884 if (!Status::State::totalTime())
2886 Statusbar::print("Unknown item length");
2890 Progressbar::ScopedLock progressbar_lock
;
2891 Statusbar::ScopedLock statusbar_lock
;
2893 unsigned songpos
= Status::State::elapsedTime();
2896 int old_timeout
= wFooter
->getTimeout();
2897 wFooter
->setTimeout(BaseScreen::defaultWindowTimeout
);
2899 // Accept single action of a given type or action chain for which all actions
2900 // can be run and one of them is of the given type. This will still not work
2901 // in some contrived cases, but allows for more flexibility than accepting
2902 // single actions only.
2903 auto hasRunnableAction
= [](BindingsConfiguration::BindingIteratorPair
&bindings
, Actions::Type type
) {
2904 bool success
= false;
2905 for (auto binding
= bindings
.first
; binding
!= bindings
.second
; ++binding
)
2907 auto &actions
= binding
->actions();
2908 for (const auto &action
: actions
)
2910 if (action
->canBeRun())
2912 if (action
->type() == type
)
2927 SeekingInProgress
= true;
2932 unsigned howmuch
= Config
.incremental_seeking
2933 ? (Timer
-t
).total_seconds()/2+Config
.seek_time
2936 NC::Key::Type input
= readKey(*wFooter
);
2938 auto k
= Bindings
.get(input
);
2939 if (hasRunnableAction(k
, Actions::Type::SeekForward
))
2941 if (songpos
< Status::State::totalTime())
2942 songpos
= std::min(songpos
+ howmuch
, Status::State::totalTime());
2944 else if (hasRunnableAction(k
, Actions::Type::SeekBackward
))
2948 if (songpos
< howmuch
)
2957 *wFooter
<< NC::Format::Bold
;
2958 std::string tracklength
;
2959 // FIXME: merge this with the code in status.cpp
2960 switch (Config
.design
)
2962 case Design::Classic
:
2964 if (Config
.display_remaining_time
)
2967 tracklength
+= MPD::Song::ShowTime(Status::State::totalTime()-songpos
);
2970 tracklength
+= MPD::Song::ShowTime(songpos
);
2972 tracklength
+= MPD::Song::ShowTime(Status::State::totalTime());
2974 *wFooter
<< NC::XY(wFooter
->getWidth()-tracklength
.length(), 1) << tracklength
;
2976 case Design::Alternative
:
2977 if (Config
.display_remaining_time
)
2980 tracklength
+= MPD::Song::ShowTime(Status::State::totalTime()-songpos
);
2983 tracklength
= MPD::Song::ShowTime(songpos
);
2985 tracklength
+= MPD::Song::ShowTime(Status::State::totalTime());
2986 *wHeader
<< NC::XY(0, 0) << tracklength
<< " ";
2990 *wFooter
<< NC::Format::NoBold
;
2991 Progressbar::draw(songpos
, Status::State::totalTime());
2994 SeekingInProgress
= false;
2995 Mpd
.Seek(Status::State::currentSongPosition(), songpos
);
2997 wFooter
->setTimeout(old_timeout
);
3000 void findItem(const SearchDirection direction
)
3002 using Global::wFooter
;
3004 Searchable
*w
= dynamic_cast<Searchable
*>(myScreen
);
3005 assert(w
!= nullptr);
3006 assert(w
->allowsSearching());
3008 std::string constraint
= w
->searchConstraint();
3011 Statusbar::ScopedLock slock
;
3012 NC::Window::ScopedPromptHook
prompt_hook(
3014 Statusbar::Helpers::FindImmediately(w
, direction
));
3015 Statusbar::put() << (boost::format("Find %1%: ") % direction
).str();
3016 constraint
= wFooter
->prompt(constraint
);
3018 catch (NC::PromptAborted
&)
3020 w
->setSearchConstraint(constraint
);
3021 w
->search(direction
, Config
.wrapped_search
, false);
3025 if (constraint
.empty())
3027 Statusbar::printf("Constraint unset");
3028 w
->clearSearchConstraint();
3031 Statusbar::printf("Using constraint \"%1%\"", constraint
);
3034 void listsChangeFinisher()
3036 if (myScreen
== myLibrary
3037 || myScreen
== myPlaylistEditor
3038 # ifdef HAVE_TAGLIB_H
3039 || myScreen
== myTagEditor
3040 # endif // HAVE_TAGLIB_H
3043 if (myScreen
->activeWindow() == &myLibrary
->Tags
)
3045 myLibrary
->Albums
.clear();
3046 myLibrary
->Albums
.refresh();
3047 myLibrary
->Songs
.clear();
3048 myLibrary
->Songs
.refresh();
3049 myLibrary
->updateTimer();
3051 else if (myScreen
->activeWindow() == &myLibrary
->Albums
)
3053 myLibrary
->Songs
.clear();
3054 myLibrary
->Songs
.refresh();
3055 myLibrary
->updateTimer();
3057 else if (myScreen
->isActiveWindow(myPlaylistEditor
->Playlists
))
3059 myPlaylistEditor
->Content
.clear();
3060 myPlaylistEditor
->Content
.refresh();
3061 myPlaylistEditor
->updateTimer();
3063 # ifdef HAVE_TAGLIB_H
3064 else if (myScreen
->activeWindow() == myTagEditor
->Dirs
)
3066 myTagEditor
->Tags
->clear();
3068 # endif // HAVE_TAGLIB_H