/*************************************************************************** * Copyright (C) 2008-2009 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 #include #include #include #include #include #include #include #include "mpdpp.h" #include "ncmpcpp.h" #include "browser.h" #include "charset.h" #include "clock.h" #include "display.h" #include "global.h" #include "help.h" #include "helpers.h" #include "media_library.h" #include "misc.h" #include "server_info.h" #include "lyrics.h" #include "playlist.h" #include "playlist_editor.h" #include "search_engine.h" #include "settings.h" #include "song.h" #include "info.h" #include "outputs.h" #include "status.h" #include "tag_editor.h" #include "tiny_tag_editor.h" #include "visualizer.h" #define CHECK_PLAYLIST_FOR_FILTERING \ if (myPlaylist->Items->isFiltered()) \ { \ ShowMessage("%s", MPD::Message::FunctionDisabledFilteringEnabled); \ continue; \ } #define CHECK_MPD_MUSIC_DIR \ if (Config.mpd_music_dir.empty()) \ { \ ShowMessage("configuration variable mpd_music_dir is not set!"); \ continue; \ } using namespace Global; using namespace MPD; BasicScreen *Global::myScreen; BasicScreen *Global::myOldScreen; Window *Global::wHeader; Window *Global::wFooter; size_t Global::MainStartY; size_t Global::MainHeight; bool Global::BlockItemListUpdate = 0; bool Global::MessagesAllowed = 0; bool Global::SeekingInProgress = 0; bool Global::RedrawHeader = 1; namespace { std::ofstream errorlog; std::streambuf *cerr_buffer; bool design_changed = 0; size_t header_height, footer_start_y, footer_height; void sighandler(GNUC_UNUSED int signal) { # if !defined(WIN32) if (signal == SIGPIPE) { ShowMessage("Broken pipe signal caught!"); return; } # endif // !WIN32 # if defined(USE_PDCURSES) resize_term(0, 0); # else // update internal screen dimensions if (!design_changed) { endwin(); refresh(); // get rid of KEY_RESIZE as it sometimes // corrupts our new cool ReadKey() function getch(); } # endif RedrawHeader = 1; MainHeight = LINES-(Config.new_design ? 7 : 4); if (COLS < 20 || MainHeight < 3) { DestroyScreen(); std::cout << "Screen is too small!\n"; exit(1); } if (!Config.header_visibility) MainHeight += 2; if (!Config.statusbar_visibility) MainHeight++; myHelp->hasToBeResized = 1; myPlaylist->hasToBeResized = 1; myBrowser->hasToBeResized = 1; mySearcher->hasToBeResized = 1; myLibrary->hasToBeResized = 1; myPlaylistEditor->hasToBeResized = 1; myInfo->hasToBeResized = 1; myLyrics->hasToBeResized = 1; mySelectedItemsAdder->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 myScreen->Resize(); if (Config.header_visibility || Config.new_design) wHeader->Resize(COLS, header_height); footer_start_y = LINES-(Config.statusbar_visibility ? 2 : 1); wFooter->MoveTo(0, footer_start_y); wFooter->Resize(COLS, Config.statusbar_visibility ? 2 : 1); myScreen->Refresh(); RedrawStatusbar = 1; StatusChanges changes; if (!Mpd.isPlaying() || design_changed) { changes.PlayerState = 1; if (design_changed) changes.Volume = 1; } // Note: routines for drawing separator if alternative user // interface is active and header is hidden are placed in // NcmpcppStatusChanges.StatusFlags changes.StatusFlags = 1; // force status update NcmpcppStatusChanged(&Mpd, changes, 0); if (design_changed) { RedrawStatusbar = 1; NcmpcppStatusChanged(&Mpd, StatusChanges(), 0); design_changed = 0; ShowMessage("User interface: %s", Config.new_design ? "Alternative" : "Classic"); } wFooter->Refresh(); } void do_at_exit() { // restore old cerr buffer std::cerr.rdbuf(cerr_buffer); errorlog.close(); Mpd.Disconnect(); # ifndef USE_PDCURSES // destroying screen somehow crashes pdcurses DestroyScreen(); # endif // USE_PDCURSES WindowTitle(""); } } int main(int argc, char *argv[]) { setlocale(LC_ALL, ""); CreateConfigDir(); DefaultConfiguration(Config); DefaultKeys(Key); ReadConfiguration(Config); ReadKeys(Key); if (getenv("MPD_HOST")) Mpd.SetHostname(getenv("MPD_HOST")); if (getenv("MPD_PORT")) Mpd.SetPort(atoi(getenv("MPD_PORT"))); if (Config.mpd_host != "localhost") Mpd.SetHostname(Config.mpd_host); if (Config.mpd_port != 6600) Mpd.SetPort(Config.mpd_port); Mpd.SetTimeout(Config.mpd_connection_timeout); if (argc > 1) ParseArgv(argc, argv); if (!ConnectToMPD()) return -1; // always execute these commands, even if ncmpcpp use exit function atexit(do_at_exit); // redirect std::cerr output to ~/.ncmpcpp/error.log file errorlog.open((config_dir + "error.log").c_str(), std::ios::app); cerr_buffer = std::cerr.rdbuf(); std::cerr.rdbuf(errorlog.rdbuf()); InitScreen("ncmpc++ ver. "VERSION, Config.colors_enabled); bool real_statusbar_visibility = Config.statusbar_visibility; if (Config.new_design) Config.statusbar_visibility = 0; SetWindowsDimensions(header_height, footer_start_y, footer_height); if (Config.header_visibility || Config.new_design) { wHeader = new Window(0, 0, COLS, header_height, "", Config.header_color, brNone); wHeader->Display(); } wFooter = new Window(0, footer_start_y, COLS, footer_height, "", Config.statusbar_color, brNone); wFooter->SetTimeout(ncmpcpp_window_timeout); wFooter->SetGetStringHelper(StatusbarGetStringHelper); wFooter->AddFDCallback(Mpd.GetFD(), StatusbarMPDCallback); wFooter->CreateHistory(); myPlaylist->SwitchTo(); myPlaylist->UpdateTimer(); Mpd.SetStatusUpdater(NcmpcppStatusChanged, 0); Mpd.SetErrorHandler(NcmpcppErrorCallback, 0); // local variables int input = 0; bool main_exit = 0; bool title_allowed = !Config.display_screens_numbers_on_start; std::string screen_title; timeval past = { 0, 0 }; // local variables end # ifndef WIN32 signal(SIGPIPE, sighandler); signal(SIGWINCH, sighandler); # endif // !WIN32 MEVENT mouse_event; mouseinterval(0); if (Config.mouse_support) mousemask(ALL_MOUSE_EVENTS, 0); if (Config.jump_to_now_playing_song_at_start) { TraceMpdStatus(); if (myPlaylist->isPlaying()) myPlaylist->Items->Highlight(myPlaylist->NowPlaying); } while (!main_exit) { if (!Mpd.Connected()) { if (!wFooter->FDCallbacksListEmpty()) wFooter->ClearFDCallbacksList(); ShowMessage("Attempting to reconnect..."); if (Mpd.Connect()) { ShowMessage("Connected to %s!", Mpd.GetHostname().c_str()); wFooter->AddFDCallback(Mpd.GetFD(), StatusbarMPDCallback); MessagesAllowed = 0; UpdateStatusImmediately = 1; # ifdef ENABLE_VISUALIZER myVisualizer->ResetFD(); if (myScreen == myVisualizer) myVisualizer->SetFD(); myVisualizer->FindOutputID(); # endif // ENABLE_VISUALIZER } } TraceMpdStatus(); MessagesAllowed = 1; // header stuff if (((Timer.tv_sec == past.tv_sec && Timer.tv_usec >= past.tv_usec+500000) || Timer.tv_sec > past.tv_sec) && (myScreen == myPlaylist || myScreen == myBrowser || myScreen == myLyrics) ) { RedrawHeader = 1; gettimeofday(&past, 0); } if (Config.header_visibility && RedrawHeader) { if (title_allowed) { if (Config.new_design) { std::basic_string title = myScreen->Title(); *wHeader << XY(0, 3) << wclrtoeol; *wHeader << fmtBold << Config.alternative_ui_separator_color; mvwhline(wHeader->Raw(), 2, 0, 0, COLS); mvwhline(wHeader->Raw(), 4, 0, 0, COLS); *wHeader << XY((COLS-Window::Length(title))/2, 3); *wHeader << Config.header_color << title << clEnd; *wHeader << clEnd << fmtBoldEnd; } else *wHeader << XY(0, 0) << wclrtoeol << fmtBold << myScreen->Title() << fmtBoldEnd; } else { *wHeader << XY(0, Config.new_design ? 3 : 0) << fmtBold << char(Key.Help[0]) << fmtBoldEnd << ":Help " << fmtBold << char(Key.Playlist[0]) << fmtBoldEnd << ":Playlist " << fmtBold << char(Key.Browser[0]) << fmtBoldEnd << ":Browse " << fmtBold << char(Key.SearchEngine[0]) << fmtBoldEnd << ":Search " << fmtBold << char(Key.MediaLibrary[0]) << fmtBoldEnd << ":Library " << fmtBold << char(Key.PlaylistEditor[0]) << fmtBoldEnd << ":Playlist editor"; # ifdef HAVE_TAGLIB_H *wHeader << " " << fmtBold << char(Key.TagEditor[0]) << fmtBoldEnd << ":Tag editor"; # endif // HAVE_TAGLIB_H # ifdef ENABLE_VISUALIZER *wHeader << " " << fmtBold << char(Key.Visualizer[0]) << fmtBoldEnd << ":Music visualizer"; # endif // ENABLE_VISUALIZER # ifdef ENABLE_CLOCK *wHeader << " " << fmtBold << char(Key.Clock[0]) << fmtBoldEnd << ":Clock"; # endif // ENABLE_CLOCK if (Config.new_design) { *wHeader << fmtBold << Config.alternative_ui_separator_color; mvwhline(wHeader->Raw(), 2, 0, 0, COLS); mvwhline(wHeader->Raw(), 4, 0, 0, COLS); *wHeader << clEnd << fmtBoldEnd; } } if (!Config.new_design) { *wHeader << Config.volume_color; *wHeader << XY(wHeader->GetWidth()-VolumeState.length(), 0) << VolumeState; *wHeader << clEnd; } wHeader->Refresh(); RedrawHeader = 0; } // header stuff end myScreen->Update(); if (input != ERR) myScreen->RefreshWindow(); wFooter->ReadKey(input); if (input == ERR) continue; if (!title_allowed) RedrawHeader = 1; title_allowed = 1; if (myScreen == myPlaylist) myPlaylist->EnableHighlighting(); else if ( myScreen == myLibrary || myScreen == myPlaylistEditor # ifdef HAVE_TAGLIB_H || myScreen == myTagEditor # endif // HAVE_TAGLIB_H ) { if (Keypressed(input, Key.Up) || Keypressed(input, Key.Down) || Keypressed(input, Key.PageUp) || Keypressed(input, Key.PageDown) || Keypressed(input, Key.Home) || Keypressed(input, Key.End) || Keypressed(input, Key.ApplyFilter) || Keypressed(input, Key.FindForward) || Keypressed(input, Key.FindBackward) || Keypressed(input, Key.NextFoundPosition) || Keypressed(input, Key.PrevFoundPosition)) { if (myScreen->ActiveWindow() == myLibrary->Artists) { myLibrary->Albums->Clear(0); myLibrary->Songs->Clear(0); } else if (myScreen->ActiveWindow() == myLibrary->Albums) { myLibrary->Songs->Clear(0); } else if (myScreen->ActiveWindow() == myPlaylistEditor->Playlists) { myPlaylistEditor->Content->Clear(0); } # ifdef HAVE_TAGLIB_H else if (myScreen->ActiveWindow() == myTagEditor->LeftColumn) { myTagEditor->Tags->Clear(0); myTagEditor->TagTypes->Refresh(); } # endif // HAVE_TAGLIB_H } } // key mapping beginning if (Keypressed(input, Key.Up)) { myScreen->Scroll(wUp, Key.Up); } else if (Keypressed(input, Key.Down)) { myScreen->Scroll(wDown, Key.Down); } else if (Keypressed(input, Key.PageUp)) { myScreen->Scroll(wPageUp, Key.PageUp); } else if (Keypressed(input, Key.PageDown)) { myScreen->Scroll(wPageDown, Key.PageDown); } else if (Keypressed(input, Key.Home)) { myScreen->Scroll(wHome); } else if (Keypressed(input, Key.End)) { myScreen->Scroll(wEnd); } else if (Config.mouse_support && input == KEY_MOUSE) { getmouse(&mouse_event); if (mouse_event.bstate & BUTTON1_PRESSED && mouse_event.y == LINES-(Config.statusbar_visibility ? 2 : 1) ) // progressbar { if (!myPlaylist->isPlaying()) continue; Mpd.Seek(Mpd.GetTotalTime()*mouse_event.x/double(COLS)); UpdateStatusImmediately = 1; } else if (mouse_event.bstate & BUTTON1_PRESSED && (Config.statusbar_visibility || Config.new_design) && Mpd.isPlaying() && mouse_event.y == (Config.new_design ? 1 : LINES-1) && mouse_event.x < 9 ) // playing/paused { Mpd.Toggle(); UpdateStatusImmediately = 1; } else if ((mouse_event.bstate & BUTTON2_PRESSED || mouse_event.bstate & BUTTON4_PRESSED) && Config.header_visibility && mouse_event.y == 0 && size_t(mouse_event.x) > COLS-VolumeState.length() ) // volume { if (mouse_event.bstate & BUTTON2_PRESSED) Mpd.SetVolume(Mpd.GetVolume()-2); else Mpd.SetVolume(Mpd.GetVolume()+2); } else if (mouse_event.bstate & (BUTTON1_PRESSED | BUTTON2_PRESSED | BUTTON3_PRESSED | BUTTON4_PRESSED)) myScreen->MouseButtonPressed(mouse_event); } if (Keypressed(input, Key.ToggleInterface)) { Config.new_design = !Config.new_design; Config.statusbar_visibility = Config.new_design ? 0 : real_statusbar_visibility; SetWindowsDimensions(header_height, footer_start_y, footer_height); UnlockProgressbar(); UnlockStatusbar(); design_changed = 1; # if !defined(WIN32) sighandler(SIGWINCH); # else sighandler(0); # endif // !WIN23 } else if (Keypressed(input, Key.GoToParentDir)) { if (myScreen == myBrowser && myBrowser->CurrentDir() != "/") { myBrowser->Main()->Reset(); myBrowser->EnterPressed(); } } else if (Keypressed(input, Key.Enter)) { myScreen->EnterPressed(); } else if (Keypressed(input, Key.Space)) { myScreen->SpacePressed(); } else if (Keypressed(input, Key.VolumeUp)) { if (myScreen == myLibrary && input == Key.VolumeUp[0]) { myLibrary->NextColumn(); } else if (myScreen == myPlaylistEditor && input == Key.VolumeUp[0]) { myPlaylistEditor->NextColumn(); } # ifdef HAVE_TAGLIB_H else if (myScreen == myTagEditor && input == Key.VolumeUp[0]) { myTagEditor->NextColumn(); } # endif // HAVE_TAGLIB_H else Mpd.SetVolume(Mpd.GetVolume()+1); } else if (Keypressed(input, Key.VolumeDown)) { if (myScreen == myLibrary && input == Key.VolumeDown[0]) { myLibrary->PrevColumn(); } else if (myScreen == myPlaylistEditor && input == Key.VolumeDown[0]) { myPlaylistEditor->PrevColumn(); } # ifdef HAVE_TAGLIB_H else if (myScreen == myTagEditor && input == Key.VolumeDown[0]) { myTagEditor->PrevColumn(); } # endif // HAVE_TAGLIB_H else Mpd.SetVolume(Mpd.GetVolume()-1); } else if (Keypressed(input, Key.Delete)) { if (!myPlaylist->Items->Empty() && myScreen == myPlaylist) { Playlist::BlockUpdate = 1; if (myPlaylist->Items->hasSelected()) { std::vector list; myPlaylist->Items->GetSelected(list); Mpd.StartCommandsList(); for (std::vector::reverse_iterator it = list.rbegin(); it != list.rend(); ++it) { Mpd.DeleteID((*myPlaylist->Items)[*it].GetID()); myPlaylist->Items->DeleteOption(*it); } Mpd.CommitCommandsList(); myPlaylist->FixPositions(list.front()); ShowMessage("Selected items deleted!"); } else { Playlist::BlockNowPlayingUpdate = 1; wFooter->SetTimeout(50); int del_counter = 0; while (!myPlaylist->Items->Empty() && Keypressed(input, Key.Delete)) { size_t id = myPlaylist->Items->Choice(); TraceMpdStatus(); Playlist::BlockUpdate = 1; myPlaylist->UpdateTimer(); // needed for keeping proper position of now playing song. if (myPlaylist->NowPlaying > int(myPlaylist->CurrentSong()->GetPosition())-del_counter) --myPlaylist->NowPlaying; Mpd.DeleteID(myPlaylist->CurrentSong()->GetID()); myPlaylist->Items->DeleteOption(id); myPlaylist->Items->Refresh(); wFooter->ReadKey(input); ++del_counter; } myPlaylist->FixPositions(myPlaylist->Items->Choice()); wFooter->SetTimeout(ncmpcpp_window_timeout); Playlist::BlockNowPlayingUpdate = 0; } } else if ( (myScreen == myBrowser && !myBrowser->Main()->Empty() && myBrowser->Main()->Current().type == itPlaylist) || (myScreen->ActiveWindow() == myPlaylistEditor->Playlists) ) { std::string name = myScreen == myBrowser ? myBrowser->Main()->Current().name : myPlaylistEditor->Playlists->Current(); LockStatusbar(); Statusbar() << "Delete playlist \"" << Shorten(TO_WSTRING(name), COLS-28) << "\" ? [" << fmtBold << 'y' << fmtBoldEnd << '/' << fmtBold << 'n' << fmtBoldEnd << "]"; wFooter->Refresh(); input = 0; do { TraceMpdStatus(); wFooter->ReadKey(input); } while (input != 'y' && input != 'n'); UnlockStatusbar(); if (input == 'y') { if (Mpd.DeletePlaylist(locale_to_utf_cpy(name))) { static const char msg[] = "Playlist \"%s\" deleted!"; ShowMessage(msg, Shorten(TO_WSTRING(name), COLS-static_strlen(msg)).c_str()); if (myBrowser->Main() && !myBrowser->isLocal() && myBrowser->CurrentDir() == "/") myBrowser->GetDirectory("/"); } } else ShowMessage("Aborted!"); if (myPlaylistEditor->Main()) // check if initialized myPlaylistEditor->Playlists->Clear(0); // make playlists list update itself } # ifndef WIN32 else if (myScreen == myBrowser && !myBrowser->Main()->Empty() && myBrowser->Main()->Current().type != itPlaylist) { if (!myBrowser->isLocal()) CHECK_MPD_MUSIC_DIR; MPD::Item &item = myBrowser->Main()->Current(); if (item.type == itSong && !Config.allow_physical_files_deletion) { ShowMessage("Deleting files is disabled by default, see man page for more details"); continue; } if (item.type == itDirectory && !Config.allow_physical_directories_deletion) { ShowMessage("Deleting directories is disabled by default, see man page for more details"); continue; } if (item.type == itDirectory && item.song) // parent dir continue; std::string name = item.type == itSong ? item.song->GetName() : item.name; LockStatusbar(); Statusbar() << "Delete " << (item.type == itSong ? "file" : "directory") << " \"" << Shorten(TO_WSTRING(name), COLS-30) << "\" ? [" << fmtBold << 'y' << fmtBoldEnd << '/' << fmtBold << 'n' << fmtBoldEnd << "] "; wFooter->Refresh(); input = 0; do { TraceMpdStatus(); wFooter->ReadKey(input); } while (input != 'y' && input != 'n'); UnlockStatusbar(); if (input == 'y') { std::string path; if (!myBrowser->isLocal()) path = Config.mpd_music_dir; path += item.type == itSong ? item.song->GetFile() : item.name; if (item.type == itDirectory) myBrowser->ClearDirectory(path); if (remove(path.c_str()) == 0) { static const char msg[] = "\"%s\" deleted!"; ShowMessage(msg, Shorten(TO_WSTRING(name), COLS-static_strlen(msg)).c_str()); if (!myBrowser->isLocal()) Mpd.UpdateDirectory(myBrowser->CurrentDir()); else myBrowser->GetDirectory(myBrowser->CurrentDir()); } else { static const char msg[] = "Couldn't remove \"%s\": %s"; ShowMessage(msg, Shorten(TO_WSTRING(name), COLS-static_strlen(msg)-25).c_str(), strerror(errno)); } } else ShowMessage("Aborted!"); } # endif // !WIN32 else if (myScreen->ActiveWindow() == myPlaylistEditor->Content && !myPlaylistEditor->Content->Empty()) { if (myPlaylistEditor->Content->hasSelected()) { std::vector list; myPlaylistEditor->Content->GetSelected(list); std::string playlist = locale_to_utf_cpy(myPlaylistEditor->Playlists->Current()); ShowMessage("Deleting selected items..."); Mpd.StartCommandsList(); for (std::vector::reverse_iterator it = list.rbegin(); it != list.rend(); ++it) { Mpd.Delete(playlist, *it); myPlaylistEditor->Content->DeleteOption(*it); } Mpd.CommitCommandsList(); ShowMessage("Selected items deleted from playlist \"%s\"!", myPlaylistEditor->Playlists->Current().c_str()); } else { wFooter->SetTimeout(50); locale_to_utf(myPlaylistEditor->Playlists->Current()); while (!myPlaylistEditor->Content->Empty() && Keypressed(input, Key.Delete)) { TraceMpdStatus(); myPlaylist->UpdateTimer(); Mpd.Delete(myPlaylistEditor->Playlists->Current(), myPlaylistEditor->Content->Choice()); myPlaylistEditor->Content->DeleteOption(myPlaylistEditor->Content->Choice()); myPlaylistEditor->Content->Refresh(); wFooter->ReadKey(input); } utf_to_locale(myPlaylistEditor->Playlists->Current()); wFooter->SetTimeout(ncmpcpp_window_timeout); } } } else if (Keypressed(input, Key.Prev)) { Mpd.Prev(); UpdateStatusImmediately = 1; } else if (Keypressed(input, Key.Next)) { Mpd.Next(); UpdateStatusImmediately = 1; } else if (Keypressed(input, Key.Pause)) { Mpd.Toggle(); UpdateStatusImmediately = 1; } else if (Keypressed(input, Key.SavePlaylist)) { LockStatusbar(); Statusbar() << "Save playlist as: "; std::string playlist_name = wFooter->GetString(); std::string real_playlist_name = locale_to_utf_cpy(playlist_name); UnlockStatusbar(); if (playlist_name.find("/") != std::string::npos) { ShowMessage("Playlist name cannot contain slashes!"); continue; } if (!playlist_name.empty()) { if (myPlaylist->Items->isFiltered()) { Mpd.StartCommandsList(); for (size_t i = 0; i < myPlaylist->Items->Size(); ++i) Mpd.AddToPlaylist(real_playlist_name, (*myPlaylist->Items)[i]); Mpd.CommitCommandsList(); if (Mpd.GetErrorMessage().empty()) ShowMessage("Filtered items added to playlist \"%s\"", playlist_name.c_str()); } else if (Mpd.SavePlaylist(real_playlist_name)) { ShowMessage("Playlist saved as: %s", playlist_name.c_str()); if (myPlaylistEditor->Main()) // check if initialized myPlaylistEditor->Playlists->Clear(0); // make playlist's list update itself } else { LockStatusbar(); Statusbar() << "Playlist already exists, overwrite: " << playlist_name << " ? [" << fmtBold << 'y' << fmtBoldEnd << '/' << fmtBold << 'n' << fmtBoldEnd << "] "; wFooter->Refresh(); input = 0; while (input != 'y' && input != 'n') { TraceMpdStatus(); wFooter->ReadKey(input); } UnlockStatusbar(); if (input == 'y') { Mpd.DeletePlaylist(real_playlist_name); if (Mpd.SavePlaylist(real_playlist_name)) ShowMessage("Playlist overwritten!"); } else ShowMessage("Aborted!"); if (myPlaylistEditor->Main()) // check if initialized myPlaylistEditor->Playlists->Clear(0); // make playlist's list update itself if (myScreen == myPlaylist) myPlaylist->EnableHighlighting(); } } if (myBrowser->Main() && !myBrowser->isLocal() && myBrowser->CurrentDir() == "/" && !myBrowser->Main()->Empty()) myBrowser->GetDirectory(myBrowser->CurrentDir()); } else if (Keypressed(input, Key.Stop)) { Mpd.Stop(); UpdateStatusImmediately = 1; } else if (Keypressed(input, Key.MvSongUp)) { if (myScreen == myPlaylist && myPlaylist->SortingInProgress()) myPlaylist->AdjustSortOrder(input); else if (myScreen == myPlaylist && !myPlaylist->Items->Empty()) { CHECK_PLAYLIST_FOR_FILTERING; wFooter->SetTimeout(50); if (myPlaylist->Items->hasSelected()) { std::vector list; myPlaylist->Items->GetSelected(list); std::vector origs(list); // NOTICE: since ncmpcpp only pretends to move the songs until the key is // released, mpd doesn't know about the change while the songs are moved // so wee need to block playlist update for this time and also if one of // the songs being moved is currently playing, now playing update to prevent // mpd from 'updating' and thus showing wrong position bool modify_now_playing = 0; for (std::vector::iterator it = list.begin(); it != list.end(); ++it) { if (*it == size_t(myPlaylist->NowPlaying) && list.front() > 0) { modify_now_playing = 1; Playlist::BlockNowPlayingUpdate = 1; break; } } while (Keypressed(input, Key.MvSongUp) && list.front() > 0) { TraceMpdStatus(); Playlist::BlockUpdate = 1; myPlaylist->UpdateTimer(); if (modify_now_playing) --myPlaylist->NowPlaying; for (std::vector::iterator it = list.begin(); it != list.end(); ++it) { --*it; myPlaylist->Items->at((*it)+1).SetPosition(*it); myPlaylist->Items->at(*it).SetPosition((*it)+1); myPlaylist->Items->Swap(*it, (*it)+1); } myPlaylist->Items->Highlight(list[(list.size()-1)/2]); myPlaylist->Items->Refresh(); wFooter->ReadKey(input); } Playlist::BlockNowPlayingUpdate = 0; Mpd.StartCommandsList(); for (size_t i = 0; i < list.size(); ++i) Mpd.Move(origs[i], list[i]); Mpd.CommitCommandsList(); } else { size_t from, to; from = to = myPlaylist->Items->Choice(); bool modify_now_playing = from == size_t(myPlaylist->NowPlaying); if (modify_now_playing) Playlist::BlockNowPlayingUpdate = 1; while (Keypressed(input, Key.MvSongUp) && to > 0) { TraceMpdStatus(); Playlist::BlockUpdate = 1; myPlaylist->UpdateTimer(); if (modify_now_playing) --myPlaylist->NowPlaying; --to; myPlaylist->Items->at(from).SetPosition(to); myPlaylist->Items->at(to).SetPosition(from); myPlaylist->Items->Swap(to, to+1); myPlaylist->Items->Scroll(wUp); myPlaylist->Items->Refresh(); wFooter->ReadKey(input); } Mpd.Move(from, to); Playlist::BlockNowPlayingUpdate = 0; UpdateStatusImmediately = 1; } wFooter->SetTimeout(ncmpcpp_window_timeout); } else if (myScreen->ActiveWindow() == myPlaylistEditor->Content && !myPlaylistEditor->Content->Empty()) { wFooter->SetTimeout(50); if (myPlaylistEditor->Content->hasSelected()) { std::vector list; myPlaylistEditor->Content->GetSelected(list); std::vector origs(list); while (Keypressed(input, Key.MvSongUp) && list.front() > 0) { TraceMpdStatus(); myPlaylist->UpdateTimer(); for (std::vector::iterator it = list.begin(); it != list.end(); ++it) { --*it; myPlaylistEditor->Content->Swap(*it, (*it)+1); } myPlaylistEditor->Content->Highlight(list[(list.size()-1)/2]); myPlaylistEditor->Content->Refresh(); wFooter->ReadKey(input); } Mpd.StartCommandsList(); for (size_t i = 0; i < list.size(); ++i) if (origs[i] != list[i]) Mpd.Move(myPlaylistEditor->Playlists->Current(), origs[i], list[i]); Mpd.CommitCommandsList(); } else { size_t from, to; from = to = myPlaylistEditor->Content->Choice(); while (Keypressed(input, Key.MvSongUp) && to > 0) { TraceMpdStatus(); myPlaylist->UpdateTimer(); --to; myPlaylistEditor->Content->Swap(to, to+1); myPlaylistEditor->Content->Scroll(wUp); myPlaylistEditor->Content->Refresh(); wFooter->ReadKey(input); } if (from != to) Mpd.Move(myPlaylistEditor->Playlists->Current(), from, to); } wFooter->SetTimeout(ncmpcpp_window_timeout); } } else if (Keypressed(input, Key.MvSongDown)) { if (myScreen == myPlaylist && myPlaylist->SortingInProgress()) myPlaylist->AdjustSortOrder(input); else if (myScreen == myPlaylist && !myPlaylist->Items->Empty()) { CHECK_PLAYLIST_FOR_FILTERING; wFooter->SetTimeout(50); if (myPlaylist->Items->hasSelected()) { std::vector list; myPlaylist->Items->GetSelected(list); std::vector origs(list); bool modify_now_playing = 0; for (std::vector::iterator it = list.begin(); it != list.end(); ++it) { if (*it == size_t(myPlaylist->NowPlaying) && list.back() < myPlaylist->Items->Size()-1) { modify_now_playing = 1; Playlist::BlockNowPlayingUpdate = 1; break; } } while (Keypressed(input, Key.MvSongDown) && list.back() < myPlaylist->Items->Size()-1) { TraceMpdStatus(); Playlist::BlockUpdate = 1; myPlaylist->UpdateTimer(); if (modify_now_playing) ++myPlaylist->NowPlaying; for (std::vector::reverse_iterator it = list.rbegin(); it != list.rend(); ++it) { ++*it; myPlaylist->Items->at((*it)-1).SetPosition(*it); myPlaylist->Items->at(*it).SetPosition((*it)-1); myPlaylist->Items->Swap(*it, (*it)-1); } myPlaylist->Items->Highlight(list[(list.size()-1)/2]); myPlaylist->Items->Refresh(); wFooter->ReadKey(input); } Playlist::BlockNowPlayingUpdate = 0; Mpd.StartCommandsList(); for (int i = list.size()-1; i >= 0; --i) Mpd.Move(origs[i], list[i]); Mpd.CommitCommandsList(); } else { size_t from, to; from = to = myPlaylist->Items->Choice(); bool modify_now_playing = from == size_t(myPlaylist->NowPlaying); if (modify_now_playing) Playlist::BlockNowPlayingUpdate = 1; while (Keypressed(input, Key.MvSongDown) && to < myPlaylist->Items->Size()-1) { TraceMpdStatus(); Playlist::BlockUpdate = 1; myPlaylist->UpdateTimer(); if (modify_now_playing) ++myPlaylist->NowPlaying; ++to; myPlaylist->Items->at(from).SetPosition(to); myPlaylist->Items->at(to).SetPosition(from); myPlaylist->Items->Swap(to, to-1); myPlaylist->Items->Scroll(wDown); myPlaylist->Items->Refresh(); wFooter->ReadKey(input); } Mpd.Move(from, to); Playlist::BlockNowPlayingUpdate = 0; UpdateStatusImmediately = 1; } wFooter->SetTimeout(ncmpcpp_window_timeout); } else if (myScreen->ActiveWindow() == myPlaylistEditor->Content && !myPlaylistEditor->Content->Empty()) { wFooter->SetTimeout(50); if (myPlaylistEditor->Content->hasSelected()) { std::vector list; myPlaylistEditor->Content->GetSelected(list); std::vector origs(list); while (Keypressed(input, Key.MvSongDown) && list.back() < myPlaylistEditor->Content->Size()-1) { TraceMpdStatus(); myPlaylist->UpdateTimer(); for (std::vector::reverse_iterator it = list.rbegin(); it != list.rend(); ++it) { ++*it; myPlaylistEditor->Content->Swap(*it, (*it)-1); } myPlaylistEditor->Content->Highlight(list[(list.size()-1)/2]); myPlaylistEditor->Content->Refresh(); wFooter->ReadKey(input); } Mpd.StartCommandsList(); for (int i = list.size()-1; i >= 0; --i) if (origs[i] != list[i]) Mpd.Move(myPlaylistEditor->Playlists->Current(), origs[i], list[i]); Mpd.CommitCommandsList(); } else { size_t from, to; from = to = myPlaylistEditor->Content->Choice(); while (Keypressed(input, Key.MvSongDown) && to < myPlaylistEditor->Content->Size()-1) { TraceMpdStatus(); myPlaylist->UpdateTimer(); ++to; myPlaylistEditor->Content->Swap(to, to-1); myPlaylistEditor->Content->Scroll(wDown); myPlaylistEditor->Content->Refresh(); wFooter->ReadKey(input); } if (from != to) Mpd.Move(myPlaylistEditor->Playlists->Current(), from, to); } wFooter->SetTimeout(ncmpcpp_window_timeout); } } else if (Keypressed(input, Key.MoveTo) && myScreen == myPlaylist) { CHECK_PLAYLIST_FOR_FILTERING; if (!myPlaylist->Items->hasSelected()) { ShowMessage("No selected items to move!"); continue; } Playlist::BlockUpdate = 1; size_t pos = myPlaylist->Items->Choice(); // if cursor is at the last item, break convention and move at the end of playlist if (pos == myPlaylist->Items->Size()-1) pos++; std::vector list; myPlaylist->Items->GetSelected(list); if (pos >= list.front() && pos <= list.back()) continue; int diff = pos-list.front(); Mpd.StartCommandsList(); if (diff > 0) { pos -= list.size(); size_t i = list.size()-1; for (std::vector::reverse_iterator it = list.rbegin(); it != list.rend(); ++it, --i) { Mpd.Move(*it, pos+i); myPlaylist->Items->Move(*it, pos+i); } } else if (diff < 0) { size_t i = 0; for (std::vector::const_iterator it = list.begin(); it != list.end(); ++it, ++i) { Mpd.Move(*it, pos+i); myPlaylist->Items->Move(*it, pos+i); } } Mpd.CommitCommandsList(); myPlaylist->Items->Highlight(pos); myPlaylist->FixPositions(); } else if (Keypressed(input, Key.Add)) { if (myScreen == myPlaylistEditor && myPlaylistEditor->Playlists->Empty()) continue; LockStatusbar(); Statusbar() << (myScreen == myPlaylistEditor ? "Add to playlist: " : "Add: "); std::string path = wFooter->GetString(); UnlockStatusbar(); if (!path.empty()) { if (myScreen == myPlaylistEditor) { Mpd.AddToPlaylist(myPlaylistEditor->Playlists->Current(), path); myPlaylistEditor->Content->Clear(0); // make it refetch content of playlist } else Mpd.Add(path); UpdateStatusImmediately = 1; } } else if (Keypressed(input, Key.SeekForward) || Keypressed(input, Key.SeekBackward)) { if (!Mpd.GetTotalTime()) { ShowMessage("Unknown item length!"); continue; } const Song *s = myPlaylist->NowPlayingSong(); if (!s) continue; LockProgressbar(); LockStatusbar(); int songpos; time_t t = time(0); songpos = Mpd.GetElapsedTime(); SeekingInProgress = 1; *wFooter << fmtBold; while (Keypressed(input, Key.SeekForward) || Keypressed(input, Key.SeekBackward)) { TraceMpdStatus(); myPlaylist->UpdateTimer(); wFooter->ReadKey(input); int howmuch = Config.incremental_seeking ? (myPlaylist->Timer()-t)/2+Config.seek_time : Config.seek_time; if (Keypressed(input, Key.SeekForward) && songpos < Mpd.GetTotalTime()) { songpos += howmuch; if (songpos > Mpd.GetTotalTime()) songpos = Mpd.GetTotalTime(); } else if (Keypressed(input, Key.SeekBackward) && songpos > 0) { songpos -= howmuch; if (songpos < 0) songpos = 0; } std::string tracklength; if (Config.new_design) { if (Config.display_remaining_time) { tracklength = "-"; tracklength += Song::ShowTime(Mpd.GetTotalTime()-songpos); } else tracklength = Song::ShowTime(songpos); tracklength += "/"; tracklength += MPD::Song::ShowTime(Mpd.GetTotalTime()); *wHeader << XY(0, 0) << tracklength << " "; wHeader->Refresh(); } else { tracklength = " ["; if (Config.display_remaining_time) { tracklength += "-"; tracklength += Song::ShowTime(Mpd.GetTotalTime()-songpos); } else tracklength += Song::ShowTime(songpos); tracklength += "/"; tracklength += MPD::Song::ShowTime(Mpd.GetTotalTime()); tracklength += "]"; *wFooter << XY(wFooter->GetWidth()-tracklength.length(), 1) << tracklength; } DrawProgressbar(songpos, Mpd.GetTotalTime()); wFooter->Refresh(); } *wFooter << fmtBoldEnd; SeekingInProgress = 0; Mpd.Seek(songpos); UpdateStatusImmediately = 1; UnlockProgressbar(); UnlockStatusbar(); } else if (Keypressed(input, Key.ToggleDisplayMode)) { if (myScreen == myPlaylist) { Config.columns_in_playlist = !Config.columns_in_playlist; ShowMessage("Playlist display mode: %s", Config.columns_in_playlist ? "Columns" : "Classic"); if (Config.columns_in_playlist) { myPlaylist->Items->SetItemDisplayer(Display::SongsInColumns); myPlaylist->Items->SetTitle(Display::Columns()); myPlaylist->Items->SetGetStringFunction(Playlist::SongInColumnsToString); } else { myPlaylist->Items->SetItemDisplayer(Display::Songs); myPlaylist->Items->SetTitle(""); myPlaylist->Items->SetGetStringFunction(Playlist::SongToString); } } else if (myScreen == myBrowser) { Config.columns_in_browser = !Config.columns_in_browser; ShowMessage("Browser display mode: %s", Config.columns_in_browser ? "Columns" : "Classic"); myBrowser->Main()->SetTitle(Config.columns_in_browser ? Display::Columns() : ""); } else if (myScreen == mySearcher) { Config.columns_in_search_engine = !Config.columns_in_search_engine; ShowMessage("Search engine display mode: %s", Config.columns_in_search_engine ? "Columns" : "Classic"); if (mySearcher->Main()->Size() > SearchEngine::StaticOptions) mySearcher->Main()->SetTitle(Config.columns_in_search_engine ? Display::Columns() : ""); } } # ifdef HAVE_CURL_CURL_H else if (Keypressed(input, Key.ToggleLyricsDB)) { const char *current_lyrics_plugin = Lyrics::GetPluginName(++Config.lyrics_db); if (!current_lyrics_plugin) { current_lyrics_plugin = Lyrics::GetPluginName(Config.lyrics_db = 0); } ShowMessage("Using lyrics database: %s", current_lyrics_plugin); } # endif // HAVE_CURL_CURL_H else if (Keypressed(input, Key.ToggleAutoCenter)) { Config.autocenter_mode = !Config.autocenter_mode; ShowMessage("Auto center mode: %s", Config.autocenter_mode ? "On" : "Off"); if (Config.autocenter_mode && myPlaylist->isPlaying() && !myPlaylist->Items->isFiltered()) myPlaylist->Items->Highlight(myPlaylist->NowPlaying); } else if (Keypressed(input, Key.UpdateDB)) { if (myScreen == myBrowser) Mpd.UpdateDirectory(locale_to_utf_cpy(myBrowser->CurrentDir())); # ifdef HAVE_TAGLIB_H else if (myScreen == myTagEditor && !Config.albums_in_tag_editor) Mpd.UpdateDirectory(myTagEditor->CurrentDir()); # endif // HAVE_TAGLIB_H else Mpd.UpdateDirectory("/"); } else if (Keypressed(input, Key.GoToNowPlaying)) { if (myScreen == myPlaylist && myPlaylist->isPlaying()) { CHECK_PLAYLIST_FOR_FILTERING; myPlaylist->Items->Highlight(myPlaylist->NowPlaying); } else if (myScreen == myBrowser) { if (const Song *s = myPlaylist->NowPlayingSong()) { myBrowser->LocateSong(*s); RedrawHeader = 1; } } } else if (Keypressed(input, Key.ToggleRepeat)) { Mpd.SetRepeat(!Mpd.GetRepeat()); UpdateStatusImmediately = 1; } else if (Keypressed(input, Key.Shuffle)) { Mpd.Shuffle(); UpdateStatusImmediately = 1; } else if (Keypressed(input, Key.ToggleRandom)) { Mpd.SetRandom(!Mpd.GetRandom()); UpdateStatusImmediately = 1; } else if (Keypressed(input, Key.ToggleSingle)) { if (myScreen == mySearcher && !mySearcher->Main()->isStatic(0)) { mySearcher->Main()->Highlight(SearchEngine::SearchButton); mySearcher->Main()->Highlighting(0); mySearcher->Main()->Refresh(); mySearcher->Main()->Highlighting(1); mySearcher->EnterPressed(); } # ifdef HAVE_TAGLIB_H else if (myScreen == myTinyTagEditor) { myTinyTagEditor->Main()->Highlight(myTinyTagEditor->Main()->Size()-2); // Save myTinyTagEditor->EnterPressed(); } # endif // HAVE_TAGLIB_H else { Mpd.SetSingle(!Mpd.GetSingle()); UpdateStatusImmediately = 1; } } else if (Keypressed(input, Key.ToggleConsume)) { Mpd.SetConsume(!Mpd.GetConsume()); UpdateStatusImmediately = 1; } else if (Keypressed(input, Key.ToggleCrossfade)) { Mpd.SetCrossfade(Mpd.GetCrossfade() ? 0 : Config.crossfade_time); UpdateStatusImmediately = 1; } else if (Keypressed(input, Key.SetCrossfade)) { LockStatusbar(); Statusbar() << "Set crossfade to: "; std::string crossfade = wFooter->GetString(3); UnlockStatusbar(); int cf = StrToInt(crossfade); if (cf > 0) { Config.crossfade_time = cf; Mpd.SetCrossfade(cf); UpdateStatusImmediately = 1; } } else if (Keypressed(input, Key.EditTags)) { if ((myScreen != myBrowser || !myBrowser->isLocal()) && myScreen != myLyrics) CHECK_MPD_MUSIC_DIR; # ifdef HAVE_TAGLIB_H if (myTinyTagEditor->SetEdited(myScreen->CurrentSong())) { myTinyTagEditor->SwitchTo(); } else if (myScreen->ActiveWindow() == myLibrary->Artists) { LockStatusbar(); Statusbar() << fmtBold << IntoStr(Config.media_lib_primary_tag) << fmtBoldEnd << ": "; std::string new_tag = wFooter->GetString(myLibrary->Artists->Current()); UnlockStatusbar(); if (!new_tag.empty() && new_tag != myLibrary->Artists->Current()) { bool success = 1; SongList list; ShowMessage("Updating tags..."); Mpd.StartSearch(1); Mpd.AddSearch(Config.media_lib_primary_tag, locale_to_utf_cpy(myLibrary->Artists->Current())); Mpd.CommitSearch(list); Song::SetFunction set = IntoSetFunction(Config.media_lib_primary_tag); if (!set) continue; for (SongList::iterator it = list.begin(); it != list.end(); ++it) { (*it)->Localize(); (*it)->SetTags(set, new_tag); ShowMessage("Updating tags in \"%s\"...", (*it)->GetName().c_str()); std::string path = Config.mpd_music_dir + (*it)->GetFile(); if (!TagEditor::WriteTags(**it)) { static const char msg[] = "Error while updating tags in \"%s\"!"; ShowMessage(msg, Shorten(TO_WSTRING((*it)->GetFile()), COLS-static_strlen(msg)).c_str()); success = 0; break; } } if (success) { Mpd.UpdateDirectory(locale_to_utf_cpy(FindSharedDir(list))); ShowMessage("Tags updated succesfully!"); } FreeSongList(list); } } else if (myScreen->ActiveWindow() == myLibrary->Albums) { LockStatusbar(); Statusbar() << fmtBold << "Album: " << fmtBoldEnd; std::string new_album = wFooter->GetString(myLibrary->Albums->Current().second.Album); UnlockStatusbar(); if (!new_album.empty() && new_album != myLibrary->Albums->Current().second.Album) { bool success = 1; ShowMessage("Updating tags..."); for (size_t i = 0; i < myLibrary->Songs->Size(); ++i) { (*myLibrary->Songs)[i].Localize(); ShowMessage("Updating tags in \"%s\"...", (*myLibrary->Songs)[i].GetName().c_str()); std::string path = Config.mpd_music_dir + (*myLibrary->Songs)[i].GetFile(); TagLib::FileRef f(locale_to_utf_cpy(path).c_str()); if (f.isNull()) { static const char msg[] = "Error while opening file \"%s\"!"; ShowMessage(msg, Shorten(TO_WSTRING((*myLibrary->Songs)[i].GetFile()), COLS-static_strlen(msg)).c_str()); success = 0; break; } f.tag()->setAlbum(ToWString(new_album)); if (!f.save()) { static const char msg[] = "Error while writing tags in \"%s\"!"; ShowMessage(msg, Shorten(TO_WSTRING((*myLibrary->Songs)[i].GetFile()), COLS-static_strlen(msg)).c_str()); success = 0; break; } } if (success) { Mpd.UpdateDirectory(locale_to_utf_cpy(FindSharedDir(myLibrary->Songs))); ShowMessage("Tags updated succesfully!"); } } } else if (myScreen->ActiveWindow() == myTagEditor->Dirs) { std::string old_dir = myTagEditor->Dirs->Current().first; LockStatusbar(); Statusbar() << fmtBold << "Directory: " << fmtBoldEnd; std::string new_dir = wFooter->GetString(old_dir); UnlockStatusbar(); if (!new_dir.empty() && new_dir != old_dir) { std::string full_old_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + locale_to_utf_cpy(old_dir); std::string full_new_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + locale_to_utf_cpy(new_dir); if (rename(full_old_dir.c_str(), full_new_dir.c_str()) == 0) { static const char msg[] = "Directory renamed to \"%s\""; ShowMessage(msg, Shorten(TO_WSTRING(new_dir), COLS-static_strlen(msg)).c_str()); Mpd.UpdateDirectory(myTagEditor->CurrentDir()); } else { static const char msg[] = "Couldn't rename \"%s\": %s"; ShowMessage(msg, Shorten(TO_WSTRING(old_dir), COLS-static_strlen(msg)-25).c_str(), strerror(errno)); } } } else # endif // HAVE_TAGLIB_H if (myScreen == myLyrics) { myLyrics->Edit(); } if (myScreen == myBrowser && myBrowser->Main()->Current().type == itDirectory) { std::string old_dir = myBrowser->Main()->Current().name; LockStatusbar(); Statusbar() << fmtBold << "Directory: " << fmtBoldEnd; std::string new_dir = wFooter->GetString(old_dir); UnlockStatusbar(); 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 += locale_to_utf_cpy(old_dir); std::string full_new_dir; if (!myBrowser->isLocal()) full_new_dir += Config.mpd_music_dir; full_new_dir += locale_to_utf_cpy(new_dir); int rename_result = rename(full_old_dir.c_str(), full_new_dir.c_str()); if (rename_result == 0) { static const char msg[] = "Directory renamed to \"%s\""; ShowMessage(msg, Shorten(TO_WSTRING(new_dir), COLS-static_strlen(msg)).c_str()); if (!myBrowser->isLocal()) Mpd.UpdateDirectory(locale_to_utf_cpy(FindSharedDir(old_dir, new_dir))); myBrowser->GetDirectory(myBrowser->CurrentDir()); } else { static const char msg[] = "Couldn't rename \"%s\": %s"; ShowMessage(msg, Shorten(TO_WSTRING(old_dir), COLS-static_strlen(msg)-25).c_str(), strerror(errno)); } } } else if (myScreen->ActiveWindow() == myPlaylistEditor->Playlists || (myScreen == myBrowser && myBrowser->Main()->Current().type == itPlaylist)) { std::string old_name = myScreen->ActiveWindow() == myPlaylistEditor->Playlists ? myPlaylistEditor->Playlists->Current() : myBrowser->Main()->Current().name; LockStatusbar(); Statusbar() << fmtBold << "Playlist: " << fmtBoldEnd; std::string new_name = wFooter->GetString(old_name); UnlockStatusbar(); if (!new_name.empty() && new_name != old_name) { if (Mpd.Rename(locale_to_utf_cpy(old_name), locale_to_utf_cpy(new_name))) { static const char msg[] = "Playlist renamed to \"%s\""; ShowMessage(msg, Shorten(TO_WSTRING(new_name), COLS-static_strlen(msg)).c_str()); if (myBrowser->Main() && !myBrowser->isLocal()) myBrowser->GetDirectory("/"); if (myPlaylistEditor->Main()) myPlaylistEditor->Playlists->Clear(0); } } } } else if (Keypressed(input, Key.GoToContainingDir)) { Song *s = myScreen->CurrentSong(); if (s) myBrowser->LocateSong(*s); } else if (Keypressed(input, Key.GoToPosition)) { if (!Mpd.GetTotalTime()) { ShowMessage("Unknown item length!"); continue; } const Song *s = myPlaylist->NowPlayingSong(); if (!s) continue; LockStatusbar(); Statusbar() << "Position to go (in %/mm:ss/seconds(s)): "; std::string position = wFooter->GetString(); UnlockStatusbar(); if (position.empty()) continue; int newpos = 0; if (position.find(':') != std::string::npos) // probably time in mm:ss { newpos = StrToInt(position)*60 + StrToInt(position.substr(position.find(':')+1)); if (newpos >= 0 && newpos <= Mpd.GetTotalTime()) Mpd.Seek(newpos); else ShowMessage("Out of bounds, 0:00-%s possible for mm:ss, %s given.", s->GetLength().c_str(), MPD::Song::ShowTime(newpos).c_str()); } else if (position.find('s') != std::string::npos) // probably position in seconds { newpos = StrToInt(position); if (newpos >= 0 && newpos <= Mpd.GetTotalTime()) Mpd.Seek(newpos); else ShowMessage("Out of bounds, 0-%d possible for seconds, %d given.", s->GetTotalLength(), newpos); } else { newpos = StrToInt(position); if (newpos >= 0 && newpos <= 100) Mpd.Seek(Mpd.GetTotalTime()*newpos/100.0); else ShowMessage("Out of bounds, 0-100 possible for %%, %d given.", newpos); } UpdateStatusImmediately = 1; } else if (Keypressed(input, Key.ReverseSelection)) { if (myScreen->allowsSelection()) { myScreen->ReverseSelection(); ShowMessage("Selection reversed!"); } } else if (Keypressed(input, Key.DeselectAll)) { if (myScreen->allowsSelection()) { List *mList = myScreen->GetList(); if (!mList->hasSelected()) continue; for (size_t i = 0; i < mList->Size(); ++i) mList->Select(i, 0); ShowMessage("Items deselected!"); } } else if (Keypressed(input, Key.AddSelected)) { mySelectedItemsAdder->SwitchTo(); } else if (Keypressed(input, Key.Crop)) { CHECK_PLAYLIST_FOR_FILTERING; if (myPlaylist->Items->hasSelected()) { Mpd.StartCommandsList(); for (int i = myPlaylist->Items->Size()-1; i >= 0; --i) { if (!myPlaylist->Items->isSelected(i) && i != myPlaylist->NowPlaying) Mpd.Delete(i); } // if mpd deletes now playing song deletion will be sluggishly slow // then so we have to assure it will be deleted at the very end. if (myPlaylist->isPlaying() && !myPlaylist->Items->isSelected(myPlaylist->NowPlaying)) Mpd.DeleteID(myPlaylist->NowPlayingSong()->GetID()); ShowMessage("Deleting all items but selected..."); Mpd.CommitCommandsList(); ShowMessage("Items deleted!"); } else { if (!myPlaylist->isPlaying()) { ShowMessage("Nothing is playing now!"); continue; } Mpd.StartCommandsList(); for (int i = myPlaylist->Items->Size()-1; i >= 0; --i) if (i != myPlaylist->NowPlaying) Mpd.Delete(i); ShowMessage("Deleting all items except now playing one..."); Mpd.CommitCommandsList(); ShowMessage("Items deleted!"); } } else if (Keypressed(input, Key.Clear)) { if (myScreen == myPlaylistEditor && myPlaylistEditor->Playlists->Empty()) continue; if (myScreen->ActiveWindow() == myPlaylistEditor->Content || Config.ask_before_clearing_main_playlist) { LockStatusbar(); Statusbar() << "Do you really want to clear playlist"; if (myScreen->ActiveWindow() == myPlaylistEditor->Content) *wFooter << " \"" << myPlaylistEditor->Playlists->Current() << "\""; *wFooter << " ? [" << fmtBold << 'y' << fmtBoldEnd << '/' << fmtBold << 'n' << fmtBoldEnd << "] "; wFooter->Refresh(); input = 0; do { TraceMpdStatus(); wFooter->ReadKey(input); } while (input != 'y' && input != 'n'); UnlockStatusbar(); if (input != 'y') { ShowMessage("Aborted!"); continue; } } if (myPlaylist->Items->isFiltered()) { ShowMessage("Deleting filtered items..."); Mpd.StartCommandsList(); for (int i = myPlaylist->Items->Size()-1; i >= 0; --i) Mpd.Delete((*myPlaylist->Items)[i].GetPosition()); Mpd.CommitCommandsList(); ShowMessage("Filtered items deleted!"); } else { if (myScreen->ActiveWindow() == myPlaylistEditor->Content) { Mpd.ClearPlaylist(locale_to_utf_cpy(myPlaylistEditor->Playlists->Current())); myPlaylistEditor->Content->Clear(0); } else { ShowMessage("Clearing playlist..."); Mpd.ClearPlaylist(); } } // if playlist is cleared, items list have to be updated, but this // can be blocked if new song was added to playlist less than one // second ago, so we need to assume it's unlocked. BlockItemListUpdate = 0; UpdateStatusImmediately = 1; } else if (Keypressed(input, Key.SortPlaylist) && myScreen == myPlaylist) { CHECK_PLAYLIST_FOR_FILTERING; myPlaylist->Sort(); } else if (Keypressed(input, Key.ApplyFilter)) { List *mList = myScreen->GetList(); if (!mList) continue; LockStatusbar(); Statusbar() << fmtBold << "Apply filter: " << fmtBoldEnd; wFooter->SetGetStringHelper(StatusbarApplyFilterImmediately); wFooter->GetString(mList->GetFilter()); wFooter->SetGetStringHelper(StatusbarGetStringHelper); UnlockStatusbar(); if (mList->isFiltered()) ShowMessage("Using filter \"%s\"", mList->GetFilter().c_str()); else ShowMessage("Filtering disabled"); if (myScreen == myPlaylist) { myPlaylist->EnableHighlighting(); Playlist::ReloadTotalLength = 1; RedrawHeader = 1; } } else if (Keypressed(input, Key.FindForward) || Keypressed(input, Key.FindBackward)) { List *mList = myScreen->GetList(); if (mList) { LockStatusbar(); Statusbar() << "Find " << (Keypressed(input, Key.FindForward) ? "forward" : "backward") << ": "; std::string findme = wFooter->GetString(); UnlockStatusbar(); myPlaylist->UpdateTimer(); if (!findme.empty()) ShowMessage("Searching..."); bool success = mList->Search(findme, myScreen == mySearcher ? SearchEngine::StaticOptions : 0, REG_ICASE | Config.regex_type); if (findme.empty()) continue; success ? ShowMessage("Searching finished!") : ShowMessage("Unable to find \"%s\"", findme.c_str()); if (Keypressed(input, Key.FindForward)) mList->NextFound(Config.wrapped_search); else mList->PrevFound(Config.wrapped_search); if (myScreen == myPlaylist) myPlaylist->EnableHighlighting(); } else if (myScreen == myHelp || myScreen == myLyrics || myScreen == myInfo) { LockStatusbar(); Statusbar() << "Find: "; std::string findme = wFooter->GetString(); UnlockStatusbar(); ShowMessage("Searching..."); Screen *s = static_cast *>(myScreen); s->Main()->RemoveFormatting(); ShowMessage("%s", findme.empty() || s->Main()->SetFormatting(fmtReverse, TO_WSTRING(findme), fmtReverseEnd) ? "Done!" : "No matching patterns found"); s->Main()->Flush(); } } else if (Keypressed(input, Key.NextFoundPosition) || Keypressed(input, Key.PrevFoundPosition)) { List *mList = myScreen->GetList(); if (!mList) continue; if (Keypressed(input, Key.NextFoundPosition)) mList->NextFound(Config.wrapped_search); else mList->PrevFound(Config.wrapped_search); } else if (Keypressed(input, Key.ToggleFindMode)) { Config.wrapped_search = !Config.wrapped_search; ShowMessage("Search mode: %s", Config.wrapped_search ? "Wrapped" : "Normal"); } else if (Keypressed(input, Key.ToggleReplayGainMode) && Mpd.Version() >= 16) { LockStatusbar(); Statusbar() << "Replay gain mode ? [" << fmtBold << 'o' << fmtBoldEnd << "ff/" << fmtBold << 't' << fmtBoldEnd << "rack/" << fmtBold << 'a' << fmtBoldEnd << "lbum]"; wFooter->Refresh(); input = 0; do { TraceMpdStatus(); wFooter->ReadKey(input); } while (input != 'o' && input != 't' && input != 'a'); UnlockStatusbar(); Mpd.SetReplayGainMode(input == 't' ? rgmTrack : (input == 'a' ? rgmAlbum : rgmOff)); ShowMessage("Replay gain mode: %s", Mpd.GetReplayGainMode().c_str()); } else if (Keypressed(input, Key.ToggleSpaceMode)) { Config.space_selects = !Config.space_selects; ShowMessage("Space mode: %s item", Config.space_selects ? "Select/deselect" : "Add"); } else if (Keypressed(input, Key.ToggleAddMode)) { Config.ncmpc_like_songs_adding = !Config.ncmpc_like_songs_adding; ShowMessage("Add mode: %s", Config.ncmpc_like_songs_adding ? "Add item to playlist, remove if already added" : "Always add item to playlist"); } else if (Keypressed(input, Key.ToggleMouse)) { Config.mouse_support = !Config.mouse_support; mousemask(Config.mouse_support ? ALL_MOUSE_EVENTS : 0, 0); ShowMessage("Mouse support %s", Config.mouse_support ? "enabled" : "disabled"); } else if (Keypressed(input, Key.SwitchTagTypeList)) { if (myScreen == myPlaylist) { LockStatusbar(); Statusbar() << "Number of random songs: "; size_t number = StrToLong(wFooter->GetString()); UnlockStatusbar(); if (number && Mpd.AddRandomSongs(number)) ShowMessage("%zu random song%s added to playlist!", number, number == 1 ? "" : "s"); } else if (myScreen == myBrowser && !myBrowser->isLocal()) { Config.browser_sort_by_mtime = !Config.browser_sort_by_mtime; myBrowser->Main()->Sort(myBrowser->CurrentDir() != "/"); ShowMessage("Sort songs by: %s", Config.browser_sort_by_mtime ? "Modification time" : "Name"); } else if (myScreen->ActiveWindow() == myLibrary->Artists || (myLibrary->Columns() == 2 && myScreen->ActiveWindow() == myLibrary->Albums)) { LockStatusbar(); Statusbar() << "Tag type ? [" << fmtBold << 'a' << fmtBoldEnd << "rtist/" << fmtBold << 'y' << fmtBoldEnd << "ear/" << fmtBold << 'g' << fmtBoldEnd << "enre/" << fmtBold << 'c' << fmtBoldEnd << "omposer/" << fmtBold << 'p' << fmtBoldEnd << "erformer] "; wFooter->Refresh(); input = 0; do { TraceMpdStatus(); wFooter->ReadKey(input); } while (input != 'a' && input != 'y' && input != 'g' && input != 'c' && input != 'p'); UnlockStatusbar(); mpd_tag_type new_tagitem = IntoTagItem(input); if (new_tagitem != Config.media_lib_primary_tag) { Config.media_lib_primary_tag = new_tagitem; std::string item_type = IntoStr(Config.media_lib_primary_tag); myLibrary->Artists->SetTitle(item_type + "s"); myLibrary->Artists->Reset(); ToLower(item_type); if (myLibrary->Columns() == 2) { myLibrary->Songs->Clear(0); myLibrary->Albums->Reset(); myLibrary->Albums->Clear(); myLibrary->Albums->SetTitle("Albums (sorted by " + item_type + ")"); myLibrary->Albums->Display(); } else { myLibrary->Artists->Clear(0); myLibrary->Artists->Display(); } ShowMessage("Switched to list of %s tag", item_type.c_str()); } } else if (myScreen == myLyrics) { myLyrics->FetchAgain(); } } else if (Keypressed(input, Key.SongInfo)) { myInfo->GetSong(); } # ifdef HAVE_CURL_CURL_H else if (Keypressed(input, Key.ArtistInfo)) { myInfo->GetArtist(); } # endif // HAVE_CURL_CURL_H else if (Keypressed(input, Key.Lyrics)) { myLyrics->SwitchTo(); } else if (Keypressed(input, Key.Quit)) { main_exit = 1; } # ifdef HAVE_TAGLIB_H else if (myScreen == myTinyTagEditor) { continue; } # endif // HAVE_TAGLIB_H else if (Keypressed(input, Key.Help)) { myHelp->SwitchTo(); } else if (Keypressed(input, Key.ScreenSwitcher)) { if (myScreen == myPlaylist) myBrowser->SwitchTo(); else myPlaylist->SwitchTo(); } else if (Keypressed(input, Key.Playlist)) { myPlaylist->SwitchTo(); } else if (Keypressed(input, Key.Browser)) { myBrowser->SwitchTo(); } else if (Keypressed(input, Key.SearchEngine)) { mySearcher->SwitchTo(); } else if (Keypressed(input, Key.MediaLibrary)) { myLibrary->SwitchTo(); } else if (Keypressed(input, Key.PlaylistEditor)) { myPlaylistEditor->SwitchTo(); } # ifdef HAVE_TAGLIB_H else if (Keypressed(input, Key.TagEditor)) { CHECK_MPD_MUSIC_DIR; myTagEditor->SwitchTo(); } # endif // HAVE_TAGLIB_H # ifdef ENABLE_OUTPUTS else if (Keypressed(input, Key.Outputs)) { myOutputs->SwitchTo(); } # endif // ENABLE_OUTPUTS # ifdef ENABLE_VISUALIZER else if (Keypressed(input, Key.Visualizer)) { myVisualizer->SwitchTo(); } # endif // ENABLE_VISUALIZER # ifdef ENABLE_CLOCK else if (Keypressed(input, Key.Clock)) { myClock->SwitchTo(); } # endif // ENABLE_CLOCK else if (Keypressed(input, Key.ServerInfo)) { myServerInfo->SwitchTo(); } // key mapping end # ifdef ENABLE_VISUALIZER // visualizer sets timmeout to 40ms, but since only it needs such small // value, we should restore defalt one after switching to another screen. if (wFooter->GetTimeout() < ncmpcpp_window_timeout && myScreen != myVisualizer) wFooter->SetTimeout(ncmpcpp_window_timeout); # endif // ENABLE_VISUALIZER } return 0; }