1 /***************************************************************************
2 * Copyright (C) 2008-2014 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
24 #include <boost/array.hpp>
25 #include <boost/date_time/posix_time/posix_time.hpp>
26 #include <boost/filesystem/operations.hpp>
27 #include <boost/locale/conversion.hpp>
28 #include <boost/lexical_cast.hpp>
39 #include "statusbar.h"
40 #include "utility/comparators.h"
41 #include "utility/conversion.h"
47 #include "media_library.h"
48 #include "menu_impl.h"
52 #include "playlist_editor.h"
53 #include "sort_playlist.h"
54 #include "search_engine.h"
55 #include "sel_items_adder.h"
56 #include "server_info.h"
57 #include "song_info.h"
59 #include "utility/readline.h"
60 #include "utility/string.h"
61 #include "utility/type_conversions.h"
62 #include "tag_editor.h"
63 #include "tiny_tag_editor.h"
64 #include "visualizer.h"
71 #endif // HAVE_TAGLIB_H
73 using Global::myScreen
;
75 namespace ph
= std::placeholders
;
80 Actions::BaseAction
*, static_cast<size_t>(Actions::Type::_numberOfActions
)
83 void populateActions();
85 bool scrollTagCanBeRun(NC::List
*&list
, SongList
*&songs
);
86 void scrollTagUpRun(NC::List
*list
, SongList
*songs
, MPD::Song::GetFunction get
);
87 void scrollTagDownRun(NC::List
*list
, SongList
*songs
, MPD::Song::GetFunction get
);
90 void findItem(const SearchDirection direction
);
91 void listsChangeFinisher();
93 template <typename Iterator
>
94 bool findSelectedRangeAndPrintInfoIfNot(Iterator
&first
, Iterator
&last
)
96 bool success
= findSelectedRange(first
, last
);
98 Statusbar::print("No range selected");
106 bool OriginalStatusbarVisibility
;
107 bool ExitMainLoop
= false;
113 void validateScreenSize()
115 using Global::MainHeight
;
117 if (COLS
< 30 || MainHeight
< 5)
120 std::cout
<< "Screen is too small to handle ncmpcpp correctly\n";
125 void initializeScreens()
128 myPlaylist
= new Playlist
;
129 myBrowser
= new Browser
;
130 mySearcher
= new SearchEngine
;
131 myLibrary
= new MediaLibrary
;
132 myPlaylistEditor
= new PlaylistEditor
;
133 myLyrics
= new Lyrics
;
134 mySelectedItemsAdder
= new SelectedItemsAdder
;
135 mySongInfo
= new SongInfo
;
136 myServerInfo
= new ServerInfo
;
137 mySortPlaylistDialog
= new SortPlaylistDialog
;
139 # ifdef HAVE_CURL_CURL_H
140 myLastfm
= new Lastfm
;
141 # endif // HAVE_CURL_CURL_H
143 # ifdef HAVE_TAGLIB_H
144 myTinyTagEditor
= new TinyTagEditor
;
145 myTagEditor
= new TagEditor
;
146 # endif // HAVE_TAGLIB_H
148 # ifdef ENABLE_VISUALIZER
149 myVisualizer
= new Visualizer
;
150 # endif // ENABLE_VISUALIZER
152 # ifdef ENABLE_OUTPUTS
153 myOutputs
= new Outputs
;
154 # endif // ENABLE_OUTPUTS
158 # endif // ENABLE_CLOCK
162 void setResizeFlags()
164 myHelp
->hasToBeResized
= 1;
165 myPlaylist
->hasToBeResized
= 1;
166 myBrowser
->hasToBeResized
= 1;
167 mySearcher
->hasToBeResized
= 1;
168 myLibrary
->hasToBeResized
= 1;
169 myPlaylistEditor
->hasToBeResized
= 1;
170 myLyrics
->hasToBeResized
= 1;
171 mySelectedItemsAdder
->hasToBeResized
= 1;
172 mySongInfo
->hasToBeResized
= 1;
173 myServerInfo
->hasToBeResized
= 1;
174 mySortPlaylistDialog
->hasToBeResized
= 1;
176 # ifdef HAVE_CURL_CURL_H
177 myLastfm
->hasToBeResized
= 1;
178 # endif // HAVE_CURL_CURL_H
180 # ifdef HAVE_TAGLIB_H
181 myTinyTagEditor
->hasToBeResized
= 1;
182 myTagEditor
->hasToBeResized
= 1;
183 # endif // HAVE_TAGLIB_H
185 # ifdef ENABLE_VISUALIZER
186 myVisualizer
->hasToBeResized
= 1;
187 # endif // ENABLE_VISUALIZER
189 # ifdef ENABLE_OUTPUTS
190 myOutputs
->hasToBeResized
= 1;
191 # endif // ENABLE_OUTPUTS
194 myClock
->hasToBeResized
= 1;
195 # endif // ENABLE_CLOCK
198 void resizeScreen(bool reload_main_window
)
200 using Global::MainHeight
;
201 using Global::wHeader
;
202 using Global::wFooter
;
204 // update internal screen dimensions
205 if (reload_main_window
)
207 rl_resize_terminal();
212 MainHeight
= LINES
-(Config
.design
== Design::Alternative
? 7 : 4);
214 validateScreenSize();
216 if (!Config
.header_visibility
)
218 if (!Config
.statusbar_visibility
)
223 applyToVisibleWindows(&BaseScreen::resize
);
225 if (Config
.header_visibility
|| Config
.design
== Design::Alternative
)
226 wHeader
->resize(COLS
, HeaderHeight
);
228 FooterStartY
= LINES
-(Config
.statusbar_visibility
? 2 : 1);
229 wFooter
->moveTo(0, FooterStartY
);
230 wFooter
->resize(COLS
, Config
.statusbar_visibility
? 2 : 1);
232 applyToVisibleWindows(&BaseScreen::refresh
);
234 Status::Changes::elapsedTime(false);
235 Status::Changes::playerState();
236 // Note: routines for drawing separator if alternative user
237 // interface is active and header is hidden are placed in
238 // NcmpcppStatusChanges.StatusFlags
239 Status::Changes::flags();
245 void setWindowsDimensions()
247 using Global::MainStartY
;
248 using Global::MainHeight
;
250 MainStartY
= Config
.design
== Design::Alternative
? 5 : 2;
251 MainHeight
= LINES
-(Config
.design
== Design::Alternative
? 7 : 4);
253 if (!Config
.header_visibility
)
258 if (!Config
.statusbar_visibility
)
261 HeaderHeight
= Config
.design
== Design::Alternative
? (Config
.header_visibility
? 5 : 3) : 2;
262 FooterStartY
= LINES
-(Config
.statusbar_visibility
? 2 : 1);
263 FooterHeight
= Config
.statusbar_visibility
? 2 : 1;
266 void confirmAction(const boost::format
&description
)
268 Statusbar::ScopedLock slock
;
269 Statusbar::put() << description
.str()
270 << " [" << NC::Format::Bold
<< 'y' << NC::Format::NoBold
271 << '/' << NC::Format::Bold
<< 'n' << NC::Format::NoBold
273 auto answer
= Statusbar::Helpers::promptReturnOneOf({"y", "n"});
275 throw NC::PromptAborted(std::move(answer
));
278 bool isMPDMusicDirSet()
280 if (Config
.mpd_music_dir
.empty())
282 Statusbar::print("Proper mpd_music_dir variable has to be set in configuration file");
288 BaseAction
&get(Actions::Type at
)
290 if (AvailableActions
[1] == nullptr)
292 BaseAction
*action
= AvailableActions
[static_cast<size_t>(at
)];
293 // action should be always present if action type in queried
294 assert(action
!= nullptr);
298 BaseAction
*get(const std::string
&name
)
300 BaseAction
*result
= 0;
301 if (AvailableActions
[1] == nullptr)
303 for (auto it
= AvailableActions
.begin(); it
!= AvailableActions
.end(); ++it
)
305 if (*it
!= nullptr && (*it
)->name() == name
)
314 UpdateEnvironment::UpdateEnvironment()
315 : BaseAction(Type::UpdateEnvironment
, "update_environment")
316 , m_past(boost::posix_time::from_time_t(0))
319 void UpdateEnvironment::run(bool update_timer
, bool refresh_window
)
323 // update timer, status if necessary etc.
324 Status::trace(update_timer
, true);
327 if ((myScreen
== myPlaylist
|| myScreen
== myBrowser
|| myScreen
== myLyrics
)
328 && (Timer
- m_past
> boost::posix_time::milliseconds(500))
336 myScreen
->refreshWindow();
339 void UpdateEnvironment::run()
344 bool MouseEvent::canBeRun()
346 return Config
.mouse_support
;
349 void MouseEvent::run()
351 using Global::VolumeState
;
352 using Global::wFooter
;
354 m_old_mouse_event
= m_mouse_event
;
355 m_mouse_event
= wFooter
->getMouseEvent();
357 //Statusbar::printf("(%1%, %2%, %3%)", m_mouse_event.bstate, m_mouse_event.x, m_mouse_event.y);
359 if (m_mouse_event
.bstate
& BUTTON1_PRESSED
360 && m_mouse_event
.y
== LINES
-(Config
.statusbar_visibility
? 2 : 1)
363 if (Status::State::player() == MPD::psStop
)
365 Mpd
.Seek(Status::State::currentSongPosition(),
366 Status::State::totalTime()*m_mouse_event
.x
/double(COLS
));
368 else if (m_mouse_event
.bstate
& BUTTON1_PRESSED
369 && (Config
.statusbar_visibility
|| Config
.design
== Design::Alternative
)
370 && Status::State::player() != MPD::psStop
371 && m_mouse_event
.y
== (Config
.design
== Design::Alternative
? 1 : LINES
-1)
372 && m_mouse_event
.x
< 9
377 else if ((m_mouse_event
.bstate
& BUTTON5_PRESSED
|| m_mouse_event
.bstate
& BUTTON4_PRESSED
)
378 && (Config
.header_visibility
|| Config
.design
== Design::Alternative
)
379 && m_mouse_event
.y
== 0 && size_t(m_mouse_event
.x
) > COLS
-VolumeState
.length()
382 if (m_mouse_event
.bstate
& BUTTON5_PRESSED
)
383 get(Type::VolumeDown
).execute();
385 get(Type::VolumeUp
).execute();
387 else if (m_mouse_event
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
| BUTTON4_PRESSED
| BUTTON5_PRESSED
))
388 myScreen
->mouseButtonPressed(m_mouse_event
);
393 myScreen
->scroll(NC::Scroll::Up
);
394 listsChangeFinisher();
397 void ScrollDown::run()
399 myScreen
->scroll(NC::Scroll::Down
);
400 listsChangeFinisher();
403 bool ScrollUpArtist::canBeRun()
405 return scrollTagCanBeRun(m_list
, m_songs
);
408 void ScrollUpArtist::run()
410 scrollTagUpRun(m_list
, m_songs
, &MPD::Song::getArtist
);
413 bool ScrollUpAlbum::canBeRun()
415 return scrollTagCanBeRun(m_list
, m_songs
);
418 void ScrollUpAlbum::run()
420 scrollTagUpRun(m_list
, m_songs
, &MPD::Song::getAlbum
);
423 bool ScrollDownArtist::canBeRun()
425 return scrollTagCanBeRun(m_list
, m_songs
);
428 void ScrollDownArtist::run()
430 scrollTagDownRun(m_list
, m_songs
, &MPD::Song::getArtist
);
433 bool ScrollDownAlbum::canBeRun()
435 return scrollTagCanBeRun(m_list
, m_songs
);
438 void ScrollDownAlbum::run()
440 scrollTagDownRun(m_list
, m_songs
, &MPD::Song::getAlbum
);
445 myScreen
->scroll(NC::Scroll::PageUp
);
446 listsChangeFinisher();
451 myScreen
->scroll(NC::Scroll::PageDown
);
452 listsChangeFinisher();
457 myScreen
->scroll(NC::Scroll::Home
);
458 listsChangeFinisher();
463 myScreen
->scroll(NC::Scroll::End
);
464 listsChangeFinisher();
467 void ToggleInterface::run()
469 switch (Config
.design
)
471 case Design::Classic
:
472 Config
.design
= Design::Alternative
;
473 Config
.statusbar_visibility
= false;
475 case Design::Alternative
:
476 Config
.design
= Design::Classic
;
477 Config
.statusbar_visibility
= OriginalStatusbarVisibility
;
480 setWindowsDimensions();
482 // unlock progressbar
483 Progressbar::ScopedLock();
484 Status::Changes::mixer();
485 Status::Changes::elapsedTime(false);
486 Statusbar::printf("User interface: %1%", Config
.design
);
489 bool JumpToParentDirectory::canBeRun()
491 return (myScreen
== myBrowser
)
492 # ifdef HAVE_TAGLIB_H
493 || (myScreen
->activeWindow() == myTagEditor
->Dirs
)
494 # endif // HAVE_TAGLIB_H
498 void JumpToParentDirectory::run()
500 if (myScreen
== myBrowser
)
502 if (!myBrowser
->inRootDirectory())
504 myBrowser
->main().reset();
505 myBrowser
->enterDirectory();
508 # ifdef HAVE_TAGLIB_H
509 else if (myScreen
== myTagEditor
)
511 if (myTagEditor
->CurrentDir() != "/")
513 myTagEditor
->Dirs
->reset();
514 myTagEditor
->enterDirectory();
517 # endif // HAVE_TAGLIB_H
520 bool RunAction::canBeRun()
522 m_ha
= dynamic_cast<HasActions
*>(myScreen
);
523 return m_ha
!= nullptr
524 && m_ha
->actionRunnable();
527 void RunAction::run()
532 bool PreviousColumn::canBeRun()
534 m_hc
= dynamic_cast<HasColumns
*>(myScreen
);
535 return m_hc
!= nullptr
536 && m_hc
->previousColumnAvailable();
539 void PreviousColumn::run()
541 m_hc
->previousColumn();
544 bool NextColumn::canBeRun()
546 m_hc
= dynamic_cast<HasColumns
*>(myScreen
);
547 return m_hc
!= nullptr
548 && m_hc
->nextColumnAvailable();
551 void NextColumn::run()
556 bool MasterScreen::canBeRun()
558 using Global::myLockedScreen
;
559 using Global::myInactiveScreen
;
561 return myLockedScreen
563 && myLockedScreen
!= myScreen
564 && myScreen
->isMergable();
567 void MasterScreen::run()
569 using Global::myInactiveScreen
;
570 using Global::myLockedScreen
;
572 myInactiveScreen
= myScreen
;
573 myScreen
= myLockedScreen
;
577 bool SlaveScreen::canBeRun()
579 using Global::myLockedScreen
;
580 using Global::myInactiveScreen
;
582 return myLockedScreen
584 && myLockedScreen
== myScreen
585 && myScreen
->isMergable();
588 void SlaveScreen::run()
590 using Global::myInactiveScreen
;
591 using Global::myLockedScreen
;
593 myScreen
= myInactiveScreen
;
594 myInactiveScreen
= myLockedScreen
;
600 int volume
= std::min(Status::State::volume()+Config
.volume_change_step
, 100u);
601 Mpd
.SetVolume(volume
);
604 void VolumeDown::run()
606 int volume
= std::max(int(Status::State::volume()-Config
.volume_change_step
), 0);
607 Mpd
.SetVolume(volume
);
610 bool AddItemToPlaylist::canBeRun()
612 m_hs
= dynamic_cast<HasSongs
*>(myScreen
);
613 return m_hs
!= nullptr && m_hs
->itemAvailable();
616 void AddItemToPlaylist::run()
618 bool success
= m_hs
->addItemToPlaylist(false);
621 myScreen
->scroll(NC::Scroll::Down
);
622 listsChangeFinisher();
626 bool PlayItem::canBeRun()
628 m_hs
= dynamic_cast<HasSongs
*>(myScreen
);
629 return m_hs
!= nullptr && m_hs
->itemAvailable();
634 bool success
= m_hs
->addItemToPlaylist(true);
636 listsChangeFinisher();
639 bool DeletePlaylistItems::canBeRun()
641 return (myScreen
== myPlaylist
&& !myPlaylist
->main().empty())
642 || (myScreen
->isActiveWindow(myPlaylistEditor
->Content
) && !myPlaylistEditor
->Content
.empty());
645 void DeletePlaylistItems::run()
647 if (myScreen
== myPlaylist
)
649 Statusbar::print("Deleting items...");
650 auto delete_fun
= std::bind(&MPD::Connection::Delete
, ph::_1
, ph::_2
);
651 deleteSelectedSongs(myPlaylist
->main(), delete_fun
);
652 Statusbar::print("Item(s) deleted");
654 else if (myScreen
->isActiveWindow(myPlaylistEditor
->Content
))
656 std::string playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
657 auto delete_fun
= std::bind(&MPD::Connection::PlaylistDelete
, ph::_1
, playlist
, ph::_2
);
658 Statusbar::print("Deleting items...");
659 deleteSelectedSongs(myPlaylistEditor
->Content
, delete_fun
);
660 Statusbar::print("Item(s) deleted");
664 bool DeleteBrowserItems::canBeRun()
666 auto check_if_deletion_allowed
= []() {
667 if (Config
.allow_for_physical_item_deletion
)
671 Statusbar::print("Flag \"allow_for_physical_item_deletion\" needs to be enabled in configuration file");
675 return myScreen
== myBrowser
676 && !myBrowser
->main().empty()
677 && isMPDMusicDirSet()
678 && check_if_deletion_allowed();
681 void DeleteBrowserItems::run()
683 auto get_name
= [](const MPD::Item
&item
) -> std::string
{
687 case MPD::Item::Type::Directory
:
688 iname
= getBasename(item
.directory().path());
690 case MPD::Item::Type::Song
:
691 iname
= item
.song().getName();
693 case MPD::Item::Type::Playlist
:
694 iname
= getBasename(item
.playlist().path());
700 boost::format question
;
701 if (hasSelected(myBrowser
->main().begin(), myBrowser
->main().end()))
702 question
= boost::format("Delete selected items?");
705 const auto &item
= myBrowser
->main().current()->value();
706 // parent directories are not accepted (and they
707 // can't be selected, so in other cases it's fine).
708 if (myBrowser
->isParentDirectory(item
))
710 const char msg
[] = "Delete \"%1%\"?";
711 question
= boost::format(msg
) % wideShorten(
712 get_name(item
), COLS
-const_strlen(msg
)-5
715 confirmAction(question
);
717 auto items
= getSelectedOrCurrent(
718 myBrowser
->main().begin(),
719 myBrowser
->main().end(),
720 myBrowser
->main().current()
722 for (const auto &item
: items
)
724 myBrowser
->remove(item
->value());
725 const char msg
[] = "Deleted %1% \"%2%\"";
726 Statusbar::printf(msg
,
727 itemTypeToString(item
->value().type()),
728 wideShorten(get_name(item
->value()), COLS
-const_strlen(msg
))
732 if (!myBrowser
->isLocal())
733 Mpd
.UpdateDirectory(myBrowser
->currentDirectory());
734 myBrowser
->requestUpdate();
737 bool DeleteStoredPlaylist::canBeRun()
739 return myScreen
->isActiveWindow(myPlaylistEditor
->Playlists
);
742 void DeleteStoredPlaylist::run()
744 if (myPlaylistEditor
->Playlists
.empty())
746 boost::format question
;
747 if (hasSelected(myPlaylistEditor
->Playlists
.begin(), myPlaylistEditor
->Playlists
.end()))
748 question
= boost::format("Delete selected playlists?");
750 question
= boost::format("Delete playlist \"%1%\"?")
751 % wideShorten(myPlaylistEditor
->Playlists
.current()->value().path(), COLS
-question
.size()-10);
752 confirmAction(question
);
753 auto list
= getSelectedOrCurrent(
754 myPlaylistEditor
->Playlists
.begin(),
755 myPlaylistEditor
->Playlists
.end(),
756 myPlaylistEditor
->Playlists
.current()
758 for (const auto &item
: list
)
759 Mpd
.DeletePlaylist(item
->value().path());
760 Statusbar::printf("%1% deleted", list
.size() == 1 ? "Playlist" : "Playlists");
761 // force playlists update. this happens automatically, but only after call
762 // to Key::read, therefore when we call PlaylistEditor::Update, it won't
763 // yet see it, so let's point that it needs to update it.
764 myPlaylistEditor
->requestPlaylistsUpdate();
767 void ReplaySong::run()
769 if (Status::State::player() != MPD::psStop
)
770 Mpd
.Seek(Status::State::currentSongPosition(), 0);
773 void PreviousSong::run()
788 void SavePlaylist::run()
790 using Global::wFooter
;
792 std::string playlist_name
;
794 Statusbar::ScopedLock slock
;
795 Statusbar::put() << "Save playlist as: ";
796 playlist_name
= wFooter
->prompt();
800 Mpd
.SavePlaylist(playlist_name
);
801 Statusbar::printf("Playlist saved as \"%1%\"", playlist_name
);
803 catch (MPD::ServerError
&e
)
805 if (e
.code() == MPD_SERVER_ERROR_EXIST
)
808 boost::format("Playlist \"%1%\" already exists, overwrite?") % playlist_name
810 Mpd
.DeletePlaylist(playlist_name
);
811 Mpd
.SavePlaylist(playlist_name
);
812 Statusbar::print("Playlist overwritten");
824 void ExecuteCommand::run()
826 using Global::wFooter
;
828 std::string cmd_name
;
830 Statusbar::ScopedLock slock
;
831 NC::Window::ScopedPromptHook
helper(*wFooter
,
832 Statusbar::Helpers::TryExecuteImmediateCommand()
834 Statusbar::put() << NC::Format::Bold
<< ":" << NC::Format::NoBold
;
835 cmd_name
= wFooter
->prompt();
838 auto cmd
= Bindings
.findCommand(cmd_name
);
841 Statusbar::printf(1, "Executing %1%...", cmd_name
);
842 bool res
= cmd
->binding().execute();
843 Statusbar::printf("Execution of command \"%1%\" %2%.",
844 cmd_name
, res
? "successful" : "unsuccessful"
848 Statusbar::printf("No command named \"%1%\"", cmd_name
);
851 bool MoveSortOrderUp::canBeRun()
853 return myScreen
== mySortPlaylistDialog
;
856 void MoveSortOrderUp::run()
858 mySortPlaylistDialog
->moveSortOrderUp();
861 bool MoveSortOrderDown::canBeRun()
863 return myScreen
== mySortPlaylistDialog
;
866 void MoveSortOrderDown::run()
868 mySortPlaylistDialog
->moveSortOrderDown();
871 bool MoveSelectedItemsUp::canBeRun()
873 return ((myScreen
== myPlaylist
874 && !myPlaylist
->main().empty())
875 || (myScreen
->isActiveWindow(myPlaylistEditor
->Content
)
876 && !myPlaylistEditor
->Content
.empty()));
879 void MoveSelectedItemsUp::run()
881 const char *filteredMsg
= "Moving items up is disabled in filtered playlist";
882 if (myScreen
== myPlaylist
)
884 if (myPlaylist
->main().isFiltered())
885 Statusbar::print(filteredMsg
);
889 std::bind(&MPD::Connection::Move
, ph::_1
, ph::_2
, ph::_3
));
891 else if (myScreen
== myPlaylistEditor
)
893 if (myPlaylistEditor
->Content
.isFiltered())
894 Statusbar::print(filteredMsg
);
897 auto playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
899 myPlaylistEditor
->Content
,
900 std::bind(&MPD::Connection::PlaylistMove
, ph::_1
, playlist
, ph::_2
, ph::_3
));
905 bool MoveSelectedItemsDown::canBeRun()
907 return ((myScreen
== myPlaylist
908 && !myPlaylist
->main().empty())
909 || (myScreen
->isActiveWindow(myPlaylistEditor
->Content
)
910 && !myPlaylistEditor
->Content
.empty()));
913 void MoveSelectedItemsDown::run()
915 const char *filteredMsg
= "Moving items down is disabled in filtered playlist";
916 if (myScreen
== myPlaylist
)
918 if (myPlaylist
->main().isFiltered())
919 Statusbar::print(filteredMsg
);
921 moveSelectedItemsDown(
923 std::bind(&MPD::Connection::Move
, ph::_1
, ph::_2
, ph::_3
));
925 else if (myScreen
== myPlaylistEditor
)
927 if (myPlaylistEditor
->Content
.isFiltered())
928 Statusbar::print(filteredMsg
);
931 auto playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
932 moveSelectedItemsDown(
933 myPlaylistEditor
->Content
,
934 std::bind(&MPD::Connection::PlaylistMove
, ph::_1
, playlist
, ph::_2
, ph::_3
));
939 bool MoveSelectedItemsTo::canBeRun()
941 return myScreen
== myPlaylist
942 || myScreen
->isActiveWindow(myPlaylistEditor
->Content
);
945 void MoveSelectedItemsTo::run()
947 if (myScreen
== myPlaylist
)
949 if (!myPlaylist
->main().empty())
950 moveSelectedItemsTo(myPlaylist
->main(), std::bind(&MPD::Connection::Move
, ph::_1
, ph::_2
, ph::_3
));
954 assert(!myPlaylistEditor
->Playlists
.empty());
955 std::string playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
956 auto move_fun
= std::bind(&MPD::Connection::PlaylistMove
, ph::_1
, playlist
, ph::_2
, ph::_3
);
957 moveSelectedItemsTo(myPlaylistEditor
->Content
, move_fun
);
963 return myScreen
!= myPlaylistEditor
964 || !myPlaylistEditor
->Playlists
.empty();
969 using Global::wFooter
;
973 Statusbar::ScopedLock slock
;
974 Statusbar::put() << (myScreen
== myPlaylistEditor
? "Add to playlist: " : "Add: ");
975 path
= wFooter
->prompt();
978 // confirm when one wants to add the whole database
980 confirmAction("Are you sure you want to add the whole database?");
982 Statusbar::put() << "Adding...";
984 if (myScreen
== myPlaylistEditor
)
985 Mpd
.AddToPlaylist(myPlaylistEditor
->Playlists
.current()->value().path(), path
);
992 catch (MPD::ServerError
&err
)
994 // If a path is not a file or directory, assume it is a playlist.
995 if (err
.code() == MPD_SERVER_ERROR_NO_EXIST
)
996 Mpd
.LoadPlaylist(path
);
1003 bool SeekForward::canBeRun()
1005 return Status::State::player() != MPD::psStop
&& Status::State::totalTime() > 0;
1008 void SeekForward::run()
1013 bool SeekBackward::canBeRun()
1015 return Status::State::player() != MPD::psStop
&& Status::State::totalTime() > 0;
1018 void SeekBackward::run()
1023 bool ToggleDisplayMode::canBeRun()
1025 return myScreen
== myPlaylist
1026 || myScreen
== myBrowser
1027 || myScreen
== mySearcher
1028 || myScreen
->isActiveWindow(myPlaylistEditor
->Content
);
1031 void ToggleDisplayMode::run()
1033 if (myScreen
== myPlaylist
)
1035 switch (Config
.playlist_display_mode
)
1037 case DisplayMode::Classic
:
1038 Config
.playlist_display_mode
= DisplayMode::Columns
;
1039 myPlaylist
->main().setItemDisplayer(std::bind(
1040 Display::SongsInColumns
, ph::_1
, std::cref(myPlaylist
->main())
1042 if (Config
.titles_visibility
)
1043 myPlaylist
->main().setTitle(Display::Columns(myPlaylist
->main().getWidth()));
1045 myPlaylist
->main().setTitle("");
1047 case DisplayMode::Columns
:
1048 Config
.playlist_display_mode
= DisplayMode::Classic
;
1049 myPlaylist
->main().setItemDisplayer(std::bind(
1050 Display::Songs
, ph::_1
, std::cref(myPlaylist
->main()), std::cref(Config
.song_list_format
)
1052 myPlaylist
->main().setTitle("");
1054 Statusbar::printf("Playlist display mode: %1%", Config
.playlist_display_mode
);
1056 else if (myScreen
== myBrowser
)
1058 switch (Config
.browser_display_mode
)
1060 case DisplayMode::Classic
:
1061 Config
.browser_display_mode
= DisplayMode::Columns
;
1062 if (Config
.titles_visibility
)
1063 myBrowser
->main().setTitle(Display::Columns(myBrowser
->main().getWidth()));
1065 myBrowser
->main().setTitle("");
1067 case DisplayMode::Columns
:
1068 Config
.browser_display_mode
= DisplayMode::Classic
;
1069 myBrowser
->main().setTitle("");
1072 Statusbar::printf("Browser display mode: %1%", Config
.browser_display_mode
);
1074 else if (myScreen
== mySearcher
)
1076 switch (Config
.search_engine_display_mode
)
1078 case DisplayMode::Classic
:
1079 Config
.search_engine_display_mode
= DisplayMode::Columns
;
1081 case DisplayMode::Columns
:
1082 Config
.search_engine_display_mode
= DisplayMode::Classic
;
1085 Statusbar::printf("Search engine display mode: %1%", Config
.search_engine_display_mode
);
1086 if (mySearcher
->main().size() > SearchEngine::StaticOptions
)
1087 mySearcher
->main().setTitle(
1088 Config
.search_engine_display_mode
== DisplayMode::Columns
1089 && Config
.titles_visibility
1090 ? Display::Columns(mySearcher
->main().getWidth())
1094 else if (myScreen
->isActiveWindow(myPlaylistEditor
->Content
))
1096 switch (Config
.playlist_editor_display_mode
)
1098 case DisplayMode::Classic
:
1099 Config
.playlist_editor_display_mode
= DisplayMode::Columns
;
1100 myPlaylistEditor
->Content
.setItemDisplayer(std::bind(
1101 Display::SongsInColumns
, ph::_1
, std::cref(myPlaylistEditor
->Content
)
1104 case DisplayMode::Columns
:
1105 Config
.playlist_editor_display_mode
= DisplayMode::Classic
;
1106 myPlaylistEditor
->Content
.setItemDisplayer(std::bind(
1107 Display::Songs
, ph::_1
, std::cref(myPlaylistEditor
->Content
), std::cref(Config
.song_list_format
)
1111 Statusbar::printf("Playlist editor display mode: %1%", Config
.playlist_editor_display_mode
);
1115 bool ToggleSeparatorsBetweenAlbums::canBeRun()
1120 void ToggleSeparatorsBetweenAlbums::run()
1122 Config
.playlist_separate_albums
= !Config
.playlist_separate_albums
;
1123 Statusbar::printf("Separators between albums: %1%",
1124 Config
.playlist_separate_albums
? "on" : "off"
1128 bool ToggleLyricsUpdateOnSongChange::canBeRun()
1130 return myScreen
== myLyrics
;
1133 void ToggleLyricsUpdateOnSongChange::run()
1135 Config
.now_playing_lyrics
= !Config
.now_playing_lyrics
;
1136 Statusbar::printf("Update lyrics if song changes: %1%",
1137 Config
.now_playing_lyrics
? "on" : "off"
1141 #ifndef HAVE_CURL_CURL_H
1142 bool ToggleLyricsFetcher::canBeRun()
1146 #endif // NOT HAVE_CURL_CURL_H
1148 void ToggleLyricsFetcher::run()
1150 # ifdef HAVE_CURL_CURL_H
1151 myLyrics
->ToggleFetcher();
1152 # endif // HAVE_CURL_CURL_H
1155 #ifndef HAVE_CURL_CURL_H
1156 bool ToggleFetchingLyricsInBackground::canBeRun()
1160 #endif // NOT HAVE_CURL_CURL_H
1162 void ToggleFetchingLyricsInBackground::run()
1164 # ifdef HAVE_CURL_CURL_H
1165 Config
.fetch_lyrics_in_background
= !Config
.fetch_lyrics_in_background
;
1166 Statusbar::printf("Fetching lyrics for playing songs in background: %1%",
1167 Config
.fetch_lyrics_in_background
? "on" : "off"
1169 # endif // HAVE_CURL_CURL_H
1172 void TogglePlayingSongCentering::run()
1174 Config
.autocenter_mode
= !Config
.autocenter_mode
;
1175 Statusbar::printf("Centering playing song: %1%",
1176 Config
.autocenter_mode
? "on" : "off"
1178 if (Config
.autocenter_mode
)
1180 auto s
= myPlaylist
->nowPlayingSong();
1182 myPlaylist
->locateSong(s
);
1186 void UpdateDatabase::run()
1188 if (myScreen
== myBrowser
)
1189 Mpd
.UpdateDirectory(myBrowser
->currentDirectory());
1190 # ifdef HAVE_TAGLIB_H
1191 else if (myScreen
== myTagEditor
)
1192 Mpd
.UpdateDirectory(myTagEditor
->CurrentDir());
1193 # endif // HAVE_TAGLIB_H
1195 Mpd
.UpdateDirectory("/");
1198 bool JumpToPlayingSong::canBeRun()
1200 return myScreen
== myPlaylist
1201 || myScreen
== myBrowser
1202 || myScreen
== myLibrary
;
1205 void JumpToPlayingSong::run()
1207 auto s
= myPlaylist
->nowPlayingSong();
1210 if (myScreen
== myPlaylist
)
1212 myPlaylist
->locateSong(s
);
1214 else if (myScreen
== myBrowser
)
1216 myBrowser
->locateSong(s
);
1218 else if (myScreen
== myLibrary
)
1220 myLibrary
->locateSong(s
);
1224 void ToggleRepeat::run()
1226 Mpd
.SetRepeat(!Status::State::repeat());
1229 bool Shuffle::canBeRun()
1231 if (myScreen
!= myPlaylist
)
1233 m_begin
= myPlaylist
->main().begin();
1234 m_end
= myPlaylist
->main().end();
1235 return findSelectedRangeAndPrintInfoIfNot(m_begin
, m_end
);
1240 if (Config
.ask_before_shuffling_playlists
)
1241 confirmAction("Do you really want to shuffle selected range?");
1242 auto begin
= myPlaylist
->main().begin();
1243 Mpd
.ShuffleRange(m_begin
-begin
, m_end
-begin
);
1244 Statusbar::print("Range shuffled");
1247 void ToggleRandom::run()
1249 Mpd
.SetRandom(!Status::State::random());
1252 bool StartSearching::canBeRun()
1254 return myScreen
== mySearcher
&& !mySearcher
->main()[0].isInactive();
1257 void StartSearching::run()
1259 mySearcher
->main().highlight(SearchEngine::SearchButton
);
1260 mySearcher
->main().setHighlighting(0);
1261 mySearcher
->main().refresh();
1262 mySearcher
->main().setHighlighting(1);
1263 mySearcher
->runAction();
1266 bool SaveTagChanges::canBeRun()
1268 # ifdef HAVE_TAGLIB_H
1269 return myScreen
== myTinyTagEditor
1270 || myScreen
->activeWindow() == myTagEditor
->TagTypes
;
1273 # endif // HAVE_TAGLIB_H
1276 void SaveTagChanges::run()
1278 # ifdef HAVE_TAGLIB_H
1279 if (myScreen
== myTinyTagEditor
)
1281 myTinyTagEditor
->main().highlight(myTinyTagEditor
->main().size()-2); // Save
1282 myTinyTagEditor
->runAction();
1284 else if (myScreen
->activeWindow() == myTagEditor
->TagTypes
)
1286 myTagEditor
->TagTypes
->highlight(myTagEditor
->TagTypes
->size()-1); // Save
1287 myTagEditor
->runAction();
1289 # endif // HAVE_TAGLIB_H
1292 void ToggleSingle::run()
1294 Mpd
.SetSingle(!Status::State::single());
1297 void ToggleConsume::run()
1299 Mpd
.SetConsume(!Status::State::consume());
1302 void ToggleCrossfade::run()
1304 Mpd
.SetCrossfade(Status::State::crossfade() ? 0 : Config
.crossfade_time
);
1307 void SetCrossfade::run()
1309 using Global::wFooter
;
1311 Statusbar::ScopedLock slock
;
1312 Statusbar::put() << "Set crossfade to: ";
1313 auto crossfade
= fromString
<unsigned>(wFooter
->prompt());
1314 lowerBoundCheck(crossfade
, 0u);
1315 Config
.crossfade_time
= crossfade
;
1316 Mpd
.SetCrossfade(crossfade
);
1319 void SetVolume::run()
1321 using Global::wFooter
;
1325 Statusbar::ScopedLock slock
;
1326 Statusbar::put() << "Set volume to: ";
1327 volume
= fromString
<unsigned>(wFooter
->prompt());
1328 boundsCheck(volume
, 0u, 100u);
1329 Mpd
.SetVolume(volume
);
1331 Statusbar::printf("Volume set to %1%%%", volume
);
1334 bool EnterDirectory::canBeRun()
1336 bool result
= false;
1337 if (myScreen
== myBrowser
&& !myBrowser
->main().empty())
1339 result
= myBrowser
->main().current()->value().type()
1340 == MPD::Item::Type::Directory
;
1342 #ifdef HAVE_TAGLIB_H
1343 else if (myScreen
->activeWindow() == myTagEditor
->Dirs
)
1345 #endif // HAVE_TAGLIB_H
1349 void EnterDirectory::run()
1351 if (myScreen
== myBrowser
)
1352 myBrowser
->enterDirectory();
1353 #ifdef HAVE_TAGLIB_H
1354 else if (myScreen
->activeWindow() == myTagEditor
->Dirs
)
1356 if (!myTagEditor
->enterDirectory())
1357 Statusbar::print("No subdirectories found");
1359 #endif // HAVE_TAGLIB_H
1362 bool EditSong::canBeRun()
1364 # ifdef HAVE_TAGLIB_H
1365 m_song
= currentSong(myScreen
);
1366 return m_song
!= nullptr && isMPDMusicDirSet();
1369 # endif // HAVE_TAGLIB_H
1372 void EditSong::run()
1374 # ifdef HAVE_TAGLIB_H
1375 myTinyTagEditor
->SetEdited(*m_song
);
1376 myTinyTagEditor
->switchTo();
1377 # endif // HAVE_TAGLIB_H
1380 bool EditLibraryTag::canBeRun()
1382 # ifdef HAVE_TAGLIB_H
1383 return myScreen
->isActiveWindow(myLibrary
->Tags
)
1384 && !myLibrary
->Tags
.empty()
1385 && isMPDMusicDirSet();
1388 # endif // HAVE_TAGLIB_H
1391 void EditLibraryTag::run()
1393 # ifdef HAVE_TAGLIB_H
1394 using Global::wFooter
;
1396 std::string new_tag
;
1398 Statusbar::ScopedLock slock
;
1399 Statusbar::put() << NC::Format::Bold
<< tagTypeToString(Config
.media_lib_primary_tag
) << NC::Format::NoBold
<< ": ";
1400 new_tag
= wFooter
->prompt(myLibrary
->Tags
.current()->value().tag());
1402 if (!new_tag
.empty() && new_tag
!= myLibrary
->Tags
.current()->value().tag())
1404 Statusbar::print("Updating tags...");
1405 Mpd
.StartSearch(true);
1406 Mpd
.AddSearch(Config
.media_lib_primary_tag
, myLibrary
->Tags
.current()->value().tag());
1407 MPD::MutableSong::SetFunction set
= tagTypeToSetFunction(Config
.media_lib_primary_tag
);
1409 bool success
= true;
1410 std::string dir_to_update
;
1411 for (MPD::SongIterator s
= Mpd
.CommitSearchSongs(), end
; s
!= end
; ++s
)
1413 MPD::MutableSong ms
= std::move(*s
);
1414 ms
.setTags(set
, new_tag
);
1415 Statusbar::printf("Updating tags in \"%1%\"...", ms
.getName());
1416 std::string path
= Config
.mpd_music_dir
+ ms
.getURI();
1417 if (!Tags::write(ms
))
1420 const char msg
[] = "Error while updating tags in \"%1%\"";
1421 Statusbar::printf(msg
, wideShorten(ms
.getURI(), COLS
-const_strlen(msg
)));
1425 if (dir_to_update
.empty())
1426 dir_to_update
= ms
.getURI();
1428 dir_to_update
= getSharedDirectory(dir_to_update
, ms
.getURI());
1432 Mpd
.UpdateDirectory(dir_to_update
);
1433 Statusbar::print("Tags updated successfully");
1436 # endif // HAVE_TAGLIB_H
1439 bool EditLibraryAlbum::canBeRun()
1441 # ifdef HAVE_TAGLIB_H
1442 return myScreen
->isActiveWindow(myLibrary
->Albums
)
1443 && !myLibrary
->Albums
.empty()
1444 && isMPDMusicDirSet();
1447 # endif // HAVE_TAGLIB_H
1450 void EditLibraryAlbum::run()
1452 # ifdef HAVE_TAGLIB_H
1453 using Global::wFooter
;
1454 // FIXME: merge this and EditLibraryTag. also, prompt on failure if user wants to continue
1455 std::string new_album
;
1457 Statusbar::ScopedLock slock
;
1458 Statusbar::put() << NC::Format::Bold
<< "Album: " << NC::Format::NoBold
;
1459 new_album
= wFooter
->prompt(myLibrary
->Albums
.current()->value().entry().album());
1461 if (!new_album
.empty() && new_album
!= myLibrary
->Albums
.current()->value().entry().album())
1464 Statusbar::print("Updating tags...");
1465 for (size_t i
= 0; i
< myLibrary
->Songs
.size(); ++i
)
1467 Statusbar::printf("Updating tags in \"%1%\"...", myLibrary
->Songs
[i
].value().getName());
1468 std::string path
= Config
.mpd_music_dir
+ myLibrary
->Songs
[i
].value().getURI();
1469 TagLib::FileRef
f(path
.c_str());
1472 const char msg
[] = "Error while opening file \"%1%\"";
1473 Statusbar::printf(msg
, wideShorten(myLibrary
->Songs
[i
].value().getURI(), COLS
-const_strlen(msg
)));
1477 f
.tag()->setAlbum(ToWString(new_album
));
1480 const char msg
[] = "Error while writing tags in \"%1%\"";
1481 Statusbar::printf(msg
, wideShorten(myLibrary
->Songs
[i
].value().getURI(), COLS
-const_strlen(msg
)));
1488 Mpd
.UpdateDirectory(getSharedDirectory(myLibrary
->Songs
.beginV(), myLibrary
->Songs
.endV()));
1489 Statusbar::print("Tags updated successfully");
1492 # endif // HAVE_TAGLIB_H
1495 bool EditDirectoryName::canBeRun()
1497 return ((myScreen
== myBrowser
1498 && !myBrowser
->main().empty()
1499 && myBrowser
->main().current()->value().type() == MPD::Item::Type::Directory
)
1500 # ifdef HAVE_TAGLIB_H
1501 || (myScreen
->activeWindow() == myTagEditor
->Dirs
1502 && !myTagEditor
->Dirs
->empty()
1503 && myTagEditor
->Dirs
->choice() > 0)
1504 # endif // HAVE_TAGLIB_H
1505 ) && isMPDMusicDirSet();
1508 void EditDirectoryName::run()
1510 using Global::wFooter
;
1511 if (myScreen
== myBrowser
)
1513 std::string old_dir
= myBrowser
->main().current()->value().directory().path();
1514 std::string new_dir
;
1516 Statusbar::ScopedLock slock
;
1517 Statusbar::put() << NC::Format::Bold
<< "Directory: " << NC::Format::NoBold
;
1518 new_dir
= wFooter
->prompt(old_dir
);
1520 if (!new_dir
.empty() && new_dir
!= old_dir
)
1522 std::string full_old_dir
;
1523 if (!myBrowser
->isLocal())
1524 full_old_dir
+= Config
.mpd_music_dir
;
1525 full_old_dir
+= old_dir
;
1526 std::string full_new_dir
;
1527 if (!myBrowser
->isLocal())
1528 full_new_dir
+= Config
.mpd_music_dir
;
1529 full_new_dir
+= new_dir
;
1530 boost::filesystem::rename(full_old_dir
, full_new_dir
);
1531 const char msg
[] = "Directory renamed to \"%1%\"";
1532 Statusbar::printf(msg
, wideShorten(new_dir
, COLS
-const_strlen(msg
)));
1533 if (!myBrowser
->isLocal())
1534 Mpd
.UpdateDirectory(getSharedDirectory(old_dir
, new_dir
));
1535 myBrowser
->requestUpdate();
1538 # ifdef HAVE_TAGLIB_H
1539 else if (myScreen
->activeWindow() == myTagEditor
->Dirs
)
1541 std::string old_dir
= myTagEditor
->Dirs
->current()->value().first
, new_dir
;
1543 Statusbar::ScopedLock slock
;
1544 Statusbar::put() << NC::Format::Bold
<< "Directory: " << NC::Format::NoBold
;
1545 new_dir
= wFooter
->prompt(old_dir
);
1547 if (!new_dir
.empty() && new_dir
!= old_dir
)
1549 std::string full_old_dir
= Config
.mpd_music_dir
+ myTagEditor
->CurrentDir() + "/" + old_dir
;
1550 std::string full_new_dir
= Config
.mpd_music_dir
+ myTagEditor
->CurrentDir() + "/" + new_dir
;
1551 if (rename(full_old_dir
.c_str(), full_new_dir
.c_str()) == 0)
1553 const char msg
[] = "Directory renamed to \"%1%\"";
1554 Statusbar::printf(msg
, wideShorten(new_dir
, COLS
-const_strlen(msg
)));
1555 Mpd
.UpdateDirectory(myTagEditor
->CurrentDir());
1559 const char msg
[] = "Couldn't rename \"%1%\": %2%";
1560 Statusbar::printf(msg
, wideShorten(old_dir
, COLS
-const_strlen(msg
)-25), strerror(errno
));
1564 # endif // HAVE_TAGLIB_H
1567 bool EditPlaylistName::canBeRun()
1569 return (myScreen
->isActiveWindow(myPlaylistEditor
->Playlists
)
1570 && !myPlaylistEditor
->Playlists
.empty())
1571 || (myScreen
== myBrowser
1572 && !myBrowser
->main().empty()
1573 && myBrowser
->main().current()->value().type() == MPD::Item::Type::Playlist
);
1576 void EditPlaylistName::run()
1578 using Global::wFooter
;
1579 std::string old_name
, new_name
;
1580 if (myScreen
->isActiveWindow(myPlaylistEditor
->Playlists
))
1581 old_name
= myPlaylistEditor
->Playlists
.current()->value().path();
1583 old_name
= myBrowser
->main().current()->value().playlist().path();
1585 Statusbar::ScopedLock slock
;
1586 Statusbar::put() << NC::Format::Bold
<< "Playlist: " << NC::Format::NoBold
;
1587 new_name
= wFooter
->prompt(old_name
);
1589 if (!new_name
.empty() && new_name
!= old_name
)
1591 Mpd
.Rename(old_name
, new_name
);
1592 const char msg
[] = "Playlist renamed to \"%1%\"";
1593 Statusbar::printf(msg
, wideShorten(new_name
, COLS
-const_strlen(msg
)));
1597 bool EditLyrics::canBeRun()
1599 return myScreen
== myLyrics
;
1602 void EditLyrics::run()
1607 bool JumpToBrowser::canBeRun()
1609 m_song
= currentSong(myScreen
);
1610 return m_song
!= nullptr;
1613 void JumpToBrowser::run()
1615 myBrowser
->locateSong(*m_song
);
1618 bool JumpToMediaLibrary::canBeRun()
1620 m_song
= currentSong(myScreen
);
1621 return m_song
!= nullptr;
1624 void JumpToMediaLibrary::run()
1626 myLibrary
->locateSong(*m_song
);
1629 bool JumpToPlaylistEditor::canBeRun()
1631 return myScreen
== myBrowser
1632 && myBrowser
->main().current()->value().type() == MPD::Item::Type::Playlist
;
1635 void JumpToPlaylistEditor::run()
1637 myPlaylistEditor
->locatePlaylist(myBrowser
->main().current()->value().playlist());
1640 void ToggleScreenLock::run()
1642 using Global::wFooter
;
1643 using Global::myLockedScreen
;
1644 const char *msg_unlockable_screen
= "Current screen can't be locked";
1645 if (myLockedScreen
!= nullptr)
1647 BaseScreen::unlock();
1648 Actions::setResizeFlags();
1650 Statusbar::print("Screen unlocked");
1652 else if (!myScreen
->isLockable())
1654 Statusbar::print(msg_unlockable_screen
);
1658 unsigned part
= Config
.locked_screen_width_part
*100;
1659 if (Config
.ask_for_locked_screen_width_part
)
1661 Statusbar::ScopedLock slock
;
1662 Statusbar::put() << "% of the locked screen's width to be reserved (20-80): ";
1663 part
= fromString
<unsigned>(wFooter
->prompt(boost::lexical_cast
<std::string
>(part
)));
1665 boundsCheck(part
, 20u, 80u);
1666 Config
.locked_screen_width_part
= part
/100.0;
1667 if (myScreen
->lock())
1668 Statusbar::printf("Screen locked (with %1%%% width)", part
);
1670 Statusbar::print(msg_unlockable_screen
);
1674 bool JumpToTagEditor::canBeRun()
1676 # ifdef HAVE_TAGLIB_H
1677 m_song
= currentSong(myScreen
);
1678 return m_song
!= nullptr && isMPDMusicDirSet();
1681 # endif // HAVE_TAGLIB_H
1684 void JumpToTagEditor::run()
1686 # ifdef HAVE_TAGLIB_H
1687 myTagEditor
->LocateSong(*m_song
);
1688 # endif // HAVE_TAGLIB_H
1691 bool JumpToPositionInSong::canBeRun()
1693 return Status::State::player() != MPD::psStop
&& Status::State::totalTime() > 0;
1696 void JumpToPositionInSong::run()
1698 using Global::wFooter
;
1700 const MPD::Song s
= myPlaylist
->nowPlayingSong();
1704 Statusbar::ScopedLock slock
;
1705 Statusbar::put() << "Position to go (in %/m:ss/seconds(s)): ";
1706 spos
= wFooter
->prompt();
1711 if (boost::regex_match(spos
, what
, rx
.assign("([0-9]+):([0-9]{2})"))) // mm:ss
1713 auto mins
= fromString
<unsigned>(what
[1]);
1714 auto secs
= fromString
<unsigned>(what
[2]);
1715 boundsCheck(secs
, 0u, 60u);
1716 Mpd
.Seek(s
.getPosition(), mins
* 60 + secs
);
1718 else if (boost::regex_match(spos
, what
, rx
.assign("([0-9]+)s"))) // position in seconds
1720 auto secs
= fromString
<unsigned>(what
[1]);
1721 Mpd
.Seek(s
.getPosition(), secs
);
1723 else if (boost::regex_match(spos
, what
, rx
.assign("([0-9]+)[%]{0,1}"))) // position in %
1725 auto percent
= fromString
<unsigned>(what
[1]);
1726 boundsCheck(percent
, 0u, 100u);
1727 int secs
= (percent
* s
.getDuration()) / 100.0;
1728 Mpd
.Seek(s
.getPosition(), secs
);
1731 Statusbar::print("Invalid format ([m]:[ss], [s]s, [%]%, [%] accepted)");
1734 bool SelectItem::canBeRun()
1736 m_list
= dynamic_cast<NC::List
*>(myScreen
->activeWindow());
1737 return m_list
!= nullptr
1739 && m_list
->currentP()->isSelectable();
1742 void SelectItem::run()
1744 auto current
= m_list
->currentP();
1745 current
->setSelected(!current
->isSelected());
1748 bool SelectRange::canBeRun()
1750 m_list
= dynamic_cast<NC::List
*>(myScreen
->activeWindow());
1751 if (m_list
== nullptr)
1753 m_begin
= m_list
->beginP();
1754 m_end
= m_list
->endP();
1755 return findRange(m_begin
, m_end
);
1758 void SelectRange::run()
1760 for (; m_begin
!= m_end
; ++m_begin
)
1761 m_begin
->setSelected(true);
1762 Statusbar::print("Range selected");
1765 bool ReverseSelection::canBeRun()
1767 m_list
= dynamic_cast<NC::List
*>(myScreen
->activeWindow());
1768 return m_list
!= nullptr;
1771 void ReverseSelection::run()
1773 for (auto &p
: *m_list
)
1774 p
.setSelected(!p
.isSelected());
1775 Statusbar::print("Selection reversed");
1778 bool RemoveSelection::canBeRun()
1780 m_list
= dynamic_cast<NC::List
*>(myScreen
->activeWindow());
1781 return m_list
!= nullptr;
1784 void RemoveSelection::run()
1786 for (auto &p
: *m_list
)
1787 p
.setSelected(false);
1788 Statusbar::print("Selection removed");
1791 bool SelectAlbum::canBeRun()
1793 auto *w
= myScreen
->activeWindow();
1794 if (m_list
!= static_cast<void *>(w
))
1795 m_list
= dynamic_cast<NC::List
*>(w
);
1796 if (m_songs
!= static_cast<void *>(w
))
1797 m_songs
= dynamic_cast<SongList
*>(w
);
1798 return m_list
!= nullptr && !m_list
->empty()
1799 && m_songs
!= nullptr;
1802 void SelectAlbum::run()
1804 const auto front
= m_songs
->beginS(), current
= m_songs
->currentS(), end
= m_songs
->endS();
1805 auto *s
= current
->get
<Bit::Song
>();
1808 auto get
= &MPD::Song::getAlbum
;
1809 const std::string tag
= s
->getTags(get
);
1811 for (auto it
= current
; it
!= front
;)
1814 s
= it
->get
<Bit::Song
>();
1815 if (s
== nullptr || s
->getTags(get
) != tag
)
1817 it
->get
<Bit::Properties
>().setSelected(true);
1820 for (auto it
= current
;;)
1822 it
->get
<Bit::Properties
>().setSelected(true);
1825 s
= it
->get
<Bit::Song
>();
1826 if (s
== nullptr || s
->getTags(get
) != tag
)
1829 Statusbar::print("Album around cursor position selected");
1832 bool SelectFoundItems::canBeRun()
1834 m_list
= dynamic_cast<NC::List
*>(myScreen
->activeWindow());
1835 if (m_list
== nullptr || m_list
->empty())
1837 m_searchable
= dynamic_cast<Searchable
*>(myScreen
);
1838 return m_searchable
!= nullptr && m_searchable
->allowsSearching();
1841 void SelectFoundItems::run()
1843 auto current_pos
= m_list
->choice();
1844 myScreen
->activeWindow()->scroll(NC::Scroll::Home
);
1845 bool found
= m_searchable
->search(SearchDirection::Forward
, false, false);
1848 Statusbar::print("Searching for items...");
1849 m_list
->currentP()->setSelected(true);
1850 while (m_searchable
->search(SearchDirection::Forward
, false, true))
1851 m_list
->currentP()->setSelected(true);
1852 Statusbar::print("Found items selected");
1854 m_list
->highlight(current_pos
);
1857 bool AddSelectedItems::canBeRun()
1859 return myScreen
!= mySelectedItemsAdder
;
1862 void AddSelectedItems::run()
1864 mySelectedItemsAdder
->switchTo();
1867 void CropMainPlaylist::run()
1869 auto &w
= myPlaylist
->main();
1870 // cropping doesn't make sense in this case
1873 if (Config
.ask_before_clearing_playlists
)
1874 confirmAction("Do you really want to crop main playlist?");
1875 Statusbar::print("Cropping playlist...");
1876 selectCurrentIfNoneSelected(w
);
1877 cropPlaylist(w
, std::bind(&MPD::Connection::Delete
, ph::_1
, ph::_2
));
1878 Statusbar::print("Playlist cropped");
1881 bool CropPlaylist::canBeRun()
1883 return myScreen
== myPlaylistEditor
;
1886 void CropPlaylist::run()
1888 auto &w
= myPlaylistEditor
->Content
;
1889 // cropping doesn't make sense in this case
1892 assert(!myPlaylistEditor
->Playlists
.empty());
1893 std::string playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
1894 if (Config
.ask_before_clearing_playlists
)
1895 confirmAction(boost::format("Do you really want to crop playlist \"%1%\"?") % playlist
);
1896 selectCurrentIfNoneSelected(w
);
1897 Statusbar::printf("Cropping playlist \"%1%\"...", playlist
);
1898 cropPlaylist(w
, std::bind(&MPD::Connection::PlaylistDelete
, ph::_1
, playlist
, ph::_2
));
1899 Statusbar::printf("Playlist \"%1%\" cropped", playlist
);
1902 void ClearMainPlaylist::run()
1904 if (!myPlaylist
->main().empty() && Config
.ask_before_clearing_playlists
)
1905 confirmAction("Do you really want to clear main playlist?");
1906 Mpd
.ClearMainPlaylist();
1907 Statusbar::print("Playlist cleared");
1908 myPlaylist
->main().reset();
1911 bool ClearPlaylist::canBeRun()
1913 return myScreen
== myPlaylistEditor
;
1916 void ClearPlaylist::run()
1918 if (myPlaylistEditor
->Playlists
.empty())
1920 std::string playlist
= myPlaylistEditor
->Playlists
.current()->value().path();
1921 if (Config
.ask_before_clearing_playlists
)
1922 confirmAction(boost::format("Do you really want to clear playlist \"%1%\"?") % playlist
);
1923 Mpd
.ClearPlaylist(playlist
);
1924 Statusbar::printf("Playlist \"%1%\" cleared", playlist
);
1927 bool SortPlaylist::canBeRun()
1929 if (myScreen
!= myPlaylist
)
1931 auto first
= myPlaylist
->main().begin(), last
= myPlaylist
->main().end();
1932 return findSelectedRangeAndPrintInfoIfNot(first
, last
);
1935 void SortPlaylist::run()
1937 mySortPlaylistDialog
->switchTo();
1940 bool ReversePlaylist::canBeRun()
1942 if (myScreen
!= myPlaylist
)
1944 m_begin
= myPlaylist
->main().begin();
1945 m_end
= myPlaylist
->main().end();
1946 return findSelectedRangeAndPrintInfoIfNot(m_begin
, m_end
);
1949 void ReversePlaylist::run()
1951 Statusbar::print("Reversing range...");
1952 Mpd
.StartCommandsList();
1953 for (--m_end
; m_begin
< m_end
; ++m_begin
, --m_end
)
1954 Mpd
.Swap(m_begin
->value().getPosition(), m_end
->value().getPosition());
1955 Mpd
.CommitCommandsList();
1956 Statusbar::print("Range reversed");
1959 bool ApplyFilter::canBeRun()
1961 m_searchable
= dynamic_cast<Searchable
*>(myScreen
);
1962 return m_searchable
!= nullptr;
1965 void ApplyFilter::run()
1967 using Global::wFooter
;
1969 std::string filter
= m_searchable
->currentFilter();
1970 if (!filter
.empty())
1972 m_searchable
->applyFilter(filter
);
1973 myScreen
->refreshWindow();
1978 Statusbar::ScopedLock slock
;
1979 NC::Window::ScopedPromptHook
helper(
1981 Statusbar::Helpers::ApplyFilterImmediately(m_searchable
));
1982 Statusbar::put() << "Apply filter: ";
1983 filter
= wFooter
->prompt(filter
);
1985 catch (NC::PromptAborted
&)
1987 m_searchable
->applyFilter(filter
);
1992 Statusbar::printf("Filtering disabled");
1994 Statusbar::printf("Using filter \"%1%\"", filter
);
1996 if (myScreen
== myPlaylist
)
1997 myPlaylist
->reloadTotalLength();
1999 listsChangeFinisher();
2002 bool Find::canBeRun()
2004 return myScreen
== myHelp
2005 || myScreen
== myLyrics
2006 # ifdef HAVE_CURL_CURL_H
2007 || myScreen
== myLastfm
2008 # endif // HAVE_CURL_CURL_H
2014 using Global::wFooter
;
2018 Statusbar::ScopedLock slock
;
2019 Statusbar::put() << "Find: ";
2020 token
= wFooter
->prompt();
2023 Statusbar::print("Searching...");
2024 auto s
= static_cast<Screen
<NC::Scrollpad
> *>(myScreen
);
2025 s
->main().removeProperties();
2026 if (token
.empty() || s
->main().setProperties(NC::Format::Reverse
, token
, NC::Format::NoReverse
, Config
.regex_type
))
2027 Statusbar::print("Done");
2029 Statusbar::print("No matching patterns found");
2033 bool FindItemBackward::canBeRun()
2035 auto w
= dynamic_cast<Searchable
*>(myScreen
);
2036 return w
&& w
->allowsSearching();
2039 void FindItemForward::run()
2041 findItem(SearchDirection::Forward
);
2042 listsChangeFinisher();
2045 bool FindItemForward::canBeRun()
2047 auto w
= dynamic_cast<Searchable
*>(myScreen
);
2048 return w
&& w
->allowsSearching();
2051 void FindItemBackward::run()
2053 findItem(SearchDirection::Backward
);
2054 listsChangeFinisher();
2057 bool NextFoundItem::canBeRun()
2059 return dynamic_cast<Searchable
*>(myScreen
);
2062 void NextFoundItem::run()
2064 Searchable
*w
= dynamic_cast<Searchable
*>(myScreen
);
2065 assert(w
!= nullptr);
2066 w
->search(SearchDirection::Forward
, Config
.wrapped_search
, true);
2067 listsChangeFinisher();
2070 bool PreviousFoundItem::canBeRun()
2072 return dynamic_cast<Searchable
*>(myScreen
);
2075 void PreviousFoundItem::run()
2077 Searchable
*w
= dynamic_cast<Searchable
*>(myScreen
);
2078 assert(w
!= nullptr);
2079 w
->search(SearchDirection::Backward
, Config
.wrapped_search
, true);
2080 listsChangeFinisher();
2083 void ToggleFindMode::run()
2085 Config
.wrapped_search
= !Config
.wrapped_search
;
2086 Statusbar::printf("Search mode: %1%",
2087 Config
.wrapped_search
? "Wrapped" : "Normal"
2091 void ToggleReplayGainMode::run()
2093 using Global::wFooter
;
2097 Statusbar::ScopedLock slock
;
2098 Statusbar::put() << "Replay gain mode? "
2099 << "[" << NC::Format::Bold
<< 'o' << NC::Format::NoBold
<< "ff"
2100 << "/" << NC::Format::Bold
<< 't' << NC::Format::NoBold
<< "rack"
2101 << "/" << NC::Format::Bold
<< 'a' << NC::Format::NoBold
<< "lbum"
2103 rgm
= Statusbar::Helpers::promptReturnOneOf({"t", "a", "o"})[0];
2108 Mpd
.SetReplayGainMode(MPD::rgmTrack
);
2111 Mpd
.SetReplayGainMode(MPD::rgmAlbum
);
2114 Mpd
.SetReplayGainMode(MPD::rgmOff
);
2116 default: // impossible
2117 throw std::runtime_error(
2118 (boost::format("ToggleReplayGainMode: impossible case reached: %1%") % rgm
).str()
2121 Statusbar::printf("Replay gain mode: %1%", Mpd
.GetReplayGainMode());
2124 void ToggleAddMode::run()
2126 std::string mode_desc
;
2127 switch (Config
.space_add_mode
)
2129 case SpaceAddMode::AddRemove
:
2130 Config
.space_add_mode
= SpaceAddMode::AlwaysAdd
;
2131 mode_desc
= "always add an item to playlist";
2133 case SpaceAddMode::AlwaysAdd
:
2134 Config
.space_add_mode
= SpaceAddMode::AddRemove
;
2135 mode_desc
= "add an item to playlist or remove if already added";
2138 Statusbar::printf("Add mode: %1%", mode_desc
);
2141 void ToggleMouse::run()
2143 Config
.mouse_support
= !Config
.mouse_support
;
2144 if (Config
.mouse_support
)
2145 NC::Mouse::enable();
2147 NC::Mouse::disable();
2148 Statusbar::printf("Mouse support %1%",
2149 Config
.mouse_support
? "enabled" : "disabled"
2153 void ToggleBitrateVisibility::run()
2155 Config
.display_bitrate
= !Config
.display_bitrate
;
2156 Statusbar::printf("Bitrate visibility %1%",
2157 Config
.display_bitrate
? "enabled" : "disabled"
2161 void AddRandomItems::run()
2163 using Global::wFooter
;
2166 Statusbar::ScopedLock slock
;
2167 Statusbar::put() << "Add random? "
2168 << "[" << NC::Format::Bold
<< 's' << NC::Format::NoBold
<< "ongs"
2169 << "/" << NC::Format::Bold
<< 'a' << NC::Format::NoBold
<< "rtists"
2170 << "/" << "album" << NC::Format::Bold
<< 'A' << NC::Format::NoBold
<< "rtists"
2171 << "/" << "al" << NC::Format::Bold
<< 'b' << NC::Format::NoBold
<< "ums"
2173 rnd_type
= Statusbar::Helpers::promptReturnOneOf({"s", "a", "A", "b"})[0];
2176 mpd_tag_type tag_type
= MPD_TAG_ARTIST
;
2177 std::string tag_type_str
;
2178 if (rnd_type
!= 's')
2180 tag_type
= charToTagType(rnd_type
);
2181 tag_type_str
= boost::locale::to_lower(tagTypeToString(tag_type
));
2184 tag_type_str
= "song";
2188 Statusbar::ScopedLock slock
;
2189 Statusbar::put() << "Number of random " << tag_type_str
<< "s: ";
2190 number
= fromString
<unsigned>(wFooter
->prompt());
2195 if (rnd_type
== 's')
2196 success
= Mpd
.AddRandomSongs(number
, Global::RNG
);
2198 success
= Mpd
.AddRandomTag(tag_type
, number
, Global::RNG
);
2200 Statusbar::printf("%1% random %2%%3% added to playlist", number
, tag_type_str
, number
== 1 ? "" : "s");
2204 bool ToggleBrowserSortMode::canBeRun()
2206 return myScreen
== myBrowser
;
2209 void ToggleBrowserSortMode::run()
2211 switch (Config
.browser_sort_mode
)
2213 case SortMode::Name
:
2214 Config
.browser_sort_mode
= SortMode::ModificationTime
;
2215 Statusbar::print("Sort songs by: modification time");
2217 case SortMode::ModificationTime
:
2218 Config
.browser_sort_mode
= SortMode::CustomFormat
;
2219 Statusbar::print("Sort songs by: custom format");
2221 case SortMode::CustomFormat
:
2222 Config
.browser_sort_mode
= SortMode::NoOp
;
2223 Statusbar::print("Do not sort songs");
2225 case SortMode::NoOp
:
2226 Config
.browser_sort_mode
= SortMode::Name
;
2227 Statusbar::print("Sort songs by: name");
2229 if (Config
.browser_sort_mode
!= SortMode::NoOp
)
2231 size_t sort_offset
= myBrowser
->inRootDirectory() ? 0 : 1;
2232 std::sort(myBrowser
->main().begin()+sort_offset
, myBrowser
->main().end(),
2233 LocaleBasedItemSorting(std::locale(), Config
.ignore_leading_the
, Config
.browser_sort_mode
)
2238 bool ToggleLibraryTagType::canBeRun()
2240 return (myScreen
->isActiveWindow(myLibrary
->Tags
))
2241 || (myLibrary
->columns() == 2 && myScreen
->isActiveWindow(myLibrary
->Albums
));
2244 void ToggleLibraryTagType::run()
2246 using Global::wFooter
;
2250 Statusbar::ScopedLock slock
;
2251 Statusbar::put() << "Tag type? "
2252 << "[" << NC::Format::Bold
<< 'a' << NC::Format::NoBold
<< "rtist"
2253 << "/" << "album" << NC::Format::Bold
<< 'A' << NC::Format::NoBold
<< "rtist"
2254 << "/" << NC::Format::Bold
<< 'y' << NC::Format::NoBold
<< "ear"
2255 << "/" << NC::Format::Bold
<< 'g' << NC::Format::NoBold
<< "enre"
2256 << "/" << NC::Format::Bold
<< 'c' << NC::Format::NoBold
<< "omposer"
2257 << "/" << NC::Format::Bold
<< 'p' << NC::Format::NoBold
<< "erformer"
2259 tag_type
= Statusbar::Helpers::promptReturnOneOf({"a", "A", "y", "g", "c", "p"})[0];
2261 mpd_tag_type new_tagitem
= charToTagType(tag_type
);
2262 if (new_tagitem
!= Config
.media_lib_primary_tag
)
2264 Config
.media_lib_primary_tag
= new_tagitem
;
2265 std::string item_type
= tagTypeToString(Config
.media_lib_primary_tag
);
2266 myLibrary
->Tags
.setTitle(Config
.titles_visibility
? item_type
+ "s" : "");
2267 myLibrary
->Tags
.reset();
2268 item_type
= boost::locale::to_lower(item_type
);
2269 std::string and_mtime
= Config
.media_library_sort_by_mtime
?
2272 if (myLibrary
->columns() == 2)
2274 myLibrary
->Songs
.clear();
2275 myLibrary
->Albums
.reset();
2276 myLibrary
->Albums
.clear();
2277 myLibrary
->Albums
.setTitle(Config
.titles_visibility
? "Albums (sorted by " + item_type
+ and_mtime
+ ")" : "");
2278 myLibrary
->Albums
.display();
2282 myLibrary
->Tags
.clear();
2283 myLibrary
->Tags
.display();
2285 Statusbar::printf("Switched to the list of %1%s", item_type
);
2289 bool ToggleMediaLibrarySortMode::canBeRun()
2291 return myScreen
== myLibrary
;
2294 void ToggleMediaLibrarySortMode::run()
2296 myLibrary
->toggleSortMode();
2299 bool RefetchLyrics::canBeRun()
2301 # ifdef HAVE_CURL_CURL_H
2302 return myScreen
== myLyrics
;
2305 # endif // HAVE_CURL_CURL_H
2308 void RefetchLyrics::run()
2310 # ifdef HAVE_CURL_CURL_H
2311 myLyrics
->Refetch();
2312 # endif // HAVE_CURL_CURL_H
2315 bool SetSelectedItemsPriority::canBeRun()
2317 if (Mpd
.Version() < 17)
2319 Statusbar::print("Priorities are supported in MPD >= 0.17.0");
2322 return myScreen
== myPlaylist
&& !myPlaylist
->main().empty();
2325 void SetSelectedItemsPriority::run()
2327 using Global::wFooter
;
2331 Statusbar::ScopedLock slock
;
2332 Statusbar::put() << "Set priority [0-255]: ";
2333 prio
= fromString
<unsigned>(wFooter
->prompt());
2334 boundsCheck(prio
, 0u, 255u);
2336 myPlaylist
->setSelectedItemsPriority(prio
);
2339 bool ToggleOutput::canBeRun()
2341 #ifdef ENABLE_OUTPUTS
2342 return myScreen
== myOutputs
;
2345 #endif // ENABLE_OUTPUTS
2348 void ToggleOutput::run()
2350 #ifdef ENABLE_OUTPUTS
2351 myOutputs
->toggleOutput();
2352 #endif // ENABLE_OUTPUTS
2355 bool ToggleVisualizationType::canBeRun()
2357 # ifdef ENABLE_VISUALIZER
2358 return myScreen
== myVisualizer
;
2361 # endif // ENABLE_VISUALIZER
2364 void ToggleVisualizationType::run()
2366 # ifdef ENABLE_VISUALIZER
2367 myVisualizer
->ToggleVisualizationType();
2368 # endif // ENABLE_VISUALIZER
2371 bool SetVisualizerSampleMultiplier::canBeRun()
2373 # ifdef ENABLE_VISUALIZER
2374 return myScreen
== myVisualizer
;
2377 # endif // ENABLE_VISUALIZER
2380 void SetVisualizerSampleMultiplier::run()
2382 # ifdef ENABLE_VISUALIZER
2383 using Global::wFooter
;
2387 Statusbar::ScopedLock slock
;
2388 Statusbar::put() << "Set visualizer sample multiplier: ";
2389 multiplier
= fromString
<double>(wFooter
->prompt());
2390 lowerBoundCheck(multiplier
, 0.0);
2391 Config
.visualizer_sample_multiplier
= multiplier
;
2393 Statusbar::printf("Visualizer sample multiplier set to %1%", multiplier
);
2394 # endif // ENABLE_VISUALIZER
2397 void ShowSongInfo::run()
2399 mySongInfo
->switchTo();
2402 bool ShowArtistInfo::canBeRun()
2404 #ifdef HAVE_CURL_CURL_H
2405 return myScreen
== myLastfm
2406 || (myScreen
->isActiveWindow(myLibrary
->Tags
)
2407 && !myLibrary
->Tags
.empty()
2408 && Config
.media_lib_primary_tag
== MPD_TAG_ARTIST
)
2409 || currentSong(myScreen
);
2412 # endif // NOT HAVE_CURL_CURL_H
2415 void ShowArtistInfo::run()
2417 # ifdef HAVE_CURL_CURL_H
2418 if (myScreen
== myLastfm
)
2420 myLastfm
->switchTo();
2425 if (myScreen
->isActiveWindow(myLibrary
->Tags
))
2427 assert(!myLibrary
->Tags
.empty());
2428 assert(Config
.media_lib_primary_tag
== MPD_TAG_ARTIST
);
2429 artist
= myLibrary
->Tags
.current()->value().tag();
2433 auto s
= currentSong(myScreen
);
2435 artist
= s
->getArtist();
2438 if (!artist
.empty())
2440 myLastfm
->queueJob(new LastFm::ArtistInfo(artist
, Config
.lastfm_preferred_language
));
2441 myLastfm
->switchTo();
2443 # endif // HAVE_CURL_CURL_H
2446 void ShowLyrics::run()
2448 myLyrics
->switchTo();
2453 ExitMainLoop
= true;
2456 void NextScreen::run()
2458 if (Config
.screen_switcher_previous
)
2460 if (auto tababble
= dynamic_cast<Tabbable
*>(myScreen
))
2461 tababble
->switchToPreviousScreen();
2463 else if (!Config
.screen_sequence
.empty())
2465 const auto &seq
= Config
.screen_sequence
;
2466 auto screen_type
= std::find(seq
.begin(), seq
.end(), myScreen
->type());
2467 if (++screen_type
== seq
.end())
2468 toScreen(seq
.front())->switchTo();
2470 toScreen(*screen_type
)->switchTo();
2474 void PreviousScreen::run()
2476 if (Config
.screen_switcher_previous
)
2478 if (auto tababble
= dynamic_cast<Tabbable
*>(myScreen
))
2479 tababble
->switchToPreviousScreen();
2481 else if (!Config
.screen_sequence
.empty())
2483 const auto &seq
= Config
.screen_sequence
;
2484 auto screen_type
= std::find(seq
.begin(), seq
.end(), myScreen
->type());
2485 if (screen_type
== seq
.begin())
2486 toScreen(seq
.back())->switchTo();
2488 toScreen(*--screen_type
)->switchTo();
2492 bool ShowHelp::canBeRun()
2494 return myScreen
!= myHelp
2495 # ifdef HAVE_TAGLIB_H
2496 && myScreen
!= myTinyTagEditor
2497 # endif // HAVE_TAGLIB_H
2501 void ShowHelp::run()
2506 bool ShowPlaylist::canBeRun()
2508 return myScreen
!= myPlaylist
2509 # ifdef HAVE_TAGLIB_H
2510 && myScreen
!= myTinyTagEditor
2511 # endif // HAVE_TAGLIB_H
2515 void ShowPlaylist::run()
2517 myPlaylist
->switchTo();
2520 bool ShowBrowser::canBeRun()
2522 return myScreen
!= myBrowser
2523 # ifdef HAVE_TAGLIB_H
2524 && myScreen
!= myTinyTagEditor
2525 # endif // HAVE_TAGLIB_H
2529 void ShowBrowser::run()
2531 myBrowser
->switchTo();
2534 bool ChangeBrowseMode::canBeRun()
2536 return myScreen
== myBrowser
;
2539 void ChangeBrowseMode::run()
2541 myBrowser
->changeBrowseMode();
2544 bool ShowSearchEngine::canBeRun()
2546 return myScreen
!= mySearcher
2547 # ifdef HAVE_TAGLIB_H
2548 && myScreen
!= myTinyTagEditor
2549 # endif // HAVE_TAGLIB_H
2553 void ShowSearchEngine::run()
2555 mySearcher
->switchTo();
2558 bool ResetSearchEngine::canBeRun()
2560 return myScreen
== mySearcher
;
2563 void ResetSearchEngine::run()
2565 mySearcher
->reset();
2568 bool ShowMediaLibrary::canBeRun()
2570 return myScreen
!= myLibrary
2571 # ifdef HAVE_TAGLIB_H
2572 && myScreen
!= myTinyTagEditor
2573 # endif // HAVE_TAGLIB_H
2577 void ShowMediaLibrary::run()
2579 myLibrary
->switchTo();
2582 bool ToggleMediaLibraryColumnsMode::canBeRun()
2584 return myScreen
== myLibrary
;
2587 void ToggleMediaLibraryColumnsMode::run()
2589 myLibrary
->toggleColumnsMode();
2590 myLibrary
->refresh();
2593 bool ShowPlaylistEditor::canBeRun()
2595 return myScreen
!= myPlaylistEditor
2596 # ifdef HAVE_TAGLIB_H
2597 && myScreen
!= myTinyTagEditor
2598 # endif // HAVE_TAGLIB_H
2602 void ShowPlaylistEditor::run()
2604 myPlaylistEditor
->switchTo();
2607 bool ShowTagEditor::canBeRun()
2609 # ifdef HAVE_TAGLIB_H
2610 return myScreen
!= myTagEditor
2611 && myScreen
!= myTinyTagEditor
;
2614 # endif // HAVE_TAGLIB_H
2617 void ShowTagEditor::run()
2619 # ifdef HAVE_TAGLIB_H
2620 if (isMPDMusicDirSet())
2621 myTagEditor
->switchTo();
2622 # endif // HAVE_TAGLIB_H
2625 bool ShowOutputs::canBeRun()
2627 # ifdef ENABLE_OUTPUTS
2628 return myScreen
!= myOutputs
2629 # ifdef HAVE_TAGLIB_H
2630 && myScreen
!= myTinyTagEditor
2631 # endif // HAVE_TAGLIB_H
2635 # endif // ENABLE_OUTPUTS
2638 void ShowOutputs::run()
2640 # ifdef ENABLE_OUTPUTS
2641 myOutputs
->switchTo();
2642 # endif // ENABLE_OUTPUTS
2645 bool ShowVisualizer::canBeRun()
2647 # ifdef ENABLE_VISUALIZER
2648 return myScreen
!= myVisualizer
2649 # ifdef HAVE_TAGLIB_H
2650 && myScreen
!= myTinyTagEditor
2651 # endif // HAVE_TAGLIB_H
2655 # endif // ENABLE_VISUALIZER
2658 void ShowVisualizer::run()
2660 # ifdef ENABLE_VISUALIZER
2661 myVisualizer
->switchTo();
2662 # endif // ENABLE_VISUALIZER
2665 bool ShowClock::canBeRun()
2667 # ifdef ENABLE_CLOCK
2668 return myScreen
!= myClock
2669 # ifdef HAVE_TAGLIB_H
2670 && myScreen
!= myTinyTagEditor
2671 # endif // HAVE_TAGLIB_H
2675 # endif // ENABLE_CLOCK
2678 void ShowClock::run()
2680 # ifdef ENABLE_CLOCK
2681 myClock
->switchTo();
2682 # endif // ENABLE_CLOCK
2685 #ifdef HAVE_TAGLIB_H
2686 bool ShowServerInfo::canBeRun()
2688 return myScreen
!= myTinyTagEditor
;
2690 #endif // HAVE_TAGLIB_H
2692 void ShowServerInfo::run()
2694 myServerInfo
->switchTo();
2701 void populateActions()
2703 auto insert_action
= [](Actions::BaseAction
*a
) {
2704 AvailableActions
[static_cast<size_t>(a
->type())] = a
;
2706 insert_action(new Actions::Dummy());
2707 insert_action(new Actions::UpdateEnvironment());
2708 insert_action(new Actions::MouseEvent());
2709 insert_action(new Actions::ScrollUp());
2710 insert_action(new Actions::ScrollDown());
2711 insert_action(new Actions::ScrollUpArtist());
2712 insert_action(new Actions::ScrollUpAlbum());
2713 insert_action(new Actions::ScrollDownArtist());
2714 insert_action(new Actions::ScrollDownAlbum());
2715 insert_action(new Actions::PageUp());
2716 insert_action(new Actions::PageDown());
2717 insert_action(new Actions::MoveHome());
2718 insert_action(new Actions::MoveEnd());
2719 insert_action(new Actions::ToggleInterface());
2720 insert_action(new Actions::JumpToParentDirectory());
2721 insert_action(new Actions::RunAction());
2722 insert_action(new Actions::SelectItem());
2723 insert_action(new Actions::SelectRange());
2724 insert_action(new Actions::PreviousColumn());
2725 insert_action(new Actions::NextColumn());
2726 insert_action(new Actions::MasterScreen());
2727 insert_action(new Actions::SlaveScreen());
2728 insert_action(new Actions::VolumeUp());
2729 insert_action(new Actions::VolumeDown());
2730 insert_action(new Actions::AddItemToPlaylist());
2731 insert_action(new Actions::DeletePlaylistItems());
2732 insert_action(new Actions::DeleteStoredPlaylist());
2733 insert_action(new Actions::DeleteBrowserItems());
2734 insert_action(new Actions::ReplaySong());
2735 insert_action(new Actions::PreviousSong());
2736 insert_action(new Actions::NextSong());
2737 insert_action(new Actions::Pause());
2738 insert_action(new Actions::Stop());
2739 insert_action(new Actions::ExecuteCommand());
2740 insert_action(new Actions::SavePlaylist());
2741 insert_action(new Actions::MoveSortOrderUp());
2742 insert_action(new Actions::MoveSortOrderDown());
2743 insert_action(new Actions::MoveSelectedItemsUp());
2744 insert_action(new Actions::MoveSelectedItemsDown());
2745 insert_action(new Actions::MoveSelectedItemsTo());
2746 insert_action(new Actions::Add());
2747 insert_action(new Actions::PlayItem());
2748 insert_action(new Actions::SeekForward());
2749 insert_action(new Actions::SeekBackward());
2750 insert_action(new Actions::ToggleDisplayMode());
2751 insert_action(new Actions::ToggleSeparatorsBetweenAlbums());
2752 insert_action(new Actions::ToggleLyricsUpdateOnSongChange());
2753 insert_action(new Actions::ToggleLyricsFetcher());
2754 insert_action(new Actions::ToggleFetchingLyricsInBackground());
2755 insert_action(new Actions::TogglePlayingSongCentering());
2756 insert_action(new Actions::UpdateDatabase());
2757 insert_action(new Actions::JumpToPlayingSong());
2758 insert_action(new Actions::ToggleRepeat());
2759 insert_action(new Actions::Shuffle());
2760 insert_action(new Actions::ToggleRandom());
2761 insert_action(new Actions::StartSearching());
2762 insert_action(new Actions::SaveTagChanges());
2763 insert_action(new Actions::ToggleSingle());
2764 insert_action(new Actions::ToggleConsume());
2765 insert_action(new Actions::ToggleCrossfade());
2766 insert_action(new Actions::SetCrossfade());
2767 insert_action(new Actions::SetVolume());
2768 insert_action(new Actions::EnterDirectory());
2769 insert_action(new Actions::EditSong());
2770 insert_action(new Actions::EditLibraryTag());
2771 insert_action(new Actions::EditLibraryAlbum());
2772 insert_action(new Actions::EditDirectoryName());
2773 insert_action(new Actions::EditPlaylistName());
2774 insert_action(new Actions::EditLyrics());
2775 insert_action(new Actions::JumpToBrowser());
2776 insert_action(new Actions::JumpToMediaLibrary());
2777 insert_action(new Actions::JumpToPlaylistEditor());
2778 insert_action(new Actions::ToggleScreenLock());
2779 insert_action(new Actions::JumpToTagEditor());
2780 insert_action(new Actions::JumpToPositionInSong());
2781 insert_action(new Actions::ReverseSelection());
2782 insert_action(new Actions::RemoveSelection());
2783 insert_action(new Actions::SelectAlbum());
2784 insert_action(new Actions::SelectFoundItems());
2785 insert_action(new Actions::AddSelectedItems());
2786 insert_action(new Actions::CropMainPlaylist());
2787 insert_action(new Actions::CropPlaylist());
2788 insert_action(new Actions::ClearMainPlaylist());
2789 insert_action(new Actions::ClearPlaylist());
2790 insert_action(new Actions::SortPlaylist());
2791 insert_action(new Actions::ReversePlaylist());
2792 insert_action(new Actions::ApplyFilter());
2793 insert_action(new Actions::Find());
2794 insert_action(new Actions::FindItemForward());
2795 insert_action(new Actions::FindItemBackward());
2796 insert_action(new Actions::NextFoundItem());
2797 insert_action(new Actions::PreviousFoundItem());
2798 insert_action(new Actions::ToggleFindMode());
2799 insert_action(new Actions::ToggleReplayGainMode());
2800 insert_action(new Actions::ToggleAddMode());
2801 insert_action(new Actions::ToggleMouse());
2802 insert_action(new Actions::ToggleBitrateVisibility());
2803 insert_action(new Actions::AddRandomItems());
2804 insert_action(new Actions::ToggleBrowserSortMode());
2805 insert_action(new Actions::ToggleLibraryTagType());
2806 insert_action(new Actions::ToggleMediaLibrarySortMode());
2807 insert_action(new Actions::RefetchLyrics());
2808 insert_action(new Actions::SetSelectedItemsPriority());
2809 insert_action(new Actions::ToggleOutput());
2810 insert_action(new Actions::ToggleVisualizationType());
2811 insert_action(new Actions::SetVisualizerSampleMultiplier());
2812 insert_action(new Actions::ShowSongInfo());
2813 insert_action(new Actions::ShowArtistInfo());
2814 insert_action(new Actions::ShowLyrics());
2815 insert_action(new Actions::Quit());
2816 insert_action(new Actions::NextScreen());
2817 insert_action(new Actions::PreviousScreen());
2818 insert_action(new Actions::ShowHelp());
2819 insert_action(new Actions::ShowPlaylist());
2820 insert_action(new Actions::ShowBrowser());
2821 insert_action(new Actions::ChangeBrowseMode());
2822 insert_action(new Actions::ShowSearchEngine());
2823 insert_action(new Actions::ResetSearchEngine());
2824 insert_action(new Actions::ShowMediaLibrary());
2825 insert_action(new Actions::ToggleMediaLibraryColumnsMode());
2826 insert_action(new Actions::ShowPlaylistEditor());
2827 insert_action(new Actions::ShowTagEditor());
2828 insert_action(new Actions::ShowOutputs());
2829 insert_action(new Actions::ShowVisualizer());
2830 insert_action(new Actions::ShowClock());
2831 insert_action(new Actions::ShowServerInfo());
2834 bool scrollTagCanBeRun(NC::List
*&list
, SongList
*&songs
)
2836 auto w
= myScreen
->activeWindow();
2837 if (list
!= static_cast<void *>(w
))
2838 list
= dynamic_cast<NC::List
*>(w
);
2839 if (songs
!= static_cast<void *>(w
))
2840 songs
= dynamic_cast<SongList
*>(w
);
2841 return list
!= nullptr && !list
->empty()
2842 && songs
!= nullptr;
2845 void scrollTagUpRun(NC::List
*list
, SongList
*songs
, MPD::Song::GetFunction get
)
2847 const auto front
= songs
->beginS();
2848 auto it
= songs
->currentS();
2849 if (auto *s
= it
->get
<Bit::Song
>())
2851 const std::string tag
= s
->getTags(get
);
2855 s
= it
->get
<Bit::Song
>();
2856 if (s
== nullptr || s
->getTags(get
) != tag
)
2859 list
->highlight(it
-front
);
2863 void scrollTagDownRun(NC::List
*list
, SongList
*songs
, MPD::Song::GetFunction get
)
2865 const auto front
= songs
->beginS(), back
= --songs
->endS();
2866 auto it
= songs
->currentS();
2867 if (auto *s
= it
->get
<Bit::Song
>())
2869 const std::string tag
= s
->getTags(get
);
2873 s
= it
->get
<Bit::Song
>();
2874 if (s
== nullptr || s
->getTags(get
) != tag
)
2877 list
->highlight(it
-front
);
2883 using Global::wHeader
;
2884 using Global::wFooter
;
2885 using Global::Timer
;
2886 using Global::SeekingInProgress
;
2888 if (!Status::State::totalTime())
2890 Statusbar::print("Unknown item length");
2894 Progressbar::ScopedLock progressbar_lock
;
2895 Statusbar::ScopedLock statusbar_lock
;
2897 unsigned songpos
= Status::State::elapsedTime();
2900 int old_timeout
= wFooter
->getTimeout();
2901 wFooter
->setTimeout(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
, Actions::Type type
) {
2908 bool success
= false;
2909 for (auto binding
= bindings
.first
; binding
!= bindings
.second
; ++binding
)
2911 auto &actions
= binding
->actions();
2912 for (const auto &action
: actions
)
2914 if (action
->canBeRun())
2916 if (action
->type() == type
)
2931 SeekingInProgress
= true;
2936 unsigned howmuch
= Config
.incremental_seeking
2937 ? (Timer
-t
).total_seconds()/2+Config
.seek_time
2940 NC::Key::Type input
= readKey(*wFooter
);
2942 auto k
= Bindings
.get(input
);
2943 if (hasRunnableAction(k
, Actions::Type::SeekForward
))
2945 if (songpos
< Status::State::totalTime())
2946 songpos
= std::min(songpos
+ howmuch
, Status::State::totalTime());
2948 else if (hasRunnableAction(k
, Actions::Type::SeekBackward
))
2952 if (songpos
< howmuch
)
2961 *wFooter
<< NC::Format::Bold
;
2962 std::string tracklength
;
2963 // FIXME: merge this with the code in status.cpp
2964 switch (Config
.design
)
2966 case Design::Classic
:
2968 if (Config
.display_remaining_time
)
2971 tracklength
+= MPD::Song::ShowTime(Status::State::totalTime()-songpos
);
2974 tracklength
+= MPD::Song::ShowTime(songpos
);
2976 tracklength
+= MPD::Song::ShowTime(Status::State::totalTime());
2978 *wFooter
<< NC::XY(wFooter
->getWidth()-tracklength
.length(), 1) << tracklength
;
2980 case Design::Alternative
:
2981 if (Config
.display_remaining_time
)
2984 tracklength
+= MPD::Song::ShowTime(Status::State::totalTime()-songpos
);
2987 tracklength
= MPD::Song::ShowTime(songpos
);
2989 tracklength
+= MPD::Song::ShowTime(Status::State::totalTime());
2990 *wHeader
<< NC::XY(0, 0) << tracklength
<< " ";
2994 *wFooter
<< NC::Format::NoBold
;
2995 Progressbar::draw(songpos
, Status::State::totalTime());
2998 SeekingInProgress
= false;
2999 Mpd
.Seek(Status::State::currentSongPosition(), songpos
);
3001 wFooter
->setTimeout(old_timeout
);
3004 void findItem(const SearchDirection direction
)
3006 using Global::wFooter
;
3008 Searchable
*w
= dynamic_cast<Searchable
*>(myScreen
);
3009 assert(w
!= nullptr);
3010 assert(w
->allowsSearching());
3012 std::string constraint
= w
->searchConstraint();
3015 Statusbar::ScopedLock slock
;
3016 NC::Window::ScopedPromptHook
prompt_hook(
3018 Statusbar::Helpers::FindImmediately(w
, direction
));
3019 Statusbar::put() << (boost::format("Find %1%: ") % direction
).str();
3020 constraint
= wFooter
->prompt(constraint
);
3022 catch (NC::PromptAborted
&)
3024 w
->setSearchConstraint(constraint
);
3025 w
->search(direction
, Config
.wrapped_search
, false);
3029 if (constraint
.empty())
3031 Statusbar::printf("Constraint unset");
3032 w
->clearSearchConstraint();
3035 Statusbar::printf("Using constraint \"%1%\"", constraint
);
3038 void listsChangeFinisher()
3040 if (myScreen
== myLibrary
3041 || myScreen
== myPlaylistEditor
3042 # ifdef HAVE_TAGLIB_H
3043 || myScreen
== myTagEditor
3044 # endif // HAVE_TAGLIB_H
3047 if (myScreen
->activeWindow() == &myLibrary
->Tags
)
3049 myLibrary
->Albums
.clear();
3050 myLibrary
->Albums
.refresh();
3051 myLibrary
->Songs
.clear();
3052 myLibrary
->Songs
.refresh();
3053 myLibrary
->updateTimer();
3055 else if (myScreen
->activeWindow() == &myLibrary
->Albums
)
3057 myLibrary
->Songs
.clear();
3058 myLibrary
->Songs
.refresh();
3059 myLibrary
->updateTimer();
3061 else if (myScreen
->isActiveWindow(myPlaylistEditor
->Playlists
))
3063 myPlaylistEditor
->Content
.clear();
3064 myPlaylistEditor
->Content
.refresh();
3065 myPlaylistEditor
->updateTimer();
3067 # ifdef HAVE_TAGLIB_H
3068 else if (myScreen
->activeWindow() == myTagEditor
->Dirs
)
3070 myTagEditor
->Tags
->clear();
3072 # endif // HAVE_TAGLIB_H