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"
43 #include "curses/menu_impl.h"
45 #include "screens/browser.h"
46 #include "screens/clock.h"
47 #include "screens/help.h"
48 #include "screens/media_library.h"
49 #include "screens/lastfm.h"
50 #include "screens/lyrics.h"
51 #include "screens/playlist.h"
52 #include "screens/playlist_editor.h"
53 #include "screens/sort_playlist.h"
54 #include "screens/search_engine.h"
55 #include "screens/sel_items_adder.h"
56 #include "screens/server_info.h"
57 #include "screens/song_info.h"
58 #include "screens/outputs.h"
59 #include "utility/readline.h"
60 #include "utility/string.h"
61 #include "utility/type_conversions.h"
62 #include "screens/tag_editor.h"
63 #include "screens/tiny_tag_editor.h"
64 #include "screens/visualizer.h"
71 #endif // HAVE_TAGLIB_H
73 using Global::myScreen
;
75 namespace ph
= std::placeholders
;
79 std::vector
<std::shared_ptr
<Actions::BaseAction
>> AvailableActions
;
81 void populateActions();
83 bool scrollTagCanBeRun(NC::List
*&list
, const SongList
*&songs
);
84 void scrollTagUpRun(NC::List
*list
, const SongList
*songs
, MPD::Song::GetFunction get
);
85 void scrollTagDownRun(NC::List
*list
, const SongList
*songs
, MPD::Song::GetFunction get
);
87 void seek(SearchDirection sd
);
88 void findItem(const SearchDirection direction
);
89 void listsChangeFinisher();
91 template <typename Iterator
>
92 bool findSelectedRangeAndPrintInfoIfNot(Iterator
&first
, Iterator
&last
)
94 bool success
= findSelectedRange(first
, last
);
96 Statusbar::print("No range selected");
100 template <typename Iterator
>
101 Iterator
nextScreenTypeInSequence(Iterator first
, Iterator last
, ScreenType type
)
103 auto it
= std::find(first
, last
, type
);
120 bool OriginalStatusbarVisibility
;
121 bool ExitMainLoop
= false;
127 void validateScreenSize()
129 using Global::MainHeight
;
131 if (COLS
< 30 || MainHeight
< 5)
134 std::cout
<< "Screen is too small to handle ncmpcpp correctly\n";
139 void initializeScreens()
142 myPlaylist
= new Playlist
;
143 myBrowser
= new Browser
;
144 mySearcher
= new SearchEngine
;
145 myLibrary
= new MediaLibrary
;
146 myPlaylistEditor
= new PlaylistEditor
;
147 myLyrics
= new Lyrics
;
148 mySelectedItemsAdder
= new SelectedItemsAdder
;
149 mySongInfo
= new SongInfo
;
150 myServerInfo
= new ServerInfo
;
151 mySortPlaylistDialog
= new SortPlaylistDialog
;
152 myLastfm
= new Lastfm
;
154 # ifdef HAVE_TAGLIB_H
155 myTinyTagEditor
= new TinyTagEditor
;
156 myTagEditor
= new TagEditor
;
157 # endif // HAVE_TAGLIB_H
159 # ifdef ENABLE_VISUALIZER
160 myVisualizer
= new Visualizer
;
161 # endif // ENABLE_VISUALIZER
163 # ifdef ENABLE_OUTPUTS
164 myOutputs
= new Outputs
;
165 # endif // ENABLE_OUTPUTS
169 # endif // ENABLE_CLOCK
173 void setResizeFlags()
175 myHelp
->hasToBeResized
= 1;
176 myPlaylist
->hasToBeResized
= 1;
177 myBrowser
->hasToBeResized
= 1;
178 mySearcher
->hasToBeResized
= 1;
179 myLibrary
->hasToBeResized
= 1;
180 myPlaylistEditor
->hasToBeResized
= 1;
181 myLyrics
->hasToBeResized
= 1;
182 mySelectedItemsAdder
->hasToBeResized
= 1;
183 mySongInfo
->hasToBeResized
= 1;
184 myServerInfo
->hasToBeResized
= 1;
185 mySortPlaylistDialog
->hasToBeResized
= 1;
186 myLastfm
->hasToBeResized
= 1;
188 # ifdef HAVE_TAGLIB_H
189 myTinyTagEditor
->hasToBeResized
= 1;
190 myTagEditor
->hasToBeResized
= 1;
191 # endif // HAVE_TAGLIB_H
193 # ifdef ENABLE_VISUALIZER
194 myVisualizer
->hasToBeResized
= 1;
195 # endif // ENABLE_VISUALIZER
197 # ifdef ENABLE_OUTPUTS
198 myOutputs
->hasToBeResized
= 1;
199 # endif // ENABLE_OUTPUTS
202 myClock
->hasToBeResized
= 1;
203 # endif // ENABLE_CLOCK
206 void resizeScreen(bool reload_main_window
)
208 using Global::MainHeight
;
209 using Global::wHeader
;
210 using Global::wFooter
;
212 // update internal screen dimensions
213 if (reload_main_window
)
215 rl_resize_terminal();
218 // Remove KEY_RESIZE from input queue, I'm not sure how these make it in.
222 MainHeight
= LINES
-(Config
.design
== Design::Alternative
? 7 : 4);
224 validateScreenSize();
226 if (!Config
.header_visibility
)
228 if (!Config
.statusbar_visibility
)
233 applyToVisibleWindows(&BaseScreen::resize
);
235 if (Config
.header_visibility
|| Config
.design
== Design::Alternative
)
236 wHeader
->resize(COLS
, HeaderHeight
);
238 FooterStartY
= LINES
-(Config
.statusbar_visibility
? 2 : 1);
239 wFooter
->moveTo(0, FooterStartY
);
240 wFooter
->resize(COLS
, Config
.statusbar_visibility
? 2 : 1);
242 applyToVisibleWindows(&BaseScreen::refresh
);
244 Status::Changes::elapsedTime(false);
245 Status::Changes::playerState();
246 // Note: routines for drawing separator if alternative user
247 // interface is active and header is hidden are placed in
248 // NcmpcppStatusChanges.StatusFlags
249 Status::Changes::flags();
255 void setWindowsDimensions()
257 using Global::MainStartY
;
258 using Global::MainHeight
;
260 MainStartY
= Config
.design
== Design::Alternative
? 5 : 2;
261 MainHeight
= LINES
-(Config
.design
== Design::Alternative
? 7 : 4);
263 if (!Config
.header_visibility
)
268 if (!Config
.statusbar_visibility
)
271 HeaderHeight
= Config
.design
== Design::Alternative
? (Config
.header_visibility
? 5 : 3) : 2;
272 FooterStartY
= LINES
-(Config
.statusbar_visibility
? 2 : 1);
273 FooterHeight
= Config
.statusbar_visibility
? 2 : 1;
276 void confirmAction(const boost::format
&description
)
278 Statusbar::ScopedLock slock
;
279 Statusbar::put() << description
.str()
280 << " [" << NC::Format::Bold
<< 'y' << NC::Format::NoBold
281 << '/' << NC::Format::Bold
<< 'n' << NC::Format::NoBold
283 auto answer
= Statusbar::Helpers::promptReturnOneOf({"y", "n"});
285 throw NC::PromptAborted(std::move(answer
));
288 bool isMPDMusicDirSet()
290 if (Config
.mpd_music_dir
.empty())
292 Statusbar::print("Proper mpd_music_dir variable has to be set in configuration file");
298 BaseAction
&get(Actions::Type at
)
300 if (AvailableActions
.empty())
302 return *AvailableActions
.at(static_cast<size_t>(at
));
305 std::shared_ptr
<BaseAction
> get_(Actions::Type at
)
307 if (AvailableActions
.empty())
309 return AvailableActions
.at(static_cast<size_t>(at
));
312 std::shared_ptr
<BaseAction
> get_(const std::string
&name
)
314 std::shared_ptr
<BaseAction
> result
;
315 if (AvailableActions
.empty())
317 for (const auto &action
: AvailableActions
)
319 if (action
->name() == name
)
328 UpdateEnvironment::UpdateEnvironment()
329 : BaseAction(Type::UpdateEnvironment
, "update_environment")
330 , m_past(boost::posix_time::from_time_t(0))
333 void UpdateEnvironment::run(bool update_timer
, bool refresh_window
, bool mpd_sync
)
337 // update timer, status if necessary etc.
338 Status::trace(update_timer
, true);
340 // show lyrics consumer notification if appropriate
341 if (auto message
= myLyrics
->tryTakeConsumerMessage())
342 Statusbar::print(*message
);
345 if ((myScreen
== myPlaylist
|| myScreen
== myBrowser
|| myScreen
== myLyrics
)
346 && (Timer
- m_past
> boost::posix_time::milliseconds(500))
354 myScreen
->refreshWindow();
356 // We want to synchronize with MPD during execution of an action chain.
359 int flags
= Mpd
.noidle();
361 Status::update(flags
);
365 void UpdateEnvironment::run()
367 run(true, true, true);
370 bool MouseEvent::canBeRun()
372 return Config
.mouse_support
;
375 void MouseEvent::run()
377 using Global::VolumeState
;
378 using Global::wFooter
;
380 m_old_mouse_event
= m_mouse_event
;
381 m_mouse_event
= wFooter
->getMouseEvent();
383 //Statusbar::printf("(%1%, %2%, %3%)", m_mouse_event.bstate, m_mouse_event.x, m_mouse_event.y);
385 if (m_mouse_event
.bstate
& BUTTON1_PRESSED
386 && m_mouse_event
.y
== LINES
-(Config
.statusbar_visibility
? 2 : 1)
389 if (Status::State::player() == MPD::psStop
)
391 Mpd
.Seek(Status::State::currentSongPosition(),
392 Status::State::totalTime()*m_mouse_event
.x
/double(COLS
));
394 else if (m_mouse_event
.bstate
& BUTTON1_PRESSED
395 && (Config
.statusbar_visibility
|| Config
.design
== Design::Alternative
)
396 && Status::State::player() != MPD::psStop
397 && m_mouse_event
.y
== (Config
.design
== Design::Alternative
? 1 : LINES
-1)
398 && m_mouse_event
.x
< 9
403 else if ((m_mouse_event
.bstate
& BUTTON5_PRESSED
|| m_mouse_event
.bstate
& BUTTON4_PRESSED
)
404 && (Config
.header_visibility
|| Config
.design
== Design::Alternative
)
405 && m_mouse_event
.y
== 0 && size_t(m_mouse_event
.x
) > COLS
-VolumeState
.length()
408 if (m_mouse_event
.bstate
& BUTTON5_PRESSED
)
409 get(Type::VolumeDown
).execute();
411 get(Type::VolumeUp
).execute();
413 else if (m_mouse_event
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
| BUTTON4_PRESSED
| BUTTON5_PRESSED
))
414 myScreen
->mouseButtonPressed(m_mouse_event
);
419 myScreen
->scroll(NC::Scroll::Up
);
420 listsChangeFinisher();
423 void ScrollDown::run()
425 myScreen
->scroll(NC::Scroll::Down
);
426 listsChangeFinisher();
429 bool ScrollUpArtist::canBeRun()
431 return scrollTagCanBeRun(m_list
, m_songs
);
434 void ScrollUpArtist::run()
436 scrollTagUpRun(m_list
, m_songs
, &MPD::Song::getArtist
);
439 bool ScrollUpAlbum::canBeRun()
441 return scrollTagCanBeRun(m_list
, m_songs
);
444 void ScrollUpAlbum::run()
446 scrollTagUpRun(m_list
, m_songs
, &MPD::Song::getAlbum
);
449 bool ScrollDownArtist::canBeRun()
451 return scrollTagCanBeRun(m_list
, m_songs
);
454 void ScrollDownArtist::run()
456 scrollTagDownRun(m_list
, m_songs
, &MPD::Song::getArtist
);
459 bool ScrollDownAlbum::canBeRun()
461 return scrollTagCanBeRun(m_list
, m_songs
);
464 void ScrollDownAlbum::run()
466 scrollTagDownRun(m_list
, m_songs
, &MPD::Song::getAlbum
);
471 myScreen
->scroll(NC::Scroll::PageUp
);
472 listsChangeFinisher();
477 myScreen
->scroll(NC::Scroll::PageDown
);
478 listsChangeFinisher();
483 myScreen
->scroll(NC::Scroll::Home
);
484 listsChangeFinisher();
489 myScreen
->scroll(NC::Scroll::End
);
490 listsChangeFinisher();
493 void ToggleInterface::run()
495 switch (Config
.design
)
497 case Design::Classic
:
498 Config
.design
= Design::Alternative
;
499 Config
.statusbar_visibility
= false;
501 case Design::Alternative
:
502 Config
.design
= Design::Classic
;
503 Config
.statusbar_visibility
= OriginalStatusbarVisibility
;
506 setWindowsDimensions();
508 // unlock progressbar
509 Progressbar::ScopedLock();
510 Status::Changes::mixer();
511 Status::Changes::elapsedTime(false);
512 Statusbar::printf("User interface: %1%", Config
.design
);
515 bool JumpToParentDirectory::canBeRun()
517 return (myScreen
== myBrowser
)
518 # ifdef HAVE_TAGLIB_H
519 || (myScreen
->activeWindow() == myTagEditor
->Dirs
)
520 # endif // HAVE_TAGLIB_H
524 void JumpToParentDirectory::run()
526 if (myScreen
== myBrowser
)
528 if (!myBrowser
->inRootDirectory())
530 myBrowser
->main().reset();
531 myBrowser
->enterDirectory();
534 # ifdef HAVE_TAGLIB_H
535 else if (myScreen
== myTagEditor
)
537 if (myTagEditor
->CurrentDir() != "/")
539 myTagEditor
->Dirs
->reset();
540 myTagEditor
->enterDirectory();
543 # endif // HAVE_TAGLIB_H
546 bool RunAction::canBeRun()
548 m_ha
= dynamic_cast<HasActions
*>(myScreen
);
549 return m_ha
!= nullptr
550 && m_ha
->actionRunnable();
553 void RunAction::run()
558 bool PreviousColumn::canBeRun()
560 m_hc
= dynamic_cast<HasColumns
*>(myScreen
);
561 return m_hc
!= nullptr
562 && m_hc
->previousColumnAvailable();
565 void PreviousColumn::run()
567 m_hc
->previousColumn();
570 bool NextColumn::canBeRun()
572 m_hc
= dynamic_cast<HasColumns
*>(myScreen
);
573 return m_hc
!= nullptr
574 && m_hc
->nextColumnAvailable();
577 void NextColumn::run()
582 bool MasterScreen::canBeRun()
584 using Global::myLockedScreen
;
585 using Global::myInactiveScreen
;
587 return myLockedScreen
589 && myLockedScreen
!= myScreen
590 && myScreen
->isMergable();
593 void MasterScreen::run()
595 using Global::myInactiveScreen
;
596 using Global::myLockedScreen
;
598 myInactiveScreen
= myScreen
;
599 myScreen
= myLockedScreen
;
603 bool SlaveScreen::canBeRun()
605 using Global::myLockedScreen
;
606 using Global::myInactiveScreen
;
608 return myLockedScreen
610 && myLockedScreen
== myScreen
611 && myScreen
->isMergable();
614 void SlaveScreen::run()
616 using Global::myInactiveScreen
;
617 using Global::myLockedScreen
;
619 myScreen
= myInactiveScreen
;
620 myInactiveScreen
= myLockedScreen
;
626 Mpd
.ChangeVolume(static_cast<int>(Config
.volume_change_step
));
629 void VolumeDown::run()
631 Mpd
.ChangeVolume(-static_cast<int>(Config
.volume_change_step
));
634 bool AddItemToPlaylist::canBeRun()
636 m_hs
= dynamic_cast<HasSongs
*>(myScreen
);
637 return m_hs
!= nullptr && m_hs
->itemAvailable();
640 void AddItemToPlaylist::run()
642 bool success
= m_hs
->addItemToPlaylist(false);
645 myScreen
->scroll(NC::Scroll::Down
);
646 listsChangeFinisher();
650 bool PlayItem::canBeRun()
652 m_hs
= dynamic_cast<HasSongs
*>(myScreen
);
653 return m_hs
!= nullptr && m_hs
->itemAvailable();
658 bool success
= m_hs
->addItemToPlaylist(true);
660 listsChangeFinisher();
663 bool DeletePlaylistItems::canBeRun()
665 return (myScreen
== myPlaylist
&& !myPlaylist
->main().empty())
666 || (myScreen
->isActiveWindow(myPlaylistEditor
->Content
) && !myPlaylistEditor
->Content
.empty());
669 void DeletePlaylistItems::run()
671 if (myScreen
== myPlaylist
)
673 Statusbar::print("Deleting items...");
674 auto delete_fun
= std::bind(&MPD::Connection::Delete
, ph::_1
, ph::_2
);
675 deleteSelectedSongs(myPlaylist
->main(), delete_fun
);
676 Statusbar::print("Item(s) deleted");
678 else if (myScreen
->isActiveWindow(myPlaylistEditor
->Content
))
680 std::string playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
681 auto delete_fun
= std::bind(&MPD::Connection::PlaylistDelete
, ph::_1
, playlist
, ph::_2
);
682 Statusbar::print("Deleting items...");
683 deleteSelectedSongs(myPlaylistEditor
->Content
, delete_fun
);
684 Statusbar::print("Item(s) deleted");
688 bool DeleteBrowserItems::canBeRun()
690 auto check_if_deletion_allowed
= []() {
691 if (Config
.allow_for_physical_item_deletion
)
695 Statusbar::print("Flag \"allow_for_physical_item_deletion\" needs to be enabled in configuration file");
699 return myScreen
== myBrowser
700 && !myBrowser
->main().empty()
701 && isMPDMusicDirSet()
702 && check_if_deletion_allowed();
705 void DeleteBrowserItems::run()
707 auto get_name
= [](const MPD::Item
&item
) -> std::string
{
711 case MPD::Item::Type::Directory
:
712 iname
= getBasename(item
.directory().path());
714 case MPD::Item::Type::Song
:
715 iname
= item
.song().getName();
717 case MPD::Item::Type::Playlist
:
718 iname
= getBasename(item
.playlist().path());
724 boost::format question
;
725 if (hasSelected(myBrowser
->main().begin(), myBrowser
->main().end()))
726 question
= boost::format("Delete selected items?");
729 const auto &item
= myBrowser
->main().current()->value();
730 // parent directories are not accepted (and they
731 // can't be selected, so in other cases it's fine).
732 if (myBrowser
->isParentDirectory(item
))
734 const char msg
[] = "Delete \"%1%\"?";
735 question
= boost::format(msg
) % wideShorten(
736 get_name(item
), COLS
-const_strlen(msg
)-5
739 confirmAction(question
);
741 auto items
= getSelectedOrCurrent(
742 myBrowser
->main().begin(),
743 myBrowser
->main().end(),
744 myBrowser
->main().current()
746 for (const auto &item
: items
)
748 myBrowser
->remove(item
->value());
749 const char msg
[] = "Deleted %1% \"%2%\"";
750 Statusbar::printf(msg
,
751 itemTypeToString(item
->value().type()),
752 wideShorten(get_name(item
->value()), COLS
-const_strlen(msg
))
756 if (!myBrowser
->isLocal())
757 Mpd
.UpdateDirectory(myBrowser
->currentDirectory());
758 myBrowser
->requestUpdate();
761 bool DeleteStoredPlaylist::canBeRun()
763 return myScreen
->isActiveWindow(myPlaylistEditor
->Playlists
);
766 void DeleteStoredPlaylist::run()
768 if (myPlaylistEditor
->Playlists
.empty())
770 boost::format question
;
771 if (hasSelected(myPlaylistEditor
->Playlists
.begin(), myPlaylistEditor
->Playlists
.end()))
772 question
= boost::format("Delete selected playlists?");
774 question
= boost::format("Delete playlist \"%1%\"?")
775 % wideShorten(myPlaylistEditor
->Playlists
.current()->value().path(), COLS
-question
.size()-10);
776 confirmAction(question
);
777 auto list
= getSelectedOrCurrent(
778 myPlaylistEditor
->Playlists
.begin(),
779 myPlaylistEditor
->Playlists
.end(),
780 myPlaylistEditor
->Playlists
.current()
782 for (const auto &item
: list
)
783 Mpd
.DeletePlaylist(item
->value().path());
784 Statusbar::printf("%1% deleted", list
.size() == 1 ? "Playlist" : "Playlists");
785 // force playlists update. this happens automatically, but only after call
786 // to Key::read, therefore when we call PlaylistEditor::Update, it won't
787 // yet see it, so let's point that it needs to update it.
788 myPlaylistEditor
->requestPlaylistsUpdate();
791 void ReplaySong::run()
793 if (Status::State::player() != MPD::psStop
)
794 Mpd
.Seek(Status::State::currentSongPosition(), 0);
797 void PreviousSong::run()
812 void SavePlaylist::run()
814 using Global::wFooter
;
816 std::string playlist_name
;
818 Statusbar::ScopedLock slock
;
819 Statusbar::put() << "Save playlist as: ";
820 playlist_name
= wFooter
->prompt();
824 Mpd
.SavePlaylist(playlist_name
);
825 Statusbar::printf("Playlist saved as \"%1%\"", playlist_name
);
827 catch (MPD::ServerError
&e
)
829 if (e
.code() == MPD_SERVER_ERROR_EXIST
)
832 boost::format("Playlist \"%1%\" already exists, overwrite?") % playlist_name
834 Mpd
.DeletePlaylist(playlist_name
);
835 Mpd
.SavePlaylist(playlist_name
);
836 Statusbar::print("Playlist overwritten");
848 void ExecuteCommand::run()
850 using Global::wFooter
;
852 std::string cmd_name
;
854 Statusbar::ScopedLock slock
;
855 NC::Window::ScopedPromptHook
helper(*wFooter
,
856 Statusbar::Helpers::TryExecuteImmediateCommand()
858 Statusbar::put() << NC::Format::Bold
<< ":" << NC::Format::NoBold
;
859 cmd_name
= wFooter
->prompt();
862 auto cmd
= Bindings
.findCommand(cmd_name
);
865 Statusbar::printf(1, "Executing %1%...", cmd_name
);
866 bool res
= cmd
->binding().execute();
867 Statusbar::printf("Execution of command \"%1%\" %2%.",
868 cmd_name
, res
? "successful" : "unsuccessful"
872 Statusbar::printf("No command named \"%1%\"", cmd_name
);
875 bool MoveSortOrderUp::canBeRun()
877 return myScreen
== mySortPlaylistDialog
;
880 void MoveSortOrderUp::run()
882 mySortPlaylistDialog
->moveSortOrderUp();
885 bool MoveSortOrderDown::canBeRun()
887 return myScreen
== mySortPlaylistDialog
;
890 void MoveSortOrderDown::run()
892 mySortPlaylistDialog
->moveSortOrderDown();
895 bool MoveSelectedItemsUp::canBeRun()
897 return ((myScreen
== myPlaylist
898 && !myPlaylist
->main().empty())
899 || (myScreen
->isActiveWindow(myPlaylistEditor
->Content
)
900 && !myPlaylistEditor
->Content
.empty()));
903 void MoveSelectedItemsUp::run()
905 const char *filteredMsg
= "Moving items up is disabled in filtered playlist";
906 if (myScreen
== myPlaylist
)
908 if (myPlaylist
->main().isFiltered())
909 Statusbar::print(filteredMsg
);
913 std::bind(&MPD::Connection::Move
, ph::_1
, ph::_2
, ph::_3
));
915 else if (myScreen
== myPlaylistEditor
)
917 if (myPlaylistEditor
->Content
.isFiltered())
918 Statusbar::print(filteredMsg
);
921 auto playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
923 myPlaylistEditor
->Content
,
924 std::bind(&MPD::Connection::PlaylistMove
, ph::_1
, playlist
, ph::_2
, ph::_3
));
929 bool MoveSelectedItemsDown::canBeRun()
931 return ((myScreen
== myPlaylist
932 && !myPlaylist
->main().empty())
933 || (myScreen
->isActiveWindow(myPlaylistEditor
->Content
)
934 && !myPlaylistEditor
->Content
.empty()));
937 void MoveSelectedItemsDown::run()
939 const char *filteredMsg
= "Moving items down is disabled in filtered playlist";
940 if (myScreen
== myPlaylist
)
942 if (myPlaylist
->main().isFiltered())
943 Statusbar::print(filteredMsg
);
945 moveSelectedItemsDown(
947 std::bind(&MPD::Connection::Move
, ph::_1
, ph::_2
, ph::_3
));
949 else if (myScreen
== myPlaylistEditor
)
951 if (myPlaylistEditor
->Content
.isFiltered())
952 Statusbar::print(filteredMsg
);
955 auto playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
956 moveSelectedItemsDown(
957 myPlaylistEditor
->Content
,
958 std::bind(&MPD::Connection::PlaylistMove
, ph::_1
, playlist
, ph::_2
, ph::_3
));
963 bool MoveSelectedItemsTo::canBeRun()
965 return myScreen
== myPlaylist
966 || myScreen
->isActiveWindow(myPlaylistEditor
->Content
);
969 void MoveSelectedItemsTo::run()
971 if (myScreen
== myPlaylist
)
973 if (!myPlaylist
->main().empty())
974 moveSelectedItemsTo(myPlaylist
->main(), std::bind(&MPD::Connection::Move
, ph::_1
, ph::_2
, ph::_3
));
978 assert(!myPlaylistEditor
->Playlists
.empty());
979 std::string playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
980 auto move_fun
= std::bind(&MPD::Connection::PlaylistMove
, ph::_1
, playlist
, ph::_2
, ph::_3
);
981 moveSelectedItemsTo(myPlaylistEditor
->Content
, move_fun
);
987 return myScreen
!= myPlaylistEditor
988 || !myPlaylistEditor
->Playlists
.empty();
993 using Global::wFooter
;
997 Statusbar::ScopedLock slock
;
998 Statusbar::put() << (myScreen
== myPlaylistEditor
? "Add to playlist: " : "Add: ");
999 path
= wFooter
->prompt();
1002 // confirm when one wants to add the whole database
1004 confirmAction("Are you sure you want to add the whole database?");
1006 Statusbar::put() << "Adding...";
1008 if (myScreen
== myPlaylistEditor
)
1009 Mpd
.AddToPlaylist(myPlaylistEditor
->Playlists
.current()->value().path(), path
);
1016 catch (MPD::ServerError
&err
)
1018 // If a path is not a file or directory, assume it is a playlist.
1019 if (err
.code() == MPD_SERVER_ERROR_NO_EXIST
)
1020 Mpd
.LoadPlaylist(path
);
1027 bool SeekForward::canBeRun()
1029 return Status::State::player() != MPD::psStop
&& Status::State::totalTime() > 0;
1032 void SeekForward::run()
1034 seek(SearchDirection::Forward
);
1037 bool SeekBackward::canBeRun()
1039 return Status::State::player() != MPD::psStop
&& Status::State::totalTime() > 0;
1042 void SeekBackward::run()
1044 seek(SearchDirection::Backward
);
1047 bool ToggleDisplayMode::canBeRun()
1049 return myScreen
== myPlaylist
1050 || myScreen
== myBrowser
1051 || myScreen
== mySearcher
1052 || myScreen
->isActiveWindow(myPlaylistEditor
->Content
);
1055 void ToggleDisplayMode::run()
1057 if (myScreen
== myPlaylist
)
1059 switch (Config
.playlist_display_mode
)
1061 case DisplayMode::Classic
:
1062 Config
.playlist_display_mode
= DisplayMode::Columns
;
1063 myPlaylist
->main().setItemDisplayer(std::bind(
1064 Display::SongsInColumns
, ph::_1
, std::cref(myPlaylist
->main())
1066 if (Config
.titles_visibility
)
1067 myPlaylist
->main().setTitle(Display::Columns(myPlaylist
->main().getWidth()));
1069 myPlaylist
->main().setTitle("");
1071 case DisplayMode::Columns
:
1072 Config
.playlist_display_mode
= DisplayMode::Classic
;
1073 myPlaylist
->main().setItemDisplayer(std::bind(
1074 Display::Songs
, ph::_1
, std::cref(myPlaylist
->main()), std::cref(Config
.song_list_format
)
1076 myPlaylist
->main().setTitle("");
1078 Statusbar::printf("Playlist display mode: %1%", Config
.playlist_display_mode
);
1080 else if (myScreen
== myBrowser
)
1082 switch (Config
.browser_display_mode
)
1084 case DisplayMode::Classic
:
1085 Config
.browser_display_mode
= DisplayMode::Columns
;
1086 if (Config
.titles_visibility
)
1087 myBrowser
->main().setTitle(Display::Columns(myBrowser
->main().getWidth()));
1089 myBrowser
->main().setTitle("");
1091 case DisplayMode::Columns
:
1092 Config
.browser_display_mode
= DisplayMode::Classic
;
1093 myBrowser
->main().setTitle("");
1096 Statusbar::printf("Browser display mode: %1%", Config
.browser_display_mode
);
1098 else if (myScreen
== mySearcher
)
1100 switch (Config
.search_engine_display_mode
)
1102 case DisplayMode::Classic
:
1103 Config
.search_engine_display_mode
= DisplayMode::Columns
;
1105 case DisplayMode::Columns
:
1106 Config
.search_engine_display_mode
= DisplayMode::Classic
;
1109 Statusbar::printf("Search engine display mode: %1%", Config
.search_engine_display_mode
);
1110 if (mySearcher
->main().size() > SearchEngine::StaticOptions
)
1111 mySearcher
->main().setTitle(
1112 Config
.search_engine_display_mode
== DisplayMode::Columns
1113 && Config
.titles_visibility
1114 ? Display::Columns(mySearcher
->main().getWidth())
1118 else if (myScreen
->isActiveWindow(myPlaylistEditor
->Content
))
1120 switch (Config
.playlist_editor_display_mode
)
1122 case DisplayMode::Classic
:
1123 Config
.playlist_editor_display_mode
= DisplayMode::Columns
;
1124 myPlaylistEditor
->Content
.setItemDisplayer(std::bind(
1125 Display::SongsInColumns
, ph::_1
, std::cref(myPlaylistEditor
->Content
)
1128 case DisplayMode::Columns
:
1129 Config
.playlist_editor_display_mode
= DisplayMode::Classic
;
1130 myPlaylistEditor
->Content
.setItemDisplayer(std::bind(
1131 Display::Songs
, ph::_1
, std::cref(myPlaylistEditor
->Content
), std::cref(Config
.song_list_format
)
1135 Statusbar::printf("Playlist editor display mode: %1%", Config
.playlist_editor_display_mode
);
1139 bool ToggleSeparatorsBetweenAlbums::canBeRun()
1144 void ToggleSeparatorsBetweenAlbums::run()
1146 Config
.playlist_separate_albums
= !Config
.playlist_separate_albums
;
1147 Statusbar::printf("Separators between albums: %1%",
1148 Config
.playlist_separate_albums
? "on" : "off"
1152 bool ToggleLyricsUpdateOnSongChange::canBeRun()
1154 return myScreen
== myLyrics
;
1157 void ToggleLyricsUpdateOnSongChange::run()
1159 Config
.now_playing_lyrics
= !Config
.now_playing_lyrics
;
1160 Statusbar::printf("Update lyrics if song changes: %1%",
1161 Config
.now_playing_lyrics
? "on" : "off"
1165 void ToggleLyricsFetcher::run()
1167 myLyrics
->toggleFetcher();
1170 void ToggleFetchingLyricsInBackground::run()
1172 Config
.fetch_lyrics_in_background
= !Config
.fetch_lyrics_in_background
;
1173 Statusbar::printf("Fetching lyrics for playing songs in background: %1%",
1174 Config
.fetch_lyrics_in_background
? "on" : "off");
1177 void TogglePlayingSongCentering::run()
1179 Config
.autocenter_mode
= !Config
.autocenter_mode
;
1180 Statusbar::printf("Centering playing song: %1%",
1181 Config
.autocenter_mode
? "on" : "off"
1183 if (Config
.autocenter_mode
)
1185 auto s
= myPlaylist
->nowPlayingSong();
1187 myPlaylist
->locateSong(s
);
1191 void UpdateDatabase::run()
1193 if (myScreen
== myBrowser
)
1194 Mpd
.UpdateDirectory(myBrowser
->currentDirectory());
1195 # ifdef HAVE_TAGLIB_H
1196 else if (myScreen
== myTagEditor
)
1197 Mpd
.UpdateDirectory(myTagEditor
->CurrentDir());
1198 # endif // HAVE_TAGLIB_H
1200 Mpd
.UpdateDirectory("/");
1203 bool JumpToPlayingSong::canBeRun()
1205 m_song
= myPlaylist
->nowPlayingSong();
1206 return !m_song
.empty()
1207 && (myScreen
== myPlaylist
1208 || myScreen
== myPlaylistEditor
1209 || myScreen
== myBrowser
1210 || myScreen
== myLibrary
);
1213 void JumpToPlayingSong::run()
1215 if (myScreen
== myPlaylist
)
1217 myPlaylist
->locateSong(m_song
);
1219 else if (myScreen
== myPlaylistEditor
)
1221 myPlaylistEditor
->locateSong(m_song
);
1223 else if (myScreen
== myBrowser
)
1225 myBrowser
->locateSong(m_song
);
1227 else if (myScreen
== myLibrary
)
1229 myLibrary
->locateSong(m_song
);
1233 void ToggleRepeat::run()
1235 Mpd
.SetRepeat(!Status::State::repeat());
1238 bool Shuffle::canBeRun()
1240 if (myScreen
!= myPlaylist
)
1242 m_begin
= myPlaylist
->main().begin();
1243 m_end
= myPlaylist
->main().end();
1244 return findSelectedRangeAndPrintInfoIfNot(m_begin
, m_end
);
1249 if (Config
.ask_before_shuffling_playlists
)
1250 confirmAction("Do you really want to shuffle selected range?");
1251 auto begin
= myPlaylist
->main().begin();
1252 Mpd
.ShuffleRange(m_begin
-begin
, m_end
-begin
);
1253 Statusbar::print("Range shuffled");
1256 void ToggleRandom::run()
1258 Mpd
.SetRandom(!Status::State::random());
1261 bool StartSearching::canBeRun()
1263 return myScreen
== mySearcher
&& !mySearcher
->main()[0].isInactive();
1266 void StartSearching::run()
1268 mySearcher
->main().highlight(SearchEngine::SearchButton
);
1269 mySearcher
->main().setHighlighting(0);
1270 mySearcher
->main().refresh();
1271 mySearcher
->main().setHighlighting(1);
1272 mySearcher
->runAction();
1275 bool SaveTagChanges::canBeRun()
1277 # ifdef HAVE_TAGLIB_H
1278 return myScreen
== myTinyTagEditor
1279 || myScreen
->activeWindow() == myTagEditor
->TagTypes
;
1282 # endif // HAVE_TAGLIB_H
1285 void SaveTagChanges::run()
1287 # ifdef HAVE_TAGLIB_H
1288 if (myScreen
== myTinyTagEditor
)
1290 myTinyTagEditor
->main().highlight(myTinyTagEditor
->main().size()-2); // Save
1291 myTinyTagEditor
->runAction();
1293 else if (myScreen
->activeWindow() == myTagEditor
->TagTypes
)
1295 myTagEditor
->TagTypes
->highlight(myTagEditor
->TagTypes
->size()-1); // Save
1296 myTagEditor
->runAction();
1298 # endif // HAVE_TAGLIB_H
1301 void ToggleSingle::run()
1303 Mpd
.SetSingle(!Status::State::single());
1306 void ToggleConsume::run()
1308 Mpd
.SetConsume(!Status::State::consume());
1311 void ToggleCrossfade::run()
1313 Mpd
.SetCrossfade(Status::State::crossfade() ? 0 : Config
.crossfade_time
);
1316 void SetCrossfade::run()
1318 using Global::wFooter
;
1320 Statusbar::ScopedLock slock
;
1321 Statusbar::put() << "Set crossfade to: ";
1322 auto crossfade
= fromString
<unsigned>(wFooter
->prompt());
1323 lowerBoundCheck(crossfade
, 0u);
1324 Config
.crossfade_time
= crossfade
;
1325 Mpd
.SetCrossfade(crossfade
);
1328 void SetVolume::run()
1330 using Global::wFooter
;
1334 Statusbar::ScopedLock slock
;
1335 Statusbar::put() << "Set volume to: ";
1336 volume
= fromString
<unsigned>(wFooter
->prompt());
1337 boundsCheck(volume
, 0u, 100u);
1338 Mpd
.SetVolume(volume
);
1340 Statusbar::printf("Volume set to %1%%%", volume
);
1343 bool EnterDirectory::canBeRun()
1345 bool result
= false;
1346 if (myScreen
== myBrowser
&& !myBrowser
->main().empty())
1348 result
= myBrowser
->main().current()->value().type()
1349 == MPD::Item::Type::Directory
;
1351 #ifdef HAVE_TAGLIB_H
1352 else if (myScreen
->activeWindow() == myTagEditor
->Dirs
)
1354 #endif // HAVE_TAGLIB_H
1358 void EnterDirectory::run()
1360 if (myScreen
== myBrowser
)
1361 myBrowser
->enterDirectory();
1362 #ifdef HAVE_TAGLIB_H
1363 else if (myScreen
->activeWindow() == myTagEditor
->Dirs
)
1365 if (!myTagEditor
->enterDirectory())
1366 Statusbar::print("No subdirectories found");
1368 #endif // HAVE_TAGLIB_H
1371 bool EditSong::canBeRun()
1373 # ifdef HAVE_TAGLIB_H
1374 m_song
= currentSong(myScreen
);
1375 return m_song
!= nullptr && isMPDMusicDirSet();
1378 # endif // HAVE_TAGLIB_H
1381 void EditSong::run()
1383 # ifdef HAVE_TAGLIB_H
1384 myTinyTagEditor
->SetEdited(*m_song
);
1385 myTinyTagEditor
->switchTo();
1386 # endif // HAVE_TAGLIB_H
1389 bool EditLibraryTag::canBeRun()
1391 # ifdef HAVE_TAGLIB_H
1392 return myScreen
->isActiveWindow(myLibrary
->Tags
)
1393 && !myLibrary
->Tags
.empty()
1394 && isMPDMusicDirSet();
1397 # endif // HAVE_TAGLIB_H
1400 void EditLibraryTag::run()
1402 # ifdef HAVE_TAGLIB_H
1403 using Global::wFooter
;
1405 std::string new_tag
;
1407 Statusbar::ScopedLock slock
;
1408 Statusbar::put() << NC::Format::Bold
<< tagTypeToString(Config
.media_lib_primary_tag
) << NC::Format::NoBold
<< ": ";
1409 new_tag
= wFooter
->prompt(myLibrary
->Tags
.current()->value().tag());
1411 if (!new_tag
.empty() && new_tag
!= myLibrary
->Tags
.current()->value().tag())
1413 Statusbar::print("Updating tags...");
1414 Mpd
.StartSearch(true);
1415 Mpd
.AddSearch(Config
.media_lib_primary_tag
, myLibrary
->Tags
.current()->value().tag());
1416 MPD::MutableSong::SetFunction set
= tagTypeToSetFunction(Config
.media_lib_primary_tag
);
1418 bool success
= true;
1419 std::string dir_to_update
;
1420 for (MPD::SongIterator s
= Mpd
.CommitSearchSongs(), end
; s
!= end
; ++s
)
1422 MPD::MutableSong ms
= std::move(*s
);
1423 ms
.setTags(set
, new_tag
);
1424 Statusbar::printf("Updating tags in \"%1%\"...", ms
.getName());
1425 std::string path
= Config
.mpd_music_dir
+ ms
.getURI();
1426 if (!Tags::write(ms
))
1429 Statusbar::printf("Error while writing tags to \"%1%\": %2%",
1430 ms
.getName(), strerror(errno
));
1434 if (dir_to_update
.empty())
1435 dir_to_update
= ms
.getURI();
1437 dir_to_update
= getSharedDirectory(dir_to_update
, ms
.getURI());
1441 Mpd
.UpdateDirectory(dir_to_update
);
1442 Statusbar::print("Tags updated successfully");
1445 # endif // HAVE_TAGLIB_H
1448 bool EditLibraryAlbum::canBeRun()
1450 # ifdef HAVE_TAGLIB_H
1451 return myScreen
->isActiveWindow(myLibrary
->Albums
)
1452 && !myLibrary
->Albums
.empty()
1453 && isMPDMusicDirSet();
1456 # endif // HAVE_TAGLIB_H
1459 void EditLibraryAlbum::run()
1461 # ifdef HAVE_TAGLIB_H
1462 using Global::wFooter
;
1463 // FIXME: merge this and EditLibraryTag. also, prompt on failure if user wants to continue
1464 std::string new_album
;
1466 Statusbar::ScopedLock slock
;
1467 Statusbar::put() << NC::Format::Bold
<< "Album: " << NC::Format::NoBold
;
1468 new_album
= wFooter
->prompt(myLibrary
->Albums
.current()->value().entry().album());
1470 if (!new_album
.empty() && new_album
!= myLibrary
->Albums
.current()->value().entry().album())
1473 Statusbar::print("Updating tags...");
1474 for (size_t i
= 0; i
< myLibrary
->Songs
.size(); ++i
)
1476 Statusbar::printf("Updating tags in \"%1%\"...", myLibrary
->Songs
[i
].value().getName());
1477 std::string path
= Config
.mpd_music_dir
+ myLibrary
->Songs
[i
].value().getURI();
1478 TagLib::FileRef
f(path
.c_str());
1481 const char msg
[] = "Error while opening file \"%1%\"";
1482 Statusbar::printf(msg
, wideShorten(myLibrary
->Songs
[i
].value().getURI(), COLS
-const_strlen(msg
)));
1486 f
.tag()->setAlbum(ToWString(new_album
));
1489 const char msg
[] = "Error while writing tags in \"%1%\"";
1490 Statusbar::printf(msg
, wideShorten(myLibrary
->Songs
[i
].value().getURI(), COLS
-const_strlen(msg
)));
1497 Mpd
.UpdateDirectory(getSharedDirectory(myLibrary
->Songs
.beginV(), myLibrary
->Songs
.endV()));
1498 Statusbar::print("Tags updated successfully");
1501 # endif // HAVE_TAGLIB_H
1504 bool EditDirectoryName::canBeRun()
1506 return ((myScreen
== myBrowser
1507 && !myBrowser
->main().empty()
1508 && myBrowser
->main().current()->value().type() == MPD::Item::Type::Directory
)
1509 # ifdef HAVE_TAGLIB_H
1510 || (myScreen
->activeWindow() == myTagEditor
->Dirs
1511 && !myTagEditor
->Dirs
->empty()
1512 && myTagEditor
->Dirs
->choice() > 0)
1513 # endif // HAVE_TAGLIB_H
1514 ) && isMPDMusicDirSet();
1517 void EditDirectoryName::run()
1519 using Global::wFooter
;
1520 if (myScreen
== myBrowser
)
1522 std::string old_dir
= myBrowser
->main().current()->value().directory().path();
1523 std::string new_dir
;
1525 Statusbar::ScopedLock slock
;
1526 Statusbar::put() << NC::Format::Bold
<< "Directory: " << NC::Format::NoBold
;
1527 new_dir
= wFooter
->prompt(old_dir
);
1529 if (!new_dir
.empty() && new_dir
!= old_dir
)
1531 std::string full_old_dir
;
1532 if (!myBrowser
->isLocal())
1533 full_old_dir
+= Config
.mpd_music_dir
;
1534 full_old_dir
+= old_dir
;
1535 std::string full_new_dir
;
1536 if (!myBrowser
->isLocal())
1537 full_new_dir
+= Config
.mpd_music_dir
;
1538 full_new_dir
+= new_dir
;
1539 boost::filesystem::rename(full_old_dir
, full_new_dir
);
1540 const char msg
[] = "Directory renamed to \"%1%\"";
1541 Statusbar::printf(msg
, wideShorten(new_dir
, COLS
-const_strlen(msg
)));
1542 if (!myBrowser
->isLocal())
1543 Mpd
.UpdateDirectory(getSharedDirectory(old_dir
, new_dir
));
1544 myBrowser
->requestUpdate();
1547 # ifdef HAVE_TAGLIB_H
1548 else if (myScreen
->activeWindow() == myTagEditor
->Dirs
)
1550 std::string old_dir
= myTagEditor
->Dirs
->current()->value().first
, new_dir
;
1552 Statusbar::ScopedLock slock
;
1553 Statusbar::put() << NC::Format::Bold
<< "Directory: " << NC::Format::NoBold
;
1554 new_dir
= wFooter
->prompt(old_dir
);
1556 if (!new_dir
.empty() && new_dir
!= old_dir
)
1558 std::string full_old_dir
= Config
.mpd_music_dir
+ myTagEditor
->CurrentDir() + "/" + old_dir
;
1559 std::string full_new_dir
= Config
.mpd_music_dir
+ myTagEditor
->CurrentDir() + "/" + new_dir
;
1560 if (rename(full_old_dir
.c_str(), full_new_dir
.c_str()) == 0)
1562 const char msg
[] = "Directory renamed to \"%1%\"";
1563 Statusbar::printf(msg
, wideShorten(new_dir
, COLS
-const_strlen(msg
)));
1564 Mpd
.UpdateDirectory(myTagEditor
->CurrentDir());
1568 const char msg
[] = "Couldn't rename \"%1%\": %2%";
1569 Statusbar::printf(msg
, wideShorten(old_dir
, COLS
-const_strlen(msg
)-25), strerror(errno
));
1573 # endif // HAVE_TAGLIB_H
1576 bool EditPlaylistName::canBeRun()
1578 return (myScreen
->isActiveWindow(myPlaylistEditor
->Playlists
)
1579 && !myPlaylistEditor
->Playlists
.empty())
1580 || (myScreen
== myBrowser
1581 && !myBrowser
->main().empty()
1582 && myBrowser
->main().current()->value().type() == MPD::Item::Type::Playlist
);
1585 void EditPlaylistName::run()
1587 using Global::wFooter
;
1588 std::string old_name
, new_name
;
1589 if (myScreen
->isActiveWindow(myPlaylistEditor
->Playlists
))
1590 old_name
= myPlaylistEditor
->Playlists
.current()->value().path();
1592 old_name
= myBrowser
->main().current()->value().playlist().path();
1594 Statusbar::ScopedLock slock
;
1595 Statusbar::put() << NC::Format::Bold
<< "Playlist: " << NC::Format::NoBold
;
1596 new_name
= wFooter
->prompt(old_name
);
1598 if (!new_name
.empty() && new_name
!= old_name
)
1600 Mpd
.Rename(old_name
, new_name
);
1601 const char msg
[] = "Playlist renamed to \"%1%\"";
1602 Statusbar::printf(msg
, wideShorten(new_name
, COLS
-const_strlen(msg
)));
1606 bool EditLyrics::canBeRun()
1608 return myScreen
== myLyrics
;
1611 void EditLyrics::run()
1616 bool JumpToBrowser::canBeRun()
1618 m_song
= currentSong(myScreen
);
1619 return m_song
!= nullptr;
1622 void JumpToBrowser::run()
1624 myBrowser
->locateSong(*m_song
);
1627 bool JumpToMediaLibrary::canBeRun()
1629 m_song
= currentSong(myScreen
);
1630 return m_song
!= nullptr;
1633 void JumpToMediaLibrary::run()
1635 myLibrary
->locateSong(*m_song
);
1638 bool JumpToPlaylistEditor::canBeRun()
1640 return myScreen
== myBrowser
1641 && myBrowser
->main().current()->value().type() == MPD::Item::Type::Playlist
;
1644 void JumpToPlaylistEditor::run()
1646 myPlaylistEditor
->locatePlaylist(myBrowser
->main().current()->value().playlist());
1649 void ToggleScreenLock::run()
1651 using Global::wFooter
;
1652 using Global::myLockedScreen
;
1653 const char *msg_unlockable_screen
= "Current screen can't be locked";
1654 if (myLockedScreen
!= nullptr)
1656 BaseScreen::unlock();
1657 Actions::setResizeFlags();
1659 Statusbar::print("Screen unlocked");
1661 else if (!myScreen
->isLockable())
1663 Statusbar::print(msg_unlockable_screen
);
1667 unsigned part
= Config
.locked_screen_width_part
*100;
1668 if (Config
.ask_for_locked_screen_width_part
)
1670 Statusbar::ScopedLock slock
;
1671 Statusbar::put() << "% of the locked screen's width to be reserved (20-80): ";
1672 part
= fromString
<unsigned>(wFooter
->prompt(boost::lexical_cast
<std::string
>(part
)));
1674 boundsCheck(part
, 20u, 80u);
1675 Config
.locked_screen_width_part
= part
/100.0;
1676 if (myScreen
->lock())
1677 Statusbar::printf("Screen locked (with %1%%% width)", part
);
1679 Statusbar::print(msg_unlockable_screen
);
1683 bool JumpToTagEditor::canBeRun()
1685 # ifdef HAVE_TAGLIB_H
1686 m_song
= currentSong(myScreen
);
1687 return m_song
!= nullptr && isMPDMusicDirSet();
1690 # endif // HAVE_TAGLIB_H
1693 void JumpToTagEditor::run()
1695 # ifdef HAVE_TAGLIB_H
1696 myTagEditor
->LocateSong(*m_song
);
1697 # endif // HAVE_TAGLIB_H
1700 bool JumpToPositionInSong::canBeRun()
1702 return Status::State::player() != MPD::psStop
&& Status::State::totalTime() > 0;
1705 void JumpToPositionInSong::run()
1707 using Global::wFooter
;
1709 const MPD::Song s
= myPlaylist
->nowPlayingSong();
1713 Statusbar::ScopedLock slock
;
1714 Statusbar::put() << "Position to go (in %/m:ss/seconds(s)): ";
1715 spos
= wFooter
->prompt();
1720 if (boost::regex_match(spos
, what
, rx
.assign("([0-9]+):([0-9]{2})"))) // mm:ss
1722 auto mins
= fromString
<unsigned>(what
[1]);
1723 auto secs
= fromString
<unsigned>(what
[2]);
1724 boundsCheck(secs
, 0u, 60u);
1725 Mpd
.Seek(s
.getPosition(), mins
* 60 + secs
);
1727 else if (boost::regex_match(spos
, what
, rx
.assign("([0-9]+)s"))) // position in seconds
1729 auto secs
= fromString
<unsigned>(what
[1]);
1730 Mpd
.Seek(s
.getPosition(), secs
);
1732 else if (boost::regex_match(spos
, what
, rx
.assign("([0-9]+)[%]{0,1}"))) // position in %
1734 auto percent
= fromString
<unsigned>(what
[1]);
1735 boundsCheck(percent
, 0u, 100u);
1736 int secs
= (percent
* s
.getDuration()) / 100.0;
1737 Mpd
.Seek(s
.getPosition(), secs
);
1740 Statusbar::print("Invalid format ([m]:[ss], [s]s, [%]%, [%] accepted)");
1743 bool SelectItem::canBeRun()
1745 m_list
= dynamic_cast<NC::List
*>(myScreen
->activeWindow());
1746 return m_list
!= nullptr
1748 && m_list
->currentP()->isSelectable();
1751 void SelectItem::run()
1753 auto current
= m_list
->currentP();
1754 current
->setSelected(!current
->isSelected());
1757 bool SelectRange::canBeRun()
1759 m_list
= dynamic_cast<NC::List
*>(myScreen
->activeWindow());
1760 if (m_list
== nullptr)
1762 m_begin
= m_list
->beginP();
1763 m_end
= m_list
->endP();
1764 return findRange(m_begin
, m_end
);
1767 void SelectRange::run()
1769 for (; m_begin
!= m_end
; ++m_begin
)
1770 m_begin
->setSelected(true);
1771 Statusbar::print("Range selected");
1774 bool ReverseSelection::canBeRun()
1776 m_list
= dynamic_cast<NC::List
*>(myScreen
->activeWindow());
1777 return m_list
!= nullptr;
1780 void ReverseSelection::run()
1782 for (auto &p
: *m_list
)
1783 p
.setSelected(!p
.isSelected());
1784 Statusbar::print("Selection reversed");
1787 bool RemoveSelection::canBeRun()
1789 m_list
= dynamic_cast<NC::List
*>(myScreen
->activeWindow());
1790 return m_list
!= nullptr;
1793 void RemoveSelection::run()
1795 for (auto &p
: *m_list
)
1796 p
.setSelected(false);
1797 Statusbar::print("Selection removed");
1800 bool SelectAlbum::canBeRun()
1802 auto *w
= myScreen
->activeWindow();
1803 if (m_list
!= static_cast<void *>(w
))
1804 m_list
= dynamic_cast<NC::List
*>(w
);
1805 if (m_songs
!= static_cast<void *>(w
))
1806 m_songs
= dynamic_cast<SongList
*>(w
);
1807 return m_list
!= nullptr && !m_list
->empty()
1808 && m_songs
!= nullptr;
1811 void SelectAlbum::run()
1813 const auto front
= m_songs
->beginS(), current
= m_songs
->currentS(), end
= m_songs
->endS();
1814 if (current
->song() == nullptr)
1816 auto get
= &MPD::Song::getAlbum
;
1817 const std::string tag
= current
->song()->getTags(get
);
1819 for (auto it
= current
; it
!= front
;)
1822 if (it
->song() == nullptr || it
->song()->getTags(get
) != tag
)
1824 it
->properties().setSelected(true);
1827 for (auto it
= current
;;)
1829 it
->properties().setSelected(true);
1832 if (it
->song() == nullptr || it
->song()->getTags(get
) != tag
)
1835 Statusbar::print("Album around cursor position selected");
1838 bool SelectFoundItems::canBeRun()
1840 m_list
= dynamic_cast<NC::List
*>(myScreen
->activeWindow());
1841 if (m_list
== nullptr || m_list
->empty())
1843 m_searchable
= dynamic_cast<Searchable
*>(myScreen
);
1844 return m_searchable
!= nullptr && m_searchable
->allowsSearching();
1847 void SelectFoundItems::run()
1849 auto current_pos
= m_list
->choice();
1850 myScreen
->activeWindow()->scroll(NC::Scroll::Home
);
1851 bool found
= m_searchable
->search(SearchDirection::Forward
, false, false);
1854 Statusbar::print("Searching for items...");
1855 m_list
->currentP()->setSelected(true);
1856 while (m_searchable
->search(SearchDirection::Forward
, false, true))
1857 m_list
->currentP()->setSelected(true);
1858 Statusbar::print("Found items selected");
1860 m_list
->highlight(current_pos
);
1863 bool AddSelectedItems::canBeRun()
1865 return myScreen
!= mySelectedItemsAdder
;
1868 void AddSelectedItems::run()
1870 mySelectedItemsAdder
->switchTo();
1873 void CropMainPlaylist::run()
1875 auto &w
= myPlaylist
->main();
1876 // cropping doesn't make sense in this case
1879 if (Config
.ask_before_clearing_playlists
)
1880 confirmAction("Do you really want to crop main playlist?");
1881 Statusbar::print("Cropping playlist...");
1882 selectCurrentIfNoneSelected(w
);
1883 cropPlaylist(w
, std::bind(&MPD::Connection::Delete
, ph::_1
, ph::_2
));
1884 Statusbar::print("Playlist cropped");
1887 bool CropPlaylist::canBeRun()
1889 return myScreen
== myPlaylistEditor
;
1892 void CropPlaylist::run()
1894 auto &w
= myPlaylistEditor
->Content
;
1895 // cropping doesn't make sense in this case
1898 assert(!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 crop playlist \"%1%\"?") % playlist
);
1902 selectCurrentIfNoneSelected(w
);
1903 Statusbar::printf("Cropping playlist \"%1%\"...", playlist
);
1904 cropPlaylist(w
, std::bind(&MPD::Connection::PlaylistDelete
, ph::_1
, playlist
, ph::_2
));
1905 Statusbar::printf("Playlist \"%1%\" cropped", playlist
);
1908 void ClearMainPlaylist::run()
1910 if (!myPlaylist
->main().empty() && Config
.ask_before_clearing_playlists
)
1911 confirmAction("Do you really want to clear main playlist?");
1912 Mpd
.ClearMainPlaylist();
1913 Statusbar::print("Playlist cleared");
1914 myPlaylist
->main().reset();
1917 bool ClearPlaylist::canBeRun()
1919 return myScreen
== myPlaylistEditor
;
1922 void ClearPlaylist::run()
1924 if (myPlaylistEditor
->Playlists
.empty())
1926 std::string playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
1927 if (Config
.ask_before_clearing_playlists
)
1928 confirmAction(boost::format("Do you really want to clear playlist \"%1%\"?") % playlist
);
1929 Mpd
.ClearPlaylist(playlist
);
1930 Statusbar::printf("Playlist \"%1%\" cleared", playlist
);
1933 bool SortPlaylist::canBeRun()
1935 if (myScreen
!= myPlaylist
)
1937 auto first
= myPlaylist
->main().begin(), last
= myPlaylist
->main().end();
1938 return findSelectedRangeAndPrintInfoIfNot(first
, last
);
1941 void SortPlaylist::run()
1943 mySortPlaylistDialog
->switchTo();
1946 bool ReversePlaylist::canBeRun()
1948 if (myScreen
!= myPlaylist
)
1950 m_begin
= myPlaylist
->main().begin();
1951 m_end
= myPlaylist
->main().end();
1952 return findSelectedRangeAndPrintInfoIfNot(m_begin
, m_end
);
1955 void ReversePlaylist::run()
1957 Statusbar::print("Reversing range...");
1958 Mpd
.StartCommandsList();
1959 for (--m_end
; m_begin
< m_end
; ++m_begin
, --m_end
)
1960 Mpd
.Swap(m_begin
->value().getPosition(), m_end
->value().getPosition());
1961 Mpd
.CommitCommandsList();
1962 Statusbar::print("Range reversed");
1965 bool ApplyFilter::canBeRun()
1967 m_filterable
= dynamic_cast<Filterable
*>(myScreen
);
1968 return m_filterable
!= nullptr
1969 && m_filterable
->allowsFiltering();
1972 void ApplyFilter::run()
1974 using Global::wFooter
;
1976 std::string filter
= m_filterable
->currentFilter();
1977 if (!filter
.empty())
1979 m_filterable
->applyFilter(filter
);
1980 myScreen
->refreshWindow();
1985 Statusbar::ScopedLock slock
;
1986 NC::Window::ScopedPromptHook
helper(
1988 Statusbar::Helpers::ApplyFilterImmediately(m_filterable
));
1989 Statusbar::put() << "Apply filter: ";
1990 filter
= wFooter
->prompt(filter
);
1992 catch (NC::PromptAborted
&)
1994 m_filterable
->applyFilter(filter
);
1999 Statusbar::printf("Filtering disabled");
2001 Statusbar::printf("Using filter \"%1%\"", filter
);
2003 if (myScreen
== myPlaylist
)
2004 myPlaylist
->reloadTotalLength();
2006 listsChangeFinisher();
2009 bool Find::canBeRun()
2011 return myScreen
== myHelp
2012 || myScreen
== myLyrics
2013 || myScreen
== myLastfm
;
2018 using Global::wFooter
;
2022 Statusbar::ScopedLock slock
;
2023 Statusbar::put() << "Find: ";
2024 token
= wFooter
->prompt();
2027 Statusbar::print("Searching...");
2028 auto s
= static_cast<Screen
<NC::Scrollpad
> *>(myScreen
);
2029 s
->main().removeProperties();
2030 if (token
.empty() || s
->main().setProperties(NC::Format::Reverse
, token
, NC::Format::NoReverse
, Config
.regex_type
))
2031 Statusbar::print("Done");
2033 Statusbar::print("No matching patterns found");
2037 bool FindItemBackward::canBeRun()
2039 auto w
= dynamic_cast<Searchable
*>(myScreen
);
2040 return w
&& w
->allowsSearching();
2043 void FindItemForward::run()
2045 findItem(SearchDirection::Forward
);
2046 listsChangeFinisher();
2049 bool FindItemForward::canBeRun()
2051 auto w
= dynamic_cast<Searchable
*>(myScreen
);
2052 return w
&& w
->allowsSearching();
2055 void FindItemBackward::run()
2057 findItem(SearchDirection::Backward
);
2058 listsChangeFinisher();
2061 bool NextFoundItem::canBeRun()
2063 return dynamic_cast<Searchable
*>(myScreen
);
2066 void NextFoundItem::run()
2068 Searchable
*w
= dynamic_cast<Searchable
*>(myScreen
);
2069 assert(w
!= nullptr);
2070 w
->search(SearchDirection::Forward
, Config
.wrapped_search
, true);
2071 listsChangeFinisher();
2074 bool PreviousFoundItem::canBeRun()
2076 return dynamic_cast<Searchable
*>(myScreen
);
2079 void PreviousFoundItem::run()
2081 Searchable
*w
= dynamic_cast<Searchable
*>(myScreen
);
2082 assert(w
!= nullptr);
2083 w
->search(SearchDirection::Backward
, Config
.wrapped_search
, true);
2084 listsChangeFinisher();
2087 void ToggleFindMode::run()
2089 Config
.wrapped_search
= !Config
.wrapped_search
;
2090 Statusbar::printf("Search mode: %1%",
2091 Config
.wrapped_search
? "Wrapped" : "Normal"
2095 void ToggleReplayGainMode::run()
2097 using Global::wFooter
;
2101 Statusbar::ScopedLock slock
;
2102 Statusbar::put() << "Replay gain mode? "
2103 << "[" << NC::Format::Bold
<< 'o' << NC::Format::NoBold
<< "ff"
2104 << "/" << NC::Format::Bold
<< 't' << NC::Format::NoBold
<< "rack"
2105 << "/" << NC::Format::Bold
<< 'a' << NC::Format::NoBold
<< "lbum"
2107 rgm
= Statusbar::Helpers::promptReturnOneOf({"t", "a", "o"})[0];
2112 Mpd
.SetReplayGainMode(MPD::rgmTrack
);
2115 Mpd
.SetReplayGainMode(MPD::rgmAlbum
);
2118 Mpd
.SetReplayGainMode(MPD::rgmOff
);
2120 default: // impossible
2121 throw std::runtime_error(
2122 (boost::format("ToggleReplayGainMode: impossible case reached: %1%") % rgm
).str()
2125 Statusbar::printf("Replay gain mode: %1%", Mpd
.GetReplayGainMode());
2128 void ToggleAddMode::run()
2130 std::string mode_desc
;
2131 switch (Config
.space_add_mode
)
2133 case SpaceAddMode::AddRemove
:
2134 Config
.space_add_mode
= SpaceAddMode::AlwaysAdd
;
2135 mode_desc
= "always add an item to playlist";
2137 case SpaceAddMode::AlwaysAdd
:
2138 Config
.space_add_mode
= SpaceAddMode::AddRemove
;
2139 mode_desc
= "add an item to playlist or remove if already added";
2142 Statusbar::printf("Add mode: %1%", mode_desc
);
2145 void ToggleMouse::run()
2147 Config
.mouse_support
= !Config
.mouse_support
;
2148 if (Config
.mouse_support
)
2149 NC::Mouse::enable();
2151 NC::Mouse::disable();
2152 Statusbar::printf("Mouse support %1%",
2153 Config
.mouse_support
? "enabled" : "disabled"
2157 void ToggleBitrateVisibility::run()
2159 Config
.display_bitrate
= !Config
.display_bitrate
;
2160 Statusbar::printf("Bitrate visibility %1%",
2161 Config
.display_bitrate
? "enabled" : "disabled"
2165 void AddRandomItems::run()
2167 using Global::wFooter
;
2170 Statusbar::ScopedLock slock
;
2171 Statusbar::put() << "Add random? "
2172 << "[" << NC::Format::Bold
<< 's' << NC::Format::NoBold
<< "ongs"
2173 << "/" << NC::Format::Bold
<< 'a' << NC::Format::NoBold
<< "rtists"
2174 << "/" << "album" << NC::Format::Bold
<< 'A' << NC::Format::NoBold
<< "rtists"
2175 << "/" << "al" << NC::Format::Bold
<< 'b' << NC::Format::NoBold
<< "ums"
2177 rnd_type
= Statusbar::Helpers::promptReturnOneOf({"s", "a", "A", "b"})[0];
2180 mpd_tag_type tag_type
= MPD_TAG_ARTIST
;
2181 std::string tag_type_str
;
2182 if (rnd_type
!= 's')
2184 tag_type
= charToTagType(rnd_type
);
2185 tag_type_str
= boost::locale::to_lower(tagTypeToString(tag_type
));
2188 tag_type_str
= "song";
2192 Statusbar::ScopedLock slock
;
2193 Statusbar::put() << "Number of random " << tag_type_str
<< "s: ";
2194 number
= fromString
<unsigned>(wFooter
->prompt());
2199 if (rnd_type
== 's')
2200 success
= Mpd
.AddRandomSongs(number
, Global::RNG
);
2202 success
= Mpd
.AddRandomTag(tag_type
, number
, Global::RNG
);
2204 Statusbar::printf("%1% random %2%%3% added to playlist", number
, tag_type_str
, number
== 1 ? "" : "s");
2208 bool ToggleBrowserSortMode::canBeRun()
2210 return myScreen
== myBrowser
;
2213 void ToggleBrowserSortMode::run()
2215 switch (Config
.browser_sort_mode
)
2217 case SortMode::Name
:
2218 Config
.browser_sort_mode
= SortMode::ModificationTime
;
2219 Statusbar::print("Sort songs by: modification time");
2221 case SortMode::ModificationTime
:
2222 Config
.browser_sort_mode
= SortMode::CustomFormat
;
2223 Statusbar::print("Sort songs by: custom format");
2225 case SortMode::CustomFormat
:
2226 Config
.browser_sort_mode
= SortMode::NoOp
;
2227 Statusbar::print("Do not sort songs");
2229 case SortMode::NoOp
:
2230 Config
.browser_sort_mode
= SortMode::Name
;
2231 Statusbar::print("Sort songs by: name");
2233 if (Config
.browser_sort_mode
!= SortMode::NoOp
)
2235 size_t sort_offset
= myBrowser
->inRootDirectory() ? 0 : 1;
2236 std::sort(myBrowser
->main().begin()+sort_offset
, myBrowser
->main().end(),
2237 LocaleBasedItemSorting(std::locale(), Config
.ignore_leading_the
, Config
.browser_sort_mode
)
2242 bool ToggleLibraryTagType::canBeRun()
2244 return (myScreen
->isActiveWindow(myLibrary
->Tags
))
2245 || (myLibrary
->columns() == 2 && myScreen
->isActiveWindow(myLibrary
->Albums
));
2248 void ToggleLibraryTagType::run()
2250 using Global::wFooter
;
2254 Statusbar::ScopedLock slock
;
2255 Statusbar::put() << "Tag type? "
2256 << "[" << NC::Format::Bold
<< 'a' << NC::Format::NoBold
<< "rtist"
2257 << "/" << "album" << NC::Format::Bold
<< 'A' << NC::Format::NoBold
<< "rtist"
2258 << "/" << NC::Format::Bold
<< 'y' << NC::Format::NoBold
<< "ear"
2259 << "/" << NC::Format::Bold
<< 'g' << NC::Format::NoBold
<< "enre"
2260 << "/" << NC::Format::Bold
<< 'c' << NC::Format::NoBold
<< "omposer"
2261 << "/" << NC::Format::Bold
<< 'p' << NC::Format::NoBold
<< "erformer"
2263 tag_type
= Statusbar::Helpers::promptReturnOneOf({"a", "A", "y", "g", "c", "p"})[0];
2265 mpd_tag_type new_tagitem
= charToTagType(tag_type
);
2266 if (new_tagitem
!= Config
.media_lib_primary_tag
)
2268 Config
.media_lib_primary_tag
= new_tagitem
;
2269 std::string item_type
= tagTypeToString(Config
.media_lib_primary_tag
);
2270 myLibrary
->Tags
.setTitle(Config
.titles_visibility
? item_type
+ "s" : "");
2271 myLibrary
->Tags
.reset();
2272 item_type
= boost::locale::to_lower(item_type
);
2273 std::string and_mtime
= Config
.media_library_sort_by_mtime
?
2276 if (myLibrary
->columns() == 2)
2278 myLibrary
->Songs
.clear();
2279 myLibrary
->Albums
.reset();
2280 myLibrary
->Albums
.clear();
2281 myLibrary
->Albums
.setTitle(Config
.titles_visibility
? "Albums (sorted by " + item_type
+ and_mtime
+ ")" : "");
2282 myLibrary
->Albums
.display();
2286 myLibrary
->Tags
.clear();
2287 myLibrary
->Tags
.display();
2289 Statusbar::printf("Switched to the list of %1%s", item_type
);
2293 bool ToggleMediaLibrarySortMode::canBeRun()
2295 return myScreen
== myLibrary
;
2298 void ToggleMediaLibrarySortMode::run()
2300 myLibrary
->toggleSortMode();
2303 bool FetchLyricsInBackground::canBeRun()
2305 m_hs
= dynamic_cast<HasSongs
*>(myScreen
);
2306 return m_hs
!= nullptr && m_hs
->itemAvailable();
2309 void FetchLyricsInBackground::run()
2311 auto songs
= m_hs
->getSelectedSongs();
2312 for (const auto &s
: songs
)
2313 myLyrics
->fetchInBackground(s
, true);
2314 Statusbar::print("Selected songs queued for lyrics fetching");
2317 bool RefetchLyrics::canBeRun()
2319 return myScreen
== myLyrics
;
2322 void RefetchLyrics::run()
2324 myLyrics
->refetchCurrent();
2327 bool SetSelectedItemsPriority::canBeRun()
2329 if (Mpd
.Version() < 17)
2331 Statusbar::print("Priorities are supported in MPD >= 0.17.0");
2334 return myScreen
== myPlaylist
&& !myPlaylist
->main().empty();
2337 void SetSelectedItemsPriority::run()
2339 using Global::wFooter
;
2343 Statusbar::ScopedLock slock
;
2344 Statusbar::put() << "Set priority [0-255]: ";
2345 prio
= fromString
<unsigned>(wFooter
->prompt());
2346 boundsCheck(prio
, 0u, 255u);
2348 myPlaylist
->setSelectedItemsPriority(prio
);
2351 bool ToggleOutput::canBeRun()
2353 #ifdef ENABLE_OUTPUTS
2354 return myScreen
== myOutputs
;
2357 #endif // ENABLE_OUTPUTS
2360 void ToggleOutput::run()
2362 #ifdef ENABLE_OUTPUTS
2363 myOutputs
->toggleOutput();
2364 #endif // ENABLE_OUTPUTS
2367 bool ToggleVisualizationType::canBeRun()
2369 # ifdef ENABLE_VISUALIZER
2370 return myScreen
== myVisualizer
;
2373 # endif // ENABLE_VISUALIZER
2376 void ToggleVisualizationType::run()
2378 # ifdef ENABLE_VISUALIZER
2379 myVisualizer
->ToggleVisualizationType();
2380 # endif // ENABLE_VISUALIZER
2383 void ShowSongInfo::run()
2385 mySongInfo
->switchTo();
2388 bool ShowArtistInfo::canBeRun()
2390 return myScreen
== myLastfm
2391 || (myScreen
->isActiveWindow(myLibrary
->Tags
)
2392 && !myLibrary
->Tags
.empty()
2393 && Config
.media_lib_primary_tag
== MPD_TAG_ARTIST
)
2394 || currentSong(myScreen
);
2397 void ShowArtistInfo::run()
2399 if (myScreen
== myLastfm
)
2401 myLastfm
->switchTo();
2406 if (myScreen
->isActiveWindow(myLibrary
->Tags
))
2408 assert(!myLibrary
->Tags
.empty());
2409 assert(Config
.media_lib_primary_tag
== MPD_TAG_ARTIST
);
2410 artist
= myLibrary
->Tags
.current()->value().tag();
2414 auto s
= currentSong(myScreen
);
2416 artist
= s
->getArtist();
2419 if (!artist
.empty())
2421 myLastfm
->queueJob(new LastFm::ArtistInfo(artist
, Config
.lastfm_preferred_language
));
2422 if (!isVisible(myLastfm
))
2423 myLastfm
->switchTo();
2427 bool ShowLyrics::canBeRun()
2429 if (myScreen
== myLyrics
)
2436 m_song
= currentSong(myScreen
);
2437 return m_song
!= nullptr;
2441 void ShowLyrics::run()
2443 if (m_song
!= nullptr)
2444 myLyrics
->fetch(*m_song
);
2445 if (myScreen
== myLyrics
|| !isVisible(myLyrics
))
2446 myLyrics
->switchTo();
2451 ExitMainLoop
= true;
2454 void NextScreen::run()
2456 if (Config
.screen_switcher_previous
)
2458 if (auto tababble
= dynamic_cast<Tabbable
*>(myScreen
))
2459 tababble
->switchToPreviousScreen();
2461 else if (!Config
.screen_sequence
.empty())
2463 auto screen
= nextScreenTypeInSequence(
2464 Config
.screen_sequence
.begin(),
2465 Config
.screen_sequence
.end(),
2467 toScreen(*screen
)->switchTo();
2471 void PreviousScreen::run()
2473 if (Config
.screen_switcher_previous
)
2475 if (auto tababble
= dynamic_cast<Tabbable
*>(myScreen
))
2476 tababble
->switchToPreviousScreen();
2478 else if (!Config
.screen_sequence
.empty())
2480 auto screen
= nextScreenTypeInSequence(
2481 Config
.screen_sequence
.rbegin(),
2482 Config
.screen_sequence
.rend(),
2484 toScreen(*screen
)->switchTo();
2488 bool ShowHelp::canBeRun()
2490 return myScreen
!= myHelp
2491 # ifdef HAVE_TAGLIB_H
2492 && myScreen
!= myTinyTagEditor
2493 # endif // HAVE_TAGLIB_H
2497 void ShowHelp::run()
2502 bool ShowPlaylist::canBeRun()
2504 return myScreen
!= myPlaylist
2505 # ifdef HAVE_TAGLIB_H
2506 && myScreen
!= myTinyTagEditor
2507 # endif // HAVE_TAGLIB_H
2511 void ShowPlaylist::run()
2513 myPlaylist
->switchTo();
2516 bool ShowBrowser::canBeRun()
2518 return myScreen
!= myBrowser
2519 # ifdef HAVE_TAGLIB_H
2520 && myScreen
!= myTinyTagEditor
2521 # endif // HAVE_TAGLIB_H
2525 void ShowBrowser::run()
2527 myBrowser
->switchTo();
2530 bool ChangeBrowseMode::canBeRun()
2532 return myScreen
== myBrowser
;
2535 void ChangeBrowseMode::run()
2537 myBrowser
->changeBrowseMode();
2540 bool ShowSearchEngine::canBeRun()
2542 return myScreen
!= mySearcher
2543 # ifdef HAVE_TAGLIB_H
2544 && myScreen
!= myTinyTagEditor
2545 # endif // HAVE_TAGLIB_H
2549 void ShowSearchEngine::run()
2551 mySearcher
->switchTo();
2554 bool ResetSearchEngine::canBeRun()
2556 return myScreen
== mySearcher
;
2559 void ResetSearchEngine::run()
2561 mySearcher
->reset();
2564 bool ShowMediaLibrary::canBeRun()
2566 return myScreen
!= myLibrary
2567 # ifdef HAVE_TAGLIB_H
2568 && myScreen
!= myTinyTagEditor
2569 # endif // HAVE_TAGLIB_H
2573 void ShowMediaLibrary::run()
2575 myLibrary
->switchTo();
2578 bool ToggleMediaLibraryColumnsMode::canBeRun()
2580 return myScreen
== myLibrary
;
2583 void ToggleMediaLibraryColumnsMode::run()
2585 myLibrary
->toggleColumnsMode();
2586 myLibrary
->refresh();
2589 bool ShowPlaylistEditor::canBeRun()
2591 return myScreen
!= myPlaylistEditor
2592 # ifdef HAVE_TAGLIB_H
2593 && myScreen
!= myTinyTagEditor
2594 # endif // HAVE_TAGLIB_H
2598 void ShowPlaylistEditor::run()
2600 myPlaylistEditor
->switchTo();
2603 bool ShowTagEditor::canBeRun()
2605 # ifdef HAVE_TAGLIB_H
2606 return myScreen
!= myTagEditor
2607 && myScreen
!= myTinyTagEditor
;
2610 # endif // HAVE_TAGLIB_H
2613 void ShowTagEditor::run()
2615 # ifdef HAVE_TAGLIB_H
2616 if (isMPDMusicDirSet())
2617 myTagEditor
->switchTo();
2618 # endif // HAVE_TAGLIB_H
2621 bool ShowOutputs::canBeRun()
2623 # ifdef ENABLE_OUTPUTS
2624 return myScreen
!= myOutputs
2625 # ifdef HAVE_TAGLIB_H
2626 && myScreen
!= myTinyTagEditor
2627 # endif // HAVE_TAGLIB_H
2631 # endif // ENABLE_OUTPUTS
2634 void ShowOutputs::run()
2636 # ifdef ENABLE_OUTPUTS
2637 myOutputs
->switchTo();
2638 # endif // ENABLE_OUTPUTS
2641 bool ShowVisualizer::canBeRun()
2643 # ifdef ENABLE_VISUALIZER
2644 return myScreen
!= myVisualizer
2645 # ifdef HAVE_TAGLIB_H
2646 && myScreen
!= myTinyTagEditor
2647 # endif // HAVE_TAGLIB_H
2651 # endif // ENABLE_VISUALIZER
2654 void ShowVisualizer::run()
2656 # ifdef ENABLE_VISUALIZER
2657 myVisualizer
->switchTo();
2658 # endif // ENABLE_VISUALIZER
2661 bool ShowClock::canBeRun()
2663 # ifdef ENABLE_CLOCK
2664 return myScreen
!= myClock
2665 # ifdef HAVE_TAGLIB_H
2666 && myScreen
!= myTinyTagEditor
2667 # endif // HAVE_TAGLIB_H
2671 # endif // ENABLE_CLOCK
2674 void ShowClock::run()
2676 # ifdef ENABLE_CLOCK
2677 myClock
->switchTo();
2678 # endif // ENABLE_CLOCK
2681 #ifdef HAVE_TAGLIB_H
2682 bool ShowServerInfo::canBeRun()
2684 return myScreen
!= myTinyTagEditor
;
2686 #endif // HAVE_TAGLIB_H
2688 void ShowServerInfo::run()
2690 myServerInfo
->switchTo();
2697 void populateActions()
2699 AvailableActions
.resize(static_cast<size_t>(Actions::Type::_numberOfActions
));
2700 auto insert_action
= [](Actions::BaseAction
*a
) {
2701 AvailableActions
.at(static_cast<size_t>(a
->type())).reset(a
);
2703 insert_action(new Actions::Dummy());
2704 insert_action(new Actions::UpdateEnvironment());
2705 insert_action(new Actions::MouseEvent());
2706 insert_action(new Actions::ScrollUp());
2707 insert_action(new Actions::ScrollDown());
2708 insert_action(new Actions::ScrollUpArtist());
2709 insert_action(new Actions::ScrollUpAlbum());
2710 insert_action(new Actions::ScrollDownArtist());
2711 insert_action(new Actions::ScrollDownAlbum());
2712 insert_action(new Actions::PageUp());
2713 insert_action(new Actions::PageDown());
2714 insert_action(new Actions::MoveHome());
2715 insert_action(new Actions::MoveEnd());
2716 insert_action(new Actions::ToggleInterface());
2717 insert_action(new Actions::JumpToParentDirectory());
2718 insert_action(new Actions::RunAction());
2719 insert_action(new Actions::SelectItem());
2720 insert_action(new Actions::SelectRange());
2721 insert_action(new Actions::PreviousColumn());
2722 insert_action(new Actions::NextColumn());
2723 insert_action(new Actions::MasterScreen());
2724 insert_action(new Actions::SlaveScreen());
2725 insert_action(new Actions::VolumeUp());
2726 insert_action(new Actions::VolumeDown());
2727 insert_action(new Actions::AddItemToPlaylist());
2728 insert_action(new Actions::DeletePlaylistItems());
2729 insert_action(new Actions::DeleteStoredPlaylist());
2730 insert_action(new Actions::DeleteBrowserItems());
2731 insert_action(new Actions::ReplaySong());
2732 insert_action(new Actions::PreviousSong());
2733 insert_action(new Actions::NextSong());
2734 insert_action(new Actions::Pause());
2735 insert_action(new Actions::Stop());
2736 insert_action(new Actions::ExecuteCommand());
2737 insert_action(new Actions::SavePlaylist());
2738 insert_action(new Actions::MoveSortOrderUp());
2739 insert_action(new Actions::MoveSortOrderDown());
2740 insert_action(new Actions::MoveSelectedItemsUp());
2741 insert_action(new Actions::MoveSelectedItemsDown());
2742 insert_action(new Actions::MoveSelectedItemsTo());
2743 insert_action(new Actions::Add());
2744 insert_action(new Actions::PlayItem());
2745 insert_action(new Actions::SeekForward());
2746 insert_action(new Actions::SeekBackward());
2747 insert_action(new Actions::ToggleDisplayMode());
2748 insert_action(new Actions::ToggleSeparatorsBetweenAlbums());
2749 insert_action(new Actions::ToggleLyricsUpdateOnSongChange());
2750 insert_action(new Actions::ToggleLyricsFetcher());
2751 insert_action(new Actions::ToggleFetchingLyricsInBackground());
2752 insert_action(new Actions::TogglePlayingSongCentering());
2753 insert_action(new Actions::UpdateDatabase());
2754 insert_action(new Actions::JumpToPlayingSong());
2755 insert_action(new Actions::ToggleRepeat());
2756 insert_action(new Actions::Shuffle());
2757 insert_action(new Actions::ToggleRandom());
2758 insert_action(new Actions::StartSearching());
2759 insert_action(new Actions::SaveTagChanges());
2760 insert_action(new Actions::ToggleSingle());
2761 insert_action(new Actions::ToggleConsume());
2762 insert_action(new Actions::ToggleCrossfade());
2763 insert_action(new Actions::SetCrossfade());
2764 insert_action(new Actions::SetVolume());
2765 insert_action(new Actions::EnterDirectory());
2766 insert_action(new Actions::EditSong());
2767 insert_action(new Actions::EditLibraryTag());
2768 insert_action(new Actions::EditLibraryAlbum());
2769 insert_action(new Actions::EditDirectoryName());
2770 insert_action(new Actions::EditPlaylistName());
2771 insert_action(new Actions::EditLyrics());
2772 insert_action(new Actions::JumpToBrowser());
2773 insert_action(new Actions::JumpToMediaLibrary());
2774 insert_action(new Actions::JumpToPlaylistEditor());
2775 insert_action(new Actions::ToggleScreenLock());
2776 insert_action(new Actions::JumpToTagEditor());
2777 insert_action(new Actions::JumpToPositionInSong());
2778 insert_action(new Actions::ReverseSelection());
2779 insert_action(new Actions::RemoveSelection());
2780 insert_action(new Actions::SelectAlbum());
2781 insert_action(new Actions::SelectFoundItems());
2782 insert_action(new Actions::AddSelectedItems());
2783 insert_action(new Actions::CropMainPlaylist());
2784 insert_action(new Actions::CropPlaylist());
2785 insert_action(new Actions::ClearMainPlaylist());
2786 insert_action(new Actions::ClearPlaylist());
2787 insert_action(new Actions::SortPlaylist());
2788 insert_action(new Actions::ReversePlaylist());
2789 insert_action(new Actions::ApplyFilter());
2790 insert_action(new Actions::Find());
2791 insert_action(new Actions::FindItemForward());
2792 insert_action(new Actions::FindItemBackward());
2793 insert_action(new Actions::NextFoundItem());
2794 insert_action(new Actions::PreviousFoundItem());
2795 insert_action(new Actions::ToggleFindMode());
2796 insert_action(new Actions::ToggleReplayGainMode());
2797 insert_action(new Actions::ToggleAddMode());
2798 insert_action(new Actions::ToggleMouse());
2799 insert_action(new Actions::ToggleBitrateVisibility());
2800 insert_action(new Actions::AddRandomItems());
2801 insert_action(new Actions::ToggleBrowserSortMode());
2802 insert_action(new Actions::ToggleLibraryTagType());
2803 insert_action(new Actions::ToggleMediaLibrarySortMode());
2804 insert_action(new Actions::FetchLyricsInBackground());
2805 insert_action(new Actions::RefetchLyrics());
2806 insert_action(new Actions::SetSelectedItemsPriority());
2807 insert_action(new Actions::ToggleOutput());
2808 insert_action(new Actions::ToggleVisualizationType());
2809 insert_action(new Actions::ShowSongInfo());
2810 insert_action(new Actions::ShowArtistInfo());
2811 insert_action(new Actions::ShowLyrics());
2812 insert_action(new Actions::Quit());
2813 insert_action(new Actions::NextScreen());
2814 insert_action(new Actions::PreviousScreen());
2815 insert_action(new Actions::ShowHelp());
2816 insert_action(new Actions::ShowPlaylist());
2817 insert_action(new Actions::ShowBrowser());
2818 insert_action(new Actions::ChangeBrowseMode());
2819 insert_action(new Actions::ShowSearchEngine());
2820 insert_action(new Actions::ResetSearchEngine());
2821 insert_action(new Actions::ShowMediaLibrary());
2822 insert_action(new Actions::ToggleMediaLibraryColumnsMode());
2823 insert_action(new Actions::ShowPlaylistEditor());
2824 insert_action(new Actions::ShowTagEditor());
2825 insert_action(new Actions::ShowOutputs());
2826 insert_action(new Actions::ShowVisualizer());
2827 insert_action(new Actions::ShowClock());
2828 insert_action(new Actions::ShowServerInfo());
2829 for (size_t i
= 0; i
< AvailableActions
.size(); ++i
)
2831 if (AvailableActions
[i
] == nullptr)
2832 throw std::logic_error("undefined action at position "
2833 + boost::lexical_cast
<std::string
>(i
));
2837 bool scrollTagCanBeRun(NC::List
*&list
, const SongList
*&songs
)
2839 auto w
= myScreen
->activeWindow();
2840 if (list
!= static_cast<void *>(w
))
2841 list
= dynamic_cast<NC::List
*>(w
);
2842 if (songs
!= static_cast<void *>(w
))
2843 songs
= dynamic_cast<SongList
*>(w
);
2844 return list
!= nullptr && !list
->empty()
2845 && songs
!= nullptr;
2848 void scrollTagUpRun(NC::List
*list
, const SongList
*songs
, MPD::Song::GetFunction get
)
2850 const auto front
= songs
->beginS();
2851 auto it
= songs
->currentS();
2852 if (it
->song() != nullptr)
2854 const std::string tag
= it
->song()->getTags(get
);
2858 if (it
->song() == nullptr || it
->song()->getTags(get
) != tag
)
2861 list
->highlight(it
-front
);
2865 void scrollTagDownRun(NC::List
*list
, const SongList
*songs
, MPD::Song::GetFunction get
)
2867 const auto front
= songs
->beginS(), back
= --songs
->endS();
2868 auto it
= songs
->currentS();
2869 if (it
->song() != nullptr)
2871 const std::string tag
= it
->song()->getTags(get
);
2875 if (it
->song() == nullptr || it
->song()->getTags(get
) != tag
)
2878 list
->highlight(it
-front
);
2882 void seek(SearchDirection sd
)
2884 using Global::wHeader
;
2885 using Global::wFooter
;
2886 using Global::Timer
;
2887 using Global::SeekingInProgress
;
2889 if (!Status::State::totalTime())
2891 Statusbar::print("Unknown item length");
2895 Progressbar::ScopedLock progressbar_lock
;
2896 Statusbar::ScopedLock statusbar_lock
;
2898 unsigned songpos
= Status::State::elapsedTime();
2901 NC::Window::ScopedTimeout stimeout
{*wFooter
, BaseScreen::defaultWindowTimeout
};
2903 // Accept single action of a given type or action chain for which all actions
2904 // can be run and one of them is of the given type. This will still not work
2905 // in some contrived cases, but allows for more flexibility than accepting
2906 // single actions only.
2907 auto hasRunnableAction
= [](BindingsConfiguration::BindingIteratorPair
&bindings
,
2908 Actions::Type type
) {
2909 bool success
= false;
2910 for (auto binding
= bindings
.first
; binding
!= bindings
.second
; ++binding
)
2912 auto &actions
= binding
->actions();
2913 for (const auto &action
: actions
)
2915 if (action
->canBeRun())
2917 if (action
->type() == type
)
2932 SeekingInProgress
= true;
2937 unsigned howmuch
= Config
.incremental_seeking
2938 ? (Timer
-t
).total_seconds()/2+Config
.seek_time
2941 NC::Key::Type input
= readKey(*wFooter
);
2945 case SearchDirection::Backward
:
2948 if (songpos
< howmuch
)
2954 case SearchDirection::Forward
:
2955 if (songpos
< Status::State::totalTime())
2956 songpos
= std::min(songpos
+ howmuch
, Status::State::totalTime());
2960 std::string tracklength
;
2961 // FIXME: merge this with the code in status.cpp
2962 switch (Config
.design
)
2964 case Design::Classic
:
2966 if (Config
.display_remaining_time
)
2969 tracklength
+= MPD::Song::ShowTime(Status::State::totalTime()-songpos
);
2972 tracklength
+= MPD::Song::ShowTime(songpos
);
2974 tracklength
+= MPD::Song::ShowTime(Status::State::totalTime());
2976 *wFooter
<< NC::XY(wFooter
->getWidth()-tracklength
.length(), 1)
2977 << Config
.statusbar_time_color
2979 << NC::FormattedColor::End(Config
.statusbar_time_color
);
2981 case Design::Alternative
:
2982 if (Config
.display_remaining_time
)
2985 tracklength
+= MPD::Song::ShowTime(Status::State::totalTime()-songpos
);
2988 tracklength
= MPD::Song::ShowTime(songpos
);
2990 tracklength
+= MPD::Song::ShowTime(Status::State::totalTime());
2991 *wHeader
<< NC::XY(0, 0)
2992 << Config
.statusbar_time_color
2994 << NC::FormattedColor::End(Config
.statusbar_time_color
)
2999 Progressbar::draw(songpos
, Status::State::totalTime());
3002 auto k
= Bindings
.get(input
);
3003 if (hasRunnableAction(k
, Actions::Type::SeekBackward
))
3004 sd
= SearchDirection::Backward
;
3005 else if (hasRunnableAction(k
, Actions::Type::SeekForward
))
3006 sd
= SearchDirection::Forward
;
3010 SeekingInProgress
= false;
3011 Mpd
.Seek(Status::State::currentSongPosition(), songpos
);
3014 void findItem(const SearchDirection direction
)
3016 using Global::wFooter
;
3018 Searchable
*w
= dynamic_cast<Searchable
*>(myScreen
);
3019 assert(w
!= nullptr);
3020 assert(w
->allowsSearching());
3022 std::string constraint
= w
->searchConstraint();
3025 Statusbar::ScopedLock slock
;
3026 NC::Window::ScopedPromptHook
prompt_hook(
3028 Statusbar::Helpers::FindImmediately(w
, direction
));
3029 Statusbar::put() << (boost::format("Find %1%: ") % direction
).str();
3030 constraint
= wFooter
->prompt(constraint
);
3032 catch (NC::PromptAborted
&)
3034 w
->setSearchConstraint(constraint
);
3035 w
->search(direction
, Config
.wrapped_search
, false);
3039 if (constraint
.empty())
3041 Statusbar::printf("Constraint unset");
3042 w
->clearSearchConstraint();
3045 Statusbar::printf("Using constraint \"%1%\"", constraint
);
3048 void listsChangeFinisher()
3050 if (myScreen
== myLibrary
3051 || myScreen
== myPlaylistEditor
3052 # ifdef HAVE_TAGLIB_H
3053 || myScreen
== myTagEditor
3054 # endif // HAVE_TAGLIB_H
3057 if (myScreen
->activeWindow() == &myLibrary
->Tags
)
3059 myLibrary
->Albums
.clear();
3060 myLibrary
->Albums
.refresh();
3061 myLibrary
->Songs
.clear();
3062 myLibrary
->Songs
.refresh();
3063 myLibrary
->updateTimer();
3065 else if (myScreen
->activeWindow() == &myLibrary
->Albums
)
3067 myLibrary
->Songs
.clear();
3068 myLibrary
->Songs
.refresh();
3069 myLibrary
->updateTimer();
3071 else if (myScreen
->isActiveWindow(myPlaylistEditor
->Playlists
))
3073 myPlaylistEditor
->Content
.clear();
3074 myPlaylistEditor
->Content
.refresh();
3075 myPlaylistEditor
->updateTimer();
3077 # ifdef HAVE_TAGLIB_H
3078 else if (myScreen
->activeWindow() == myTagEditor
->Dirs
)
3080 myTagEditor
->Tags
->clear();
3082 # endif // HAVE_TAGLIB_H