You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3077 lines
75 KiB
3077 lines
75 KiB
/*************************************************************************** |
|
* Copyright (C) 2008-2016 by Andrzej Rybczak * |
|
* electricityispower@gmail.com * |
|
* * |
|
* This program is free software; you can redistribute it and/or modify * |
|
* it under the terms of the GNU General Public License as published by * |
|
* the Free Software Foundation; either version 2 of the License, or * |
|
* (at your option) any later version. * |
|
* * |
|
* This program is distributed in the hope that it will be useful, * |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of * |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * |
|
* GNU General Public License for more details. * |
|
* * |
|
* You should have received a copy of the GNU General Public License * |
|
* along with this program; if not, write to the * |
|
* Free Software Foundation, Inc., * |
|
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * |
|
***************************************************************************/ |
|
|
|
#include <cassert> |
|
#include <cerrno> |
|
#include <cstring> |
|
#include <boost/array.hpp> |
|
#include <boost/date_time/posix_time/posix_time.hpp> |
|
#include <boost/filesystem/operations.hpp> |
|
#include <boost/locale/conversion.hpp> |
|
#include <boost/lexical_cast.hpp> |
|
#include <algorithm> |
|
#include <iostream> |
|
|
|
#include "actions.h" |
|
#include "charset.h" |
|
#include "config.h" |
|
#include "display.h" |
|
#include "global.h" |
|
#include "mpdpp.h" |
|
#include "helpers.h" |
|
#include "statusbar.h" |
|
#include "utility/comparators.h" |
|
#include "utility/conversion.h" |
|
|
|
#include "bindings.h" |
|
#include "browser.h" |
|
#include "clock.h" |
|
#include "help.h" |
|
#include "media_library.h" |
|
#include "menu_impl.h" |
|
#include "lastfm.h" |
|
#include "lyrics.h" |
|
#include "playlist.h" |
|
#include "playlist_editor.h" |
|
#include "sort_playlist.h" |
|
#include "search_engine.h" |
|
#include "sel_items_adder.h" |
|
#include "server_info.h" |
|
#include "song_info.h" |
|
#include "outputs.h" |
|
#include "utility/readline.h" |
|
#include "utility/string.h" |
|
#include "utility/type_conversions.h" |
|
#include "tag_editor.h" |
|
#include "tiny_tag_editor.h" |
|
#include "visualizer.h" |
|
#include "title.h" |
|
#include "tags.h" |
|
|
|
#ifdef HAVE_TAGLIB_H |
|
# include "fileref.h" |
|
# include "tag.h" |
|
#endif // HAVE_TAGLIB_H |
|
|
|
using Global::myScreen; |
|
|
|
namespace ph = std::placeholders; |
|
|
|
namespace { |
|
|
|
std::vector<std::unique_ptr<Actions::BaseAction>> AvailableActions; |
|
|
|
void populateActions(); |
|
|
|
bool scrollTagCanBeRun(NC::List *&list, SongList *&songs); |
|
void scrollTagUpRun(NC::List *list, SongList *songs, MPD::Song::GetFunction get); |
|
void scrollTagDownRun(NC::List *list, SongList *songs, MPD::Song::GetFunction get); |
|
|
|
void seek(); |
|
void findItem(const SearchDirection direction); |
|
void listsChangeFinisher(); |
|
|
|
template <typename Iterator> |
|
bool findSelectedRangeAndPrintInfoIfNot(Iterator &first, Iterator &last) |
|
{ |
|
bool success = findSelectedRange(first, last); |
|
if (!success) |
|
Statusbar::print("No range selected"); |
|
return success; |
|
} |
|
|
|
} |
|
|
|
namespace Actions { |
|
|
|
bool OriginalStatusbarVisibility; |
|
bool ExitMainLoop = false; |
|
|
|
size_t HeaderHeight; |
|
size_t FooterHeight; |
|
size_t FooterStartY; |
|
|
|
void validateScreenSize() |
|
{ |
|
using Global::MainHeight; |
|
|
|
if (COLS < 30 || MainHeight < 5) |
|
{ |
|
NC::destroyScreen(); |
|
std::cout << "Screen is too small to handle ncmpcpp correctly\n"; |
|
exit(1); |
|
} |
|
} |
|
|
|
void initializeScreens() |
|
{ |
|
myHelp = new Help; |
|
myPlaylist = new Playlist; |
|
myBrowser = new Browser; |
|
mySearcher = new SearchEngine; |
|
myLibrary = new MediaLibrary; |
|
myPlaylistEditor = new PlaylistEditor; |
|
myLyrics = new Lyrics; |
|
mySelectedItemsAdder = new SelectedItemsAdder; |
|
mySongInfo = new SongInfo; |
|
myServerInfo = new ServerInfo; |
|
mySortPlaylistDialog = new SortPlaylistDialog; |
|
myLastfm = new Lastfm; |
|
|
|
# ifdef HAVE_TAGLIB_H |
|
myTinyTagEditor = new TinyTagEditor; |
|
myTagEditor = new TagEditor; |
|
# endif // HAVE_TAGLIB_H |
|
|
|
# ifdef ENABLE_VISUALIZER |
|
myVisualizer = new Visualizer; |
|
# endif // ENABLE_VISUALIZER |
|
|
|
# ifdef ENABLE_OUTPUTS |
|
myOutputs = new Outputs; |
|
# endif // ENABLE_OUTPUTS |
|
|
|
# ifdef ENABLE_CLOCK |
|
myClock = new Clock; |
|
# endif // ENABLE_CLOCK |
|
|
|
} |
|
|
|
void setResizeFlags() |
|
{ |
|
myHelp->hasToBeResized = 1; |
|
myPlaylist->hasToBeResized = 1; |
|
myBrowser->hasToBeResized = 1; |
|
mySearcher->hasToBeResized = 1; |
|
myLibrary->hasToBeResized = 1; |
|
myPlaylistEditor->hasToBeResized = 1; |
|
myLyrics->hasToBeResized = 1; |
|
mySelectedItemsAdder->hasToBeResized = 1; |
|
mySongInfo->hasToBeResized = 1; |
|
myServerInfo->hasToBeResized = 1; |
|
mySortPlaylistDialog->hasToBeResized = 1; |
|
myLastfm->hasToBeResized = 1; |
|
|
|
# ifdef HAVE_TAGLIB_H |
|
myTinyTagEditor->hasToBeResized = 1; |
|
myTagEditor->hasToBeResized = 1; |
|
# endif // HAVE_TAGLIB_H |
|
|
|
# ifdef ENABLE_VISUALIZER |
|
myVisualizer->hasToBeResized = 1; |
|
# endif // ENABLE_VISUALIZER |
|
|
|
# ifdef ENABLE_OUTPUTS |
|
myOutputs->hasToBeResized = 1; |
|
# endif // ENABLE_OUTPUTS |
|
|
|
# ifdef ENABLE_CLOCK |
|
myClock->hasToBeResized = 1; |
|
# endif // ENABLE_CLOCK |
|
} |
|
|
|
void resizeScreen(bool reload_main_window) |
|
{ |
|
using Global::MainHeight; |
|
using Global::wHeader; |
|
using Global::wFooter; |
|
|
|
// update internal screen dimensions |
|
if (reload_main_window) |
|
{ |
|
rl_resize_terminal(); |
|
endwin(); |
|
refresh(); |
|
} |
|
|
|
MainHeight = LINES-(Config.design == Design::Alternative ? 7 : 4); |
|
|
|
validateScreenSize(); |
|
|
|
if (!Config.header_visibility) |
|
MainHeight += 2; |
|
if (!Config.statusbar_visibility) |
|
++MainHeight; |
|
|
|
setResizeFlags(); |
|
|
|
applyToVisibleWindows(&BaseScreen::resize); |
|
|
|
if (Config.header_visibility || Config.design == Design::Alternative) |
|
wHeader->resize(COLS, HeaderHeight); |
|
|
|
FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1); |
|
wFooter->moveTo(0, FooterStartY); |
|
wFooter->resize(COLS, Config.statusbar_visibility ? 2 : 1); |
|
|
|
applyToVisibleWindows(&BaseScreen::refresh); |
|
|
|
Status::Changes::elapsedTime(false); |
|
Status::Changes::playerState(); |
|
// Note: routines for drawing separator if alternative user |
|
// interface is active and header is hidden are placed in |
|
// NcmpcppStatusChanges.StatusFlags |
|
Status::Changes::flags(); |
|
drawHeader(); |
|
wFooter->refresh(); |
|
refresh(); |
|
} |
|
|
|
void setWindowsDimensions() |
|
{ |
|
using Global::MainStartY; |
|
using Global::MainHeight; |
|
|
|
MainStartY = Config.design == Design::Alternative ? 5 : 2; |
|
MainHeight = LINES-(Config.design == Design::Alternative ? 7 : 4); |
|
|
|
if (!Config.header_visibility) |
|
{ |
|
MainStartY -= 2; |
|
MainHeight += 2; |
|
} |
|
if (!Config.statusbar_visibility) |
|
++MainHeight; |
|
|
|
HeaderHeight = Config.design == Design::Alternative ? (Config.header_visibility ? 5 : 3) : 2; |
|
FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1); |
|
FooterHeight = Config.statusbar_visibility ? 2 : 1; |
|
} |
|
|
|
void confirmAction(const boost::format &description) |
|
{ |
|
Statusbar::ScopedLock slock; |
|
Statusbar::put() << description.str() |
|
<< " [" << NC::Format::Bold << 'y' << NC::Format::NoBold |
|
<< '/' << NC::Format::Bold << 'n' << NC::Format::NoBold |
|
<< "] "; |
|
auto answer = Statusbar::Helpers::promptReturnOneOf({"y", "n"}); |
|
if (answer == "n") |
|
throw NC::PromptAborted(std::move(answer)); |
|
} |
|
|
|
bool isMPDMusicDirSet() |
|
{ |
|
if (Config.mpd_music_dir.empty()) |
|
{ |
|
Statusbar::print("Proper mpd_music_dir variable has to be set in configuration file"); |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
BaseAction &get(Actions::Type at) |
|
{ |
|
if (AvailableActions.empty()) |
|
populateActions(); |
|
BaseAction *action = AvailableActions.at(static_cast<size_t>(at)).get(); |
|
// action should be always present if action type in queried |
|
assert(action != nullptr); |
|
return *action; |
|
} |
|
|
|
BaseAction *get(const std::string &name) |
|
{ |
|
BaseAction *result = nullptr; |
|
if (AvailableActions.empty()) |
|
populateActions(); |
|
for (const auto &action : AvailableActions) |
|
{ |
|
if (action->name() == name) |
|
{ |
|
result = action.get(); |
|
break; |
|
} |
|
} |
|
return result; |
|
} |
|
|
|
UpdateEnvironment::UpdateEnvironment() |
|
: BaseAction(Type::UpdateEnvironment, "update_environment") |
|
, m_past(boost::posix_time::from_time_t(0)) |
|
{ } |
|
|
|
void UpdateEnvironment::run(bool update_timer, bool refresh_window) |
|
{ |
|
using Global::Timer; |
|
|
|
// update timer, status if necessary etc. |
|
Status::trace(update_timer, true); |
|
|
|
// show lyrics consumer notification if appropriate |
|
if (auto message = myLyrics->tryTakeConsumerMessage()) |
|
Statusbar::print(*message); |
|
|
|
// header stuff |
|
if ((myScreen == myPlaylist || myScreen == myBrowser || myScreen == myLyrics) |
|
&& (Timer - m_past > boost::posix_time::milliseconds(500)) |
|
) |
|
{ |
|
drawHeader(); |
|
m_past = Timer; |
|
} |
|
|
|
if (refresh_window) |
|
myScreen->refreshWindow(); |
|
} |
|
|
|
void UpdateEnvironment::run() |
|
{ |
|
run(true, true); |
|
} |
|
|
|
bool MouseEvent::canBeRun() |
|
{ |
|
return Config.mouse_support; |
|
} |
|
|
|
void MouseEvent::run() |
|
{ |
|
using Global::VolumeState; |
|
using Global::wFooter; |
|
|
|
m_old_mouse_event = m_mouse_event; |
|
m_mouse_event = wFooter->getMouseEvent(); |
|
|
|
//Statusbar::printf("(%1%, %2%, %3%)", m_mouse_event.bstate, m_mouse_event.x, m_mouse_event.y); |
|
|
|
if (m_mouse_event.bstate & BUTTON1_PRESSED |
|
&& m_mouse_event.y == LINES-(Config.statusbar_visibility ? 2 : 1) |
|
) // progressbar |
|
{ |
|
if (Status::State::player() == MPD::psStop) |
|
return; |
|
Mpd.Seek(Status::State::currentSongPosition(), |
|
Status::State::totalTime()*m_mouse_event.x/double(COLS)); |
|
} |
|
else if (m_mouse_event.bstate & BUTTON1_PRESSED |
|
&& (Config.statusbar_visibility || Config.design == Design::Alternative) |
|
&& Status::State::player() != MPD::psStop |
|
&& m_mouse_event.y == (Config.design == Design::Alternative ? 1 : LINES-1) |
|
&& m_mouse_event.x < 9 |
|
) // playing/paused |
|
{ |
|
Mpd.Toggle(); |
|
} |
|
else if ((m_mouse_event.bstate & BUTTON5_PRESSED || m_mouse_event.bstate & BUTTON4_PRESSED) |
|
&& (Config.header_visibility || Config.design == Design::Alternative) |
|
&& m_mouse_event.y == 0 && size_t(m_mouse_event.x) > COLS-VolumeState.length() |
|
) // volume |
|
{ |
|
if (m_mouse_event.bstate & BUTTON5_PRESSED) |
|
get(Type::VolumeDown).execute(); |
|
else |
|
get(Type::VolumeUp).execute(); |
|
} |
|
else if (m_mouse_event.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED | BUTTON4_PRESSED | BUTTON5_PRESSED)) |
|
myScreen->mouseButtonPressed(m_mouse_event); |
|
} |
|
|
|
void ScrollUp::run() |
|
{ |
|
myScreen->scroll(NC::Scroll::Up); |
|
listsChangeFinisher(); |
|
} |
|
|
|
void ScrollDown::run() |
|
{ |
|
myScreen->scroll(NC::Scroll::Down); |
|
listsChangeFinisher(); |
|
} |
|
|
|
bool ScrollUpArtist::canBeRun() |
|
{ |
|
return scrollTagCanBeRun(m_list, m_songs); |
|
} |
|
|
|
void ScrollUpArtist::run() |
|
{ |
|
scrollTagUpRun(m_list, m_songs, &MPD::Song::getArtist); |
|
} |
|
|
|
bool ScrollUpAlbum::canBeRun() |
|
{ |
|
return scrollTagCanBeRun(m_list, m_songs); |
|
} |
|
|
|
void ScrollUpAlbum::run() |
|
{ |
|
scrollTagUpRun(m_list, m_songs, &MPD::Song::getAlbum); |
|
} |
|
|
|
bool ScrollDownArtist::canBeRun() |
|
{ |
|
return scrollTagCanBeRun(m_list, m_songs); |
|
} |
|
|
|
void ScrollDownArtist::run() |
|
{ |
|
scrollTagDownRun(m_list, m_songs, &MPD::Song::getArtist); |
|
} |
|
|
|
bool ScrollDownAlbum::canBeRun() |
|
{ |
|
return scrollTagCanBeRun(m_list, m_songs); |
|
} |
|
|
|
void ScrollDownAlbum::run() |
|
{ |
|
scrollTagDownRun(m_list, m_songs, &MPD::Song::getAlbum); |
|
} |
|
|
|
void PageUp::run() |
|
{ |
|
myScreen->scroll(NC::Scroll::PageUp); |
|
listsChangeFinisher(); |
|
} |
|
|
|
void PageDown::run() |
|
{ |
|
myScreen->scroll(NC::Scroll::PageDown); |
|
listsChangeFinisher(); |
|
} |
|
|
|
void MoveHome::run() |
|
{ |
|
myScreen->scroll(NC::Scroll::Home); |
|
listsChangeFinisher(); |
|
} |
|
|
|
void MoveEnd::run() |
|
{ |
|
myScreen->scroll(NC::Scroll::End); |
|
listsChangeFinisher(); |
|
} |
|
|
|
void ToggleInterface::run() |
|
{ |
|
switch (Config.design) |
|
{ |
|
case Design::Classic: |
|
Config.design = Design::Alternative; |
|
Config.statusbar_visibility = false; |
|
break; |
|
case Design::Alternative: |
|
Config.design = Design::Classic; |
|
Config.statusbar_visibility = OriginalStatusbarVisibility; |
|
break; |
|
} |
|
setWindowsDimensions(); |
|
resizeScreen(false); |
|
// unlock progressbar |
|
Progressbar::ScopedLock(); |
|
Status::Changes::mixer(); |
|
Status::Changes::elapsedTime(false); |
|
Statusbar::printf("User interface: %1%", Config.design); |
|
} |
|
|
|
bool JumpToParentDirectory::canBeRun() |
|
{ |
|
return (myScreen == myBrowser) |
|
# ifdef HAVE_TAGLIB_H |
|
|| (myScreen->activeWindow() == myTagEditor->Dirs) |
|
# endif // HAVE_TAGLIB_H |
|
; |
|
} |
|
|
|
void JumpToParentDirectory::run() |
|
{ |
|
if (myScreen == myBrowser) |
|
{ |
|
if (!myBrowser->inRootDirectory()) |
|
{ |
|
myBrowser->main().reset(); |
|
myBrowser->enterDirectory(); |
|
} |
|
} |
|
# ifdef HAVE_TAGLIB_H |
|
else if (myScreen == myTagEditor) |
|
{ |
|
if (myTagEditor->CurrentDir() != "/") |
|
{ |
|
myTagEditor->Dirs->reset(); |
|
myTagEditor->enterDirectory(); |
|
} |
|
} |
|
# endif // HAVE_TAGLIB_H |
|
} |
|
|
|
bool RunAction::canBeRun() |
|
{ |
|
m_ha = dynamic_cast<HasActions *>(myScreen); |
|
return m_ha != nullptr |
|
&& m_ha->actionRunnable(); |
|
} |
|
|
|
void RunAction::run() |
|
{ |
|
m_ha->runAction(); |
|
} |
|
|
|
bool PreviousColumn::canBeRun() |
|
{ |
|
m_hc = dynamic_cast<HasColumns *>(myScreen); |
|
return m_hc != nullptr |
|
&& m_hc->previousColumnAvailable(); |
|
} |
|
|
|
void PreviousColumn::run() |
|
{ |
|
m_hc->previousColumn(); |
|
} |
|
|
|
bool NextColumn::canBeRun() |
|
{ |
|
m_hc = dynamic_cast<HasColumns *>(myScreen); |
|
return m_hc != nullptr |
|
&& m_hc->nextColumnAvailable(); |
|
} |
|
|
|
void NextColumn::run() |
|
{ |
|
m_hc->nextColumn(); |
|
} |
|
|
|
bool MasterScreen::canBeRun() |
|
{ |
|
using Global::myLockedScreen; |
|
using Global::myInactiveScreen; |
|
|
|
return myLockedScreen |
|
&& myInactiveScreen |
|
&& myLockedScreen != myScreen |
|
&& myScreen->isMergable(); |
|
} |
|
|
|
void MasterScreen::run() |
|
{ |
|
using Global::myInactiveScreen; |
|
using Global::myLockedScreen; |
|
|
|
myInactiveScreen = myScreen; |
|
myScreen = myLockedScreen; |
|
drawHeader(); |
|
} |
|
|
|
bool SlaveScreen::canBeRun() |
|
{ |
|
using Global::myLockedScreen; |
|
using Global::myInactiveScreen; |
|
|
|
return myLockedScreen |
|
&& myInactiveScreen |
|
&& myLockedScreen == myScreen |
|
&& myScreen->isMergable(); |
|
} |
|
|
|
void SlaveScreen::run() |
|
{ |
|
using Global::myInactiveScreen; |
|
using Global::myLockedScreen; |
|
|
|
myScreen = myInactiveScreen; |
|
myInactiveScreen = myLockedScreen; |
|
drawHeader(); |
|
} |
|
|
|
void VolumeUp::run() |
|
{ |
|
int volume = std::min(Status::State::volume()+Config.volume_change_step, 100u); |
|
Mpd.SetVolume(volume); |
|
} |
|
|
|
void VolumeDown::run() |
|
{ |
|
int volume = std::max(int(Status::State::volume()-Config.volume_change_step), 0); |
|
Mpd.SetVolume(volume); |
|
} |
|
|
|
bool AddItemToPlaylist::canBeRun() |
|
{ |
|
m_hs = dynamic_cast<HasSongs *>(myScreen); |
|
return m_hs != nullptr && m_hs->itemAvailable(); |
|
} |
|
|
|
void AddItemToPlaylist::run() |
|
{ |
|
bool success = m_hs->addItemToPlaylist(false); |
|
if (success) |
|
{ |
|
myScreen->scroll(NC::Scroll::Down); |
|
listsChangeFinisher(); |
|
} |
|
} |
|
|
|
bool PlayItem::canBeRun() |
|
{ |
|
m_hs = dynamic_cast<HasSongs *>(myScreen); |
|
return m_hs != nullptr && m_hs->itemAvailable(); |
|
} |
|
|
|
void PlayItem::run() |
|
{ |
|
bool success = m_hs->addItemToPlaylist(true); |
|
if (success) |
|
listsChangeFinisher(); |
|
} |
|
|
|
bool DeletePlaylistItems::canBeRun() |
|
{ |
|
return (myScreen == myPlaylist && !myPlaylist->main().empty()) |
|
|| (myScreen->isActiveWindow(myPlaylistEditor->Content) && !myPlaylistEditor->Content.empty()); |
|
} |
|
|
|
void DeletePlaylistItems::run() |
|
{ |
|
if (myScreen == myPlaylist) |
|
{ |
|
Statusbar::print("Deleting items..."); |
|
auto delete_fun = std::bind(&MPD::Connection::Delete, ph::_1, ph::_2); |
|
deleteSelectedSongs(myPlaylist->main(), delete_fun); |
|
Statusbar::print("Item(s) deleted"); |
|
} |
|
else if (myScreen->isActiveWindow(myPlaylistEditor->Content)) |
|
{ |
|
std::string playlist = myPlaylistEditor->Playlists.current()->value().path(); |
|
auto delete_fun = std::bind(&MPD::Connection::PlaylistDelete, ph::_1, playlist, ph::_2); |
|
Statusbar::print("Deleting items..."); |
|
deleteSelectedSongs(myPlaylistEditor->Content, delete_fun); |
|
Statusbar::print("Item(s) deleted"); |
|
} |
|
} |
|
|
|
bool DeleteBrowserItems::canBeRun() |
|
{ |
|
auto check_if_deletion_allowed = []() { |
|
if (Config.allow_for_physical_item_deletion) |
|
return true; |
|
else |
|
{ |
|
Statusbar::print("Flag \"allow_for_physical_item_deletion\" needs to be enabled in configuration file"); |
|
return false; |
|
} |
|
}; |
|
return myScreen == myBrowser |
|
&& !myBrowser->main().empty() |
|
&& isMPDMusicDirSet() |
|
&& check_if_deletion_allowed(); |
|
} |
|
|
|
void DeleteBrowserItems::run() |
|
{ |
|
auto get_name = [](const MPD::Item &item) -> std::string { |
|
std::string iname; |
|
switch (item.type()) |
|
{ |
|
case MPD::Item::Type::Directory: |
|
iname = getBasename(item.directory().path()); |
|
break; |
|
case MPD::Item::Type::Song: |
|
iname = item.song().getName(); |
|
break; |
|
case MPD::Item::Type::Playlist: |
|
iname = getBasename(item.playlist().path()); |
|
break; |
|
} |
|
return iname; |
|
}; |
|
|
|
boost::format question; |
|
if (hasSelected(myBrowser->main().begin(), myBrowser->main().end())) |
|
question = boost::format("Delete selected items?"); |
|
else |
|
{ |
|
const auto &item = myBrowser->main().current()->value(); |
|
// parent directories are not accepted (and they |
|
// can't be selected, so in other cases it's fine). |
|
if (myBrowser->isParentDirectory(item)) |
|
return; |
|
const char msg[] = "Delete \"%1%\"?"; |
|
question = boost::format(msg) % wideShorten( |
|
get_name(item), COLS-const_strlen(msg)-5 |
|
); |
|
} |
|
confirmAction(question); |
|
|
|
auto items = getSelectedOrCurrent( |
|
myBrowser->main().begin(), |
|
myBrowser->main().end(), |
|
myBrowser->main().current() |
|
); |
|
for (const auto &item : items) |
|
{ |
|
myBrowser->remove(item->value()); |
|
const char msg[] = "Deleted %1% \"%2%\""; |
|
Statusbar::printf(msg, |
|
itemTypeToString(item->value().type()), |
|
wideShorten(get_name(item->value()), COLS-const_strlen(msg)) |
|
); |
|
} |
|
|
|
if (!myBrowser->isLocal()) |
|
Mpd.UpdateDirectory(myBrowser->currentDirectory()); |
|
myBrowser->requestUpdate(); |
|
} |
|
|
|
bool DeleteStoredPlaylist::canBeRun() |
|
{ |
|
return myScreen->isActiveWindow(myPlaylistEditor->Playlists); |
|
} |
|
|
|
void DeleteStoredPlaylist::run() |
|
{ |
|
if (myPlaylistEditor->Playlists.empty()) |
|
return; |
|
boost::format question; |
|
if (hasSelected(myPlaylistEditor->Playlists.begin(), myPlaylistEditor->Playlists.end())) |
|
question = boost::format("Delete selected playlists?"); |
|
else |
|
question = boost::format("Delete playlist \"%1%\"?") |
|
% wideShorten(myPlaylistEditor->Playlists.current()->value().path(), COLS-question.size()-10); |
|
confirmAction(question); |
|
auto list = getSelectedOrCurrent( |
|
myPlaylistEditor->Playlists.begin(), |
|
myPlaylistEditor->Playlists.end(), |
|
myPlaylistEditor->Playlists.current() |
|
); |
|
for (const auto &item : list) |
|
Mpd.DeletePlaylist(item->value().path()); |
|
Statusbar::printf("%1% deleted", list.size() == 1 ? "Playlist" : "Playlists"); |
|
// force playlists update. this happens automatically, but only after call |
|
// to Key::read, therefore when we call PlaylistEditor::Update, it won't |
|
// yet see it, so let's point that it needs to update it. |
|
myPlaylistEditor->requestPlaylistsUpdate(); |
|
} |
|
|
|
void ReplaySong::run() |
|
{ |
|
if (Status::State::player() != MPD::psStop) |
|
Mpd.Seek(Status::State::currentSongPosition(), 0); |
|
} |
|
|
|
void PreviousSong::run() |
|
{ |
|
Mpd.Prev(); |
|
} |
|
|
|
void NextSong::run() |
|
{ |
|
Mpd.Next(); |
|
} |
|
|
|
void Pause::run() |
|
{ |
|
Mpd.Toggle(); |
|
} |
|
|
|
void SavePlaylist::run() |
|
{ |
|
using Global::wFooter; |
|
|
|
std::string playlist_name; |
|
{ |
|
Statusbar::ScopedLock slock; |
|
Statusbar::put() << "Save playlist as: "; |
|
playlist_name = wFooter->prompt(); |
|
} |
|
try |
|
{ |
|
Mpd.SavePlaylist(playlist_name); |
|
Statusbar::printf("Playlist saved as \"%1%\"", playlist_name); |
|
} |
|
catch (MPD::ServerError &e) |
|
{ |
|
if (e.code() == MPD_SERVER_ERROR_EXIST) |
|
{ |
|
confirmAction( |
|
boost::format("Playlist \"%1%\" already exists, overwrite?") % playlist_name |
|
); |
|
Mpd.DeletePlaylist(playlist_name); |
|
Mpd.SavePlaylist(playlist_name); |
|
Statusbar::print("Playlist overwritten"); |
|
} |
|
else |
|
throw; |
|
} |
|
} |
|
|
|
void Stop::run() |
|
{ |
|
Mpd.Stop(); |
|
} |
|
|
|
void ExecuteCommand::run() |
|
{ |
|
using Global::wFooter; |
|
|
|
std::string cmd_name; |
|
{ |
|
Statusbar::ScopedLock slock; |
|
NC::Window::ScopedPromptHook helper(*wFooter, |
|
Statusbar::Helpers::TryExecuteImmediateCommand() |
|
); |
|
Statusbar::put() << NC::Format::Bold << ":" << NC::Format::NoBold; |
|
cmd_name = wFooter->prompt(); |
|
} |
|
|
|
auto cmd = Bindings.findCommand(cmd_name); |
|
if (cmd) |
|
{ |
|
Statusbar::printf(1, "Executing %1%...", cmd_name); |
|
bool res = cmd->binding().execute(); |
|
Statusbar::printf("Execution of command \"%1%\" %2%.", |
|
cmd_name, res ? "successful" : "unsuccessful" |
|
); |
|
} |
|
else |
|
Statusbar::printf("No command named \"%1%\"", cmd_name); |
|
} |
|
|
|
bool MoveSortOrderUp::canBeRun() |
|
{ |
|
return myScreen == mySortPlaylistDialog; |
|
} |
|
|
|
void MoveSortOrderUp::run() |
|
{ |
|
mySortPlaylistDialog->moveSortOrderUp(); |
|
} |
|
|
|
bool MoveSortOrderDown::canBeRun() |
|
{ |
|
return myScreen == mySortPlaylistDialog; |
|
} |
|
|
|
void MoveSortOrderDown::run() |
|
{ |
|
mySortPlaylistDialog->moveSortOrderDown(); |
|
} |
|
|
|
bool MoveSelectedItemsUp::canBeRun() |
|
{ |
|
return ((myScreen == myPlaylist |
|
&& !myPlaylist->main().empty()) |
|
|| (myScreen->isActiveWindow(myPlaylistEditor->Content) |
|
&& !myPlaylistEditor->Content.empty())); |
|
} |
|
|
|
void MoveSelectedItemsUp::run() |
|
{ |
|
const char *filteredMsg = "Moving items up is disabled in filtered playlist"; |
|
if (myScreen == myPlaylist) |
|
{ |
|
if (myPlaylist->main().isFiltered()) |
|
Statusbar::print(filteredMsg); |
|
else |
|
moveSelectedItemsUp( |
|
myPlaylist->main(), |
|
std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3)); |
|
} |
|
else if (myScreen == myPlaylistEditor) |
|
{ |
|
if (myPlaylistEditor->Content.isFiltered()) |
|
Statusbar::print(filteredMsg); |
|
else |
|
{ |
|
auto playlist = myPlaylistEditor->Playlists.current()->value().path(); |
|
moveSelectedItemsUp( |
|
myPlaylistEditor->Content, |
|
std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3)); |
|
} |
|
} |
|
} |
|
|
|
bool MoveSelectedItemsDown::canBeRun() |
|
{ |
|
return ((myScreen == myPlaylist |
|
&& !myPlaylist->main().empty()) |
|
|| (myScreen->isActiveWindow(myPlaylistEditor->Content) |
|
&& !myPlaylistEditor->Content.empty())); |
|
} |
|
|
|
void MoveSelectedItemsDown::run() |
|
{ |
|
const char *filteredMsg = "Moving items down is disabled in filtered playlist"; |
|
if (myScreen == myPlaylist) |
|
{ |
|
if (myPlaylist->main().isFiltered()) |
|
Statusbar::print(filteredMsg); |
|
else |
|
moveSelectedItemsDown( |
|
myPlaylist->main(), |
|
std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3)); |
|
} |
|
else if (myScreen == myPlaylistEditor) |
|
{ |
|
if (myPlaylistEditor->Content.isFiltered()) |
|
Statusbar::print(filteredMsg); |
|
else |
|
{ |
|
auto playlist = myPlaylistEditor->Playlists.current()->value().path(); |
|
moveSelectedItemsDown( |
|
myPlaylistEditor->Content, |
|
std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3)); |
|
} |
|
} |
|
} |
|
|
|
bool MoveSelectedItemsTo::canBeRun() |
|
{ |
|
return myScreen == myPlaylist |
|
|| myScreen->isActiveWindow(myPlaylistEditor->Content); |
|
} |
|
|
|
void MoveSelectedItemsTo::run() |
|
{ |
|
if (myScreen == myPlaylist) |
|
{ |
|
if (!myPlaylist->main().empty()) |
|
moveSelectedItemsTo(myPlaylist->main(), std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3)); |
|
} |
|
else |
|
{ |
|
assert(!myPlaylistEditor->Playlists.empty()); |
|
std::string playlist = myPlaylistEditor->Playlists.current()->value().path(); |
|
auto move_fun = std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3); |
|
moveSelectedItemsTo(myPlaylistEditor->Content, move_fun); |
|
} |
|
} |
|
|
|
bool Add::canBeRun() |
|
{ |
|
return myScreen != myPlaylistEditor |
|
|| !myPlaylistEditor->Playlists.empty(); |
|
} |
|
|
|
void Add::run() |
|
{ |
|
using Global::wFooter; |
|
|
|
std::string path; |
|
{ |
|
Statusbar::ScopedLock slock; |
|
Statusbar::put() << (myScreen == myPlaylistEditor ? "Add to playlist: " : "Add: "); |
|
path = wFooter->prompt(); |
|
} |
|
|
|
// confirm when one wants to add the whole database |
|
if (path.empty()) |
|
confirmAction("Are you sure you want to add the whole database?"); |
|
|
|
Statusbar::put() << "Adding..."; |
|
wFooter->refresh(); |
|
if (myScreen == myPlaylistEditor) |
|
Mpd.AddToPlaylist(myPlaylistEditor->Playlists.current()->value().path(), path); |
|
else |
|
{ |
|
try |
|
{ |
|
Mpd.Add(path); |
|
} |
|
catch (MPD::ServerError &err) |
|
{ |
|
// If a path is not a file or directory, assume it is a playlist. |
|
if (err.code() == MPD_SERVER_ERROR_NO_EXIST) |
|
Mpd.LoadPlaylist(path); |
|
else |
|
throw; |
|
} |
|
} |
|
} |
|
|
|
bool SeekForward::canBeRun() |
|
{ |
|
return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0; |
|
} |
|
|
|
void SeekForward::run() |
|
{ |
|
seek(); |
|
} |
|
|
|
bool SeekBackward::canBeRun() |
|
{ |
|
return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0; |
|
} |
|
|
|
void SeekBackward::run() |
|
{ |
|
seek(); |
|
} |
|
|
|
bool ToggleDisplayMode::canBeRun() |
|
{ |
|
return myScreen == myPlaylist |
|
|| myScreen == myBrowser |
|
|| myScreen == mySearcher |
|
|| myScreen->isActiveWindow(myPlaylistEditor->Content); |
|
} |
|
|
|
void ToggleDisplayMode::run() |
|
{ |
|
if (myScreen == myPlaylist) |
|
{ |
|
switch (Config.playlist_display_mode) |
|
{ |
|
case DisplayMode::Classic: |
|
Config.playlist_display_mode = DisplayMode::Columns; |
|
myPlaylist->main().setItemDisplayer(std::bind( |
|
Display::SongsInColumns, ph::_1, std::cref(myPlaylist->main()) |
|
)); |
|
if (Config.titles_visibility) |
|
myPlaylist->main().setTitle(Display::Columns(myPlaylist->main().getWidth())); |
|
else |
|
myPlaylist->main().setTitle(""); |
|
break; |
|
case DisplayMode::Columns: |
|
Config.playlist_display_mode = DisplayMode::Classic; |
|
myPlaylist->main().setItemDisplayer(std::bind( |
|
Display::Songs, ph::_1, std::cref(myPlaylist->main()), std::cref(Config.song_list_format) |
|
)); |
|
myPlaylist->main().setTitle(""); |
|
} |
|
Statusbar::printf("Playlist display mode: %1%", Config.playlist_display_mode); |
|
} |
|
else if (myScreen == myBrowser) |
|
{ |
|
switch (Config.browser_display_mode) |
|
{ |
|
case DisplayMode::Classic: |
|
Config.browser_display_mode = DisplayMode::Columns; |
|
if (Config.titles_visibility) |
|
myBrowser->main().setTitle(Display::Columns(myBrowser->main().getWidth())); |
|
else |
|
myBrowser->main().setTitle(""); |
|
break; |
|
case DisplayMode::Columns: |
|
Config.browser_display_mode = DisplayMode::Classic; |
|
myBrowser->main().setTitle(""); |
|
break; |
|
} |
|
Statusbar::printf("Browser display mode: %1%", Config.browser_display_mode); |
|
} |
|
else if (myScreen == mySearcher) |
|
{ |
|
switch (Config.search_engine_display_mode) |
|
{ |
|
case DisplayMode::Classic: |
|
Config.search_engine_display_mode = DisplayMode::Columns; |
|
break; |
|
case DisplayMode::Columns: |
|
Config.search_engine_display_mode = DisplayMode::Classic; |
|
break; |
|
} |
|
Statusbar::printf("Search engine display mode: %1%", Config.search_engine_display_mode); |
|
if (mySearcher->main().size() > SearchEngine::StaticOptions) |
|
mySearcher->main().setTitle( |
|
Config.search_engine_display_mode == DisplayMode::Columns |
|
&& Config.titles_visibility |
|
? Display::Columns(mySearcher->main().getWidth()) |
|
: "" |
|
); |
|
} |
|
else if (myScreen->isActiveWindow(myPlaylistEditor->Content)) |
|
{ |
|
switch (Config.playlist_editor_display_mode) |
|
{ |
|
case DisplayMode::Classic: |
|
Config.playlist_editor_display_mode = DisplayMode::Columns; |
|
myPlaylistEditor->Content.setItemDisplayer(std::bind( |
|
Display::SongsInColumns, ph::_1, std::cref(myPlaylistEditor->Content) |
|
)); |
|
break; |
|
case DisplayMode::Columns: |
|
Config.playlist_editor_display_mode = DisplayMode::Classic; |
|
myPlaylistEditor->Content.setItemDisplayer(std::bind( |
|
Display::Songs, ph::_1, std::cref(myPlaylistEditor->Content), std::cref(Config.song_list_format) |
|
)); |
|
break; |
|
} |
|
Statusbar::printf("Playlist editor display mode: %1%", Config.playlist_editor_display_mode); |
|
} |
|
} |
|
|
|
bool ToggleSeparatorsBetweenAlbums::canBeRun() |
|
{ |
|
return true; |
|
} |
|
|
|
void ToggleSeparatorsBetweenAlbums::run() |
|
{ |
|
Config.playlist_separate_albums = !Config.playlist_separate_albums; |
|
Statusbar::printf("Separators between albums: %1%", |
|
Config.playlist_separate_albums ? "on" : "off" |
|
); |
|
} |
|
|
|
bool ToggleLyricsUpdateOnSongChange::canBeRun() |
|
{ |
|
return myScreen == myLyrics; |
|
} |
|
|
|
void ToggleLyricsUpdateOnSongChange::run() |
|
{ |
|
Config.now_playing_lyrics = !Config.now_playing_lyrics; |
|
Statusbar::printf("Update lyrics if song changes: %1%", |
|
Config.now_playing_lyrics ? "on" : "off" |
|
); |
|
} |
|
|
|
void ToggleLyricsFetcher::run() |
|
{ |
|
myLyrics->toggleFetcher(); |
|
} |
|
|
|
void ToggleFetchingLyricsInBackground::run() |
|
{ |
|
Config.fetch_lyrics_in_background = !Config.fetch_lyrics_in_background; |
|
Statusbar::printf("Fetching lyrics for playing songs in background: %1%", |
|
Config.fetch_lyrics_in_background ? "on" : "off"); |
|
} |
|
|
|
void TogglePlayingSongCentering::run() |
|
{ |
|
Config.autocenter_mode = !Config.autocenter_mode; |
|
Statusbar::printf("Centering playing song: %1%", |
|
Config.autocenter_mode ? "on" : "off" |
|
); |
|
if (Config.autocenter_mode) |
|
{ |
|
auto s = myPlaylist->nowPlayingSong(); |
|
if (!s.empty()) |
|
myPlaylist->locateSong(s); |
|
} |
|
} |
|
|
|
void UpdateDatabase::run() |
|
{ |
|
if (myScreen == myBrowser) |
|
Mpd.UpdateDirectory(myBrowser->currentDirectory()); |
|
# ifdef HAVE_TAGLIB_H |
|
else if (myScreen == myTagEditor) |
|
Mpd.UpdateDirectory(myTagEditor->CurrentDir()); |
|
# endif // HAVE_TAGLIB_H |
|
else |
|
Mpd.UpdateDirectory("/"); |
|
} |
|
|
|
bool JumpToPlayingSong::canBeRun() |
|
{ |
|
return myScreen == myPlaylist |
|
|| myScreen == myBrowser |
|
|| myScreen == myLibrary; |
|
} |
|
|
|
void JumpToPlayingSong::run() |
|
{ |
|
auto s = myPlaylist->nowPlayingSong(); |
|
if (s.empty()) |
|
return; |
|
if (myScreen == myPlaylist) |
|
{ |
|
myPlaylist->locateSong(s); |
|
} |
|
else if (myScreen == myBrowser) |
|
{ |
|
myBrowser->locateSong(s); |
|
} |
|
else if (myScreen == myLibrary) |
|
{ |
|
myLibrary->locateSong(s); |
|
} |
|
} |
|
|
|
void ToggleRepeat::run() |
|
{ |
|
Mpd.SetRepeat(!Status::State::repeat()); |
|
} |
|
|
|
bool Shuffle::canBeRun() |
|
{ |
|
if (myScreen != myPlaylist) |
|
return false; |
|
m_begin = myPlaylist->main().begin(); |
|
m_end = myPlaylist->main().end(); |
|
return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end); |
|
} |
|
|
|
void Shuffle::run() |
|
{ |
|
if (Config.ask_before_shuffling_playlists) |
|
confirmAction("Do you really want to shuffle selected range?"); |
|
auto begin = myPlaylist->main().begin(); |
|
Mpd.ShuffleRange(m_begin-begin, m_end-begin); |
|
Statusbar::print("Range shuffled"); |
|
} |
|
|
|
void ToggleRandom::run() |
|
{ |
|
Mpd.SetRandom(!Status::State::random()); |
|
} |
|
|
|
bool StartSearching::canBeRun() |
|
{ |
|
return myScreen == mySearcher && !mySearcher->main()[0].isInactive(); |
|
} |
|
|
|
void StartSearching::run() |
|
{ |
|
mySearcher->main().highlight(SearchEngine::SearchButton); |
|
mySearcher->main().setHighlighting(0); |
|
mySearcher->main().refresh(); |
|
mySearcher->main().setHighlighting(1); |
|
mySearcher->runAction(); |
|
} |
|
|
|
bool SaveTagChanges::canBeRun() |
|
{ |
|
# ifdef HAVE_TAGLIB_H |
|
return myScreen == myTinyTagEditor |
|
|| myScreen->activeWindow() == myTagEditor->TagTypes; |
|
# else |
|
return false; |
|
# endif // HAVE_TAGLIB_H |
|
} |
|
|
|
void SaveTagChanges::run() |
|
{ |
|
# ifdef HAVE_TAGLIB_H |
|
if (myScreen == myTinyTagEditor) |
|
{ |
|
myTinyTagEditor->main().highlight(myTinyTagEditor->main().size()-2); // Save |
|
myTinyTagEditor->runAction(); |
|
} |
|
else if (myScreen->activeWindow() == myTagEditor->TagTypes) |
|
{ |
|
myTagEditor->TagTypes->highlight(myTagEditor->TagTypes->size()-1); // Save |
|
myTagEditor->runAction(); |
|
} |
|
# endif // HAVE_TAGLIB_H |
|
} |
|
|
|
void ToggleSingle::run() |
|
{ |
|
Mpd.SetSingle(!Status::State::single()); |
|
} |
|
|
|
void ToggleConsume::run() |
|
{ |
|
Mpd.SetConsume(!Status::State::consume()); |
|
} |
|
|
|
void ToggleCrossfade::run() |
|
{ |
|
Mpd.SetCrossfade(Status::State::crossfade() ? 0 : Config.crossfade_time); |
|
} |
|
|
|
void SetCrossfade::run() |
|
{ |
|
using Global::wFooter; |
|
|
|
Statusbar::ScopedLock slock; |
|
Statusbar::put() << "Set crossfade to: "; |
|
auto crossfade = fromString<unsigned>(wFooter->prompt()); |
|
lowerBoundCheck(crossfade, 0u); |
|
Config.crossfade_time = crossfade; |
|
Mpd.SetCrossfade(crossfade); |
|
} |
|
|
|
void SetVolume::run() |
|
{ |
|
using Global::wFooter; |
|
|
|
unsigned volume; |
|
{ |
|
Statusbar::ScopedLock slock; |
|
Statusbar::put() << "Set volume to: "; |
|
volume = fromString<unsigned>(wFooter->prompt()); |
|
boundsCheck(volume, 0u, 100u); |
|
Mpd.SetVolume(volume); |
|
} |
|
Statusbar::printf("Volume set to %1%%%", volume); |
|
} |
|
|
|
bool EnterDirectory::canBeRun() |
|
{ |
|
bool result = false; |
|
if (myScreen == myBrowser && !myBrowser->main().empty()) |
|
{ |
|
result = myBrowser->main().current()->value().type() |
|
== MPD::Item::Type::Directory; |
|
} |
|
#ifdef HAVE_TAGLIB_H |
|
else if (myScreen->activeWindow() == myTagEditor->Dirs) |
|
result = true; |
|
#endif // HAVE_TAGLIB_H |
|
return result; |
|
} |
|
|
|
void EnterDirectory::run() |
|
{ |
|
if (myScreen == myBrowser) |
|
myBrowser->enterDirectory(); |
|
#ifdef HAVE_TAGLIB_H |
|
else if (myScreen->activeWindow() == myTagEditor->Dirs) |
|
{ |
|
if (!myTagEditor->enterDirectory()) |
|
Statusbar::print("No subdirectories found"); |
|
} |
|
#endif // HAVE_TAGLIB_H |
|
} |
|
|
|
bool EditSong::canBeRun() |
|
{ |
|
# ifdef HAVE_TAGLIB_H |
|
m_song = currentSong(myScreen); |
|
return m_song != nullptr && isMPDMusicDirSet(); |
|
# else |
|
return false; |
|
# endif // HAVE_TAGLIB_H |
|
} |
|
|
|
void EditSong::run() |
|
{ |
|
# ifdef HAVE_TAGLIB_H |
|
myTinyTagEditor->SetEdited(*m_song); |
|
myTinyTagEditor->switchTo(); |
|
# endif // HAVE_TAGLIB_H |
|
} |
|
|
|
bool EditLibraryTag::canBeRun() |
|
{ |
|
# ifdef HAVE_TAGLIB_H |
|
return myScreen->isActiveWindow(myLibrary->Tags) |
|
&& !myLibrary->Tags.empty() |
|
&& isMPDMusicDirSet(); |
|
# else |
|
return false; |
|
# endif // HAVE_TAGLIB_H |
|
} |
|
|
|
void EditLibraryTag::run() |
|
{ |
|
# ifdef HAVE_TAGLIB_H |
|
using Global::wFooter; |
|
|
|
std::string new_tag; |
|
{ |
|
Statusbar::ScopedLock slock; |
|
Statusbar::put() << NC::Format::Bold << tagTypeToString(Config.media_lib_primary_tag) << NC::Format::NoBold << ": "; |
|
new_tag = wFooter->prompt(myLibrary->Tags.current()->value().tag()); |
|
} |
|
if (!new_tag.empty() && new_tag != myLibrary->Tags.current()->value().tag()) |
|
{ |
|
Statusbar::print("Updating tags..."); |
|
Mpd.StartSearch(true); |
|
Mpd.AddSearch(Config.media_lib_primary_tag, myLibrary->Tags.current()->value().tag()); |
|
MPD::MutableSong::SetFunction set = tagTypeToSetFunction(Config.media_lib_primary_tag); |
|
assert(set); |
|
bool success = true; |
|
std::string dir_to_update; |
|
for (MPD::SongIterator s = Mpd.CommitSearchSongs(), end; s != end; ++s) |
|
{ |
|
MPD::MutableSong ms = std::move(*s); |
|
ms.setTags(set, new_tag); |
|
Statusbar::printf("Updating tags in \"%1%\"...", ms.getName()); |
|
std::string path = Config.mpd_music_dir + ms.getURI(); |
|
if (!Tags::write(ms)) |
|
{ |
|
success = false; |
|
Statusbar::printf("Error while writing tags to \"%1%\": %2%", |
|
ms.getName(), strerror(errno)); |
|
s.finish(); |
|
break; |
|
} |
|
if (dir_to_update.empty()) |
|
dir_to_update = ms.getURI(); |
|
else |
|
dir_to_update = getSharedDirectory(dir_to_update, ms.getURI()); |
|
}; |
|
if (success) |
|
{ |
|
Mpd.UpdateDirectory(dir_to_update); |
|
Statusbar::print("Tags updated successfully"); |
|
} |
|
} |
|
# endif // HAVE_TAGLIB_H |
|
} |
|
|
|
bool EditLibraryAlbum::canBeRun() |
|
{ |
|
# ifdef HAVE_TAGLIB_H |
|
return myScreen->isActiveWindow(myLibrary->Albums) |
|
&& !myLibrary->Albums.empty() |
|
&& isMPDMusicDirSet(); |
|
# else |
|
return false; |
|
# endif // HAVE_TAGLIB_H |
|
} |
|
|
|
void EditLibraryAlbum::run() |
|
{ |
|
# ifdef HAVE_TAGLIB_H |
|
using Global::wFooter; |
|
// FIXME: merge this and EditLibraryTag. also, prompt on failure if user wants to continue |
|
std::string new_album; |
|
{ |
|
Statusbar::ScopedLock slock; |
|
Statusbar::put() << NC::Format::Bold << "Album: " << NC::Format::NoBold; |
|
new_album = wFooter->prompt(myLibrary->Albums.current()->value().entry().album()); |
|
} |
|
if (!new_album.empty() && new_album != myLibrary->Albums.current()->value().entry().album()) |
|
{ |
|
bool success = 1; |
|
Statusbar::print("Updating tags..."); |
|
for (size_t i = 0; i < myLibrary->Songs.size(); ++i) |
|
{ |
|
Statusbar::printf("Updating tags in \"%1%\"...", myLibrary->Songs[i].value().getName()); |
|
std::string path = Config.mpd_music_dir + myLibrary->Songs[i].value().getURI(); |
|
TagLib::FileRef f(path.c_str()); |
|
if (f.isNull()) |
|
{ |
|
const char msg[] = "Error while opening file \"%1%\""; |
|
Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg))); |
|
success = 0; |
|
break; |
|
} |
|
f.tag()->setAlbum(ToWString(new_album)); |
|
if (!f.save()) |
|
{ |
|
const char msg[] = "Error while writing tags in \"%1%\""; |
|
Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg))); |
|
success = 0; |
|
break; |
|
} |
|
} |
|
if (success) |
|
{ |
|
Mpd.UpdateDirectory(getSharedDirectory(myLibrary->Songs.beginV(), myLibrary->Songs.endV())); |
|
Statusbar::print("Tags updated successfully"); |
|
} |
|
} |
|
# endif // HAVE_TAGLIB_H |
|
} |
|
|
|
bool EditDirectoryName::canBeRun() |
|
{ |
|
return ((myScreen == myBrowser |
|
&& !myBrowser->main().empty() |
|
&& myBrowser->main().current()->value().type() == MPD::Item::Type::Directory) |
|
# ifdef HAVE_TAGLIB_H |
|
|| (myScreen->activeWindow() == myTagEditor->Dirs |
|
&& !myTagEditor->Dirs->empty() |
|
&& myTagEditor->Dirs->choice() > 0) |
|
# endif // HAVE_TAGLIB_H |
|
) && isMPDMusicDirSet(); |
|
} |
|
|
|
void EditDirectoryName::run() |
|
{ |
|
using Global::wFooter; |
|
if (myScreen == myBrowser) |
|
{ |
|
std::string old_dir = myBrowser->main().current()->value().directory().path(); |
|
std::string new_dir; |
|
{ |
|
Statusbar::ScopedLock slock; |
|
Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold; |
|
new_dir = wFooter->prompt(old_dir); |
|
} |
|
if (!new_dir.empty() && new_dir != old_dir) |
|
{ |
|
std::string full_old_dir; |
|
if (!myBrowser->isLocal()) |
|
full_old_dir += Config.mpd_music_dir; |
|
full_old_dir += old_dir; |
|
std::string full_new_dir; |
|
if (!myBrowser->isLocal()) |
|
full_new_dir += Config.mpd_music_dir; |
|
full_new_dir += new_dir; |
|
boost::filesystem::rename(full_old_dir, full_new_dir); |
|
const char msg[] = "Directory renamed to \"%1%\""; |
|
Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg))); |
|
if (!myBrowser->isLocal()) |
|
Mpd.UpdateDirectory(getSharedDirectory(old_dir, new_dir)); |
|
myBrowser->requestUpdate(); |
|
} |
|
} |
|
# ifdef HAVE_TAGLIB_H |
|
else if (myScreen->activeWindow() == myTagEditor->Dirs) |
|
{ |
|
std::string old_dir = myTagEditor->Dirs->current()->value().first, new_dir; |
|
{ |
|
Statusbar::ScopedLock slock; |
|
Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold; |
|
new_dir = wFooter->prompt(old_dir); |
|
} |
|
if (!new_dir.empty() && new_dir != old_dir) |
|
{ |
|
std::string full_old_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + old_dir; |
|
std::string full_new_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + new_dir; |
|
if (rename(full_old_dir.c_str(), full_new_dir.c_str()) == 0) |
|
{ |
|
const char msg[] = "Directory renamed to \"%1%\""; |
|
Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg))); |
|
Mpd.UpdateDirectory(myTagEditor->CurrentDir()); |
|
} |
|
else |
|
{ |
|
const char msg[] = "Couldn't rename \"%1%\": %2%"; |
|
Statusbar::printf(msg, wideShorten(old_dir, COLS-const_strlen(msg)-25), strerror(errno)); |
|
} |
|
} |
|
} |
|
# endif // HAVE_TAGLIB_H |
|
} |
|
|
|
bool EditPlaylistName::canBeRun() |
|
{ |
|
return (myScreen->isActiveWindow(myPlaylistEditor->Playlists) |
|
&& !myPlaylistEditor->Playlists.empty()) |
|
|| (myScreen == myBrowser |
|
&& !myBrowser->main().empty() |
|
&& myBrowser->main().current()->value().type() == MPD::Item::Type::Playlist); |
|
} |
|
|
|
void EditPlaylistName::run() |
|
{ |
|
using Global::wFooter; |
|
std::string old_name, new_name; |
|
if (myScreen->isActiveWindow(myPlaylistEditor->Playlists)) |
|
old_name = myPlaylistEditor->Playlists.current()->value().path(); |
|
else |
|
old_name = myBrowser->main().current()->value().playlist().path(); |
|
{ |
|
Statusbar::ScopedLock slock; |
|
Statusbar::put() << NC::Format::Bold << "Playlist: " << NC::Format::NoBold; |
|
new_name = wFooter->prompt(old_name); |
|
} |
|
if (!new_name.empty() && new_name != old_name) |
|
{ |
|
Mpd.Rename(old_name, new_name); |
|
const char msg[] = "Playlist renamed to \"%1%\""; |
|
Statusbar::printf(msg, wideShorten(new_name, COLS-const_strlen(msg))); |
|
} |
|
} |
|
|
|
bool EditLyrics::canBeRun() |
|
{ |
|
return myScreen == myLyrics; |
|
} |
|
|
|
void EditLyrics::run() |
|
{ |
|
myLyrics->edit(); |
|
} |
|
|
|
bool JumpToBrowser::canBeRun() |
|
{ |
|
m_song = currentSong(myScreen); |
|
return m_song != nullptr; |
|
} |
|
|
|
void JumpToBrowser::run() |
|
{ |
|
myBrowser->locateSong(*m_song); |
|
} |
|
|
|
bool JumpToMediaLibrary::canBeRun() |
|
{ |
|
m_song = currentSong(myScreen); |
|
return m_song != nullptr; |
|
} |
|
|
|
void JumpToMediaLibrary::run() |
|
{ |
|
myLibrary->locateSong(*m_song); |
|
} |
|
|
|
bool JumpToPlaylistEditor::canBeRun() |
|
{ |
|
return myScreen == myBrowser |
|
&& myBrowser->main().current()->value().type() == MPD::Item::Type::Playlist; |
|
} |
|
|
|
void JumpToPlaylistEditor::run() |
|
{ |
|
myPlaylistEditor->locatePlaylist(myBrowser->main().current()->value().playlist()); |
|
} |
|
|
|
void ToggleScreenLock::run() |
|
{ |
|
using Global::wFooter; |
|
using Global::myLockedScreen; |
|
const char *msg_unlockable_screen = "Current screen can't be locked"; |
|
if (myLockedScreen != nullptr) |
|
{ |
|
BaseScreen::unlock(); |
|
Actions::setResizeFlags(); |
|
myScreen->resize(); |
|
Statusbar::print("Screen unlocked"); |
|
} |
|
else if (!myScreen->isLockable()) |
|
{ |
|
Statusbar::print(msg_unlockable_screen); |
|
} |
|
else |
|
{ |
|
unsigned part = Config.locked_screen_width_part*100; |
|
if (Config.ask_for_locked_screen_width_part) |
|
{ |
|
Statusbar::ScopedLock slock; |
|
Statusbar::put() << "% of the locked screen's width to be reserved (20-80): "; |
|
part = fromString<unsigned>(wFooter->prompt(boost::lexical_cast<std::string>(part))); |
|
} |
|
boundsCheck(part, 20u, 80u); |
|
Config.locked_screen_width_part = part/100.0; |
|
if (myScreen->lock()) |
|
Statusbar::printf("Screen locked (with %1%%% width)", part); |
|
else |
|
Statusbar::print(msg_unlockable_screen); |
|
} |
|
} |
|
|
|
bool JumpToTagEditor::canBeRun() |
|
{ |
|
# ifdef HAVE_TAGLIB_H |
|
m_song = currentSong(myScreen); |
|
return m_song != nullptr && isMPDMusicDirSet(); |
|
# else |
|
return false; |
|
# endif // HAVE_TAGLIB_H |
|
} |
|
|
|
void JumpToTagEditor::run() |
|
{ |
|
# ifdef HAVE_TAGLIB_H |
|
myTagEditor->LocateSong(*m_song); |
|
# endif // HAVE_TAGLIB_H |
|
} |
|
|
|
bool JumpToPositionInSong::canBeRun() |
|
{ |
|
return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0; |
|
} |
|
|
|
void JumpToPositionInSong::run() |
|
{ |
|
using Global::wFooter; |
|
|
|
const MPD::Song s = myPlaylist->nowPlayingSong(); |
|
|
|
std::string spos; |
|
{ |
|
Statusbar::ScopedLock slock; |
|
Statusbar::put() << "Position to go (in %/m:ss/seconds(s)): "; |
|
spos = wFooter->prompt(); |
|
} |
|
|
|
boost::regex rx; |
|
boost::smatch what; |
|
if (boost::regex_match(spos, what, rx.assign("([0-9]+):([0-9]{2})"))) // mm:ss |
|
{ |
|
auto mins = fromString<unsigned>(what[1]); |
|
auto secs = fromString<unsigned>(what[2]); |
|
boundsCheck(secs, 0u, 60u); |
|
Mpd.Seek(s.getPosition(), mins * 60 + secs); |
|
} |
|
else if (boost::regex_match(spos, what, rx.assign("([0-9]+)s"))) // position in seconds |
|
{ |
|
auto secs = fromString<unsigned>(what[1]); |
|
Mpd.Seek(s.getPosition(), secs); |
|
} |
|
else if (boost::regex_match(spos, what, rx.assign("([0-9]+)[%]{0,1}"))) // position in % |
|
{ |
|
auto percent = fromString<unsigned>(what[1]); |
|
boundsCheck(percent, 0u, 100u); |
|
int secs = (percent * s.getDuration()) / 100.0; |
|
Mpd.Seek(s.getPosition(), secs); |
|
} |
|
else |
|
Statusbar::print("Invalid format ([m]:[ss], [s]s, [%]%, [%] accepted)"); |
|
} |
|
|
|
bool SelectItem::canBeRun() |
|
{ |
|
m_list = dynamic_cast<NC::List *>(myScreen->activeWindow()); |
|
return m_list != nullptr |
|
&& !m_list->empty() |
|
&& m_list->currentP()->isSelectable(); |
|
} |
|
|
|
void SelectItem::run() |
|
{ |
|
auto current = m_list->currentP(); |
|
current->setSelected(!current->isSelected()); |
|
} |
|
|
|
bool SelectRange::canBeRun() |
|
{ |
|
m_list = dynamic_cast<NC::List *>(myScreen->activeWindow()); |
|
if (m_list == nullptr) |
|
return false; |
|
m_begin = m_list->beginP(); |
|
m_end = m_list->endP(); |
|
return findRange(m_begin, m_end); |
|
} |
|
|
|
void SelectRange::run() |
|
{ |
|
for (; m_begin != m_end; ++m_begin) |
|
m_begin->setSelected(true); |
|
Statusbar::print("Range selected"); |
|
} |
|
|
|
bool ReverseSelection::canBeRun() |
|
{ |
|
m_list = dynamic_cast<NC::List *>(myScreen->activeWindow()); |
|
return m_list != nullptr; |
|
} |
|
|
|
void ReverseSelection::run() |
|
{ |
|
for (auto &p : *m_list) |
|
p.setSelected(!p.isSelected()); |
|
Statusbar::print("Selection reversed"); |
|
} |
|
|
|
bool RemoveSelection::canBeRun() |
|
{ |
|
m_list = dynamic_cast<NC::List *>(myScreen->activeWindow()); |
|
return m_list != nullptr; |
|
} |
|
|
|
void RemoveSelection::run() |
|
{ |
|
for (auto &p : *m_list) |
|
p.setSelected(false); |
|
Statusbar::print("Selection removed"); |
|
} |
|
|
|
bool SelectAlbum::canBeRun() |
|
{ |
|
auto *w = myScreen->activeWindow(); |
|
if (m_list != static_cast<void *>(w)) |
|
m_list = dynamic_cast<NC::List *>(w); |
|
if (m_songs != static_cast<void *>(w)) |
|
m_songs = dynamic_cast<SongList *>(w); |
|
return m_list != nullptr && !m_list->empty() |
|
&& m_songs != nullptr; |
|
} |
|
|
|
void SelectAlbum::run() |
|
{ |
|
const auto front = m_songs->beginS(), current = m_songs->currentS(), end = m_songs->endS(); |
|
auto *s = current->get<Bit::Song>(); |
|
if (s == nullptr) |
|
return; |
|
auto get = &MPD::Song::getAlbum; |
|
const std::string tag = s->getTags(get); |
|
// go up |
|
for (auto it = current; it != front;) |
|
{ |
|
--it; |
|
s = it->get<Bit::Song>(); |
|
if (s == nullptr || s->getTags(get) != tag) |
|
break; |
|
it->get<Bit::Properties>().setSelected(true); |
|
} |
|
// go down |
|
for (auto it = current;;) |
|
{ |
|
it->get<Bit::Properties>().setSelected(true); |
|
if (++it == end) |
|
break; |
|
s = it->get<Bit::Song>(); |
|
if (s == nullptr || s->getTags(get) != tag) |
|
break; |
|
} |
|
Statusbar::print("Album around cursor position selected"); |
|
} |
|
|
|
bool SelectFoundItems::canBeRun() |
|
{ |
|
m_list = dynamic_cast<NC::List *>(myScreen->activeWindow()); |
|
if (m_list == nullptr || m_list->empty()) |
|
return false; |
|
m_searchable = dynamic_cast<Searchable *>(myScreen); |
|
return m_searchable != nullptr && m_searchable->allowsSearching(); |
|
} |
|
|
|
void SelectFoundItems::run() |
|
{ |
|
auto current_pos = m_list->choice(); |
|
myScreen->activeWindow()->scroll(NC::Scroll::Home); |
|
bool found = m_searchable->search(SearchDirection::Forward, false, false); |
|
if (found) |
|
{ |
|
Statusbar::print("Searching for items..."); |
|
m_list->currentP()->setSelected(true); |
|
while (m_searchable->search(SearchDirection::Forward, false, true)) |
|
m_list->currentP()->setSelected(true); |
|
Statusbar::print("Found items selected"); |
|
} |
|
m_list->highlight(current_pos); |
|
} |
|
|
|
bool AddSelectedItems::canBeRun() |
|
{ |
|
return myScreen != mySelectedItemsAdder; |
|
} |
|
|
|
void AddSelectedItems::run() |
|
{ |
|
mySelectedItemsAdder->switchTo(); |
|
} |
|
|
|
void CropMainPlaylist::run() |
|
{ |
|
auto &w = myPlaylist->main(); |
|
// cropping doesn't make sense in this case |
|
if (w.size() <= 1) |
|
return; |
|
if (Config.ask_before_clearing_playlists) |
|
confirmAction("Do you really want to crop main playlist?"); |
|
Statusbar::print("Cropping playlist..."); |
|
selectCurrentIfNoneSelected(w); |
|
cropPlaylist(w, std::bind(&MPD::Connection::Delete, ph::_1, ph::_2)); |
|
Statusbar::print("Playlist cropped"); |
|
} |
|
|
|
bool CropPlaylist::canBeRun() |
|
{ |
|
return myScreen == myPlaylistEditor; |
|
} |
|
|
|
void CropPlaylist::run() |
|
{ |
|
auto &w = myPlaylistEditor->Content; |
|
// cropping doesn't make sense in this case |
|
if (w.size() <= 1) |
|
return; |
|
assert(!myPlaylistEditor->Playlists.empty()); |
|
std::string playlist = myPlaylistEditor->Playlists.current()->value().path(); |
|
if (Config.ask_before_clearing_playlists) |
|
confirmAction(boost::format("Do you really want to crop playlist \"%1%\"?") % playlist); |
|
selectCurrentIfNoneSelected(w); |
|
Statusbar::printf("Cropping playlist \"%1%\"...", playlist); |
|
cropPlaylist(w, std::bind(&MPD::Connection::PlaylistDelete, ph::_1, playlist, ph::_2)); |
|
Statusbar::printf("Playlist \"%1%\" cropped", playlist); |
|
} |
|
|
|
void ClearMainPlaylist::run() |
|
{ |
|
if (!myPlaylist->main().empty() && Config.ask_before_clearing_playlists) |
|
confirmAction("Do you really want to clear main playlist?"); |
|
Mpd.ClearMainPlaylist(); |
|
Statusbar::print("Playlist cleared"); |
|
myPlaylist->main().reset(); |
|
} |
|
|
|
bool ClearPlaylist::canBeRun() |
|
{ |
|
return myScreen == myPlaylistEditor; |
|
} |
|
|
|
void ClearPlaylist::run() |
|
{ |
|
if (myPlaylistEditor->Playlists.empty()) |
|
return; |
|
std::string playlist = myPlaylistEditor->Playlists.current()->value().path(); |
|
if (Config.ask_before_clearing_playlists) |
|
confirmAction(boost::format("Do you really want to clear playlist \"%1%\"?") % playlist); |
|
Mpd.ClearPlaylist(playlist); |
|
Statusbar::printf("Playlist \"%1%\" cleared", playlist); |
|
} |
|
|
|
bool SortPlaylist::canBeRun() |
|
{ |
|
if (myScreen != myPlaylist) |
|
return false; |
|
auto first = myPlaylist->main().begin(), last = myPlaylist->main().end(); |
|
return findSelectedRangeAndPrintInfoIfNot(first, last); |
|
} |
|
|
|
void SortPlaylist::run() |
|
{ |
|
mySortPlaylistDialog->switchTo(); |
|
} |
|
|
|
bool ReversePlaylist::canBeRun() |
|
{ |
|
if (myScreen != myPlaylist) |
|
return false; |
|
m_begin = myPlaylist->main().begin(); |
|
m_end = myPlaylist->main().end(); |
|
return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end); |
|
} |
|
|
|
void ReversePlaylist::run() |
|
{ |
|
Statusbar::print("Reversing range..."); |
|
Mpd.StartCommandsList(); |
|
for (--m_end; m_begin < m_end; ++m_begin, --m_end) |
|
Mpd.Swap(m_begin->value().getPosition(), m_end->value().getPosition()); |
|
Mpd.CommitCommandsList(); |
|
Statusbar::print("Range reversed"); |
|
} |
|
|
|
bool ApplyFilter::canBeRun() |
|
{ |
|
m_filterable = dynamic_cast<Filterable *>(myScreen); |
|
return m_filterable != nullptr |
|
&& m_filterable->allowsFiltering(); |
|
} |
|
|
|
void ApplyFilter::run() |
|
{ |
|
using Global::wFooter; |
|
|
|
std::string filter = m_filterable->currentFilter(); |
|
if (!filter.empty()) |
|
{ |
|
m_filterable->applyFilter(filter); |
|
myScreen->refreshWindow(); |
|
} |
|
|
|
try |
|
{ |
|
Statusbar::ScopedLock slock; |
|
NC::Window::ScopedPromptHook helper( |
|
*wFooter, |
|
Statusbar::Helpers::ApplyFilterImmediately(m_filterable)); |
|
Statusbar::put() << "Apply filter: "; |
|
filter = wFooter->prompt(filter); |
|
} |
|
catch (NC::PromptAborted &) |
|
{ |
|
m_filterable->applyFilter(filter); |
|
throw; |
|
} |
|
|
|
if (filter.empty()) |
|
Statusbar::printf("Filtering disabled"); |
|
else |
|
Statusbar::printf("Using filter \"%1%\"", filter); |
|
|
|
if (myScreen == myPlaylist) |
|
myPlaylist->reloadTotalLength(); |
|
|
|
listsChangeFinisher(); |
|
} |
|
|
|
bool Find::canBeRun() |
|
{ |
|
return myScreen == myHelp |
|
|| myScreen == myLyrics |
|
|| myScreen == myLastfm; |
|
} |
|
|
|
void Find::run() |
|
{ |
|
using Global::wFooter; |
|
|
|
std::string token; |
|
{ |
|
Statusbar::ScopedLock slock; |
|
Statusbar::put() << "Find: "; |
|
token = wFooter->prompt(); |
|
} |
|
|
|
Statusbar::print("Searching..."); |
|
auto s = static_cast<Screen<NC::Scrollpad> *>(myScreen); |
|
s->main().removeProperties(); |
|
if (token.empty() || s->main().setProperties(NC::Format::Reverse, token, NC::Format::NoReverse, Config.regex_type)) |
|
Statusbar::print("Done"); |
|
else |
|
Statusbar::print("No matching patterns found"); |
|
s->main().flush(); |
|
} |
|
|
|
bool FindItemBackward::canBeRun() |
|
{ |
|
auto w = dynamic_cast<Searchable *>(myScreen); |
|
return w && w->allowsSearching(); |
|
} |
|
|
|
void FindItemForward::run() |
|
{ |
|
findItem(SearchDirection::Forward); |
|
listsChangeFinisher(); |
|
} |
|
|
|
bool FindItemForward::canBeRun() |
|
{ |
|
auto w = dynamic_cast<Searchable *>(myScreen); |
|
return w && w->allowsSearching(); |
|
} |
|
|
|
void FindItemBackward::run() |
|
{ |
|
findItem(SearchDirection::Backward); |
|
listsChangeFinisher(); |
|
} |
|
|
|
bool NextFoundItem::canBeRun() |
|
{ |
|
return dynamic_cast<Searchable *>(myScreen); |
|
} |
|
|
|
void NextFoundItem::run() |
|
{ |
|
Searchable *w = dynamic_cast<Searchable *>(myScreen); |
|
assert(w != nullptr); |
|
w->search(SearchDirection::Forward, Config.wrapped_search, true); |
|
listsChangeFinisher(); |
|
} |
|
|
|
bool PreviousFoundItem::canBeRun() |
|
{ |
|
return dynamic_cast<Searchable *>(myScreen); |
|
} |
|
|
|
void PreviousFoundItem::run() |
|
{ |
|
Searchable *w = dynamic_cast<Searchable *>(myScreen); |
|
assert(w != nullptr); |
|
w->search(SearchDirection::Backward, Config.wrapped_search, true); |
|
listsChangeFinisher(); |
|
} |
|
|
|
void ToggleFindMode::run() |
|
{ |
|
Config.wrapped_search = !Config.wrapped_search; |
|
Statusbar::printf("Search mode: %1%", |
|
Config.wrapped_search ? "Wrapped" : "Normal" |
|
); |
|
} |
|
|
|
void ToggleReplayGainMode::run() |
|
{ |
|
using Global::wFooter; |
|
|
|
char rgm = 0; |
|
{ |
|
Statusbar::ScopedLock slock; |
|
Statusbar::put() << "Replay gain mode? " |
|
<< "[" << NC::Format::Bold << 'o' << NC::Format::NoBold << "ff" |
|
<< "/" << NC::Format::Bold << 't' << NC::Format::NoBold << "rack" |
|
<< "/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "lbum" |
|
<< "] "; |
|
rgm = Statusbar::Helpers::promptReturnOneOf({"t", "a", "o"})[0]; |
|
} |
|
switch (rgm) |
|
{ |
|
case 't': |
|
Mpd.SetReplayGainMode(MPD::rgmTrack); |
|
break; |
|
case 'a': |
|
Mpd.SetReplayGainMode(MPD::rgmAlbum); |
|
break; |
|
case 'o': |
|
Mpd.SetReplayGainMode(MPD::rgmOff); |
|
break; |
|
default: // impossible |
|
throw std::runtime_error( |
|
(boost::format("ToggleReplayGainMode: impossible case reached: %1%") % rgm).str() |
|
); |
|
} |
|
Statusbar::printf("Replay gain mode: %1%", Mpd.GetReplayGainMode()); |
|
} |
|
|
|
void ToggleAddMode::run() |
|
{ |
|
std::string mode_desc; |
|
switch (Config.space_add_mode) |
|
{ |
|
case SpaceAddMode::AddRemove: |
|
Config.space_add_mode = SpaceAddMode::AlwaysAdd; |
|
mode_desc = "always add an item to playlist"; |
|
break; |
|
case SpaceAddMode::AlwaysAdd: |
|
Config.space_add_mode = SpaceAddMode::AddRemove; |
|
mode_desc = "add an item to playlist or remove if already added"; |
|
break; |
|
} |
|
Statusbar::printf("Add mode: %1%", mode_desc); |
|
} |
|
|
|
void ToggleMouse::run() |
|
{ |
|
Config.mouse_support = !Config.mouse_support; |
|
if (Config.mouse_support) |
|
NC::Mouse::enable(); |
|
else |
|
NC::Mouse::disable(); |
|
Statusbar::printf("Mouse support %1%", |
|
Config.mouse_support ? "enabled" : "disabled" |
|
); |
|
} |
|
|
|
void ToggleBitrateVisibility::run() |
|
{ |
|
Config.display_bitrate = !Config.display_bitrate; |
|
Statusbar::printf("Bitrate visibility %1%", |
|
Config.display_bitrate ? "enabled" : "disabled" |
|
); |
|
} |
|
|
|
void AddRandomItems::run() |
|
{ |
|
using Global::wFooter; |
|
char rnd_type = 0; |
|
{ |
|
Statusbar::ScopedLock slock; |
|
Statusbar::put() << "Add random? " |
|
<< "[" << NC::Format::Bold << 's' << NC::Format::NoBold << "ongs" |
|
<< "/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtists" |
|
<< "/" << "album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtists" |
|
<< "/" << "al" << NC::Format::Bold << 'b' << NC::Format::NoBold << "ums" |
|
<< "] "; |
|
rnd_type = Statusbar::Helpers::promptReturnOneOf({"s", "a", "A", "b"})[0]; |
|
} |
|
|
|
mpd_tag_type tag_type = MPD_TAG_ARTIST; |
|
std::string tag_type_str ; |
|
if (rnd_type != 's') |
|
{ |
|
tag_type = charToTagType(rnd_type); |
|
tag_type_str = boost::locale::to_lower(tagTypeToString(tag_type)); |
|
} |
|
else |
|
tag_type_str = "song"; |
|
|
|
unsigned number; |
|
{ |
|
Statusbar::ScopedLock slock; |
|
Statusbar::put() << "Number of random " << tag_type_str << "s: "; |
|
number = fromString<unsigned>(wFooter->prompt()); |
|
} |
|
if (number > 0) |
|
{ |
|
bool success; |
|
if (rnd_type == 's') |
|
success = Mpd.AddRandomSongs(number, Global::RNG); |
|
else |
|
success = Mpd.AddRandomTag(tag_type, number, Global::RNG); |
|
if (success) |
|
Statusbar::printf("%1% random %2%%3% added to playlist", number, tag_type_str, number == 1 ? "" : "s"); |
|
} |
|
} |
|
|
|
bool ToggleBrowserSortMode::canBeRun() |
|
{ |
|
return myScreen == myBrowser; |
|
} |
|
|
|
void ToggleBrowserSortMode::run() |
|
{ |
|
switch (Config.browser_sort_mode) |
|
{ |
|
case SortMode::Name: |
|
Config.browser_sort_mode = SortMode::ModificationTime; |
|
Statusbar::print("Sort songs by: modification time"); |
|
break; |
|
case SortMode::ModificationTime: |
|
Config.browser_sort_mode = SortMode::CustomFormat; |
|
Statusbar::print("Sort songs by: custom format"); |
|
break; |
|
case SortMode::CustomFormat: |
|
Config.browser_sort_mode = SortMode::NoOp; |
|
Statusbar::print("Do not sort songs"); |
|
break; |
|
case SortMode::NoOp: |
|
Config.browser_sort_mode = SortMode::Name; |
|
Statusbar::print("Sort songs by: name"); |
|
} |
|
if (Config.browser_sort_mode != SortMode::NoOp) |
|
{ |
|
size_t sort_offset = myBrowser->inRootDirectory() ? 0 : 1; |
|
std::sort(myBrowser->main().begin()+sort_offset, myBrowser->main().end(), |
|
LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode) |
|
); |
|
} |
|
} |
|
|
|
bool ToggleLibraryTagType::canBeRun() |
|
{ |
|
return (myScreen->isActiveWindow(myLibrary->Tags)) |
|
|| (myLibrary->columns() == 2 && myScreen->isActiveWindow(myLibrary->Albums)); |
|
} |
|
|
|
void ToggleLibraryTagType::run() |
|
{ |
|
using Global::wFooter; |
|
|
|
char tag_type = 0; |
|
{ |
|
Statusbar::ScopedLock slock; |
|
Statusbar::put() << "Tag type? " |
|
<< "[" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtist" |
|
<< "/" << "album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtist" |
|
<< "/" << NC::Format::Bold << 'y' << NC::Format::NoBold << "ear" |
|
<< "/" << NC::Format::Bold << 'g' << NC::Format::NoBold << "enre" |
|
<< "/" << NC::Format::Bold << 'c' << NC::Format::NoBold << "omposer" |
|
<< "/" << NC::Format::Bold << 'p' << NC::Format::NoBold << "erformer" |
|
<< "] "; |
|
tag_type = Statusbar::Helpers::promptReturnOneOf({"a", "A", "y", "g", "c", "p"})[0]; |
|
} |
|
mpd_tag_type new_tagitem = charToTagType(tag_type); |
|
if (new_tagitem != Config.media_lib_primary_tag) |
|
{ |
|
Config.media_lib_primary_tag = new_tagitem; |
|
std::string item_type = tagTypeToString(Config.media_lib_primary_tag); |
|
myLibrary->Tags.setTitle(Config.titles_visibility ? item_type + "s" : ""); |
|
myLibrary->Tags.reset(); |
|
item_type = boost::locale::to_lower(item_type); |
|
std::string and_mtime = Config.media_library_sort_by_mtime ? |
|
" and mtime" : |
|
""; |
|
if (myLibrary->columns() == 2) |
|
{ |
|
myLibrary->Songs.clear(); |
|
myLibrary->Albums.reset(); |
|
myLibrary->Albums.clear(); |
|
myLibrary->Albums.setTitle(Config.titles_visibility ? "Albums (sorted by " + item_type + and_mtime + ")" : ""); |
|
myLibrary->Albums.display(); |
|
} |
|
else |
|
{ |
|
myLibrary->Tags.clear(); |
|
myLibrary->Tags.display(); |
|
} |
|
Statusbar::printf("Switched to the list of %1%s", item_type); |
|
} |
|
} |
|
|
|
bool ToggleMediaLibrarySortMode::canBeRun() |
|
{ |
|
return myScreen == myLibrary; |
|
} |
|
|
|
void ToggleMediaLibrarySortMode::run() |
|
{ |
|
myLibrary->toggleSortMode(); |
|
} |
|
|
|
bool FetchLyricsInBackground::canBeRun() |
|
{ |
|
m_hs = dynamic_cast<HasSongs *>(myScreen); |
|
return m_hs != nullptr && m_hs->itemAvailable(); |
|
} |
|
|
|
void FetchLyricsInBackground::run() |
|
{ |
|
auto songs = m_hs->getSelectedSongs(); |
|
for (const auto &s : songs) |
|
myLyrics->fetchInBackground(s, true); |
|
Statusbar::print("Selected songs queued for lyrics fetching"); |
|
} |
|
|
|
bool RefetchLyrics::canBeRun() |
|
{ |
|
return myScreen == myLyrics; |
|
} |
|
|
|
void RefetchLyrics::run() |
|
{ |
|
myLyrics->refetchCurrent(); |
|
} |
|
|
|
bool SetSelectedItemsPriority::canBeRun() |
|
{ |
|
if (Mpd.Version() < 17) |
|
{ |
|
Statusbar::print("Priorities are supported in MPD >= 0.17.0"); |
|
return false; |
|
} |
|
return myScreen == myPlaylist && !myPlaylist->main().empty(); |
|
} |
|
|
|
void SetSelectedItemsPriority::run() |
|
{ |
|
using Global::wFooter; |
|
|
|
unsigned prio; |
|
{ |
|
Statusbar::ScopedLock slock; |
|
Statusbar::put() << "Set priority [0-255]: "; |
|
prio = fromString<unsigned>(wFooter->prompt()); |
|
boundsCheck(prio, 0u, 255u); |
|
} |
|
myPlaylist->setSelectedItemsPriority(prio); |
|
} |
|
|
|
bool ToggleOutput::canBeRun() |
|
{ |
|
#ifdef ENABLE_OUTPUTS |
|
return myScreen == myOutputs; |
|
#else |
|
return false; |
|
#endif // ENABLE_OUTPUTS |
|
} |
|
|
|
void ToggleOutput::run() |
|
{ |
|
#ifdef ENABLE_OUTPUTS |
|
myOutputs->toggleOutput(); |
|
#endif // ENABLE_OUTPUTS |
|
} |
|
|
|
bool ToggleVisualizationType::canBeRun() |
|
{ |
|
# ifdef ENABLE_VISUALIZER |
|
return myScreen == myVisualizer; |
|
# else |
|
return false; |
|
# endif // ENABLE_VISUALIZER |
|
} |
|
|
|
void ToggleVisualizationType::run() |
|
{ |
|
# ifdef ENABLE_VISUALIZER |
|
myVisualizer->ToggleVisualizationType(); |
|
# endif // ENABLE_VISUALIZER |
|
} |
|
|
|
bool SetVisualizerSampleMultiplier::canBeRun() |
|
{ |
|
# ifdef ENABLE_VISUALIZER |
|
return myScreen == myVisualizer; |
|
# else |
|
return false; |
|
# endif // ENABLE_VISUALIZER |
|
} |
|
|
|
void SetVisualizerSampleMultiplier::run() |
|
{ |
|
# ifdef ENABLE_VISUALIZER |
|
using Global::wFooter; |
|
|
|
double multiplier; |
|
{ |
|
Statusbar::ScopedLock slock; |
|
Statusbar::put() << "Set visualizer sample multiplier: "; |
|
multiplier = fromString<double>(wFooter->prompt()); |
|
lowerBoundCheck(multiplier, 0.0); |
|
Config.visualizer_sample_multiplier = multiplier; |
|
} |
|
Statusbar::printf("Visualizer sample multiplier set to %1%", multiplier); |
|
# endif // ENABLE_VISUALIZER |
|
} |
|
|
|
void ShowSongInfo::run() |
|
{ |
|
mySongInfo->switchTo(); |
|
} |
|
|
|
bool ShowArtistInfo::canBeRun() |
|
{ |
|
return myScreen == myLastfm |
|
|| (myScreen->isActiveWindow(myLibrary->Tags) |
|
&& !myLibrary->Tags.empty() |
|
&& Config.media_lib_primary_tag == MPD_TAG_ARTIST) |
|
|| currentSong(myScreen); |
|
} |
|
|
|
void ShowArtistInfo::run() |
|
{ |
|
if (myScreen == myLastfm) |
|
{ |
|
myLastfm->switchTo(); |
|
return; |
|
} |
|
|
|
std::string artist; |
|
if (myScreen->isActiveWindow(myLibrary->Tags)) |
|
{ |
|
assert(!myLibrary->Tags.empty()); |
|
assert(Config.media_lib_primary_tag == MPD_TAG_ARTIST); |
|
artist = myLibrary->Tags.current()->value().tag(); |
|
} |
|
else |
|
{ |
|
auto s = currentSong(myScreen); |
|
assert(s); |
|
artist = s->getArtist(); |
|
} |
|
|
|
if (!artist.empty()) |
|
{ |
|
myLastfm->queueJob(new LastFm::ArtistInfo(artist, Config.lastfm_preferred_language)); |
|
myLastfm->switchTo(); |
|
} |
|
} |
|
|
|
bool ShowLyrics::canBeRun() |
|
{ |
|
if (myScreen == myLyrics) |
|
{ |
|
m_song = nullptr; |
|
return true; |
|
} |
|
else |
|
{ |
|
m_song = currentSong(myScreen); |
|
return m_song != nullptr; |
|
} |
|
} |
|
|
|
void ShowLyrics::run() |
|
{ |
|
if (m_song != nullptr) |
|
myLyrics->fetch(*m_song); |
|
myLyrics->switchTo(); |
|
} |
|
|
|
void Quit::run() |
|
{ |
|
ExitMainLoop = true; |
|
} |
|
|
|
void NextScreen::run() |
|
{ |
|
if (Config.screen_switcher_previous) |
|
{ |
|
if (auto tababble = dynamic_cast<Tabbable *>(myScreen)) |
|
tababble->switchToPreviousScreen(); |
|
} |
|
else if (!Config.screen_sequence.empty()) |
|
{ |
|
const auto &seq = Config.screen_sequence; |
|
auto screen_type = std::find(seq.begin(), seq.end(), myScreen->type()); |
|
if (++screen_type == seq.end()) |
|
toScreen(seq.front())->switchTo(); |
|
else |
|
toScreen(*screen_type)->switchTo(); |
|
} |
|
} |
|
|
|
void PreviousScreen::run() |
|
{ |
|
if (Config.screen_switcher_previous) |
|
{ |
|
if (auto tababble = dynamic_cast<Tabbable *>(myScreen)) |
|
tababble->switchToPreviousScreen(); |
|
} |
|
else if (!Config.screen_sequence.empty()) |
|
{ |
|
const auto &seq = Config.screen_sequence; |
|
auto screen_type = std::find(seq.begin(), seq.end(), myScreen->type()); |
|
if (screen_type == seq.begin()) |
|
toScreen(seq.back())->switchTo(); |
|
else |
|
toScreen(*--screen_type)->switchTo(); |
|
} |
|
} |
|
|
|
bool ShowHelp::canBeRun() |
|
{ |
|
return myScreen != myHelp |
|
# ifdef HAVE_TAGLIB_H |
|
&& myScreen != myTinyTagEditor |
|
# endif // HAVE_TAGLIB_H |
|
; |
|
} |
|
|
|
void ShowHelp::run() |
|
{ |
|
myHelp->switchTo(); |
|
} |
|
|
|
bool ShowPlaylist::canBeRun() |
|
{ |
|
return myScreen != myPlaylist |
|
# ifdef HAVE_TAGLIB_H |
|
&& myScreen != myTinyTagEditor |
|
# endif // HAVE_TAGLIB_H |
|
; |
|
} |
|
|
|
void ShowPlaylist::run() |
|
{ |
|
myPlaylist->switchTo(); |
|
} |
|
|
|
bool ShowBrowser::canBeRun() |
|
{ |
|
return myScreen != myBrowser |
|
# ifdef HAVE_TAGLIB_H |
|
&& myScreen != myTinyTagEditor |
|
# endif // HAVE_TAGLIB_H |
|
; |
|
} |
|
|
|
void ShowBrowser::run() |
|
{ |
|
myBrowser->switchTo(); |
|
} |
|
|
|
bool ChangeBrowseMode::canBeRun() |
|
{ |
|
return myScreen == myBrowser; |
|
} |
|
|
|
void ChangeBrowseMode::run() |
|
{ |
|
myBrowser->changeBrowseMode(); |
|
} |
|
|
|
bool ShowSearchEngine::canBeRun() |
|
{ |
|
return myScreen != mySearcher |
|
# ifdef HAVE_TAGLIB_H |
|
&& myScreen != myTinyTagEditor |
|
# endif // HAVE_TAGLIB_H |
|
; |
|
} |
|
|
|
void ShowSearchEngine::run() |
|
{ |
|
mySearcher->switchTo(); |
|
} |
|
|
|
bool ResetSearchEngine::canBeRun() |
|
{ |
|
return myScreen == mySearcher; |
|
} |
|
|
|
void ResetSearchEngine::run() |
|
{ |
|
mySearcher->reset(); |
|
} |
|
|
|
bool ShowMediaLibrary::canBeRun() |
|
{ |
|
return myScreen != myLibrary |
|
# ifdef HAVE_TAGLIB_H |
|
&& myScreen != myTinyTagEditor |
|
# endif // HAVE_TAGLIB_H |
|
; |
|
} |
|
|
|
void ShowMediaLibrary::run() |
|
{ |
|
myLibrary->switchTo(); |
|
} |
|
|
|
bool ToggleMediaLibraryColumnsMode::canBeRun() |
|
{ |
|
return myScreen == myLibrary; |
|
} |
|
|
|
void ToggleMediaLibraryColumnsMode::run() |
|
{ |
|
myLibrary->toggleColumnsMode(); |
|
myLibrary->refresh(); |
|
} |
|
|
|
bool ShowPlaylistEditor::canBeRun() |
|
{ |
|
return myScreen != myPlaylistEditor |
|
# ifdef HAVE_TAGLIB_H |
|
&& myScreen != myTinyTagEditor |
|
# endif // HAVE_TAGLIB_H |
|
; |
|
} |
|
|
|
void ShowPlaylistEditor::run() |
|
{ |
|
myPlaylistEditor->switchTo(); |
|
} |
|
|
|
bool ShowTagEditor::canBeRun() |
|
{ |
|
# ifdef HAVE_TAGLIB_H |
|
return myScreen != myTagEditor |
|
&& myScreen != myTinyTagEditor; |
|
# else |
|
return false; |
|
# endif // HAVE_TAGLIB_H |
|
} |
|
|
|
void ShowTagEditor::run() |
|
{ |
|
# ifdef HAVE_TAGLIB_H |
|
if (isMPDMusicDirSet()) |
|
myTagEditor->switchTo(); |
|
# endif // HAVE_TAGLIB_H |
|
} |
|
|
|
bool ShowOutputs::canBeRun() |
|
{ |
|
# ifdef ENABLE_OUTPUTS |
|
return myScreen != myOutputs |
|
# ifdef HAVE_TAGLIB_H |
|
&& myScreen != myTinyTagEditor |
|
# endif // HAVE_TAGLIB_H |
|
; |
|
# else |
|
return false; |
|
# endif // ENABLE_OUTPUTS |
|
} |
|
|
|
void ShowOutputs::run() |
|
{ |
|
# ifdef ENABLE_OUTPUTS |
|
myOutputs->switchTo(); |
|
# endif // ENABLE_OUTPUTS |
|
} |
|
|
|
bool ShowVisualizer::canBeRun() |
|
{ |
|
# ifdef ENABLE_VISUALIZER |
|
return myScreen != myVisualizer |
|
# ifdef HAVE_TAGLIB_H |
|
&& myScreen != myTinyTagEditor |
|
# endif // HAVE_TAGLIB_H |
|
; |
|
# else |
|
return false; |
|
# endif // ENABLE_VISUALIZER |
|
} |
|
|
|
void ShowVisualizer::run() |
|
{ |
|
# ifdef ENABLE_VISUALIZER |
|
myVisualizer->switchTo(); |
|
# endif // ENABLE_VISUALIZER |
|
} |
|
|
|
bool ShowClock::canBeRun() |
|
{ |
|
# ifdef ENABLE_CLOCK |
|
return myScreen != myClock |
|
# ifdef HAVE_TAGLIB_H |
|
&& myScreen != myTinyTagEditor |
|
# endif // HAVE_TAGLIB_H |
|
; |
|
# else |
|
return false; |
|
# endif // ENABLE_CLOCK |
|
} |
|
|
|
void ShowClock::run() |
|
{ |
|
# ifdef ENABLE_CLOCK |
|
myClock->switchTo(); |
|
# endif // ENABLE_CLOCK |
|
} |
|
|
|
#ifdef HAVE_TAGLIB_H |
|
bool ShowServerInfo::canBeRun() |
|
{ |
|
return myScreen != myTinyTagEditor; |
|
} |
|
#endif // HAVE_TAGLIB_H |
|
|
|
void ShowServerInfo::run() |
|
{ |
|
myServerInfo->switchTo(); |
|
} |
|
|
|
} |
|
|
|
namespace { |
|
|
|
void populateActions() |
|
{ |
|
AvailableActions.resize(static_cast<size_t>(Actions::Type::_numberOfActions)); |
|
auto insert_action = [](Actions::BaseAction *a) { |
|
AvailableActions.at(static_cast<size_t>(a->type())).reset(a); |
|
}; |
|
insert_action(new Actions::Dummy()); |
|
insert_action(new Actions::UpdateEnvironment()); |
|
insert_action(new Actions::MouseEvent()); |
|
insert_action(new Actions::ScrollUp()); |
|
insert_action(new Actions::ScrollDown()); |
|
insert_action(new Actions::ScrollUpArtist()); |
|
insert_action(new Actions::ScrollUpAlbum()); |
|
insert_action(new Actions::ScrollDownArtist()); |
|
insert_action(new Actions::ScrollDownAlbum()); |
|
insert_action(new Actions::PageUp()); |
|
insert_action(new Actions::PageDown()); |
|
insert_action(new Actions::MoveHome()); |
|
insert_action(new Actions::MoveEnd()); |
|
insert_action(new Actions::ToggleInterface()); |
|
insert_action(new Actions::JumpToParentDirectory()); |
|
insert_action(new Actions::RunAction()); |
|
insert_action(new Actions::SelectItem()); |
|
insert_action(new Actions::SelectRange()); |
|
insert_action(new Actions::PreviousColumn()); |
|
insert_action(new Actions::NextColumn()); |
|
insert_action(new Actions::MasterScreen()); |
|
insert_action(new Actions::SlaveScreen()); |
|
insert_action(new Actions::VolumeUp()); |
|
insert_action(new Actions::VolumeDown()); |
|
insert_action(new Actions::AddItemToPlaylist()); |
|
insert_action(new Actions::DeletePlaylistItems()); |
|
insert_action(new Actions::DeleteStoredPlaylist()); |
|
insert_action(new Actions::DeleteBrowserItems()); |
|
insert_action(new Actions::ReplaySong()); |
|
insert_action(new Actions::PreviousSong()); |
|
insert_action(new Actions::NextSong()); |
|
insert_action(new Actions::Pause()); |
|
insert_action(new Actions::Stop()); |
|
insert_action(new Actions::ExecuteCommand()); |
|
insert_action(new Actions::SavePlaylist()); |
|
insert_action(new Actions::MoveSortOrderUp()); |
|
insert_action(new Actions::MoveSortOrderDown()); |
|
insert_action(new Actions::MoveSelectedItemsUp()); |
|
insert_action(new Actions::MoveSelectedItemsDown()); |
|
insert_action(new Actions::MoveSelectedItemsTo()); |
|
insert_action(new Actions::Add()); |
|
insert_action(new Actions::PlayItem()); |
|
insert_action(new Actions::SeekForward()); |
|
insert_action(new Actions::SeekBackward()); |
|
insert_action(new Actions::ToggleDisplayMode()); |
|
insert_action(new Actions::ToggleSeparatorsBetweenAlbums()); |
|
insert_action(new Actions::ToggleLyricsUpdateOnSongChange()); |
|
insert_action(new Actions::ToggleLyricsFetcher()); |
|
insert_action(new Actions::ToggleFetchingLyricsInBackground()); |
|
insert_action(new Actions::TogglePlayingSongCentering()); |
|
insert_action(new Actions::UpdateDatabase()); |
|
insert_action(new Actions::JumpToPlayingSong()); |
|
insert_action(new Actions::ToggleRepeat()); |
|
insert_action(new Actions::Shuffle()); |
|
insert_action(new Actions::ToggleRandom()); |
|
insert_action(new Actions::StartSearching()); |
|
insert_action(new Actions::SaveTagChanges()); |
|
insert_action(new Actions::ToggleSingle()); |
|
insert_action(new Actions::ToggleConsume()); |
|
insert_action(new Actions::ToggleCrossfade()); |
|
insert_action(new Actions::SetCrossfade()); |
|
insert_action(new Actions::SetVolume()); |
|
insert_action(new Actions::EnterDirectory()); |
|
insert_action(new Actions::EditSong()); |
|
insert_action(new Actions::EditLibraryTag()); |
|
insert_action(new Actions::EditLibraryAlbum()); |
|
insert_action(new Actions::EditDirectoryName()); |
|
insert_action(new Actions::EditPlaylistName()); |
|
insert_action(new Actions::EditLyrics()); |
|
insert_action(new Actions::JumpToBrowser()); |
|
insert_action(new Actions::JumpToMediaLibrary()); |
|
insert_action(new Actions::JumpToPlaylistEditor()); |
|
insert_action(new Actions::ToggleScreenLock()); |
|
insert_action(new Actions::JumpToTagEditor()); |
|
insert_action(new Actions::JumpToPositionInSong()); |
|
insert_action(new Actions::ReverseSelection()); |
|
insert_action(new Actions::RemoveSelection()); |
|
insert_action(new Actions::SelectAlbum()); |
|
insert_action(new Actions::SelectFoundItems()); |
|
insert_action(new Actions::AddSelectedItems()); |
|
insert_action(new Actions::CropMainPlaylist()); |
|
insert_action(new Actions::CropPlaylist()); |
|
insert_action(new Actions::ClearMainPlaylist()); |
|
insert_action(new Actions::ClearPlaylist()); |
|
insert_action(new Actions::SortPlaylist()); |
|
insert_action(new Actions::ReversePlaylist()); |
|
insert_action(new Actions::ApplyFilter()); |
|
insert_action(new Actions::Find()); |
|
insert_action(new Actions::FindItemForward()); |
|
insert_action(new Actions::FindItemBackward()); |
|
insert_action(new Actions::NextFoundItem()); |
|
insert_action(new Actions::PreviousFoundItem()); |
|
insert_action(new Actions::ToggleFindMode()); |
|
insert_action(new Actions::ToggleReplayGainMode()); |
|
insert_action(new Actions::ToggleAddMode()); |
|
insert_action(new Actions::ToggleMouse()); |
|
insert_action(new Actions::ToggleBitrateVisibility()); |
|
insert_action(new Actions::AddRandomItems()); |
|
insert_action(new Actions::ToggleBrowserSortMode()); |
|
insert_action(new Actions::ToggleLibraryTagType()); |
|
insert_action(new Actions::ToggleMediaLibrarySortMode()); |
|
insert_action(new Actions::FetchLyricsInBackground()); |
|
insert_action(new Actions::RefetchLyrics()); |
|
insert_action(new Actions::SetSelectedItemsPriority()); |
|
insert_action(new Actions::ToggleOutput()); |
|
insert_action(new Actions::ToggleVisualizationType()); |
|
insert_action(new Actions::SetVisualizerSampleMultiplier()); |
|
insert_action(new Actions::ShowSongInfo()); |
|
insert_action(new Actions::ShowArtistInfo()); |
|
insert_action(new Actions::ShowLyrics()); |
|
insert_action(new Actions::Quit()); |
|
insert_action(new Actions::NextScreen()); |
|
insert_action(new Actions::PreviousScreen()); |
|
insert_action(new Actions::ShowHelp()); |
|
insert_action(new Actions::ShowPlaylist()); |
|
insert_action(new Actions::ShowBrowser()); |
|
insert_action(new Actions::ChangeBrowseMode()); |
|
insert_action(new Actions::ShowSearchEngine()); |
|
insert_action(new Actions::ResetSearchEngine()); |
|
insert_action(new Actions::ShowMediaLibrary()); |
|
insert_action(new Actions::ToggleMediaLibraryColumnsMode()); |
|
insert_action(new Actions::ShowPlaylistEditor()); |
|
insert_action(new Actions::ShowTagEditor()); |
|
insert_action(new Actions::ShowOutputs()); |
|
insert_action(new Actions::ShowVisualizer()); |
|
insert_action(new Actions::ShowClock()); |
|
insert_action(new Actions::ShowServerInfo()); |
|
for (size_t i = 0; i < AvailableActions.size(); ++i) |
|
{ |
|
if (AvailableActions[i] == nullptr) |
|
throw std::logic_error("undefined action at position " |
|
+ boost::lexical_cast<std::string>(i)); |
|
} |
|
} |
|
|
|
bool scrollTagCanBeRun(NC::List *&list, SongList *&songs) |
|
{ |
|
auto w = myScreen->activeWindow(); |
|
if (list != static_cast<void *>(w)) |
|
list = dynamic_cast<NC::List *>(w); |
|
if (songs != static_cast<void *>(w)) |
|
songs = dynamic_cast<SongList *>(w); |
|
return list != nullptr && !list->empty() |
|
&& songs != nullptr; |
|
} |
|
|
|
void scrollTagUpRun(NC::List *list, SongList *songs, MPD::Song::GetFunction get) |
|
{ |
|
const auto front = songs->beginS(); |
|
auto it = songs->currentS(); |
|
if (auto *s = it->get<Bit::Song>()) |
|
{ |
|
const std::string tag = s->getTags(get); |
|
while (it != front) |
|
{ |
|
--it; |
|
s = it->get<Bit::Song>(); |
|
if (s == nullptr || s->getTags(get) != tag) |
|
break; |
|
} |
|
list->highlight(it-front); |
|
} |
|
} |
|
|
|
void scrollTagDownRun(NC::List *list, SongList *songs, MPD::Song::GetFunction get) |
|
{ |
|
const auto front = songs->beginS(), back = --songs->endS(); |
|
auto it = songs->currentS(); |
|
if (auto *s = it->get<Bit::Song>()) |
|
{ |
|
const std::string tag = s->getTags(get); |
|
while (it != back) |
|
{ |
|
++it; |
|
s = it->get<Bit::Song>(); |
|
if (s == nullptr || s->getTags(get) != tag) |
|
break; |
|
} |
|
list->highlight(it-front); |
|
} |
|
} |
|
|
|
void seek() |
|
{ |
|
using Global::wHeader; |
|
using Global::wFooter; |
|
using Global::Timer; |
|
using Global::SeekingInProgress; |
|
|
|
if (!Status::State::totalTime()) |
|
{ |
|
Statusbar::print("Unknown item length"); |
|
return; |
|
} |
|
|
|
Progressbar::ScopedLock progressbar_lock; |
|
Statusbar::ScopedLock statusbar_lock; |
|
|
|
unsigned songpos = Status::State::elapsedTime(); |
|
auto t = Timer; |
|
|
|
int old_timeout = wFooter->getTimeout(); |
|
wFooter->setTimeout(BaseScreen::defaultWindowTimeout); |
|
|
|
// Accept single action of a given type or action chain for which all actions |
|
// can be run and one of them is of the given type. This will still not work |
|
// in some contrived cases, but allows for more flexibility than accepting |
|
// single actions only. |
|
auto hasRunnableAction = [](BindingsConfiguration::BindingIteratorPair &bindings, Actions::Type type) { |
|
bool success = false; |
|
for (auto binding = bindings.first; binding != bindings.second; ++binding) |
|
{ |
|
auto &actions = binding->actions(); |
|
for (const auto &action : actions) |
|
{ |
|
if (action->canBeRun()) |
|
{ |
|
if (action->type() == type) |
|
success = true; |
|
} |
|
else |
|
{ |
|
success = false; |
|
break; |
|
} |
|
} |
|
if (success) |
|
break; |
|
} |
|
return success; |
|
}; |
|
|
|
SeekingInProgress = true; |
|
while (true) |
|
{ |
|
Status::trace(); |
|
|
|
unsigned howmuch = Config.incremental_seeking |
|
? (Timer-t).total_seconds()/2+Config.seek_time |
|
: Config.seek_time; |
|
|
|
NC::Key::Type input = readKey(*wFooter); |
|
|
|
auto k = Bindings.get(input); |
|
if (hasRunnableAction(k, Actions::Type::SeekForward)) |
|
{ |
|
if (songpos < Status::State::totalTime()) |
|
songpos = std::min(songpos + howmuch, Status::State::totalTime()); |
|
} |
|
else if (hasRunnableAction(k, Actions::Type::SeekBackward)) |
|
{ |
|
if (songpos > 0) |
|
{ |
|
if (songpos < howmuch) |
|
songpos = 0; |
|
else |
|
songpos -= howmuch; |
|
} |
|
} |
|
else |
|
break; |
|
|
|
*wFooter << NC::Format::Bold; |
|
std::string tracklength; |
|
// FIXME: merge this with the code in status.cpp |
|
switch (Config.design) |
|
{ |
|
case Design::Classic: |
|
tracklength = " ["; |
|
if (Config.display_remaining_time) |
|
{ |
|
tracklength += "-"; |
|
tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos); |
|
} |
|
else |
|
tracklength += MPD::Song::ShowTime(songpos); |
|
tracklength += "/"; |
|
tracklength += MPD::Song::ShowTime(Status::State::totalTime()); |
|
tracklength += "]"; |
|
*wFooter << NC::XY(wFooter->getWidth()-tracklength.length(), 1) << tracklength; |
|
break; |
|
case Design::Alternative: |
|
if (Config.display_remaining_time) |
|
{ |
|
tracklength = "-"; |
|
tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos); |
|
} |
|
else |
|
tracklength = MPD::Song::ShowTime(songpos); |
|
tracklength += "/"; |
|
tracklength += MPD::Song::ShowTime(Status::State::totalTime()); |
|
*wHeader << NC::XY(0, 0) << tracklength << " "; |
|
wHeader->refresh(); |
|
break; |
|
} |
|
*wFooter << NC::Format::NoBold; |
|
Progressbar::draw(songpos, Status::State::totalTime()); |
|
wFooter->refresh(); |
|
} |
|
SeekingInProgress = false; |
|
Mpd.Seek(Status::State::currentSongPosition(), songpos); |
|
|
|
wFooter->setTimeout(old_timeout); |
|
} |
|
|
|
void findItem(const SearchDirection direction) |
|
{ |
|
using Global::wFooter; |
|
|
|
Searchable *w = dynamic_cast<Searchable *>(myScreen); |
|
assert(w != nullptr); |
|
assert(w->allowsSearching()); |
|
|
|
std::string constraint = w->searchConstraint(); |
|
try |
|
{ |
|
Statusbar::ScopedLock slock; |
|
NC::Window::ScopedPromptHook prompt_hook( |
|
*wFooter, |
|
Statusbar::Helpers::FindImmediately(w, direction)); |
|
Statusbar::put() << (boost::format("Find %1%: ") % direction).str(); |
|
constraint = wFooter->prompt(constraint); |
|
} |
|
catch (NC::PromptAborted &) |
|
{ |
|
w->setSearchConstraint(constraint); |
|
w->search(direction, Config.wrapped_search, false); |
|
throw; |
|
} |
|
|
|
if (constraint.empty()) |
|
{ |
|
Statusbar::printf("Constraint unset"); |
|
w->clearSearchConstraint(); |
|
} |
|
else |
|
Statusbar::printf("Using constraint \"%1%\"", constraint); |
|
} |
|
|
|
void listsChangeFinisher() |
|
{ |
|
if (myScreen == myLibrary |
|
|| myScreen == myPlaylistEditor |
|
# ifdef HAVE_TAGLIB_H |
|
|| myScreen == myTagEditor |
|
# endif // HAVE_TAGLIB_H |
|
) |
|
{ |
|
if (myScreen->activeWindow() == &myLibrary->Tags) |
|
{ |
|
myLibrary->Albums.clear(); |
|
myLibrary->Albums.refresh(); |
|
myLibrary->Songs.clear(); |
|
myLibrary->Songs.refresh(); |
|
myLibrary->updateTimer(); |
|
} |
|
else if (myScreen->activeWindow() == &myLibrary->Albums) |
|
{ |
|
myLibrary->Songs.clear(); |
|
myLibrary->Songs.refresh(); |
|
myLibrary->updateTimer(); |
|
} |
|
else if (myScreen->isActiveWindow(myPlaylistEditor->Playlists)) |
|
{ |
|
myPlaylistEditor->Content.clear(); |
|
myPlaylistEditor->Content.refresh(); |
|
myPlaylistEditor->updateTimer(); |
|
} |
|
# ifdef HAVE_TAGLIB_H |
|
else if (myScreen->activeWindow() == myTagEditor->Dirs) |
|
{ |
|
myTagEditor->Tags->clear(); |
|
} |
|
# endif // HAVE_TAGLIB_H |
|
} |
|
} |
|
|
|
}
|
|
|