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
;
79 std::vector
<std::unique_ptr
<Actions::BaseAction
>> AvailableActions
;
81 void populateActions();
83 bool scrollTagCanBeRun(NC::List
*&list
, SongList
*&songs
);
84 void scrollTagUpRun(NC::List
*list
, SongList
*songs
, MPD::Song::GetFunction get
);
85 void scrollTagDownRun(NC::List
*list
, SongList
*songs
, MPD::Song::GetFunction get
);
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");
104 bool OriginalStatusbarVisibility
;
105 bool ExitMainLoop
= false;
111 void validateScreenSize()
113 using Global::MainHeight
;
115 if (COLS
< 30 || MainHeight
< 5)
118 std::cout
<< "Screen is too small to handle ncmpcpp correctly\n";
123 void initializeScreens()
126 myPlaylist
= new Playlist
;
127 myBrowser
= new Browser
;
128 mySearcher
= new SearchEngine
;
129 myLibrary
= new MediaLibrary
;
130 myPlaylistEditor
= new PlaylistEditor
;
131 myLyrics
= new Lyrics
;
132 mySelectedItemsAdder
= new SelectedItemsAdder
;
133 mySongInfo
= new SongInfo
;
134 myServerInfo
= new ServerInfo
;
135 mySortPlaylistDialog
= new SortPlaylistDialog
;
136 myLastfm
= new Lastfm
;
138 # ifdef HAVE_TAGLIB_H
139 myTinyTagEditor
= new TinyTagEditor
;
140 myTagEditor
= new TagEditor
;
141 # endif // HAVE_TAGLIB_H
143 # ifdef ENABLE_VISUALIZER
144 myVisualizer
= new Visualizer
;
145 # endif // ENABLE_VISUALIZER
147 # ifdef ENABLE_OUTPUTS
148 myOutputs
= new Outputs
;
149 # endif // ENABLE_OUTPUTS
153 # endif // ENABLE_CLOCK
157 void setResizeFlags()
159 myHelp
->hasToBeResized
= 1;
160 myPlaylist
->hasToBeResized
= 1;
161 myBrowser
->hasToBeResized
= 1;
162 mySearcher
->hasToBeResized
= 1;
163 myLibrary
->hasToBeResized
= 1;
164 myPlaylistEditor
->hasToBeResized
= 1;
165 myLyrics
->hasToBeResized
= 1;
166 mySelectedItemsAdder
->hasToBeResized
= 1;
167 mySongInfo
->hasToBeResized
= 1;
168 myServerInfo
->hasToBeResized
= 1;
169 mySortPlaylistDialog
->hasToBeResized
= 1;
170 myLastfm
->hasToBeResized
= 1;
172 # ifdef HAVE_TAGLIB_H
173 myTinyTagEditor
->hasToBeResized
= 1;
174 myTagEditor
->hasToBeResized
= 1;
175 # endif // HAVE_TAGLIB_H
177 # ifdef ENABLE_VISUALIZER
178 myVisualizer
->hasToBeResized
= 1;
179 # endif // ENABLE_VISUALIZER
181 # ifdef ENABLE_OUTPUTS
182 myOutputs
->hasToBeResized
= 1;
183 # endif // ENABLE_OUTPUTS
186 myClock
->hasToBeResized
= 1;
187 # endif // ENABLE_CLOCK
190 void resizeScreen(bool reload_main_window
)
192 using Global::MainHeight
;
193 using Global::wHeader
;
194 using Global::wFooter
;
196 // update internal screen dimensions
197 if (reload_main_window
)
199 rl_resize_terminal();
204 MainHeight
= LINES
-(Config
.design
== Design::Alternative
? 7 : 4);
206 validateScreenSize();
208 if (!Config
.header_visibility
)
210 if (!Config
.statusbar_visibility
)
215 applyToVisibleWindows(&BaseScreen::resize
);
217 if (Config
.header_visibility
|| Config
.design
== Design::Alternative
)
218 wHeader
->resize(COLS
, HeaderHeight
);
220 FooterStartY
= LINES
-(Config
.statusbar_visibility
? 2 : 1);
221 wFooter
->moveTo(0, FooterStartY
);
222 wFooter
->resize(COLS
, Config
.statusbar_visibility
? 2 : 1);
224 applyToVisibleWindows(&BaseScreen::refresh
);
226 Status::Changes::elapsedTime(false);
227 Status::Changes::playerState();
228 // Note: routines for drawing separator if alternative user
229 // interface is active and header is hidden are placed in
230 // NcmpcppStatusChanges.StatusFlags
231 Status::Changes::flags();
237 void setWindowsDimensions()
239 using Global::MainStartY
;
240 using Global::MainHeight
;
242 MainStartY
= Config
.design
== Design::Alternative
? 5 : 2;
243 MainHeight
= LINES
-(Config
.design
== Design::Alternative
? 7 : 4);
245 if (!Config
.header_visibility
)
250 if (!Config
.statusbar_visibility
)
253 HeaderHeight
= Config
.design
== Design::Alternative
? (Config
.header_visibility
? 5 : 3) : 2;
254 FooterStartY
= LINES
-(Config
.statusbar_visibility
? 2 : 1);
255 FooterHeight
= Config
.statusbar_visibility
? 2 : 1;
258 void confirmAction(const boost::format
&description
)
260 Statusbar::ScopedLock slock
;
261 Statusbar::put() << description
.str()
262 << " [" << NC::Format::Bold
<< 'y' << NC::Format::NoBold
263 << '/' << NC::Format::Bold
<< 'n' << NC::Format::NoBold
265 auto answer
= Statusbar::Helpers::promptReturnOneOf({"y", "n"});
267 throw NC::PromptAborted(std::move(answer
));
270 bool isMPDMusicDirSet()
272 if (Config
.mpd_music_dir
.empty())
274 Statusbar::print("Proper mpd_music_dir variable has to be set in configuration file");
280 BaseAction
&get(Actions::Type at
)
282 if (AvailableActions
.empty())
284 BaseAction
*action
= AvailableActions
.at(static_cast<size_t>(at
)).get();
285 // action should be always present if action type in queried
286 assert(action
!= nullptr);
290 BaseAction
*get(const std::string
&name
)
292 BaseAction
*result
= nullptr;
293 if (AvailableActions
.empty())
295 for (const auto &action
: AvailableActions
)
297 if (action
->name() == name
)
299 result
= action
.get();
306 UpdateEnvironment::UpdateEnvironment()
307 : BaseAction(Type::UpdateEnvironment
, "update_environment")
308 , m_past(boost::posix_time::from_time_t(0))
311 void UpdateEnvironment::run(bool update_timer
, bool refresh_window
)
315 // update timer, status if necessary etc.
316 Status::trace(update_timer
, true);
318 // show lyrics consumer notification if appropriate
319 if (auto message
= myLyrics
->tryTakeConsumerMessage())
320 Statusbar::print(*message
);
323 if ((myScreen
== myPlaylist
|| myScreen
== myBrowser
|| myScreen
== myLyrics
)
324 && (Timer
- m_past
> boost::posix_time::milliseconds(500))
332 myScreen
->refreshWindow();
335 void UpdateEnvironment::run()
340 bool MouseEvent::canBeRun()
342 return Config
.mouse_support
;
345 void MouseEvent::run()
347 using Global::VolumeState
;
348 using Global::wFooter
;
350 m_old_mouse_event
= m_mouse_event
;
351 m_mouse_event
= wFooter
->getMouseEvent();
353 //Statusbar::printf("(%1%, %2%, %3%)", m_mouse_event.bstate, m_mouse_event.x, m_mouse_event.y);
355 if (m_mouse_event
.bstate
& BUTTON1_PRESSED
356 && m_mouse_event
.y
== LINES
-(Config
.statusbar_visibility
? 2 : 1)
359 if (Status::State::player() == MPD::psStop
)
361 Mpd
.Seek(Status::State::currentSongPosition(),
362 Status::State::totalTime()*m_mouse_event
.x
/double(COLS
));
364 else if (m_mouse_event
.bstate
& BUTTON1_PRESSED
365 && (Config
.statusbar_visibility
|| Config
.design
== Design::Alternative
)
366 && Status::State::player() != MPD::psStop
367 && m_mouse_event
.y
== (Config
.design
== Design::Alternative
? 1 : LINES
-1)
368 && m_mouse_event
.x
< 9
373 else if ((m_mouse_event
.bstate
& BUTTON5_PRESSED
|| m_mouse_event
.bstate
& BUTTON4_PRESSED
)
374 && (Config
.header_visibility
|| Config
.design
== Design::Alternative
)
375 && m_mouse_event
.y
== 0 && size_t(m_mouse_event
.x
) > COLS
-VolumeState
.length()
378 if (m_mouse_event
.bstate
& BUTTON5_PRESSED
)
379 get(Type::VolumeDown
).execute();
381 get(Type::VolumeUp
).execute();
383 else if (m_mouse_event
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
| BUTTON4_PRESSED
| BUTTON5_PRESSED
))
384 myScreen
->mouseButtonPressed(m_mouse_event
);
389 myScreen
->scroll(NC::Scroll::Up
);
390 listsChangeFinisher();
393 void ScrollDown::run()
395 myScreen
->scroll(NC::Scroll::Down
);
396 listsChangeFinisher();
399 bool ScrollUpArtist::canBeRun()
401 return scrollTagCanBeRun(m_list
, m_songs
);
404 void ScrollUpArtist::run()
406 scrollTagUpRun(m_list
, m_songs
, &MPD::Song::getArtist
);
409 bool ScrollUpAlbum::canBeRun()
411 return scrollTagCanBeRun(m_list
, m_songs
);
414 void ScrollUpAlbum::run()
416 scrollTagUpRun(m_list
, m_songs
, &MPD::Song::getAlbum
);
419 bool ScrollDownArtist::canBeRun()
421 return scrollTagCanBeRun(m_list
, m_songs
);
424 void ScrollDownArtist::run()
426 scrollTagDownRun(m_list
, m_songs
, &MPD::Song::getArtist
);
429 bool ScrollDownAlbum::canBeRun()
431 return scrollTagCanBeRun(m_list
, m_songs
);
434 void ScrollDownAlbum::run()
436 scrollTagDownRun(m_list
, m_songs
, &MPD::Song::getAlbum
);
441 myScreen
->scroll(NC::Scroll::PageUp
);
442 listsChangeFinisher();
447 myScreen
->scroll(NC::Scroll::PageDown
);
448 listsChangeFinisher();
453 myScreen
->scroll(NC::Scroll::Home
);
454 listsChangeFinisher();
459 myScreen
->scroll(NC::Scroll::End
);
460 listsChangeFinisher();
463 void ToggleInterface::run()
465 switch (Config
.design
)
467 case Design::Classic
:
468 Config
.design
= Design::Alternative
;
469 Config
.statusbar_visibility
= false;
471 case Design::Alternative
:
472 Config
.design
= Design::Classic
;
473 Config
.statusbar_visibility
= OriginalStatusbarVisibility
;
476 setWindowsDimensions();
478 // unlock progressbar
479 Progressbar::ScopedLock();
480 Status::Changes::mixer();
481 Status::Changes::elapsedTime(false);
482 Statusbar::printf("User interface: %1%", Config
.design
);
485 bool JumpToParentDirectory::canBeRun()
487 return (myScreen
== myBrowser
)
488 # ifdef HAVE_TAGLIB_H
489 || (myScreen
->activeWindow() == myTagEditor
->Dirs
)
490 # endif // HAVE_TAGLIB_H
494 void JumpToParentDirectory::run()
496 if (myScreen
== myBrowser
)
498 if (!myBrowser
->inRootDirectory())
500 myBrowser
->main().reset();
501 myBrowser
->enterDirectory();
504 # ifdef HAVE_TAGLIB_H
505 else if (myScreen
== myTagEditor
)
507 if (myTagEditor
->CurrentDir() != "/")
509 myTagEditor
->Dirs
->reset();
510 myTagEditor
->enterDirectory();
513 # endif // HAVE_TAGLIB_H
516 bool RunAction::canBeRun()
518 m_ha
= dynamic_cast<HasActions
*>(myScreen
);
519 return m_ha
!= nullptr
520 && m_ha
->actionRunnable();
523 void RunAction::run()
528 bool PreviousColumn::canBeRun()
530 m_hc
= dynamic_cast<HasColumns
*>(myScreen
);
531 return m_hc
!= nullptr
532 && m_hc
->previousColumnAvailable();
535 void PreviousColumn::run()
537 m_hc
->previousColumn();
540 bool NextColumn::canBeRun()
542 m_hc
= dynamic_cast<HasColumns
*>(myScreen
);
543 return m_hc
!= nullptr
544 && m_hc
->nextColumnAvailable();
547 void NextColumn::run()
552 bool MasterScreen::canBeRun()
554 using Global::myLockedScreen
;
555 using Global::myInactiveScreen
;
557 return myLockedScreen
559 && myLockedScreen
!= myScreen
560 && myScreen
->isMergable();
563 void MasterScreen::run()
565 using Global::myInactiveScreen
;
566 using Global::myLockedScreen
;
568 myInactiveScreen
= myScreen
;
569 myScreen
= myLockedScreen
;
573 bool SlaveScreen::canBeRun()
575 using Global::myLockedScreen
;
576 using Global::myInactiveScreen
;
578 return myLockedScreen
580 && myLockedScreen
== myScreen
581 && myScreen
->isMergable();
584 void SlaveScreen::run()
586 using Global::myInactiveScreen
;
587 using Global::myLockedScreen
;
589 myScreen
= myInactiveScreen
;
590 myInactiveScreen
= myLockedScreen
;
596 int volume
= std::min(Status::State::volume()+Config
.volume_change_step
, 100u);
597 Mpd
.SetVolume(volume
);
600 void VolumeDown::run()
602 int volume
= std::max(int(Status::State::volume()-Config
.volume_change_step
), 0);
603 Mpd
.SetVolume(volume
);
606 bool AddItemToPlaylist::canBeRun()
608 m_hs
= dynamic_cast<HasSongs
*>(myScreen
);
609 return m_hs
!= nullptr && m_hs
->itemAvailable();
612 void AddItemToPlaylist::run()
614 bool success
= m_hs
->addItemToPlaylist(false);
617 myScreen
->scroll(NC::Scroll::Down
);
618 listsChangeFinisher();
622 bool PlayItem::canBeRun()
624 m_hs
= dynamic_cast<HasSongs
*>(myScreen
);
625 return m_hs
!= nullptr && m_hs
->itemAvailable();
630 bool success
= m_hs
->addItemToPlaylist(true);
632 listsChangeFinisher();
635 bool DeletePlaylistItems::canBeRun()
637 return (myScreen
== myPlaylist
&& !myPlaylist
->main().empty())
638 || (myScreen
->isActiveWindow(myPlaylistEditor
->Content
) && !myPlaylistEditor
->Content
.empty());
641 void DeletePlaylistItems::run()
643 if (myScreen
== myPlaylist
)
645 Statusbar::print("Deleting items...");
646 auto delete_fun
= std::bind(&MPD::Connection::Delete
, ph::_1
, ph::_2
);
647 deleteSelectedSongs(myPlaylist
->main(), delete_fun
);
648 Statusbar::print("Item(s) deleted");
650 else if (myScreen
->isActiveWindow(myPlaylistEditor
->Content
))
652 std::string playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
653 auto delete_fun
= std::bind(&MPD::Connection::PlaylistDelete
, ph::_1
, playlist
, ph::_2
);
654 Statusbar::print("Deleting items...");
655 deleteSelectedSongs(myPlaylistEditor
->Content
, delete_fun
);
656 Statusbar::print("Item(s) deleted");
660 bool DeleteBrowserItems::canBeRun()
662 auto check_if_deletion_allowed
= []() {
663 if (Config
.allow_for_physical_item_deletion
)
667 Statusbar::print("Flag \"allow_for_physical_item_deletion\" needs to be enabled in configuration file");
671 return myScreen
== myBrowser
672 && !myBrowser
->main().empty()
673 && isMPDMusicDirSet()
674 && check_if_deletion_allowed();
677 void DeleteBrowserItems::run()
679 auto get_name
= [](const MPD::Item
&item
) -> std::string
{
683 case MPD::Item::Type::Directory
:
684 iname
= getBasename(item
.directory().path());
686 case MPD::Item::Type::Song
:
687 iname
= item
.song().getName();
689 case MPD::Item::Type::Playlist
:
690 iname
= getBasename(item
.playlist().path());
696 boost::format question
;
697 if (hasSelected(myBrowser
->main().begin(), myBrowser
->main().end()))
698 question
= boost::format("Delete selected items?");
701 const auto &item
= myBrowser
->main().current()->value();
702 // parent directories are not accepted (and they
703 // can't be selected, so in other cases it's fine).
704 if (myBrowser
->isParentDirectory(item
))
706 const char msg
[] = "Delete \"%1%\"?";
707 question
= boost::format(msg
) % wideShorten(
708 get_name(item
), COLS
-const_strlen(msg
)-5
711 confirmAction(question
);
713 auto items
= getSelectedOrCurrent(
714 myBrowser
->main().begin(),
715 myBrowser
->main().end(),
716 myBrowser
->main().current()
718 for (const auto &item
: items
)
720 myBrowser
->remove(item
->value());
721 const char msg
[] = "Deleted %1% \"%2%\"";
722 Statusbar::printf(msg
,
723 itemTypeToString(item
->value().type()),
724 wideShorten(get_name(item
->value()), COLS
-const_strlen(msg
))
728 if (!myBrowser
->isLocal())
729 Mpd
.UpdateDirectory(myBrowser
->currentDirectory());
730 myBrowser
->requestUpdate();
733 bool DeleteStoredPlaylist::canBeRun()
735 return myScreen
->isActiveWindow(myPlaylistEditor
->Playlists
);
738 void DeleteStoredPlaylist::run()
740 if (myPlaylistEditor
->Playlists
.empty())
742 boost::format question
;
743 if (hasSelected(myPlaylistEditor
->Playlists
.begin(), myPlaylistEditor
->Playlists
.end()))
744 question
= boost::format("Delete selected playlists?");
746 question
= boost::format("Delete playlist \"%1%\"?")
747 % wideShorten(myPlaylistEditor
->Playlists
.current()->value().path(), COLS
-question
.size()-10);
748 confirmAction(question
);
749 auto list
= getSelectedOrCurrent(
750 myPlaylistEditor
->Playlists
.begin(),
751 myPlaylistEditor
->Playlists
.end(),
752 myPlaylistEditor
->Playlists
.current()
754 for (const auto &item
: list
)
755 Mpd
.DeletePlaylist(item
->value().path());
756 Statusbar::printf("%1% deleted", list
.size() == 1 ? "Playlist" : "Playlists");
757 // force playlists update. this happens automatically, but only after call
758 // to Key::read, therefore when we call PlaylistEditor::Update, it won't
759 // yet see it, so let's point that it needs to update it.
760 myPlaylistEditor
->requestPlaylistsUpdate();
763 void ReplaySong::run()
765 if (Status::State::player() != MPD::psStop
)
766 Mpd
.Seek(Status::State::currentSongPosition(), 0);
769 void PreviousSong::run()
784 void SavePlaylist::run()
786 using Global::wFooter
;
788 std::string playlist_name
;
790 Statusbar::ScopedLock slock
;
791 Statusbar::put() << "Save playlist as: ";
792 playlist_name
= wFooter
->prompt();
796 Mpd
.SavePlaylist(playlist_name
);
797 Statusbar::printf("Playlist saved as \"%1%\"", playlist_name
);
799 catch (MPD::ServerError
&e
)
801 if (e
.code() == MPD_SERVER_ERROR_EXIST
)
804 boost::format("Playlist \"%1%\" already exists, overwrite?") % playlist_name
806 Mpd
.DeletePlaylist(playlist_name
);
807 Mpd
.SavePlaylist(playlist_name
);
808 Statusbar::print("Playlist overwritten");
820 void ExecuteCommand::run()
822 using Global::wFooter
;
824 std::string cmd_name
;
826 Statusbar::ScopedLock slock
;
827 NC::Window::ScopedPromptHook
helper(*wFooter
,
828 Statusbar::Helpers::TryExecuteImmediateCommand()
830 Statusbar::put() << NC::Format::Bold
<< ":" << NC::Format::NoBold
;
831 cmd_name
= wFooter
->prompt();
834 auto cmd
= Bindings
.findCommand(cmd_name
);
837 Statusbar::printf(1, "Executing %1%...", cmd_name
);
838 bool res
= cmd
->binding().execute();
839 Statusbar::printf("Execution of command \"%1%\" %2%.",
840 cmd_name
, res
? "successful" : "unsuccessful"
844 Statusbar::printf("No command named \"%1%\"", cmd_name
);
847 bool MoveSortOrderUp::canBeRun()
849 return myScreen
== mySortPlaylistDialog
;
852 void MoveSortOrderUp::run()
854 mySortPlaylistDialog
->moveSortOrderUp();
857 bool MoveSortOrderDown::canBeRun()
859 return myScreen
== mySortPlaylistDialog
;
862 void MoveSortOrderDown::run()
864 mySortPlaylistDialog
->moveSortOrderDown();
867 bool MoveSelectedItemsUp::canBeRun()
869 return ((myScreen
== myPlaylist
870 && !myPlaylist
->main().empty())
871 || (myScreen
->isActiveWindow(myPlaylistEditor
->Content
)
872 && !myPlaylistEditor
->Content
.empty()));
875 void MoveSelectedItemsUp::run()
877 const char *filteredMsg
= "Moving items up is disabled in filtered playlist";
878 if (myScreen
== myPlaylist
)
880 if (myPlaylist
->main().isFiltered())
881 Statusbar::print(filteredMsg
);
885 std::bind(&MPD::Connection::Move
, ph::_1
, ph::_2
, ph::_3
));
887 else if (myScreen
== myPlaylistEditor
)
889 if (myPlaylistEditor
->Content
.isFiltered())
890 Statusbar::print(filteredMsg
);
893 auto playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
895 myPlaylistEditor
->Content
,
896 std::bind(&MPD::Connection::PlaylistMove
, ph::_1
, playlist
, ph::_2
, ph::_3
));
901 bool MoveSelectedItemsDown::canBeRun()
903 return ((myScreen
== myPlaylist
904 && !myPlaylist
->main().empty())
905 || (myScreen
->isActiveWindow(myPlaylistEditor
->Content
)
906 && !myPlaylistEditor
->Content
.empty()));
909 void MoveSelectedItemsDown::run()
911 const char *filteredMsg
= "Moving items down is disabled in filtered playlist";
912 if (myScreen
== myPlaylist
)
914 if (myPlaylist
->main().isFiltered())
915 Statusbar::print(filteredMsg
);
917 moveSelectedItemsDown(
919 std::bind(&MPD::Connection::Move
, ph::_1
, ph::_2
, ph::_3
));
921 else if (myScreen
== myPlaylistEditor
)
923 if (myPlaylistEditor
->Content
.isFiltered())
924 Statusbar::print(filteredMsg
);
927 auto playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
928 moveSelectedItemsDown(
929 myPlaylistEditor
->Content
,
930 std::bind(&MPD::Connection::PlaylistMove
, ph::_1
, playlist
, ph::_2
, ph::_3
));
935 bool MoveSelectedItemsTo::canBeRun()
937 return myScreen
== myPlaylist
938 || myScreen
->isActiveWindow(myPlaylistEditor
->Content
);
941 void MoveSelectedItemsTo::run()
943 if (myScreen
== myPlaylist
)
945 if (!myPlaylist
->main().empty())
946 moveSelectedItemsTo(myPlaylist
->main(), std::bind(&MPD::Connection::Move
, ph::_1
, ph::_2
, ph::_3
));
950 assert(!myPlaylistEditor
->Playlists
.empty());
951 std::string playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
952 auto move_fun
= std::bind(&MPD::Connection::PlaylistMove
, ph::_1
, playlist
, ph::_2
, ph::_3
);
953 moveSelectedItemsTo(myPlaylistEditor
->Content
, move_fun
);
959 return myScreen
!= myPlaylistEditor
960 || !myPlaylistEditor
->Playlists
.empty();
965 using Global::wFooter
;
969 Statusbar::ScopedLock slock
;
970 Statusbar::put() << (myScreen
== myPlaylistEditor
? "Add to playlist: " : "Add: ");
971 path
= wFooter
->prompt();
974 // confirm when one wants to add the whole database
976 confirmAction("Are you sure you want to add the whole database?");
978 Statusbar::put() << "Adding...";
980 if (myScreen
== myPlaylistEditor
)
981 Mpd
.AddToPlaylist(myPlaylistEditor
->Playlists
.current()->value().path(), path
);
988 catch (MPD::ServerError
&err
)
990 // If a path is not a file or directory, assume it is a playlist.
991 if (err
.code() == MPD_SERVER_ERROR_NO_EXIST
)
992 Mpd
.LoadPlaylist(path
);
999 bool SeekForward::canBeRun()
1001 return Status::State::player() != MPD::psStop
&& Status::State::totalTime() > 0;
1004 void SeekForward::run()
1009 bool SeekBackward::canBeRun()
1011 return Status::State::player() != MPD::psStop
&& Status::State::totalTime() > 0;
1014 void SeekBackward::run()
1019 bool ToggleDisplayMode::canBeRun()
1021 return myScreen
== myPlaylist
1022 || myScreen
== myBrowser
1023 || myScreen
== mySearcher
1024 || myScreen
->isActiveWindow(myPlaylistEditor
->Content
);
1027 void ToggleDisplayMode::run()
1029 if (myScreen
== myPlaylist
)
1031 switch (Config
.playlist_display_mode
)
1033 case DisplayMode::Classic
:
1034 Config
.playlist_display_mode
= DisplayMode::Columns
;
1035 myPlaylist
->main().setItemDisplayer(std::bind(
1036 Display::SongsInColumns
, ph::_1
, std::cref(myPlaylist
->main())
1038 if (Config
.titles_visibility
)
1039 myPlaylist
->main().setTitle(Display::Columns(myPlaylist
->main().getWidth()));
1041 myPlaylist
->main().setTitle("");
1043 case DisplayMode::Columns
:
1044 Config
.playlist_display_mode
= DisplayMode::Classic
;
1045 myPlaylist
->main().setItemDisplayer(std::bind(
1046 Display::Songs
, ph::_1
, std::cref(myPlaylist
->main()), std::cref(Config
.song_list_format
)
1048 myPlaylist
->main().setTitle("");
1050 Statusbar::printf("Playlist display mode: %1%", Config
.playlist_display_mode
);
1052 else if (myScreen
== myBrowser
)
1054 switch (Config
.browser_display_mode
)
1056 case DisplayMode::Classic
:
1057 Config
.browser_display_mode
= DisplayMode::Columns
;
1058 if (Config
.titles_visibility
)
1059 myBrowser
->main().setTitle(Display::Columns(myBrowser
->main().getWidth()));
1061 myBrowser
->main().setTitle("");
1063 case DisplayMode::Columns
:
1064 Config
.browser_display_mode
= DisplayMode::Classic
;
1065 myBrowser
->main().setTitle("");
1068 Statusbar::printf("Browser display mode: %1%", Config
.browser_display_mode
);
1070 else if (myScreen
== mySearcher
)
1072 switch (Config
.search_engine_display_mode
)
1074 case DisplayMode::Classic
:
1075 Config
.search_engine_display_mode
= DisplayMode::Columns
;
1077 case DisplayMode::Columns
:
1078 Config
.search_engine_display_mode
= DisplayMode::Classic
;
1081 Statusbar::printf("Search engine display mode: %1%", Config
.search_engine_display_mode
);
1082 if (mySearcher
->main().size() > SearchEngine::StaticOptions
)
1083 mySearcher
->main().setTitle(
1084 Config
.search_engine_display_mode
== DisplayMode::Columns
1085 && Config
.titles_visibility
1086 ? Display::Columns(mySearcher
->main().getWidth())
1090 else if (myScreen
->isActiveWindow(myPlaylistEditor
->Content
))
1092 switch (Config
.playlist_editor_display_mode
)
1094 case DisplayMode::Classic
:
1095 Config
.playlist_editor_display_mode
= DisplayMode::Columns
;
1096 myPlaylistEditor
->Content
.setItemDisplayer(std::bind(
1097 Display::SongsInColumns
, ph::_1
, std::cref(myPlaylistEditor
->Content
)
1100 case DisplayMode::Columns
:
1101 Config
.playlist_editor_display_mode
= DisplayMode::Classic
;
1102 myPlaylistEditor
->Content
.setItemDisplayer(std::bind(
1103 Display::Songs
, ph::_1
, std::cref(myPlaylistEditor
->Content
), std::cref(Config
.song_list_format
)
1107 Statusbar::printf("Playlist editor display mode: %1%", Config
.playlist_editor_display_mode
);
1111 bool ToggleSeparatorsBetweenAlbums::canBeRun()
1116 void ToggleSeparatorsBetweenAlbums::run()
1118 Config
.playlist_separate_albums
= !Config
.playlist_separate_albums
;
1119 Statusbar::printf("Separators between albums: %1%",
1120 Config
.playlist_separate_albums
? "on" : "off"
1124 bool ToggleLyricsUpdateOnSongChange::canBeRun()
1126 return myScreen
== myLyrics
;
1129 void ToggleLyricsUpdateOnSongChange::run()
1131 Config
.now_playing_lyrics
= !Config
.now_playing_lyrics
;
1132 Statusbar::printf("Update lyrics if song changes: %1%",
1133 Config
.now_playing_lyrics
? "on" : "off"
1137 void ToggleLyricsFetcher::run()
1139 myLyrics
->toggleFetcher();
1142 void ToggleFetchingLyricsInBackground::run()
1144 Config
.fetch_lyrics_in_background
= !Config
.fetch_lyrics_in_background
;
1145 Statusbar::printf("Fetching lyrics for playing songs in background: %1%",
1146 Config
.fetch_lyrics_in_background
? "on" : "off");
1149 void TogglePlayingSongCentering::run()
1151 Config
.autocenter_mode
= !Config
.autocenter_mode
;
1152 Statusbar::printf("Centering playing song: %1%",
1153 Config
.autocenter_mode
? "on" : "off"
1155 if (Config
.autocenter_mode
)
1157 auto s
= myPlaylist
->nowPlayingSong();
1159 myPlaylist
->locateSong(s
);
1163 void UpdateDatabase::run()
1165 if (myScreen
== myBrowser
)
1166 Mpd
.UpdateDirectory(myBrowser
->currentDirectory());
1167 # ifdef HAVE_TAGLIB_H
1168 else if (myScreen
== myTagEditor
)
1169 Mpd
.UpdateDirectory(myTagEditor
->CurrentDir());
1170 # endif // HAVE_TAGLIB_H
1172 Mpd
.UpdateDirectory("/");
1175 bool JumpToPlayingSong::canBeRun()
1177 return myScreen
== myPlaylist
1178 || myScreen
== myBrowser
1179 || myScreen
== myLibrary
;
1182 void JumpToPlayingSong::run()
1184 auto s
= myPlaylist
->nowPlayingSong();
1187 if (myScreen
== myPlaylist
)
1189 myPlaylist
->locateSong(s
);
1191 else if (myScreen
== myBrowser
)
1193 myBrowser
->locateSong(s
);
1195 else if (myScreen
== myLibrary
)
1197 myLibrary
->locateSong(s
);
1201 void ToggleRepeat::run()
1203 Mpd
.SetRepeat(!Status::State::repeat());
1206 bool Shuffle::canBeRun()
1208 if (myScreen
!= myPlaylist
)
1210 m_begin
= myPlaylist
->main().begin();
1211 m_end
= myPlaylist
->main().end();
1212 return findSelectedRangeAndPrintInfoIfNot(m_begin
, m_end
);
1217 if (Config
.ask_before_shuffling_playlists
)
1218 confirmAction("Do you really want to shuffle selected range?");
1219 auto begin
= myPlaylist
->main().begin();
1220 Mpd
.ShuffleRange(m_begin
-begin
, m_end
-begin
);
1221 Statusbar::print("Range shuffled");
1224 void ToggleRandom::run()
1226 Mpd
.SetRandom(!Status::State::random());
1229 bool StartSearching::canBeRun()
1231 return myScreen
== mySearcher
&& !mySearcher
->main()[0].isInactive();
1234 void StartSearching::run()
1236 mySearcher
->main().highlight(SearchEngine::SearchButton
);
1237 mySearcher
->main().setHighlighting(0);
1238 mySearcher
->main().refresh();
1239 mySearcher
->main().setHighlighting(1);
1240 mySearcher
->runAction();
1243 bool SaveTagChanges::canBeRun()
1245 # ifdef HAVE_TAGLIB_H
1246 return myScreen
== myTinyTagEditor
1247 || myScreen
->activeWindow() == myTagEditor
->TagTypes
;
1250 # endif // HAVE_TAGLIB_H
1253 void SaveTagChanges::run()
1255 # ifdef HAVE_TAGLIB_H
1256 if (myScreen
== myTinyTagEditor
)
1258 myTinyTagEditor
->main().highlight(myTinyTagEditor
->main().size()-2); // Save
1259 myTinyTagEditor
->runAction();
1261 else if (myScreen
->activeWindow() == myTagEditor
->TagTypes
)
1263 myTagEditor
->TagTypes
->highlight(myTagEditor
->TagTypes
->size()-1); // Save
1264 myTagEditor
->runAction();
1266 # endif // HAVE_TAGLIB_H
1269 void ToggleSingle::run()
1271 Mpd
.SetSingle(!Status::State::single());
1274 void ToggleConsume::run()
1276 Mpd
.SetConsume(!Status::State::consume());
1279 void ToggleCrossfade::run()
1281 Mpd
.SetCrossfade(Status::State::crossfade() ? 0 : Config
.crossfade_time
);
1284 void SetCrossfade::run()
1286 using Global::wFooter
;
1288 Statusbar::ScopedLock slock
;
1289 Statusbar::put() << "Set crossfade to: ";
1290 auto crossfade
= fromString
<unsigned>(wFooter
->prompt());
1291 lowerBoundCheck(crossfade
, 0u);
1292 Config
.crossfade_time
= crossfade
;
1293 Mpd
.SetCrossfade(crossfade
);
1296 void SetVolume::run()
1298 using Global::wFooter
;
1302 Statusbar::ScopedLock slock
;
1303 Statusbar::put() << "Set volume to: ";
1304 volume
= fromString
<unsigned>(wFooter
->prompt());
1305 boundsCheck(volume
, 0u, 100u);
1306 Mpd
.SetVolume(volume
);
1308 Statusbar::printf("Volume set to %1%%%", volume
);
1311 bool EnterDirectory::canBeRun()
1313 bool result
= false;
1314 if (myScreen
== myBrowser
&& !myBrowser
->main().empty())
1316 result
= myBrowser
->main().current()->value().type()
1317 == MPD::Item::Type::Directory
;
1319 #ifdef HAVE_TAGLIB_H
1320 else if (myScreen
->activeWindow() == myTagEditor
->Dirs
)
1322 #endif // HAVE_TAGLIB_H
1326 void EnterDirectory::run()
1328 if (myScreen
== myBrowser
)
1329 myBrowser
->enterDirectory();
1330 #ifdef HAVE_TAGLIB_H
1331 else if (myScreen
->activeWindow() == myTagEditor
->Dirs
)
1333 if (!myTagEditor
->enterDirectory())
1334 Statusbar::print("No subdirectories found");
1336 #endif // HAVE_TAGLIB_H
1339 bool EditSong::canBeRun()
1341 # ifdef HAVE_TAGLIB_H
1342 m_song
= currentSong(myScreen
);
1343 return m_song
!= nullptr && isMPDMusicDirSet();
1346 # endif // HAVE_TAGLIB_H
1349 void EditSong::run()
1351 # ifdef HAVE_TAGLIB_H
1352 myTinyTagEditor
->SetEdited(*m_song
);
1353 myTinyTagEditor
->switchTo();
1354 # endif // HAVE_TAGLIB_H
1357 bool EditLibraryTag::canBeRun()
1359 # ifdef HAVE_TAGLIB_H
1360 return myScreen
->isActiveWindow(myLibrary
->Tags
)
1361 && !myLibrary
->Tags
.empty()
1362 && isMPDMusicDirSet();
1365 # endif // HAVE_TAGLIB_H
1368 void EditLibraryTag::run()
1370 # ifdef HAVE_TAGLIB_H
1371 using Global::wFooter
;
1373 std::string new_tag
;
1375 Statusbar::ScopedLock slock
;
1376 Statusbar::put() << NC::Format::Bold
<< tagTypeToString(Config
.media_lib_primary_tag
) << NC::Format::NoBold
<< ": ";
1377 new_tag
= wFooter
->prompt(myLibrary
->Tags
.current()->value().tag());
1379 if (!new_tag
.empty() && new_tag
!= myLibrary
->Tags
.current()->value().tag())
1381 Statusbar::print("Updating tags...");
1382 Mpd
.StartSearch(true);
1383 Mpd
.AddSearch(Config
.media_lib_primary_tag
, myLibrary
->Tags
.current()->value().tag());
1384 MPD::MutableSong::SetFunction set
= tagTypeToSetFunction(Config
.media_lib_primary_tag
);
1386 bool success
= true;
1387 std::string dir_to_update
;
1388 for (MPD::SongIterator s
= Mpd
.CommitSearchSongs(), end
; s
!= end
; ++s
)
1390 MPD::MutableSong ms
= std::move(*s
);
1391 ms
.setTags(set
, new_tag
);
1392 Statusbar::printf("Updating tags in \"%1%\"...", ms
.getName());
1393 std::string path
= Config
.mpd_music_dir
+ ms
.getURI();
1394 if (!Tags::write(ms
))
1397 Statusbar::printf("Error while writing tags to \"%1%\": %2%",
1398 ms
.getName(), strerror(errno
));
1402 if (dir_to_update
.empty())
1403 dir_to_update
= ms
.getURI();
1405 dir_to_update
= getSharedDirectory(dir_to_update
, ms
.getURI());
1409 Mpd
.UpdateDirectory(dir_to_update
);
1410 Statusbar::print("Tags updated successfully");
1413 # endif // HAVE_TAGLIB_H
1416 bool EditLibraryAlbum::canBeRun()
1418 # ifdef HAVE_TAGLIB_H
1419 return myScreen
->isActiveWindow(myLibrary
->Albums
)
1420 && !myLibrary
->Albums
.empty()
1421 && isMPDMusicDirSet();
1424 # endif // HAVE_TAGLIB_H
1427 void EditLibraryAlbum::run()
1429 # ifdef HAVE_TAGLIB_H
1430 using Global::wFooter
;
1431 // FIXME: merge this and EditLibraryTag. also, prompt on failure if user wants to continue
1432 std::string new_album
;
1434 Statusbar::ScopedLock slock
;
1435 Statusbar::put() << NC::Format::Bold
<< "Album: " << NC::Format::NoBold
;
1436 new_album
= wFooter
->prompt(myLibrary
->Albums
.current()->value().entry().album());
1438 if (!new_album
.empty() && new_album
!= myLibrary
->Albums
.current()->value().entry().album())
1441 Statusbar::print("Updating tags...");
1442 for (size_t i
= 0; i
< myLibrary
->Songs
.size(); ++i
)
1444 Statusbar::printf("Updating tags in \"%1%\"...", myLibrary
->Songs
[i
].value().getName());
1445 std::string path
= Config
.mpd_music_dir
+ myLibrary
->Songs
[i
].value().getURI();
1446 TagLib::FileRef
f(path
.c_str());
1449 const char msg
[] = "Error while opening file \"%1%\"";
1450 Statusbar::printf(msg
, wideShorten(myLibrary
->Songs
[i
].value().getURI(), COLS
-const_strlen(msg
)));
1454 f
.tag()->setAlbum(ToWString(new_album
));
1457 const char msg
[] = "Error while writing tags in \"%1%\"";
1458 Statusbar::printf(msg
, wideShorten(myLibrary
->Songs
[i
].value().getURI(), COLS
-const_strlen(msg
)));
1465 Mpd
.UpdateDirectory(getSharedDirectory(myLibrary
->Songs
.beginV(), myLibrary
->Songs
.endV()));
1466 Statusbar::print("Tags updated successfully");
1469 # endif // HAVE_TAGLIB_H
1472 bool EditDirectoryName::canBeRun()
1474 return ((myScreen
== myBrowser
1475 && !myBrowser
->main().empty()
1476 && myBrowser
->main().current()->value().type() == MPD::Item::Type::Directory
)
1477 # ifdef HAVE_TAGLIB_H
1478 || (myScreen
->activeWindow() == myTagEditor
->Dirs
1479 && !myTagEditor
->Dirs
->empty()
1480 && myTagEditor
->Dirs
->choice() > 0)
1481 # endif // HAVE_TAGLIB_H
1482 ) && isMPDMusicDirSet();
1485 void EditDirectoryName::run()
1487 using Global::wFooter
;
1488 if (myScreen
== myBrowser
)
1490 std::string old_dir
= myBrowser
->main().current()->value().directory().path();
1491 std::string new_dir
;
1493 Statusbar::ScopedLock slock
;
1494 Statusbar::put() << NC::Format::Bold
<< "Directory: " << NC::Format::NoBold
;
1495 new_dir
= wFooter
->prompt(old_dir
);
1497 if (!new_dir
.empty() && new_dir
!= old_dir
)
1499 std::string full_old_dir
;
1500 if (!myBrowser
->isLocal())
1501 full_old_dir
+= Config
.mpd_music_dir
;
1502 full_old_dir
+= old_dir
;
1503 std::string full_new_dir
;
1504 if (!myBrowser
->isLocal())
1505 full_new_dir
+= Config
.mpd_music_dir
;
1506 full_new_dir
+= new_dir
;
1507 boost::filesystem::rename(full_old_dir
, full_new_dir
);
1508 const char msg
[] = "Directory renamed to \"%1%\"";
1509 Statusbar::printf(msg
, wideShorten(new_dir
, COLS
-const_strlen(msg
)));
1510 if (!myBrowser
->isLocal())
1511 Mpd
.UpdateDirectory(getSharedDirectory(old_dir
, new_dir
));
1512 myBrowser
->requestUpdate();
1515 # ifdef HAVE_TAGLIB_H
1516 else if (myScreen
->activeWindow() == myTagEditor
->Dirs
)
1518 std::string old_dir
= myTagEditor
->Dirs
->current()->value().first
, new_dir
;
1520 Statusbar::ScopedLock slock
;
1521 Statusbar::put() << NC::Format::Bold
<< "Directory: " << NC::Format::NoBold
;
1522 new_dir
= wFooter
->prompt(old_dir
);
1524 if (!new_dir
.empty() && new_dir
!= old_dir
)
1526 std::string full_old_dir
= Config
.mpd_music_dir
+ myTagEditor
->CurrentDir() + "/" + old_dir
;
1527 std::string full_new_dir
= Config
.mpd_music_dir
+ myTagEditor
->CurrentDir() + "/" + new_dir
;
1528 if (rename(full_old_dir
.c_str(), full_new_dir
.c_str()) == 0)
1530 const char msg
[] = "Directory renamed to \"%1%\"";
1531 Statusbar::printf(msg
, wideShorten(new_dir
, COLS
-const_strlen(msg
)));
1532 Mpd
.UpdateDirectory(myTagEditor
->CurrentDir());
1536 const char msg
[] = "Couldn't rename \"%1%\": %2%";
1537 Statusbar::printf(msg
, wideShorten(old_dir
, COLS
-const_strlen(msg
)-25), strerror(errno
));
1541 # endif // HAVE_TAGLIB_H
1544 bool EditPlaylistName::canBeRun()
1546 return (myScreen
->isActiveWindow(myPlaylistEditor
->Playlists
)
1547 && !myPlaylistEditor
->Playlists
.empty())
1548 || (myScreen
== myBrowser
1549 && !myBrowser
->main().empty()
1550 && myBrowser
->main().current()->value().type() == MPD::Item::Type::Playlist
);
1553 void EditPlaylistName::run()
1555 using Global::wFooter
;
1556 std::string old_name
, new_name
;
1557 if (myScreen
->isActiveWindow(myPlaylistEditor
->Playlists
))
1558 old_name
= myPlaylistEditor
->Playlists
.current()->value().path();
1560 old_name
= myBrowser
->main().current()->value().playlist().path();
1562 Statusbar::ScopedLock slock
;
1563 Statusbar::put() << NC::Format::Bold
<< "Playlist: " << NC::Format::NoBold
;
1564 new_name
= wFooter
->prompt(old_name
);
1566 if (!new_name
.empty() && new_name
!= old_name
)
1568 Mpd
.Rename(old_name
, new_name
);
1569 const char msg
[] = "Playlist renamed to \"%1%\"";
1570 Statusbar::printf(msg
, wideShorten(new_name
, COLS
-const_strlen(msg
)));
1574 bool EditLyrics::canBeRun()
1576 return myScreen
== myLyrics
;
1579 void EditLyrics::run()
1584 bool JumpToBrowser::canBeRun()
1586 m_song
= currentSong(myScreen
);
1587 return m_song
!= nullptr;
1590 void JumpToBrowser::run()
1592 myBrowser
->locateSong(*m_song
);
1595 bool JumpToMediaLibrary::canBeRun()
1597 m_song
= currentSong(myScreen
);
1598 return m_song
!= nullptr;
1601 void JumpToMediaLibrary::run()
1603 myLibrary
->locateSong(*m_song
);
1606 bool JumpToPlaylistEditor::canBeRun()
1608 return myScreen
== myBrowser
1609 && myBrowser
->main().current()->value().type() == MPD::Item::Type::Playlist
;
1612 void JumpToPlaylistEditor::run()
1614 myPlaylistEditor
->locatePlaylist(myBrowser
->main().current()->value().playlist());
1617 void ToggleScreenLock::run()
1619 using Global::wFooter
;
1620 using Global::myLockedScreen
;
1621 const char *msg_unlockable_screen
= "Current screen can't be locked";
1622 if (myLockedScreen
!= nullptr)
1624 BaseScreen::unlock();
1625 Actions::setResizeFlags();
1627 Statusbar::print("Screen unlocked");
1629 else if (!myScreen
->isLockable())
1631 Statusbar::print(msg_unlockable_screen
);
1635 unsigned part
= Config
.locked_screen_width_part
*100;
1636 if (Config
.ask_for_locked_screen_width_part
)
1638 Statusbar::ScopedLock slock
;
1639 Statusbar::put() << "% of the locked screen's width to be reserved (20-80): ";
1640 part
= fromString
<unsigned>(wFooter
->prompt(boost::lexical_cast
<std::string
>(part
)));
1642 boundsCheck(part
, 20u, 80u);
1643 Config
.locked_screen_width_part
= part
/100.0;
1644 if (myScreen
->lock())
1645 Statusbar::printf("Screen locked (with %1%%% width)", part
);
1647 Statusbar::print(msg_unlockable_screen
);
1651 bool JumpToTagEditor::canBeRun()
1653 # ifdef HAVE_TAGLIB_H
1654 m_song
= currentSong(myScreen
);
1655 return m_song
!= nullptr && isMPDMusicDirSet();
1658 # endif // HAVE_TAGLIB_H
1661 void JumpToTagEditor::run()
1663 # ifdef HAVE_TAGLIB_H
1664 myTagEditor
->LocateSong(*m_song
);
1665 # endif // HAVE_TAGLIB_H
1668 bool JumpToPositionInSong::canBeRun()
1670 return Status::State::player() != MPD::psStop
&& Status::State::totalTime() > 0;
1673 void JumpToPositionInSong::run()
1675 using Global::wFooter
;
1677 const MPD::Song s
= myPlaylist
->nowPlayingSong();
1681 Statusbar::ScopedLock slock
;
1682 Statusbar::put() << "Position to go (in %/m:ss/seconds(s)): ";
1683 spos
= wFooter
->prompt();
1688 if (boost::regex_match(spos
, what
, rx
.assign("([0-9]+):([0-9]{2})"))) // mm:ss
1690 auto mins
= fromString
<unsigned>(what
[1]);
1691 auto secs
= fromString
<unsigned>(what
[2]);
1692 boundsCheck(secs
, 0u, 60u);
1693 Mpd
.Seek(s
.getPosition(), mins
* 60 + secs
);
1695 else if (boost::regex_match(spos
, what
, rx
.assign("([0-9]+)s"))) // position in seconds
1697 auto secs
= fromString
<unsigned>(what
[1]);
1698 Mpd
.Seek(s
.getPosition(), secs
);
1700 else if (boost::regex_match(spos
, what
, rx
.assign("([0-9]+)[%]{0,1}"))) // position in %
1702 auto percent
= fromString
<unsigned>(what
[1]);
1703 boundsCheck(percent
, 0u, 100u);
1704 int secs
= (percent
* s
.getDuration()) / 100.0;
1705 Mpd
.Seek(s
.getPosition(), secs
);
1708 Statusbar::print("Invalid format ([m]:[ss], [s]s, [%]%, [%] accepted)");
1711 bool SelectItem::canBeRun()
1713 m_list
= dynamic_cast<NC::List
*>(myScreen
->activeWindow());
1714 return m_list
!= nullptr
1716 && m_list
->currentP()->isSelectable();
1719 void SelectItem::run()
1721 auto current
= m_list
->currentP();
1722 current
->setSelected(!current
->isSelected());
1725 bool SelectRange::canBeRun()
1727 m_list
= dynamic_cast<NC::List
*>(myScreen
->activeWindow());
1728 if (m_list
== nullptr)
1730 m_begin
= m_list
->beginP();
1731 m_end
= m_list
->endP();
1732 return findRange(m_begin
, m_end
);
1735 void SelectRange::run()
1737 for (; m_begin
!= m_end
; ++m_begin
)
1738 m_begin
->setSelected(true);
1739 Statusbar::print("Range selected");
1742 bool ReverseSelection::canBeRun()
1744 m_list
= dynamic_cast<NC::List
*>(myScreen
->activeWindow());
1745 return m_list
!= nullptr;
1748 void ReverseSelection::run()
1750 for (auto &p
: *m_list
)
1751 p
.setSelected(!p
.isSelected());
1752 Statusbar::print("Selection reversed");
1755 bool RemoveSelection::canBeRun()
1757 m_list
= dynamic_cast<NC::List
*>(myScreen
->activeWindow());
1758 return m_list
!= nullptr;
1761 void RemoveSelection::run()
1763 for (auto &p
: *m_list
)
1764 p
.setSelected(false);
1765 Statusbar::print("Selection removed");
1768 bool SelectAlbum::canBeRun()
1770 auto *w
= myScreen
->activeWindow();
1771 if (m_list
!= static_cast<void *>(w
))
1772 m_list
= dynamic_cast<NC::List
*>(w
);
1773 if (m_songs
!= static_cast<void *>(w
))
1774 m_songs
= dynamic_cast<SongList
*>(w
);
1775 return m_list
!= nullptr && !m_list
->empty()
1776 && m_songs
!= nullptr;
1779 void SelectAlbum::run()
1781 const auto front
= m_songs
->beginS(), current
= m_songs
->currentS(), end
= m_songs
->endS();
1782 auto *s
= current
->get
<Bit::Song
>();
1785 auto get
= &MPD::Song::getAlbum
;
1786 const std::string tag
= s
->getTags(get
);
1788 for (auto it
= current
; it
!= front
;)
1791 s
= it
->get
<Bit::Song
>();
1792 if (s
== nullptr || s
->getTags(get
) != tag
)
1794 it
->get
<Bit::Properties
>().setSelected(true);
1797 for (auto it
= current
;;)
1799 it
->get
<Bit::Properties
>().setSelected(true);
1802 s
= it
->get
<Bit::Song
>();
1803 if (s
== nullptr || s
->getTags(get
) != tag
)
1806 Statusbar::print("Album around cursor position selected");
1809 bool SelectFoundItems::canBeRun()
1811 m_list
= dynamic_cast<NC::List
*>(myScreen
->activeWindow());
1812 if (m_list
== nullptr || m_list
->empty())
1814 m_searchable
= dynamic_cast<Searchable
*>(myScreen
);
1815 return m_searchable
!= nullptr && m_searchable
->allowsSearching();
1818 void SelectFoundItems::run()
1820 auto current_pos
= m_list
->choice();
1821 myScreen
->activeWindow()->scroll(NC::Scroll::Home
);
1822 bool found
= m_searchable
->search(SearchDirection::Forward
, false, false);
1825 Statusbar::print("Searching for items...");
1826 m_list
->currentP()->setSelected(true);
1827 while (m_searchable
->search(SearchDirection::Forward
, false, true))
1828 m_list
->currentP()->setSelected(true);
1829 Statusbar::print("Found items selected");
1831 m_list
->highlight(current_pos
);
1834 bool AddSelectedItems::canBeRun()
1836 return myScreen
!= mySelectedItemsAdder
;
1839 void AddSelectedItems::run()
1841 mySelectedItemsAdder
->switchTo();
1844 void CropMainPlaylist::run()
1846 auto &w
= myPlaylist
->main();
1847 // cropping doesn't make sense in this case
1850 if (Config
.ask_before_clearing_playlists
)
1851 confirmAction("Do you really want to crop main playlist?");
1852 Statusbar::print("Cropping playlist...");
1853 selectCurrentIfNoneSelected(w
);
1854 cropPlaylist(w
, std::bind(&MPD::Connection::Delete
, ph::_1
, ph::_2
));
1855 Statusbar::print("Playlist cropped");
1858 bool CropPlaylist::canBeRun()
1860 return myScreen
== myPlaylistEditor
;
1863 void CropPlaylist::run()
1865 auto &w
= myPlaylistEditor
->Content
;
1866 // cropping doesn't make sense in this case
1869 assert(!myPlaylistEditor
->Playlists
.empty());
1870 std::string playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
1871 if (Config
.ask_before_clearing_playlists
)
1872 confirmAction(boost::format("Do you really want to crop playlist \"%1%\"?") % playlist
);
1873 selectCurrentIfNoneSelected(w
);
1874 Statusbar::printf("Cropping playlist \"%1%\"...", playlist
);
1875 cropPlaylist(w
, std::bind(&MPD::Connection::PlaylistDelete
, ph::_1
, playlist
, ph::_2
));
1876 Statusbar::printf("Playlist \"%1%\" cropped", playlist
);
1879 void ClearMainPlaylist::run()
1881 if (!myPlaylist
->main().empty() && Config
.ask_before_clearing_playlists
)
1882 confirmAction("Do you really want to clear main playlist?");
1883 Mpd
.ClearMainPlaylist();
1884 Statusbar::print("Playlist cleared");
1885 myPlaylist
->main().reset();
1888 bool ClearPlaylist::canBeRun()
1890 return myScreen
== myPlaylistEditor
;
1893 void ClearPlaylist::run()
1895 if (myPlaylistEditor
->Playlists
.empty())
1897 std::string playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
1898 if (Config
.ask_before_clearing_playlists
)
1899 confirmAction(boost::format("Do you really want to clear playlist \"%1%\"?") % playlist
);
1900 Mpd
.ClearPlaylist(playlist
);
1901 Statusbar::printf("Playlist \"%1%\" cleared", playlist
);
1904 bool SortPlaylist::canBeRun()
1906 if (myScreen
!= myPlaylist
)
1908 auto first
= myPlaylist
->main().begin(), last
= myPlaylist
->main().end();
1909 return findSelectedRangeAndPrintInfoIfNot(first
, last
);
1912 void SortPlaylist::run()
1914 mySortPlaylistDialog
->switchTo();
1917 bool ReversePlaylist::canBeRun()
1919 if (myScreen
!= myPlaylist
)
1921 m_begin
= myPlaylist
->main().begin();
1922 m_end
= myPlaylist
->main().end();
1923 return findSelectedRangeAndPrintInfoIfNot(m_begin
, m_end
);
1926 void ReversePlaylist::run()
1928 Statusbar::print("Reversing range...");
1929 Mpd
.StartCommandsList();
1930 for (--m_end
; m_begin
< m_end
; ++m_begin
, --m_end
)
1931 Mpd
.Swap(m_begin
->value().getPosition(), m_end
->value().getPosition());
1932 Mpd
.CommitCommandsList();
1933 Statusbar::print("Range reversed");
1936 bool ApplyFilter::canBeRun()
1938 m_filterable
= dynamic_cast<Filterable
*>(myScreen
);
1939 return m_filterable
!= nullptr
1940 && m_filterable
->allowsFiltering();
1943 void ApplyFilter::run()
1945 using Global::wFooter
;
1947 std::string filter
= m_filterable
->currentFilter();
1948 if (!filter
.empty())
1950 m_filterable
->applyFilter(filter
);
1951 myScreen
->refreshWindow();
1956 Statusbar::ScopedLock slock
;
1957 NC::Window::ScopedPromptHook
helper(
1959 Statusbar::Helpers::ApplyFilterImmediately(m_filterable
));
1960 Statusbar::put() << "Apply filter: ";
1961 filter
= wFooter
->prompt(filter
);
1963 catch (NC::PromptAborted
&)
1965 m_filterable
->applyFilter(filter
);
1970 Statusbar::printf("Filtering disabled");
1972 Statusbar::printf("Using filter \"%1%\"", filter
);
1974 if (myScreen
== myPlaylist
)
1975 myPlaylist
->reloadTotalLength();
1977 listsChangeFinisher();
1980 bool Find::canBeRun()
1982 return myScreen
== myHelp
1983 || myScreen
== myLyrics
1984 || myScreen
== myLastfm
;
1989 using Global::wFooter
;
1993 Statusbar::ScopedLock slock
;
1994 Statusbar::put() << "Find: ";
1995 token
= wFooter
->prompt();
1998 Statusbar::print("Searching...");
1999 auto s
= static_cast<Screen
<NC::Scrollpad
> *>(myScreen
);
2000 s
->main().removeProperties();
2001 if (token
.empty() || s
->main().setProperties(NC::Format::Reverse
, token
, NC::Format::NoReverse
, Config
.regex_type
))
2002 Statusbar::print("Done");
2004 Statusbar::print("No matching patterns found");
2008 bool FindItemBackward::canBeRun()
2010 auto w
= dynamic_cast<Searchable
*>(myScreen
);
2011 return w
&& w
->allowsSearching();
2014 void FindItemForward::run()
2016 findItem(SearchDirection::Forward
);
2017 listsChangeFinisher();
2020 bool FindItemForward::canBeRun()
2022 auto w
= dynamic_cast<Searchable
*>(myScreen
);
2023 return w
&& w
->allowsSearching();
2026 void FindItemBackward::run()
2028 findItem(SearchDirection::Backward
);
2029 listsChangeFinisher();
2032 bool NextFoundItem::canBeRun()
2034 return dynamic_cast<Searchable
*>(myScreen
);
2037 void NextFoundItem::run()
2039 Searchable
*w
= dynamic_cast<Searchable
*>(myScreen
);
2040 assert(w
!= nullptr);
2041 w
->search(SearchDirection::Forward
, Config
.wrapped_search
, true);
2042 listsChangeFinisher();
2045 bool PreviousFoundItem::canBeRun()
2047 return dynamic_cast<Searchable
*>(myScreen
);
2050 void PreviousFoundItem::run()
2052 Searchable
*w
= dynamic_cast<Searchable
*>(myScreen
);
2053 assert(w
!= nullptr);
2054 w
->search(SearchDirection::Backward
, Config
.wrapped_search
, true);
2055 listsChangeFinisher();
2058 void ToggleFindMode::run()
2060 Config
.wrapped_search
= !Config
.wrapped_search
;
2061 Statusbar::printf("Search mode: %1%",
2062 Config
.wrapped_search
? "Wrapped" : "Normal"
2066 void ToggleReplayGainMode::run()
2068 using Global::wFooter
;
2072 Statusbar::ScopedLock slock
;
2073 Statusbar::put() << "Replay gain mode? "
2074 << "[" << NC::Format::Bold
<< 'o' << NC::Format::NoBold
<< "ff"
2075 << "/" << NC::Format::Bold
<< 't' << NC::Format::NoBold
<< "rack"
2076 << "/" << NC::Format::Bold
<< 'a' << NC::Format::NoBold
<< "lbum"
2078 rgm
= Statusbar::Helpers::promptReturnOneOf({"t", "a", "o"})[0];
2083 Mpd
.SetReplayGainMode(MPD::rgmTrack
);
2086 Mpd
.SetReplayGainMode(MPD::rgmAlbum
);
2089 Mpd
.SetReplayGainMode(MPD::rgmOff
);
2091 default: // impossible
2092 throw std::runtime_error(
2093 (boost::format("ToggleReplayGainMode: impossible case reached: %1%") % rgm
).str()
2096 Statusbar::printf("Replay gain mode: %1%", Mpd
.GetReplayGainMode());
2099 void ToggleAddMode::run()
2101 std::string mode_desc
;
2102 switch (Config
.space_add_mode
)
2104 case SpaceAddMode::AddRemove
:
2105 Config
.space_add_mode
= SpaceAddMode::AlwaysAdd
;
2106 mode_desc
= "always add an item to playlist";
2108 case SpaceAddMode::AlwaysAdd
:
2109 Config
.space_add_mode
= SpaceAddMode::AddRemove
;
2110 mode_desc
= "add an item to playlist or remove if already added";
2113 Statusbar::printf("Add mode: %1%", mode_desc
);
2116 void ToggleMouse::run()
2118 Config
.mouse_support
= !Config
.mouse_support
;
2119 if (Config
.mouse_support
)
2120 NC::Mouse::enable();
2122 NC::Mouse::disable();
2123 Statusbar::printf("Mouse support %1%",
2124 Config
.mouse_support
? "enabled" : "disabled"
2128 void ToggleBitrateVisibility::run()
2130 Config
.display_bitrate
= !Config
.display_bitrate
;
2131 Statusbar::printf("Bitrate visibility %1%",
2132 Config
.display_bitrate
? "enabled" : "disabled"
2136 void AddRandomItems::run()
2138 using Global::wFooter
;
2141 Statusbar::ScopedLock slock
;
2142 Statusbar::put() << "Add random? "
2143 << "[" << NC::Format::Bold
<< 's' << NC::Format::NoBold
<< "ongs"
2144 << "/" << NC::Format::Bold
<< 'a' << NC::Format::NoBold
<< "rtists"
2145 << "/" << "album" << NC::Format::Bold
<< 'A' << NC::Format::NoBold
<< "rtists"
2146 << "/" << "al" << NC::Format::Bold
<< 'b' << NC::Format::NoBold
<< "ums"
2148 rnd_type
= Statusbar::Helpers::promptReturnOneOf({"s", "a", "A", "b"})[0];
2151 mpd_tag_type tag_type
= MPD_TAG_ARTIST
;
2152 std::string tag_type_str
;
2153 if (rnd_type
!= 's')
2155 tag_type
= charToTagType(rnd_type
);
2156 tag_type_str
= boost::locale::to_lower(tagTypeToString(tag_type
));
2159 tag_type_str
= "song";
2163 Statusbar::ScopedLock slock
;
2164 Statusbar::put() << "Number of random " << tag_type_str
<< "s: ";
2165 number
= fromString
<unsigned>(wFooter
->prompt());
2170 if (rnd_type
== 's')
2171 success
= Mpd
.AddRandomSongs(number
, Global::RNG
);
2173 success
= Mpd
.AddRandomTag(tag_type
, number
, Global::RNG
);
2175 Statusbar::printf("%1% random %2%%3% added to playlist", number
, tag_type_str
, number
== 1 ? "" : "s");
2179 bool ToggleBrowserSortMode::canBeRun()
2181 return myScreen
== myBrowser
;
2184 void ToggleBrowserSortMode::run()
2186 switch (Config
.browser_sort_mode
)
2188 case SortMode::Name
:
2189 Config
.browser_sort_mode
= SortMode::ModificationTime
;
2190 Statusbar::print("Sort songs by: modification time");
2192 case SortMode::ModificationTime
:
2193 Config
.browser_sort_mode
= SortMode::CustomFormat
;
2194 Statusbar::print("Sort songs by: custom format");
2196 case SortMode::CustomFormat
:
2197 Config
.browser_sort_mode
= SortMode::NoOp
;
2198 Statusbar::print("Do not sort songs");
2200 case SortMode::NoOp
:
2201 Config
.browser_sort_mode
= SortMode::Name
;
2202 Statusbar::print("Sort songs by: name");
2204 if (Config
.browser_sort_mode
!= SortMode::NoOp
)
2206 size_t sort_offset
= myBrowser
->inRootDirectory() ? 0 : 1;
2207 std::sort(myBrowser
->main().begin()+sort_offset
, myBrowser
->main().end(),
2208 LocaleBasedItemSorting(std::locale(), Config
.ignore_leading_the
, Config
.browser_sort_mode
)
2213 bool ToggleLibraryTagType::canBeRun()
2215 return (myScreen
->isActiveWindow(myLibrary
->Tags
))
2216 || (myLibrary
->columns() == 2 && myScreen
->isActiveWindow(myLibrary
->Albums
));
2219 void ToggleLibraryTagType::run()
2221 using Global::wFooter
;
2225 Statusbar::ScopedLock slock
;
2226 Statusbar::put() << "Tag type? "
2227 << "[" << NC::Format::Bold
<< 'a' << NC::Format::NoBold
<< "rtist"
2228 << "/" << "album" << NC::Format::Bold
<< 'A' << NC::Format::NoBold
<< "rtist"
2229 << "/" << NC::Format::Bold
<< 'y' << NC::Format::NoBold
<< "ear"
2230 << "/" << NC::Format::Bold
<< 'g' << NC::Format::NoBold
<< "enre"
2231 << "/" << NC::Format::Bold
<< 'c' << NC::Format::NoBold
<< "omposer"
2232 << "/" << NC::Format::Bold
<< 'p' << NC::Format::NoBold
<< "erformer"
2234 tag_type
= Statusbar::Helpers::promptReturnOneOf({"a", "A", "y", "g", "c", "p"})[0];
2236 mpd_tag_type new_tagitem
= charToTagType(tag_type
);
2237 if (new_tagitem
!= Config
.media_lib_primary_tag
)
2239 Config
.media_lib_primary_tag
= new_tagitem
;
2240 std::string item_type
= tagTypeToString(Config
.media_lib_primary_tag
);
2241 myLibrary
->Tags
.setTitle(Config
.titles_visibility
? item_type
+ "s" : "");
2242 myLibrary
->Tags
.reset();
2243 item_type
= boost::locale::to_lower(item_type
);
2244 std::string and_mtime
= Config
.media_library_sort_by_mtime
?
2247 if (myLibrary
->columns() == 2)
2249 myLibrary
->Songs
.clear();
2250 myLibrary
->Albums
.reset();
2251 myLibrary
->Albums
.clear();
2252 myLibrary
->Albums
.setTitle(Config
.titles_visibility
? "Albums (sorted by " + item_type
+ and_mtime
+ ")" : "");
2253 myLibrary
->Albums
.display();
2257 myLibrary
->Tags
.clear();
2258 myLibrary
->Tags
.display();
2260 Statusbar::printf("Switched to the list of %1%s", item_type
);
2264 bool ToggleMediaLibrarySortMode::canBeRun()
2266 return myScreen
== myLibrary
;
2269 void ToggleMediaLibrarySortMode::run()
2271 myLibrary
->toggleSortMode();
2274 bool FetchLyricsInBackground::canBeRun()
2276 m_hs
= dynamic_cast<HasSongs
*>(myScreen
);
2277 return m_hs
!= nullptr && m_hs
->itemAvailable();
2280 void FetchLyricsInBackground::run()
2282 auto songs
= m_hs
->getSelectedSongs();
2283 for (const auto &s
: songs
)
2284 myLyrics
->fetchInBackground(s
, true);
2285 Statusbar::print("Selected songs queued for lyrics fetching");
2288 bool RefetchLyrics::canBeRun()
2290 return myScreen
== myLyrics
;
2293 void RefetchLyrics::run()
2295 myLyrics
->refetchCurrent();
2298 bool SetSelectedItemsPriority::canBeRun()
2300 if (Mpd
.Version() < 17)
2302 Statusbar::print("Priorities are supported in MPD >= 0.17.0");
2305 return myScreen
== myPlaylist
&& !myPlaylist
->main().empty();
2308 void SetSelectedItemsPriority::run()
2310 using Global::wFooter
;
2314 Statusbar::ScopedLock slock
;
2315 Statusbar::put() << "Set priority [0-255]: ";
2316 prio
= fromString
<unsigned>(wFooter
->prompt());
2317 boundsCheck(prio
, 0u, 255u);
2319 myPlaylist
->setSelectedItemsPriority(prio
);
2322 bool ToggleOutput::canBeRun()
2324 #ifdef ENABLE_OUTPUTS
2325 return myScreen
== myOutputs
;
2328 #endif // ENABLE_OUTPUTS
2331 void ToggleOutput::run()
2333 #ifdef ENABLE_OUTPUTS
2334 myOutputs
->toggleOutput();
2335 #endif // ENABLE_OUTPUTS
2338 bool ToggleVisualizationType::canBeRun()
2340 # ifdef ENABLE_VISUALIZER
2341 return myScreen
== myVisualizer
;
2344 # endif // ENABLE_VISUALIZER
2347 void ToggleVisualizationType::run()
2349 # ifdef ENABLE_VISUALIZER
2350 myVisualizer
->ToggleVisualizationType();
2351 # endif // ENABLE_VISUALIZER
2354 bool SetVisualizerSampleMultiplier::canBeRun()
2356 # ifdef ENABLE_VISUALIZER
2357 return myScreen
== myVisualizer
;
2360 # endif // ENABLE_VISUALIZER
2363 void SetVisualizerSampleMultiplier::run()
2365 # ifdef ENABLE_VISUALIZER
2366 using Global::wFooter
;
2370 Statusbar::ScopedLock slock
;
2371 Statusbar::put() << "Set visualizer sample multiplier: ";
2372 multiplier
= fromString
<double>(wFooter
->prompt());
2373 lowerBoundCheck(multiplier
, 0.0);
2374 Config
.visualizer_sample_multiplier
= multiplier
;
2376 Statusbar::printf("Visualizer sample multiplier set to %1%", multiplier
);
2377 # endif // ENABLE_VISUALIZER
2380 void ShowSongInfo::run()
2382 mySongInfo
->switchTo();
2385 bool ShowArtistInfo::canBeRun()
2387 return myScreen
== myLastfm
2388 || (myScreen
->isActiveWindow(myLibrary
->Tags
)
2389 && !myLibrary
->Tags
.empty()
2390 && Config
.media_lib_primary_tag
== MPD_TAG_ARTIST
)
2391 || currentSong(myScreen
);
2394 void ShowArtistInfo::run()
2396 if (myScreen
== myLastfm
)
2398 myLastfm
->switchTo();
2403 if (myScreen
->isActiveWindow(myLibrary
->Tags
))
2405 assert(!myLibrary
->Tags
.empty());
2406 assert(Config
.media_lib_primary_tag
== MPD_TAG_ARTIST
);
2407 artist
= myLibrary
->Tags
.current()->value().tag();
2411 auto s
= currentSong(myScreen
);
2413 artist
= s
->getArtist();
2416 if (!artist
.empty())
2418 myLastfm
->queueJob(new LastFm::ArtistInfo(artist
, Config
.lastfm_preferred_language
));
2419 myLastfm
->switchTo();
2423 bool ShowLyrics::canBeRun()
2425 if (myScreen
== myLyrics
)
2432 m_song
= currentSong(myScreen
);
2433 return m_song
!= nullptr;
2437 void ShowLyrics::run()
2439 if (m_song
!= nullptr)
2440 myLyrics
->fetch(*m_song
);
2441 myLyrics
->switchTo();
2446 ExitMainLoop
= true;
2449 void NextScreen::run()
2451 if (Config
.screen_switcher_previous
)
2453 if (auto tababble
= dynamic_cast<Tabbable
*>(myScreen
))
2454 tababble
->switchToPreviousScreen();
2456 else if (!Config
.screen_sequence
.empty())
2458 const auto &seq
= Config
.screen_sequence
;
2459 auto screen_type
= std::find(seq
.begin(), seq
.end(), myScreen
->type());
2460 if (++screen_type
== seq
.end())
2461 toScreen(seq
.front())->switchTo();
2463 toScreen(*screen_type
)->switchTo();
2467 void PreviousScreen::run()
2469 if (Config
.screen_switcher_previous
)
2471 if (auto tababble
= dynamic_cast<Tabbable
*>(myScreen
))
2472 tababble
->switchToPreviousScreen();
2474 else if (!Config
.screen_sequence
.empty())
2476 const auto &seq
= Config
.screen_sequence
;
2477 auto screen_type
= std::find(seq
.begin(), seq
.end(), myScreen
->type());
2478 if (screen_type
== seq
.begin())
2479 toScreen(seq
.back())->switchTo();
2481 toScreen(*--screen_type
)->switchTo();
2485 bool ShowHelp::canBeRun()
2487 return myScreen
!= myHelp
2488 # ifdef HAVE_TAGLIB_H
2489 && myScreen
!= myTinyTagEditor
2490 # endif // HAVE_TAGLIB_H
2494 void ShowHelp::run()
2499 bool ShowPlaylist::canBeRun()
2501 return myScreen
!= myPlaylist
2502 # ifdef HAVE_TAGLIB_H
2503 && myScreen
!= myTinyTagEditor
2504 # endif // HAVE_TAGLIB_H
2508 void ShowPlaylist::run()
2510 myPlaylist
->switchTo();
2513 bool ShowBrowser::canBeRun()
2515 return myScreen
!= myBrowser
2516 # ifdef HAVE_TAGLIB_H
2517 && myScreen
!= myTinyTagEditor
2518 # endif // HAVE_TAGLIB_H
2522 void ShowBrowser::run()
2524 myBrowser
->switchTo();
2527 bool ChangeBrowseMode::canBeRun()
2529 return myScreen
== myBrowser
;
2532 void ChangeBrowseMode::run()
2534 myBrowser
->changeBrowseMode();
2537 bool ShowSearchEngine::canBeRun()
2539 return myScreen
!= mySearcher
2540 # ifdef HAVE_TAGLIB_H
2541 && myScreen
!= myTinyTagEditor
2542 # endif // HAVE_TAGLIB_H
2546 void ShowSearchEngine::run()
2548 mySearcher
->switchTo();
2551 bool ResetSearchEngine::canBeRun()
2553 return myScreen
== mySearcher
;
2556 void ResetSearchEngine::run()
2558 mySearcher
->reset();
2561 bool ShowMediaLibrary::canBeRun()
2563 return myScreen
!= myLibrary
2564 # ifdef HAVE_TAGLIB_H
2565 && myScreen
!= myTinyTagEditor
2566 # endif // HAVE_TAGLIB_H
2570 void ShowMediaLibrary::run()
2572 myLibrary
->switchTo();
2575 bool ToggleMediaLibraryColumnsMode::canBeRun()
2577 return myScreen
== myLibrary
;
2580 void ToggleMediaLibraryColumnsMode::run()
2582 myLibrary
->toggleColumnsMode();
2583 myLibrary
->refresh();
2586 bool ShowPlaylistEditor::canBeRun()
2588 return myScreen
!= myPlaylistEditor
2589 # ifdef HAVE_TAGLIB_H
2590 && myScreen
!= myTinyTagEditor
2591 # endif // HAVE_TAGLIB_H
2595 void ShowPlaylistEditor::run()
2597 myPlaylistEditor
->switchTo();
2600 bool ShowTagEditor::canBeRun()
2602 # ifdef HAVE_TAGLIB_H
2603 return myScreen
!= myTagEditor
2604 && myScreen
!= myTinyTagEditor
;
2607 # endif // HAVE_TAGLIB_H
2610 void ShowTagEditor::run()
2612 # ifdef HAVE_TAGLIB_H
2613 if (isMPDMusicDirSet())
2614 myTagEditor
->switchTo();
2615 # endif // HAVE_TAGLIB_H
2618 bool ShowOutputs::canBeRun()
2620 # ifdef ENABLE_OUTPUTS
2621 return myScreen
!= myOutputs
2622 # ifdef HAVE_TAGLIB_H
2623 && myScreen
!= myTinyTagEditor
2624 # endif // HAVE_TAGLIB_H
2628 # endif // ENABLE_OUTPUTS
2631 void ShowOutputs::run()
2633 # ifdef ENABLE_OUTPUTS
2634 myOutputs
->switchTo();
2635 # endif // ENABLE_OUTPUTS
2638 bool ShowVisualizer::canBeRun()
2640 # ifdef ENABLE_VISUALIZER
2641 return myScreen
!= myVisualizer
2642 # ifdef HAVE_TAGLIB_H
2643 && myScreen
!= myTinyTagEditor
2644 # endif // HAVE_TAGLIB_H
2648 # endif // ENABLE_VISUALIZER
2651 void ShowVisualizer::run()
2653 # ifdef ENABLE_VISUALIZER
2654 myVisualizer
->switchTo();
2655 # endif // ENABLE_VISUALIZER
2658 bool ShowClock::canBeRun()
2660 # ifdef ENABLE_CLOCK
2661 return myScreen
!= myClock
2662 # ifdef HAVE_TAGLIB_H
2663 && myScreen
!= myTinyTagEditor
2664 # endif // HAVE_TAGLIB_H
2668 # endif // ENABLE_CLOCK
2671 void ShowClock::run()
2673 # ifdef ENABLE_CLOCK
2674 myClock
->switchTo();
2675 # endif // ENABLE_CLOCK
2678 #ifdef HAVE_TAGLIB_H
2679 bool ShowServerInfo::canBeRun()
2681 return myScreen
!= myTinyTagEditor
;
2683 #endif // HAVE_TAGLIB_H
2685 void ShowServerInfo::run()
2687 myServerInfo
->switchTo();
2694 void populateActions()
2696 AvailableActions
.resize(static_cast<size_t>(Actions::Type::_numberOfActions
));
2697 auto insert_action
= [](Actions::BaseAction
*a
) {
2698 AvailableActions
.at(static_cast<size_t>(a
->type())).reset(a
);
2700 insert_action(new Actions::Dummy());
2701 insert_action(new Actions::UpdateEnvironment());
2702 insert_action(new Actions::MouseEvent());
2703 insert_action(new Actions::ScrollUp());
2704 insert_action(new Actions::ScrollDown());
2705 insert_action(new Actions::ScrollUpArtist());
2706 insert_action(new Actions::ScrollUpAlbum());
2707 insert_action(new Actions::ScrollDownArtist());
2708 insert_action(new Actions::ScrollDownAlbum());
2709 insert_action(new Actions::PageUp());
2710 insert_action(new Actions::PageDown());
2711 insert_action(new Actions::MoveHome());
2712 insert_action(new Actions::MoveEnd());
2713 insert_action(new Actions::ToggleInterface());
2714 insert_action(new Actions::JumpToParentDirectory());
2715 insert_action(new Actions::RunAction());
2716 insert_action(new Actions::SelectItem());
2717 insert_action(new Actions::SelectRange());
2718 insert_action(new Actions::PreviousColumn());
2719 insert_action(new Actions::NextColumn());
2720 insert_action(new Actions::MasterScreen());
2721 insert_action(new Actions::SlaveScreen());
2722 insert_action(new Actions::VolumeUp());
2723 insert_action(new Actions::VolumeDown());
2724 insert_action(new Actions::AddItemToPlaylist());
2725 insert_action(new Actions::DeletePlaylistItems());
2726 insert_action(new Actions::DeleteStoredPlaylist());
2727 insert_action(new Actions::DeleteBrowserItems());
2728 insert_action(new Actions::ReplaySong());
2729 insert_action(new Actions::PreviousSong());
2730 insert_action(new Actions::NextSong());
2731 insert_action(new Actions::Pause());
2732 insert_action(new Actions::Stop());
2733 insert_action(new Actions::ExecuteCommand());
2734 insert_action(new Actions::SavePlaylist());
2735 insert_action(new Actions::MoveSortOrderUp());
2736 insert_action(new Actions::MoveSortOrderDown());
2737 insert_action(new Actions::MoveSelectedItemsUp());
2738 insert_action(new Actions::MoveSelectedItemsDown());
2739 insert_action(new Actions::MoveSelectedItemsTo());
2740 insert_action(new Actions::Add());
2741 insert_action(new Actions::PlayItem());
2742 insert_action(new Actions::SeekForward());
2743 insert_action(new Actions::SeekBackward());
2744 insert_action(new Actions::ToggleDisplayMode());
2745 insert_action(new Actions::ToggleSeparatorsBetweenAlbums());
2746 insert_action(new Actions::ToggleLyricsUpdateOnSongChange());
2747 insert_action(new Actions::ToggleLyricsFetcher());
2748 insert_action(new Actions::ToggleFetchingLyricsInBackground());
2749 insert_action(new Actions::TogglePlayingSongCentering());
2750 insert_action(new Actions::UpdateDatabase());
2751 insert_action(new Actions::JumpToPlayingSong());
2752 insert_action(new Actions::ToggleRepeat());
2753 insert_action(new Actions::Shuffle());
2754 insert_action(new Actions::ToggleRandom());
2755 insert_action(new Actions::StartSearching());
2756 insert_action(new Actions::SaveTagChanges());
2757 insert_action(new Actions::ToggleSingle());
2758 insert_action(new Actions::ToggleConsume());
2759 insert_action(new Actions::ToggleCrossfade());
2760 insert_action(new Actions::SetCrossfade());
2761 insert_action(new Actions::SetVolume());
2762 insert_action(new Actions::EnterDirectory());
2763 insert_action(new Actions::EditSong());
2764 insert_action(new Actions::EditLibraryTag());
2765 insert_action(new Actions::EditLibraryAlbum());
2766 insert_action(new Actions::EditDirectoryName());
2767 insert_action(new Actions::EditPlaylistName());
2768 insert_action(new Actions::EditLyrics());
2769 insert_action(new Actions::JumpToBrowser());
2770 insert_action(new Actions::JumpToMediaLibrary());
2771 insert_action(new Actions::JumpToPlaylistEditor());
2772 insert_action(new Actions::ToggleScreenLock());
2773 insert_action(new Actions::JumpToTagEditor());
2774 insert_action(new Actions::JumpToPositionInSong());
2775 insert_action(new Actions::ReverseSelection());
2776 insert_action(new Actions::RemoveSelection());
2777 insert_action(new Actions::SelectAlbum());
2778 insert_action(new Actions::SelectFoundItems());
2779 insert_action(new Actions::AddSelectedItems());
2780 insert_action(new Actions::CropMainPlaylist());
2781 insert_action(new Actions::CropPlaylist());
2782 insert_action(new Actions::ClearMainPlaylist());
2783 insert_action(new Actions::ClearPlaylist());
2784 insert_action(new Actions::SortPlaylist());
2785 insert_action(new Actions::ReversePlaylist());
2786 insert_action(new Actions::ApplyFilter());
2787 insert_action(new Actions::Find());
2788 insert_action(new Actions::FindItemForward());
2789 insert_action(new Actions::FindItemBackward());
2790 insert_action(new Actions::NextFoundItem());
2791 insert_action(new Actions::PreviousFoundItem());
2792 insert_action(new Actions::ToggleFindMode());
2793 insert_action(new Actions::ToggleReplayGainMode());
2794 insert_action(new Actions::ToggleAddMode());
2795 insert_action(new Actions::ToggleMouse());
2796 insert_action(new Actions::ToggleBitrateVisibility());
2797 insert_action(new Actions::AddRandomItems());
2798 insert_action(new Actions::ToggleBrowserSortMode());
2799 insert_action(new Actions::ToggleLibraryTagType());
2800 insert_action(new Actions::ToggleMediaLibrarySortMode());
2801 insert_action(new Actions::FetchLyricsInBackground());
2802 insert_action(new Actions::RefetchLyrics());
2803 insert_action(new Actions::SetSelectedItemsPriority());
2804 insert_action(new Actions::ToggleOutput());
2805 insert_action(new Actions::ToggleVisualizationType());
2806 insert_action(new Actions::SetVisualizerSampleMultiplier());
2807 insert_action(new Actions::ShowSongInfo());
2808 insert_action(new Actions::ShowArtistInfo());
2809 insert_action(new Actions::ShowLyrics());
2810 insert_action(new Actions::Quit());
2811 insert_action(new Actions::NextScreen());
2812 insert_action(new Actions::PreviousScreen());
2813 insert_action(new Actions::ShowHelp());
2814 insert_action(new Actions::ShowPlaylist());
2815 insert_action(new Actions::ShowBrowser());
2816 insert_action(new Actions::ChangeBrowseMode());
2817 insert_action(new Actions::ShowSearchEngine());
2818 insert_action(new Actions::ResetSearchEngine());
2819 insert_action(new Actions::ShowMediaLibrary());
2820 insert_action(new Actions::ToggleMediaLibraryColumnsMode());
2821 insert_action(new Actions::ShowPlaylistEditor());
2822 insert_action(new Actions::ShowTagEditor());
2823 insert_action(new Actions::ShowOutputs());
2824 insert_action(new Actions::ShowVisualizer());
2825 insert_action(new Actions::ShowClock());
2826 insert_action(new Actions::ShowServerInfo());
2827 for (size_t i
= 0; i
< AvailableActions
.size(); ++i
)
2829 if (AvailableActions
[i
] == nullptr)
2830 throw std::logic_error("undefined action at position "
2831 + boost::lexical_cast
<std::string
>(i
));
2835 bool scrollTagCanBeRun(NC::List
*&list
, SongList
*&songs
)
2837 auto w
= myScreen
->activeWindow();
2838 if (list
!= static_cast<void *>(w
))
2839 list
= dynamic_cast<NC::List
*>(w
);
2840 if (songs
!= static_cast<void *>(w
))
2841 songs
= dynamic_cast<SongList
*>(w
);
2842 return list
!= nullptr && !list
->empty()
2843 && songs
!= nullptr;
2846 void scrollTagUpRun(NC::List
*list
, SongList
*songs
, MPD::Song::GetFunction get
)
2848 const auto front
= songs
->beginS();
2849 auto it
= songs
->currentS();
2850 if (auto *s
= it
->get
<Bit::Song
>())
2852 const std::string tag
= s
->getTags(get
);
2856 s
= it
->get
<Bit::Song
>();
2857 if (s
== nullptr || s
->getTags(get
) != tag
)
2860 list
->highlight(it
-front
);
2864 void scrollTagDownRun(NC::List
*list
, SongList
*songs
, MPD::Song::GetFunction get
)
2866 const auto front
= songs
->beginS(), back
= --songs
->endS();
2867 auto it
= songs
->currentS();
2868 if (auto *s
= it
->get
<Bit::Song
>())
2870 const std::string tag
= s
->getTags(get
);
2874 s
= it
->get
<Bit::Song
>();
2875 if (s
== nullptr || s
->getTags(get
) != tag
)
2878 list
->highlight(it
-front
);
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 int old_timeout
= wFooter
->getTimeout();
2902 wFooter
->setTimeout(BaseScreen::defaultWindowTimeout
);
2904 // Accept single action of a given type or action chain for which all actions
2905 // can be run and one of them is of the given type. This will still not work
2906 // in some contrived cases, but allows for more flexibility than accepting
2907 // single actions only.
2908 auto hasRunnableAction
= [](BindingsConfiguration::BindingIteratorPair
&bindings
, 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
);
2943 auto k
= Bindings
.get(input
);
2944 if (hasRunnableAction(k
, Actions::Type::SeekForward
))
2946 if (songpos
< Status::State::totalTime())
2947 songpos
= std::min(songpos
+ howmuch
, Status::State::totalTime());
2949 else if (hasRunnableAction(k
, Actions::Type::SeekBackward
))
2953 if (songpos
< howmuch
)
2962 *wFooter
<< NC::Format::Bold
;
2963 std::string tracklength
;
2964 // FIXME: merge this with the code in status.cpp
2965 switch (Config
.design
)
2967 case Design::Classic
:
2969 if (Config
.display_remaining_time
)
2972 tracklength
+= MPD::Song::ShowTime(Status::State::totalTime()-songpos
);
2975 tracklength
+= MPD::Song::ShowTime(songpos
);
2977 tracklength
+= MPD::Song::ShowTime(Status::State::totalTime());
2979 *wFooter
<< NC::XY(wFooter
->getWidth()-tracklength
.length(), 1) << tracklength
;
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) << tracklength
<< " ";
2995 *wFooter
<< NC::Format::NoBold
;
2996 Progressbar::draw(songpos
, Status::State::totalTime());
2999 SeekingInProgress
= false;
3000 Mpd
.Seek(Status::State::currentSongPosition(), songpos
);
3002 wFooter
->setTimeout(old_timeout
);
3005 void findItem(const SearchDirection direction
)
3007 using Global::wFooter
;
3009 Searchable
*w
= dynamic_cast<Searchable
*>(myScreen
);
3010 assert(w
!= nullptr);
3011 assert(w
->allowsSearching());
3013 std::string constraint
= w
->searchConstraint();
3016 Statusbar::ScopedLock slock
;
3017 NC::Window::ScopedPromptHook
prompt_hook(
3019 Statusbar::Helpers::FindImmediately(w
, direction
));
3020 Statusbar::put() << (boost::format("Find %1%: ") % direction
).str();
3021 constraint
= wFooter
->prompt(constraint
);
3023 catch (NC::PromptAborted
&)
3025 w
->setSearchConstraint(constraint
);
3026 w
->search(direction
, Config
.wrapped_search
, false);
3030 if (constraint
.empty())
3032 Statusbar::printf("Constraint unset");
3033 w
->clearSearchConstraint();
3036 Statusbar::printf("Using constraint \"%1%\"", constraint
);
3039 void listsChangeFinisher()
3041 if (myScreen
== myLibrary
3042 || myScreen
== myPlaylistEditor
3043 # ifdef HAVE_TAGLIB_H
3044 || myScreen
== myTagEditor
3045 # endif // HAVE_TAGLIB_H
3048 if (myScreen
->activeWindow() == &myLibrary
->Tags
)
3050 myLibrary
->Albums
.clear();
3051 myLibrary
->Albums
.refresh();
3052 myLibrary
->Songs
.clear();
3053 myLibrary
->Songs
.refresh();
3054 myLibrary
->updateTimer();
3056 else if (myScreen
->activeWindow() == &myLibrary
->Albums
)
3058 myLibrary
->Songs
.clear();
3059 myLibrary
->Songs
.refresh();
3060 myLibrary
->updateTimer();
3062 else if (myScreen
->isActiveWindow(myPlaylistEditor
->Playlists
))
3064 myPlaylistEditor
->Content
.clear();
3065 myPlaylistEditor
->Content
.refresh();
3066 myPlaylistEditor
->updateTimer();
3068 # ifdef HAVE_TAGLIB_H
3069 else if (myScreen
->activeWindow() == myTagEditor
->Dirs
)
3071 myTagEditor
->Tags
->clear();
3073 # endif // HAVE_TAGLIB_H