1 /***************************************************************************
2 * Copyright (C) 2008-2021 by Andrzej Rybczak *
3 * andrzej@rybczak.net *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
20
21 #include <cassert>
22 #include <cerrno>
23 #include <cstring>
24 #include <boost/date_time/posix_time/posix_time.hpp>
25 #include <boost/filesystem/operations.hpp>
26 #include <boost/locale/conversion.hpp>
27 #include <boost/lexical_cast.hpp>
28 #include <algorithm>
29 #include <iostream>
30
31 #include "actions.h"
32 #include "charset.h"
33 #include "config.h"
34 #include "display.h"
35 #include "global.h"
36 #include "mpdpp.h"
37 #include "helpers.h"
38 #include "statusbar.h"
39 #include "utility/comparators.h"
40 #include "utility/conversion.h"
41 #include "utility/scoped_value.h"
42
43 #include "curses/menu_impl.h"
44 #include "bindings.h"
45 #include "screens/browser.h"
46 #include "screens/clock.h"
47 #include "screens/help.h"
48 #include "screens/media_library.h"
49 #include "screens/lastfm.h"
50 #include "screens/lyrics.h"
51 #include "screens/playlist.h"
52 #include "screens/playlist_editor.h"
53 #include "screens/sort_playlist.h"
54 #include "screens/search_engine.h"
55 #include "screens/sel_items_adder.h"
56 #include "screens/server_info.h"
57 #include "screens/song_info.h"
58 #include "screens/outputs.h"
59 #include "utility/readline.h"
60 #include "utility/string.h"
61 #include "utility/type_conversions.h"
62 #include "screens/tag_editor.h"
63 #include "screens/tiny_tag_editor.h"
64 #include "screens/visualizer.h"
65 #include "title.h"
66 #include "tags.h"
67
68 #ifdef HAVE_TAGLIB_H
69 # include "fileref.h"
70 # include "tag.h"
71 #endif // HAVE_TAGLIB_H
72
73 using Global::myScreen;
74
75 namespace ph = std::placeholders;
76
77 namespace {
78
79 std::vector<std::shared_ptr<Actions::BaseAction>> AvailableActions;
80
81 void populateActions();
82
83 bool scrollTagCanBeRun(NC::List *&list, const SongList *&songs);
84 void scrollTagUpRun(NC::List *list, const SongList *songs, MPD::Song::GetFunction get);
85 void scrollTagDownRun(NC::List *list, const SongList *songs, MPD::Song::GetFunction get);
86
87 void seek(SearchDirection sd);
88 void findItem(const SearchDirection direction);
89 void listsChangeFinisher();
90
91 template <typename Iterator>
findSelectedRangeAndPrintInfoIfNot(Iterator & first,Iterator & last)92 bool findSelectedRangeAndPrintInfoIfNot(Iterator &first, Iterator &last)
93 {
94 bool success = findSelectedRange(first, last);
95 if (!success)
96 Statusbar::print("No range selected");
97 return success;
98 }
99
100 template <typename Iterator>
nextScreenTypeInSequence(Iterator first,Iterator last,ScreenType type)101 Iterator nextScreenTypeInSequence(Iterator first, Iterator last, ScreenType type)
102 {
103 auto it = std::find(first, last, type);
104 if (it == last)
105 return first;
106 else
107 {
108 ++it;
109 if (it == last)
110 return first;
111 else
112 return it;
113 }
114 }
115
116 }
117
118 namespace Actions {
119
120 bool OriginalStatusbarVisibility;
121 bool ExitMainLoop = false;
122
123 size_t HeaderHeight;
124 size_t FooterHeight;
125 size_t FooterStartY;
126
validateScreenSize()127 void validateScreenSize()
128 {
129 using Global::MainHeight;
130
131 if (COLS < 30 || MainHeight < 5)
132 {
133 NC::destroyScreen();
134 std::cout << "Screen is too small to handle ncmpcpp correctly\n";
135 exit(1);
136 }
137 }
138
initializeScreens()139 void initializeScreens()
140 {
141 myHelp = new Help;
142 myPlaylist = new Playlist;
143 myBrowser = new Browser;
144 mySearcher = new SearchEngine;
145 myLibrary = new MediaLibrary;
146 myPlaylistEditor = new PlaylistEditor;
147 myLyrics = new Lyrics;
148 mySelectedItemsAdder = new SelectedItemsAdder;
149 mySongInfo = new SongInfo;
150 myServerInfo = new ServerInfo;
151 mySortPlaylistDialog = new SortPlaylistDialog;
152 myLastfm = new Lastfm;
153
154 # ifdef HAVE_TAGLIB_H
155 myTinyTagEditor = new TinyTagEditor;
156 myTagEditor = new TagEditor;
157 # endif // HAVE_TAGLIB_H
158
159 # ifdef ENABLE_VISUALIZER
160 myVisualizer = new Visualizer;
161 # endif // ENABLE_VISUALIZER
162
163 # ifdef ENABLE_OUTPUTS
164 myOutputs = new Outputs;
165 # endif // ENABLE_OUTPUTS
166
167 # ifdef ENABLE_CLOCK
168 myClock = new Clock;
169 # endif // ENABLE_CLOCK
170
171 }
172
setResizeFlags()173 void setResizeFlags()
174 {
175 myHelp->hasToBeResized = 1;
176 myPlaylist->hasToBeResized = 1;
177 myBrowser->hasToBeResized = 1;
178 mySearcher->hasToBeResized = 1;
179 myLibrary->hasToBeResized = 1;
180 myPlaylistEditor->hasToBeResized = 1;
181 myLyrics->hasToBeResized = 1;
182 mySelectedItemsAdder->hasToBeResized = 1;
183 mySongInfo->hasToBeResized = 1;
184 myServerInfo->hasToBeResized = 1;
185 mySortPlaylistDialog->hasToBeResized = 1;
186 myLastfm->hasToBeResized = 1;
187
188 # ifdef HAVE_TAGLIB_H
189 myTinyTagEditor->hasToBeResized = 1;
190 myTagEditor->hasToBeResized = 1;
191 # endif // HAVE_TAGLIB_H
192
193 # ifdef ENABLE_VISUALIZER
194 myVisualizer->hasToBeResized = 1;
195 # endif // ENABLE_VISUALIZER
196
197 # ifdef ENABLE_OUTPUTS
198 myOutputs->hasToBeResized = 1;
199 # endif // ENABLE_OUTPUTS
200
201 # ifdef ENABLE_CLOCK
202 myClock->hasToBeResized = 1;
203 # endif // ENABLE_CLOCK
204 }
205
resizeScreen(bool reload_main_window)206 void resizeScreen(bool reload_main_window)
207 {
208 using Global::MainHeight;
209 using Global::wHeader;
210 using Global::wFooter;
211
212 // update internal screen dimensions
213 if (reload_main_window)
214 {
215 rl_resize_terminal();
216 endwin();
217 refresh();
218 // Remove KEY_RESIZE from input queue, I'm not sure how these make it in.
219 getch();
220 }
221
222 MainHeight = std::max(LINES-(Config.design == Design::Alternative ? 7 : 4), 0);
223
224 validateScreenSize();
225
226 if (!Config.header_visibility)
227 MainHeight += 2;
228 if (!Config.statusbar_visibility)
229 ++MainHeight;
230
231 setResizeFlags();
232
233 applyToVisibleWindows(&BaseScreen::resize);
234
235 if (Config.header_visibility || Config.design == Design::Alternative)
236 wHeader->resize(COLS, HeaderHeight);
237
238 FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1);
239 wFooter->moveTo(0, FooterStartY);
240 wFooter->resize(COLS, Config.statusbar_visibility ? 2 : 1);
241
242 applyToVisibleWindows(&BaseScreen::refresh);
243
244 Status::Changes::elapsedTime(false);
245 Status::Changes::playerState();
246 // Note: routines for drawing separator if alternative user
247 // interface is active and header is hidden are placed in
248 // NcmpcppStatusChanges.StatusFlags
249 Status::Changes::flags();
250 drawHeader();
251 wFooter->refresh();
252 refresh();
253 }
254
setWindowsDimensions()255 void setWindowsDimensions()
256 {
257 using Global::MainStartY;
258 using Global::MainHeight;
259
260 MainStartY = Config.design == Design::Alternative ? 5 : 2;
261 MainHeight = std::max(LINES-(Config.design == Design::Alternative ? 7 : 4), 0);
262
263 if (!Config.header_visibility)
264 {
265 MainStartY -= 2;
266 MainHeight += 2;
267 }
268 if (!Config.statusbar_visibility)
269 ++MainHeight;
270
271 HeaderHeight = Config.design == Design::Alternative ? (Config.header_visibility ? 5 : 3) : 2;
272 FooterStartY = LINES-(Config.statusbar_visibility ? 2 : 1);
273 FooterHeight = Config.statusbar_visibility ? 2 : 1;
274 }
275
confirmAction(const boost::format & description)276 void confirmAction(const boost::format &description)
277 {
278 Statusbar::ScopedLock slock;
279 Statusbar::put() << description.str()
280 << " [" << NC::Format::Bold << 'y' << NC::Format::NoBold
281 << '/' << NC::Format::Bold << 'n' << NC::Format::NoBold
282 << "] ";
283 char answer = Statusbar::Helpers::promptReturnOneOf({'y', 'n'});
284 if (answer == 'n')
285 throw NC::PromptAborted(std::string(1, answer));
286 }
287
isMPDMusicDirSet()288 bool isMPDMusicDirSet()
289 {
290 if (Config.mpd_music_dir.empty())
291 {
292 Statusbar::print("Proper mpd_music_dir variable has to be set in configuration file");
293 return false;
294 }
295 return true;
296 }
297
get(Actions::Type at)298 BaseAction &get(Actions::Type at)
299 {
300 if (AvailableActions.empty())
301 populateActions();
302 return *AvailableActions.at(static_cast<size_t>(at));
303 }
304
get_(Actions::Type at)305 std::shared_ptr<BaseAction> get_(Actions::Type at)
306 {
307 if (AvailableActions.empty())
308 populateActions();
309 return AvailableActions.at(static_cast<size_t>(at));
310 }
311
get_(const std::string & name)312 std::shared_ptr<BaseAction> get_(const std::string &name)
313 {
314 std::shared_ptr<BaseAction> result;
315 if (AvailableActions.empty())
316 populateActions();
317 for (const auto &action : AvailableActions)
318 {
319 if (action->name() == name)
320 {
321 result = action;
322 break;
323 }
324 }
325 return result;
326 }
327
UpdateEnvironment()328 UpdateEnvironment::UpdateEnvironment()
329 : BaseAction(Type::UpdateEnvironment, "update_environment")
330 , m_past(boost::posix_time::from_time_t(0))
331 { }
332
run(bool update_timer,bool refresh_window,bool mpd_sync)333 void UpdateEnvironment::run(bool update_timer, bool refresh_window, bool mpd_sync)
334 {
335 using Global::Timer;
336
337 // update timer, status if necessary etc.
338 Status::trace(update_timer, true);
339
340 // show lyrics consumer notification if appropriate
341 if (auto message = myLyrics->tryTakeConsumerMessage())
342 Statusbar::print(*message);
343
344 // header stuff
345 if ((myScreen == myPlaylist || myScreen == myBrowser || myScreen == myLyrics)
346 && (Timer - m_past > boost::posix_time::milliseconds(500))
347 )
348 {
349 drawHeader();
350 m_past = Timer;
351 }
352
353 if (refresh_window)
354 myScreen->refreshWindow();
355
356 // We want to synchronize with MPD during execution of an action chain.
357 if (mpd_sync)
358 {
359 int flags = Mpd.noidle();
360 if (flags)
361 Status::update(flags);
362 }
363 }
364
run()365 void UpdateEnvironment::run()
366 {
367 run(true, true, true);
368 }
369
canBeRun()370 bool MouseEvent::canBeRun()
371 {
372 return Config.mouse_support;
373 }
374
run()375 void MouseEvent::run()
376 {
377 using Global::VolumeState;
378 using Global::wFooter;
379
380 m_old_mouse_event = m_mouse_event;
381 m_mouse_event = wFooter->getMouseEvent();
382
383 //Statusbar::printf("(%1%, %2%, %3%)", m_mouse_event.bstate, m_mouse_event.x, m_mouse_event.y);
384
385 if (m_mouse_event.bstate & BUTTON1_PRESSED
386 && m_mouse_event.y == LINES-(Config.statusbar_visibility ? 2 : 1)
387 ) // progressbar
388 {
389 if (Status::State::player() == MPD::psStop)
390 return;
391 Mpd.Seek(Status::State::currentSongPosition(),
392 Status::State::totalTime()*m_mouse_event.x/double(COLS));
393 }
394 else if (m_mouse_event.bstate & BUTTON1_PRESSED
395 && (Config.statusbar_visibility || Config.design == Design::Alternative)
396 && Status::State::player() != MPD::psStop
397 && m_mouse_event.y == (Config.design == Design::Alternative ? 1 : LINES-1)
398 && m_mouse_event.x < 9
399 ) // playing/paused
400 {
401 Mpd.Toggle();
402 }
403 else if ((m_mouse_event.bstate & BUTTON5_PRESSED || m_mouse_event.bstate & BUTTON4_PRESSED)
404 && (Config.header_visibility || Config.design == Design::Alternative)
405 && m_mouse_event.y == 0 && size_t(m_mouse_event.x) > COLS-VolumeState.length()
406 ) // volume
407 {
408 if (m_mouse_event.bstate & BUTTON5_PRESSED)
409 get(Type::VolumeDown).execute();
410 else
411 get(Type::VolumeUp).execute();
412 }
413 else if (m_mouse_event.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED | BUTTON4_PRESSED | BUTTON5_PRESSED))
414 myScreen->mouseButtonPressed(m_mouse_event);
415 }
416
run()417 void ScrollUp::run()
418 {
419 myScreen->scroll(NC::Scroll::Up);
420 listsChangeFinisher();
421 }
422
run()423 void ScrollDown::run()
424 {
425 myScreen->scroll(NC::Scroll::Down);
426 listsChangeFinisher();
427 }
428
canBeRun()429 bool ScrollUpArtist::canBeRun()
430 {
431 return scrollTagCanBeRun(m_list, m_songs);
432 }
433
run()434 void ScrollUpArtist::run()
435 {
436 scrollTagUpRun(m_list, m_songs, &MPD::Song::getArtist);
437 }
438
canBeRun()439 bool ScrollUpAlbum::canBeRun()
440 {
441 return scrollTagCanBeRun(m_list, m_songs);
442 }
443
run()444 void ScrollUpAlbum::run()
445 {
446 scrollTagUpRun(m_list, m_songs, &MPD::Song::getAlbum);
447 }
448
canBeRun()449 bool ScrollDownArtist::canBeRun()
450 {
451 return scrollTagCanBeRun(m_list, m_songs);
452 }
453
run()454 void ScrollDownArtist::run()
455 {
456 scrollTagDownRun(m_list, m_songs, &MPD::Song::getArtist);
457 }
458
canBeRun()459 bool ScrollDownAlbum::canBeRun()
460 {
461 return scrollTagCanBeRun(m_list, m_songs);
462 }
463
run()464 void ScrollDownAlbum::run()
465 {
466 scrollTagDownRun(m_list, m_songs, &MPD::Song::getAlbum);
467 }
468
run()469 void PageUp::run()
470 {
471 myScreen->scroll(NC::Scroll::PageUp);
472 listsChangeFinisher();
473 }
474
run()475 void PageDown::run()
476 {
477 myScreen->scroll(NC::Scroll::PageDown);
478 listsChangeFinisher();
479 }
480
run()481 void MoveHome::run()
482 {
483 myScreen->scroll(NC::Scroll::Home);
484 listsChangeFinisher();
485 }
486
run()487 void MoveEnd::run()
488 {
489 myScreen->scroll(NC::Scroll::End);
490 listsChangeFinisher();
491 }
492
run()493 void ToggleInterface::run()
494 {
495 switch (Config.design)
496 {
497 case Design::Classic:
498 Config.design = Design::Alternative;
499 Config.statusbar_visibility = false;
500 break;
501 case Design::Alternative:
502 Config.design = Design::Classic;
503 Config.statusbar_visibility = OriginalStatusbarVisibility;
504 break;
505 }
506 setWindowsDimensions();
507 resizeScreen(false);
508 // unlock progressbar
509 Progressbar::ScopedLock();
510 Status::Changes::mixer();
511 Status::Changes::elapsedTime(false);
512 Statusbar::printf("User interface: %1%", Config.design);
513 }
514
canBeRun()515 bool JumpToParentDirectory::canBeRun()
516 {
517 return (myScreen == myBrowser)
518 # ifdef HAVE_TAGLIB_H
519 || (myScreen->activeWindow() == myTagEditor->Dirs)
520 # endif // HAVE_TAGLIB_H
521 ;
522 }
523
run()524 void JumpToParentDirectory::run()
525 {
526 if (myScreen == myBrowser)
527 {
528 if (!myBrowser->inRootDirectory())
529 {
530 myBrowser->main().reset();
531 myBrowser->enterDirectory();
532 }
533 }
534 # ifdef HAVE_TAGLIB_H
535 else if (myScreen == myTagEditor)
536 {
537 if (myTagEditor->CurrentDir() != "/")
538 {
539 myTagEditor->Dirs->reset();
540 myTagEditor->enterDirectory();
541 }
542 }
543 # endif // HAVE_TAGLIB_H
544 }
545
canBeRun()546 bool RunAction::canBeRun()
547 {
548 m_ha = dynamic_cast<HasActions *>(myScreen);
549 return m_ha != nullptr
550 && m_ha->actionRunnable();
551 }
552
run()553 void RunAction::run()
554 {
555 m_ha->runAction();
556 }
557
canBeRun()558 bool PreviousColumn::canBeRun()
559 {
560 m_hc = dynamic_cast<HasColumns *>(myScreen);
561 return m_hc != nullptr
562 && m_hc->previousColumnAvailable();
563 }
564
run()565 void PreviousColumn::run()
566 {
567 m_hc->previousColumn();
568 }
569
canBeRun()570 bool NextColumn::canBeRun()
571 {
572 m_hc = dynamic_cast<HasColumns *>(myScreen);
573 return m_hc != nullptr
574 && m_hc->nextColumnAvailable();
575 }
576
run()577 void NextColumn::run()
578 {
579 m_hc->nextColumn();
580 }
581
canBeRun()582 bool MasterScreen::canBeRun()
583 {
584 using Global::myLockedScreen;
585 using Global::myInactiveScreen;
586
587 return myLockedScreen
588 && myInactiveScreen
589 && myLockedScreen != myScreen
590 && myScreen->isMergable();
591 }
592
run()593 void MasterScreen::run()
594 {
595 using Global::myInactiveScreen;
596 using Global::myLockedScreen;
597
598 myInactiveScreen = myScreen;
599 myScreen = myLockedScreen;
600 drawHeader();
601 }
602
canBeRun()603 bool SlaveScreen::canBeRun()
604 {
605 using Global::myLockedScreen;
606 using Global::myInactiveScreen;
607
608 return myLockedScreen
609 && myInactiveScreen
610 && myLockedScreen == myScreen
611 && myScreen->isMergable();
612 }
613
run()614 void SlaveScreen::run()
615 {
616 using Global::myInactiveScreen;
617 using Global::myLockedScreen;
618
619 myScreen = myInactiveScreen;
620 myInactiveScreen = myLockedScreen;
621 drawHeader();
622 }
623
canBeRun()624 bool VolumeUp::canBeRun()
625 {
626 return Status::State::volume() >= 0;
627 }
628
run()629 void VolumeUp::run()
630 {
631 Mpd.ChangeVolume(static_cast<int>(Config.volume_change_step));
632 }
633
canBeRun()634 bool VolumeDown::canBeRun()
635 {
636 return Status::State::volume() >= 0;
637 }
638
run()639 void VolumeDown::run()
640 {
641 Mpd.ChangeVolume(-static_cast<int>(Config.volume_change_step));
642 }
643
canBeRun()644 bool AddItemToPlaylist::canBeRun()
645 {
646 m_hs = dynamic_cast<HasSongs *>(myScreen);
647 return m_hs != nullptr && m_hs->itemAvailable();
648 }
649
run()650 void AddItemToPlaylist::run()
651 {
652 bool success = m_hs->addItemToPlaylist(false);
653 if (success)
654 {
655 myScreen->scroll(NC::Scroll::Down);
656 listsChangeFinisher();
657 }
658 }
659
canBeRun()660 bool PlayItem::canBeRun()
661 {
662 m_hs = dynamic_cast<HasSongs *>(myScreen);
663 return m_hs != nullptr && m_hs->itemAvailable();
664 }
665
run()666 void PlayItem::run()
667 {
668 bool success = m_hs->addItemToPlaylist(true);
669 if (success)
670 listsChangeFinisher();
671 }
672
canBeRun()673 bool DeletePlaylistItems::canBeRun()
674 {
675 return (myScreen == myPlaylist && !myPlaylist->main().empty())
676 || (myScreen->isActiveWindow(myPlaylistEditor->Content) && !myPlaylistEditor->Content.empty());
677 }
678
run()679 void DeletePlaylistItems::run()
680 {
681 if (myScreen == myPlaylist)
682 {
683 Statusbar::print("Deleting items...");
684 deleteSelectedSongsFromPlaylist(myPlaylist->main());
685 Statusbar::print("Item(s) deleted");
686 }
687 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
688 {
689 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
690 auto delete_fun = std::bind(&MPD::Connection::PlaylistDelete, ph::_1, playlist, ph::_2);
691 Statusbar::print("Deleting items...");
692 deleteSelectedSongs(myPlaylistEditor->Content, delete_fun);
693 Statusbar::print("Item(s) deleted");
694 }
695 }
696
canBeRun()697 bool DeleteBrowserItems::canBeRun()
698 {
699 auto check_if_deletion_allowed = []() {
700 if (Config.allow_for_physical_item_deletion)
701 return true;
702 else
703 {
704 Statusbar::print("Flag \"allow_for_physical_item_deletion\" needs to be enabled in configuration file");
705 return false;
706 }
707 };
708 return myScreen == myBrowser
709 && !myBrowser->main().empty()
710 && isMPDMusicDirSet()
711 && check_if_deletion_allowed();
712 }
713
run()714 void DeleteBrowserItems::run()
715 {
716 auto get_name = [](const MPD::Item &item) -> std::string {
717 std::string iname;
718 switch (item.type())
719 {
720 case MPD::Item::Type::Directory:
721 iname = getBasename(item.directory().path());
722 break;
723 case MPD::Item::Type::Song:
724 iname = item.song().getName();
725 break;
726 case MPD::Item::Type::Playlist:
727 iname = getBasename(item.playlist().path());
728 break;
729 }
730 return iname;
731 };
732
733 boost::format question;
734 if (hasSelected(myBrowser->main().begin(), myBrowser->main().end()))
735 question = boost::format("Delete selected items?");
736 else
737 {
738 const auto &item = myBrowser->main().current()->value();
739 // parent directories are not accepted (and they
740 // can't be selected, so in other cases it's fine).
741 if (myBrowser->isParentDirectory(item))
742 return;
743 const char msg[] = "Delete \"%1%\"?";
744 question = boost::format(msg) % wideShorten(
745 get_name(item), COLS-const_strlen(msg)-5
746 );
747 }
748 confirmAction(question);
749
750 auto items = getSelectedOrCurrent(
751 myBrowser->main().begin(),
752 myBrowser->main().end(),
753 myBrowser->main().current()
754 );
755 for (const auto &item : items)
756 {
757 myBrowser->remove(item->value());
758 const char msg[] = "Deleted %1% \"%2%\"";
759 Statusbar::printf(msg,
760 itemTypeToString(item->value().type()),
761 wideShorten(get_name(item->value()), COLS-const_strlen(msg))
762 );
763 }
764
765 if (!myBrowser->isLocal())
766 Mpd.UpdateDirectory(myBrowser->currentDirectory());
767 myBrowser->requestUpdate();
768 }
769
canBeRun()770 bool DeleteStoredPlaylist::canBeRun()
771 {
772 return myScreen->isActiveWindow(myPlaylistEditor->Playlists);
773 }
774
run()775 void DeleteStoredPlaylist::run()
776 {
777 if (myPlaylistEditor->Playlists.empty())
778 return;
779 boost::format question;
780 if (hasSelected(myPlaylistEditor->Playlists.begin(), myPlaylistEditor->Playlists.end()))
781 question = boost::format("Delete selected playlists?");
782 else
783 question = boost::format("Delete playlist \"%1%\"?")
784 % wideShorten(myPlaylistEditor->Playlists.current()->value().path(), COLS-question.size()-10);
785 confirmAction(question);
786 auto list = getSelectedOrCurrent(
787 myPlaylistEditor->Playlists.begin(),
788 myPlaylistEditor->Playlists.end(),
789 myPlaylistEditor->Playlists.current()
790 );
791 for (const auto &item : list)
792 Mpd.DeletePlaylist(item->value().path());
793 Statusbar::printf("%1% deleted", list.size() == 1 ? "Playlist" : "Playlists");
794 // force playlists update. this happens automatically, but only after call
795 // to Key::read, therefore when we call PlaylistEditor::Update, it won't
796 // yet see it, so let's point that it needs to update it.
797 myPlaylistEditor->requestPlaylistsUpdate();
798 }
799
canBeRun()800 bool ReplaySong::canBeRun()
801 {
802 return Status::State::currentSongPosition() >= 0;
803 }
804
run()805 void ReplaySong::run()
806 {
807 Mpd.Play(Status::State::currentSongPosition());
808 }
809
run()810 void PreviousSong::run()
811 {
812 Mpd.Prev();
813 }
814
run()815 void NextSong::run()
816 {
817 Mpd.Next();
818 }
819
canBeRun()820 bool Pause::canBeRun()
821 {
822 return Status::State::player() != MPD::psStop;
823 }
824
run()825 void Pause::run()
826 {
827 Mpd.Toggle();
828 }
829
run()830 void SavePlaylist::run()
831 {
832 using Global::wFooter;
833
834 std::string playlist_name;
835 {
836 Statusbar::ScopedLock slock;
837 Statusbar::put() << "Save playlist as: ";
838 playlist_name = wFooter->prompt();
839 }
840 try
841 {
842 Mpd.SavePlaylist(playlist_name);
843 Statusbar::printf("Playlist saved as \"%1%\"", playlist_name);
844 }
845 catch (MPD::ServerError &e)
846 {
847 if (e.code() == MPD_SERVER_ERROR_EXIST)
848 {
849 confirmAction(
850 boost::format("Playlist \"%1%\" already exists, overwrite?") % playlist_name
851 );
852 Mpd.DeletePlaylist(playlist_name);
853 Mpd.SavePlaylist(playlist_name);
854 Statusbar::print("Playlist overwritten");
855 }
856 else
857 throw;
858 }
859 }
860
run()861 void Stop::run()
862 {
863 Mpd.Stop();
864 }
865
run()866 void Play::run()
867 {
868 Mpd.Play();
869 }
870
run()871 void ExecuteCommand::run()
872 {
873 using Global::wFooter;
874
875 std::string cmd_name;
876 {
877 Statusbar::ScopedLock slock;
878 NC::Window::ScopedPromptHook helper(*wFooter,
879 Statusbar::Helpers::TryExecuteImmediateCommand()
880 );
881 Statusbar::put() << NC::Format::Bold << ":" << NC::Format::NoBold;
882 cmd_name = wFooter->prompt();
883 }
884
885 auto cmd = Bindings.findCommand(cmd_name);
886 if (cmd)
887 {
888 Statusbar::printf(1, "Executing %1%...", cmd_name);
889 bool res = cmd->binding().execute();
890 Statusbar::printf("Execution of command \"%1%\" %2%.",
891 cmd_name, res ? "successful" : "unsuccessful"
892 );
893 }
894 else
895 Statusbar::printf("No command named \"%1%\"", cmd_name);
896 }
897
canBeRun()898 bool MoveSortOrderUp::canBeRun()
899 {
900 return myScreen == mySortPlaylistDialog;
901 }
902
run()903 void MoveSortOrderUp::run()
904 {
905 mySortPlaylistDialog->moveSortOrderUp();
906 }
907
canBeRun()908 bool MoveSortOrderDown::canBeRun()
909 {
910 return myScreen == mySortPlaylistDialog;
911 }
912
run()913 void MoveSortOrderDown::run()
914 {
915 mySortPlaylistDialog->moveSortOrderDown();
916 }
917
canBeRun()918 bool MoveSelectedItemsUp::canBeRun()
919 {
920 return ((myScreen == myPlaylist
921 && !myPlaylist->main().empty())
922 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
923 && !myPlaylistEditor->Content.empty()));
924 }
925
run()926 void MoveSelectedItemsUp::run()
927 {
928 const char *filteredMsg = "Moving items up is disabled in filtered playlist";
929 if (myScreen == myPlaylist)
930 {
931 if (myPlaylist->main().isFiltered())
932 Statusbar::print(filteredMsg);
933 else
934 moveSelectedItemsUp(
935 myPlaylist->main(),
936 std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
937 }
938 else if (myScreen == myPlaylistEditor)
939 {
940 if (myPlaylistEditor->Content.isFiltered())
941 Statusbar::print(filteredMsg);
942 else
943 {
944 auto playlist = myPlaylistEditor->Playlists.current()->value().path();
945 moveSelectedItemsUp(
946 myPlaylistEditor->Content,
947 std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3));
948 }
949 }
950 }
951
canBeRun()952 bool MoveSelectedItemsDown::canBeRun()
953 {
954 return ((myScreen == myPlaylist
955 && !myPlaylist->main().empty())
956 || (myScreen->isActiveWindow(myPlaylistEditor->Content)
957 && !myPlaylistEditor->Content.empty()));
958 }
959
run()960 void MoveSelectedItemsDown::run()
961 {
962 const char *filteredMsg = "Moving items down is disabled in filtered playlist";
963 if (myScreen == myPlaylist)
964 {
965 if (myPlaylist->main().isFiltered())
966 Statusbar::print(filteredMsg);
967 else
968 moveSelectedItemsDown(
969 myPlaylist->main(),
970 std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
971 }
972 else if (myScreen == myPlaylistEditor)
973 {
974 if (myPlaylistEditor->Content.isFiltered())
975 Statusbar::print(filteredMsg);
976 else
977 {
978 auto playlist = myPlaylistEditor->Playlists.current()->value().path();
979 moveSelectedItemsDown(
980 myPlaylistEditor->Content,
981 std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3));
982 }
983 }
984 }
985
canBeRun()986 bool MoveSelectedItemsTo::canBeRun()
987 {
988 return myScreen == myPlaylist
989 || myScreen->isActiveWindow(myPlaylistEditor->Content);
990 }
991
run()992 void MoveSelectedItemsTo::run()
993 {
994 if (myScreen == myPlaylist)
995 {
996 if (!myPlaylist->main().empty())
997 moveSelectedItemsTo(myPlaylist->main(), std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
998 }
999 else
1000 {
1001 assert(!myPlaylistEditor->Playlists.empty());
1002 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
1003 auto move_fun = std::bind(&MPD::Connection::PlaylistMove, ph::_1, playlist, ph::_2, ph::_3);
1004 moveSelectedItemsTo(myPlaylistEditor->Content, move_fun);
1005 }
1006 }
1007
canBeRun()1008 bool Add::canBeRun()
1009 {
1010 return myScreen != myPlaylistEditor
1011 || !myPlaylistEditor->Playlists.empty();
1012 }
1013
run()1014 void Add::run()
1015 {
1016 using Global::wFooter;
1017
1018 std::string path;
1019 {
1020 Statusbar::ScopedLock slock;
1021 Statusbar::put() << (myScreen == myPlaylistEditor ? "Add to playlist: " : "Add: ");
1022 path = wFooter->prompt();
1023 }
1024
1025 // confirm when one wants to add the whole database
1026 if (path.empty())
1027 confirmAction("Are you sure you want to add the whole database?");
1028
1029 Statusbar::put() << "Adding...";
1030 wFooter->refresh();
1031 if (myScreen == myPlaylistEditor)
1032 Mpd.AddToPlaylist(myPlaylistEditor->Playlists.current()->value().path(), path);
1033 else
1034 {
1035 try
1036 {
1037 Mpd.Add(path);
1038 }
1039 catch (MPD::ServerError &err)
1040 {
1041 // If a path is not a file or directory, assume it is a playlist.
1042 if (err.code() == MPD_SERVER_ERROR_NO_EXIST)
1043 Mpd.LoadPlaylist(path);
1044 else
1045 throw;
1046 }
1047 }
1048 }
1049
canBeRun()1050 bool Load::canBeRun()
1051 {
1052 return myScreen != myPlaylistEditor;
1053 }
1054
run()1055 void Load::run()
1056 {
1057 using Global::wFooter;
1058
1059 std::string path;
1060 {
1061 Statusbar::ScopedLock slock;
1062 Statusbar::put() << "Load playlist: ";
1063 path = wFooter->prompt();
1064 }
1065
1066 Statusbar::put() << "Loading...";
1067 wFooter->refresh();
1068 Mpd.LoadPlaylist(path);
1069 }
1070
canBeRun()1071 bool SeekForward::canBeRun()
1072 {
1073 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1074 }
1075
run()1076 void SeekForward::run()
1077 {
1078 seek(SearchDirection::Forward);
1079 }
1080
canBeRun()1081 bool SeekBackward::canBeRun()
1082 {
1083 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1084 }
1085
run()1086 void SeekBackward::run()
1087 {
1088 seek(SearchDirection::Backward);
1089 }
1090
canBeRun()1091 bool ToggleDisplayMode::canBeRun()
1092 {
1093 return myScreen == myPlaylist
1094 || myScreen == myBrowser
1095 || myScreen == mySearcher
1096 || myScreen->isActiveWindow(myPlaylistEditor->Content);
1097 }
1098
run()1099 void ToggleDisplayMode::run()
1100 {
1101 if (myScreen == myPlaylist)
1102 {
1103 switch (Config.playlist_display_mode)
1104 {
1105 case DisplayMode::Classic:
1106 Config.playlist_display_mode = DisplayMode::Columns;
1107 myPlaylist->main().setItemDisplayer(std::bind(
1108 Display::SongsInColumns, ph::_1, std::cref(myPlaylist->main())
1109 ));
1110 if (Config.titles_visibility)
1111 myPlaylist->main().setTitle(Display::Columns(myPlaylist->main().getWidth()));
1112 else
1113 myPlaylist->main().setTitle("");
1114 break;
1115 case DisplayMode::Columns:
1116 Config.playlist_display_mode = DisplayMode::Classic;
1117 myPlaylist->main().setItemDisplayer(std::bind(
1118 Display::Songs, ph::_1, std::cref(myPlaylist->main()), std::cref(Config.song_list_format)
1119 ));
1120 myPlaylist->main().setTitle("");
1121 }
1122 Statusbar::printf("Playlist display mode: %1%", Config.playlist_display_mode);
1123 }
1124 else if (myScreen == myBrowser)
1125 {
1126 switch (Config.browser_display_mode)
1127 {
1128 case DisplayMode::Classic:
1129 Config.browser_display_mode = DisplayMode::Columns;
1130 if (Config.titles_visibility)
1131 myBrowser->main().setTitle(Display::Columns(myBrowser->main().getWidth()));
1132 else
1133 myBrowser->main().setTitle("");
1134 break;
1135 case DisplayMode::Columns:
1136 Config.browser_display_mode = DisplayMode::Classic;
1137 myBrowser->main().setTitle("");
1138 break;
1139 }
1140 Statusbar::printf("Browser display mode: %1%", Config.browser_display_mode);
1141 }
1142 else if (myScreen == mySearcher)
1143 {
1144 switch (Config.search_engine_display_mode)
1145 {
1146 case DisplayMode::Classic:
1147 Config.search_engine_display_mode = DisplayMode::Columns;
1148 break;
1149 case DisplayMode::Columns:
1150 Config.search_engine_display_mode = DisplayMode::Classic;
1151 break;
1152 }
1153 Statusbar::printf("Search engine display mode: %1%", Config.search_engine_display_mode);
1154 if (mySearcher->main().size() > SearchEngine::StaticOptions)
1155 mySearcher->main().setTitle(
1156 Config.search_engine_display_mode == DisplayMode::Columns
1157 && Config.titles_visibility
1158 ? Display::Columns(mySearcher->main().getWidth())
1159 : ""
1160 );
1161 }
1162 else if (myScreen->isActiveWindow(myPlaylistEditor->Content))
1163 {
1164 switch (Config.playlist_editor_display_mode)
1165 {
1166 case DisplayMode::Classic:
1167 Config.playlist_editor_display_mode = DisplayMode::Columns;
1168 myPlaylistEditor->Content.setItemDisplayer(std::bind(
1169 Display::SongsInColumns, ph::_1, std::cref(myPlaylistEditor->Content)
1170 ));
1171 break;
1172 case DisplayMode::Columns:
1173 Config.playlist_editor_display_mode = DisplayMode::Classic;
1174 myPlaylistEditor->Content.setItemDisplayer(std::bind(
1175 Display::Songs, ph::_1, std::cref(myPlaylistEditor->Content), std::cref(Config.song_list_format)
1176 ));
1177 break;
1178 }
1179 Statusbar::printf("Playlist editor display mode: %1%", Config.playlist_editor_display_mode);
1180 }
1181 }
1182
canBeRun()1183 bool ToggleSeparatorsBetweenAlbums::canBeRun()
1184 {
1185 return true;
1186 }
1187
run()1188 void ToggleSeparatorsBetweenAlbums::run()
1189 {
1190 Config.playlist_separate_albums = !Config.playlist_separate_albums;
1191 Statusbar::printf("Separators between albums: %1%",
1192 Config.playlist_separate_albums ? "on" : "off"
1193 );
1194 }
1195
canBeRun()1196 bool ToggleLyricsUpdateOnSongChange::canBeRun()
1197 {
1198 return myScreen == myLyrics;
1199 }
1200
run()1201 void ToggleLyricsUpdateOnSongChange::run()
1202 {
1203 Config.now_playing_lyrics = !Config.now_playing_lyrics;
1204 Statusbar::printf("Update lyrics if song changes: %1%",
1205 Config.now_playing_lyrics ? "on" : "off"
1206 );
1207 }
1208
run()1209 void ToggleLyricsFetcher::run()
1210 {
1211 myLyrics->toggleFetcher();
1212 }
1213
run()1214 void ToggleFetchingLyricsInBackground::run()
1215 {
1216 Config.fetch_lyrics_in_background = !Config.fetch_lyrics_in_background;
1217 Statusbar::printf("Fetching lyrics for playing songs in background: %1%",
1218 Config.fetch_lyrics_in_background ? "on" : "off");
1219 }
1220
run()1221 void TogglePlayingSongCentering::run()
1222 {
1223 Config.autocenter_mode = !Config.autocenter_mode;
1224 Statusbar::printf("Centering playing song: %1%",
1225 Config.autocenter_mode ? "on" : "off"
1226 );
1227 if (Config.autocenter_mode)
1228 {
1229 auto s = myPlaylist->nowPlayingSong();
1230 if (!s.empty())
1231 myPlaylist->locateSong(s);
1232 }
1233 }
1234
run()1235 void UpdateDatabase::run()
1236 {
1237 if (myScreen == myBrowser)
1238 Mpd.UpdateDirectory(myBrowser->currentDirectory());
1239 # ifdef HAVE_TAGLIB_H
1240 else if (myScreen == myTagEditor)
1241 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1242 # endif // HAVE_TAGLIB_H
1243 else
1244 Mpd.UpdateDirectory("/");
1245 }
1246
canBeRun()1247 bool JumpToPlayingSong::canBeRun()
1248 {
1249 m_song = myPlaylist->nowPlayingSong();
1250 return !m_song.empty()
1251 && (myScreen == myPlaylist
1252 || myScreen == myPlaylistEditor
1253 || myScreen == myBrowser
1254 || myScreen == myLibrary);
1255 }
1256
run()1257 void JumpToPlayingSong::run()
1258 {
1259 if (myScreen == myPlaylist)
1260 {
1261 myPlaylist->locateSong(m_song);
1262 }
1263 else if (myScreen == myPlaylistEditor)
1264 {
1265 myPlaylistEditor->locateSong(m_song);
1266 }
1267 else if (myScreen == myBrowser)
1268 {
1269 myBrowser->locateSong(m_song);
1270 }
1271 else if (myScreen == myLibrary)
1272 {
1273 myLibrary->locateSong(m_song);
1274 }
1275 }
1276
run()1277 void ToggleRepeat::run()
1278 {
1279 Mpd.SetRepeat(!Status::State::repeat());
1280 }
1281
canBeRun()1282 bool Shuffle::canBeRun()
1283 {
1284 if (myScreen != myPlaylist)
1285 return false;
1286 m_begin = myPlaylist->main().begin();
1287 m_end = myPlaylist->main().end();
1288 return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end);
1289 }
1290
run()1291 void Shuffle::run()
1292 {
1293 if (Config.ask_before_shuffling_playlists)
1294 confirmAction("Do you really want to shuffle selected range?");
1295 auto begin = myPlaylist->main().begin();
1296 Mpd.ShuffleRange(m_begin-begin, m_end-begin);
1297 Statusbar::print("Range shuffled");
1298 }
1299
run()1300 void ToggleRandom::run()
1301 {
1302 Mpd.SetRandom(!Status::State::random());
1303 }
1304
canBeRun()1305 bool StartSearching::canBeRun()
1306 {
1307 return myScreen == mySearcher && !mySearcher->main()[0].isInactive();
1308 }
1309
run()1310 void StartSearching::run()
1311 {
1312 mySearcher->main().highlight(SearchEngine::SearchButton);
1313 mySearcher->main().setHighlighting(0);
1314 mySearcher->main().refresh();
1315 mySearcher->main().setHighlighting(1);
1316 mySearcher->runAction();
1317 }
1318
canBeRun()1319 bool SaveTagChanges::canBeRun()
1320 {
1321 # ifdef HAVE_TAGLIB_H
1322 return myScreen == myTinyTagEditor
1323 || myScreen->activeWindow() == myTagEditor->TagTypes;
1324 # else
1325 return false;
1326 # endif // HAVE_TAGLIB_H
1327 }
1328
run()1329 void SaveTagChanges::run()
1330 {
1331 # ifdef HAVE_TAGLIB_H
1332 if (myScreen == myTinyTagEditor)
1333 {
1334 myTinyTagEditor->main().highlight(myTinyTagEditor->main().size()-2); // Save
1335 myTinyTagEditor->runAction();
1336 }
1337 else if (myScreen->activeWindow() == myTagEditor->TagTypes)
1338 {
1339 myTagEditor->TagTypes->highlight(myTagEditor->TagTypes->size()-1); // Save
1340 myTagEditor->runAction();
1341 }
1342 # endif // HAVE_TAGLIB_H
1343 }
1344
run()1345 void ToggleSingle::run()
1346 {
1347 Mpd.SetSingle(!Status::State::single());
1348 }
1349
run()1350 void ToggleConsume::run()
1351 {
1352 Mpd.SetConsume(!Status::State::consume());
1353 }
1354
run()1355 void ToggleCrossfade::run()
1356 {
1357 Mpd.SetCrossfade(Status::State::crossfade() ? 0 : Config.crossfade_time);
1358 }
1359
run()1360 void SetCrossfade::run()
1361 {
1362 using Global::wFooter;
1363
1364 Statusbar::ScopedLock slock;
1365 Statusbar::put() << "Set crossfade to: ";
1366 auto crossfade = fromString<unsigned>(wFooter->prompt());
1367 lowerBoundCheck(crossfade, 0u);
1368 Config.crossfade_time = crossfade;
1369 Mpd.SetCrossfade(crossfade);
1370 }
1371
canBeRun()1372 bool SetVolume::canBeRun()
1373 {
1374 return Status::State::volume() >= 0;
1375 }
1376
run()1377 void SetVolume::run()
1378 {
1379 using Global::wFooter;
1380
1381 unsigned volume;
1382 {
1383 Statusbar::ScopedLock slock;
1384 Statusbar::put() << "Set volume to: ";
1385 volume = fromString<unsigned>(wFooter->prompt());
1386 boundsCheck(volume, 0u, 100u);
1387 Mpd.SetVolume(volume);
1388 }
1389 Statusbar::printf("Volume set to %1%%%", volume);
1390 }
1391
canBeRun()1392 bool EnterDirectory::canBeRun()
1393 {
1394 bool result = false;
1395 if (myScreen == myBrowser && !myBrowser->main().empty())
1396 {
1397 result = myBrowser->main().current()->value().type()
1398 == MPD::Item::Type::Directory;
1399 }
1400 #ifdef HAVE_TAGLIB_H
1401 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1402 result = true;
1403 #endif // HAVE_TAGLIB_H
1404 return result;
1405 }
1406
run()1407 void EnterDirectory::run()
1408 {
1409 if (myScreen == myBrowser)
1410 myBrowser->enterDirectory();
1411 #ifdef HAVE_TAGLIB_H
1412 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1413 {
1414 if (!myTagEditor->enterDirectory())
1415 Statusbar::print("No subdirectories found");
1416 }
1417 #endif // HAVE_TAGLIB_H
1418 }
1419
canBeRun()1420 bool EditSong::canBeRun()
1421 {
1422 # ifdef HAVE_TAGLIB_H
1423 m_song = currentSong(myScreen);
1424 return m_song != nullptr && isMPDMusicDirSet();
1425 # else
1426 return false;
1427 # endif // HAVE_TAGLIB_H
1428 }
1429
run()1430 void EditSong::run()
1431 {
1432 # ifdef HAVE_TAGLIB_H
1433 myTinyTagEditor->SetEdited(*m_song);
1434 myTinyTagEditor->switchTo();
1435 # endif // HAVE_TAGLIB_H
1436 }
1437
canBeRun()1438 bool EditLibraryTag::canBeRun()
1439 {
1440 # ifdef HAVE_TAGLIB_H
1441 return myScreen->isActiveWindow(myLibrary->Tags)
1442 && !myLibrary->Tags.empty()
1443 && isMPDMusicDirSet();
1444 # else
1445 return false;
1446 # endif // HAVE_TAGLIB_H
1447 }
1448
run()1449 void EditLibraryTag::run()
1450 {
1451 # ifdef HAVE_TAGLIB_H
1452 using Global::wFooter;
1453
1454 std::string new_tag;
1455 {
1456 Statusbar::ScopedLock slock;
1457 Statusbar::put() << NC::Format::Bold << tagTypeToString(Config.media_lib_primary_tag) << NC::Format::NoBold << ": ";
1458 new_tag = wFooter->prompt(myLibrary->Tags.current()->value().tag());
1459 }
1460 if (!new_tag.empty() && new_tag != myLibrary->Tags.current()->value().tag())
1461 {
1462 Statusbar::print("Updating tags...");
1463 Mpd.StartSearch(true);
1464 Mpd.AddSearch(Config.media_lib_primary_tag, myLibrary->Tags.current()->value().tag());
1465 MPD::MutableSong::SetFunction set = tagTypeToSetFunction(Config.media_lib_primary_tag);
1466 assert(set);
1467 bool success = true;
1468 std::string dir_to_update;
1469 for (MPD::SongIterator s = Mpd.CommitSearchSongs(), end; s != end; ++s)
1470 {
1471 MPD::MutableSong ms = std::move(*s);
1472 ms.setTags(set, new_tag);
1473 Statusbar::printf("Updating tags in \"%1%\"...", ms.getName());
1474 std::string path = Config.mpd_music_dir + ms.getURI();
1475 if (!Tags::write(ms))
1476 {
1477 success = false;
1478 Statusbar::printf("Error while writing tags to \"%1%\": %2%",
1479 ms.getName(), strerror(errno));
1480 s.finish();
1481 break;
1482 }
1483 if (dir_to_update.empty())
1484 dir_to_update = ms.getURI();
1485 else
1486 dir_to_update = getSharedDirectory(dir_to_update, ms.getURI());
1487 };
1488 if (success)
1489 {
1490 Mpd.UpdateDirectory(dir_to_update);
1491 Statusbar::print("Tags updated successfully");
1492 }
1493 }
1494 # endif // HAVE_TAGLIB_H
1495 }
1496
canBeRun()1497 bool EditLibraryAlbum::canBeRun()
1498 {
1499 # ifdef HAVE_TAGLIB_H
1500 return myScreen->isActiveWindow(myLibrary->Albums)
1501 && !myLibrary->Albums.empty()
1502 && isMPDMusicDirSet();
1503 # else
1504 return false;
1505 # endif // HAVE_TAGLIB_H
1506 }
1507
run()1508 void EditLibraryAlbum::run()
1509 {
1510 # ifdef HAVE_TAGLIB_H
1511 using Global::wFooter;
1512 // FIXME: merge this and EditLibraryTag. also, prompt on failure if user wants to continue
1513 std::string new_album;
1514 {
1515 Statusbar::ScopedLock slock;
1516 Statusbar::put() << NC::Format::Bold << "Album: " << NC::Format::NoBold;
1517 new_album = wFooter->prompt(myLibrary->Albums.current()->value().entry().album());
1518 }
1519 if (!new_album.empty() && new_album != myLibrary->Albums.current()->value().entry().album())
1520 {
1521 bool success = 1;
1522 Statusbar::print("Updating tags...");
1523 for (size_t i = 0; i < myLibrary->Songs.size(); ++i)
1524 {
1525 Statusbar::printf("Updating tags in \"%1%\"...", myLibrary->Songs[i].value().getName());
1526 std::string path = Config.mpd_music_dir + myLibrary->Songs[i].value().getURI();
1527 TagLib::FileRef f(path.c_str());
1528 if (f.isNull())
1529 {
1530 const char msg[] = "Error while opening file \"%1%\"";
1531 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1532 success = 0;
1533 break;
1534 }
1535 f.tag()->setAlbum(ToWString(new_album));
1536 if (!f.save())
1537 {
1538 const char msg[] = "Error while writing tags in \"%1%\"";
1539 Statusbar::printf(msg, wideShorten(myLibrary->Songs[i].value().getURI(), COLS-const_strlen(msg)));
1540 success = 0;
1541 break;
1542 }
1543 }
1544 if (success)
1545 {
1546 Mpd.UpdateDirectory(getSharedDirectory(myLibrary->Songs.beginV(), myLibrary->Songs.endV()));
1547 Statusbar::print("Tags updated successfully");
1548 }
1549 }
1550 # endif // HAVE_TAGLIB_H
1551 }
1552
canBeRun()1553 bool EditDirectoryName::canBeRun()
1554 {
1555 return ((myScreen == myBrowser
1556 && !myBrowser->main().empty()
1557 && myBrowser->main().current()->value().type() == MPD::Item::Type::Directory)
1558 # ifdef HAVE_TAGLIB_H
1559 || (myScreen->activeWindow() == myTagEditor->Dirs
1560 && !myTagEditor->Dirs->empty()
1561 && myTagEditor->Dirs->choice() > 0)
1562 # endif // HAVE_TAGLIB_H
1563 ) && isMPDMusicDirSet();
1564 }
1565
run()1566 void EditDirectoryName::run()
1567 {
1568 using Global::wFooter;
1569 if (myScreen == myBrowser)
1570 {
1571 std::string old_dir = myBrowser->main().current()->value().directory().path();
1572 std::string new_dir;
1573 {
1574 Statusbar::ScopedLock slock;
1575 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1576 new_dir = wFooter->prompt(old_dir);
1577 }
1578 if (!new_dir.empty() && new_dir != old_dir)
1579 {
1580 std::string full_old_dir;
1581 if (!myBrowser->isLocal())
1582 full_old_dir += Config.mpd_music_dir;
1583 full_old_dir += old_dir;
1584 std::string full_new_dir;
1585 if (!myBrowser->isLocal())
1586 full_new_dir += Config.mpd_music_dir;
1587 full_new_dir += new_dir;
1588 boost::filesystem::rename(full_old_dir, full_new_dir);
1589 const char msg[] = "Directory renamed to \"%1%\"";
1590 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1591 if (!myBrowser->isLocal())
1592 Mpd.UpdateDirectory(getSharedDirectory(old_dir, new_dir));
1593 myBrowser->requestUpdate();
1594 }
1595 }
1596 # ifdef HAVE_TAGLIB_H
1597 else if (myScreen->activeWindow() == myTagEditor->Dirs)
1598 {
1599 std::string old_dir = myTagEditor->Dirs->current()->value().first, new_dir;
1600 {
1601 Statusbar::ScopedLock slock;
1602 Statusbar::put() << NC::Format::Bold << "Directory: " << NC::Format::NoBold;
1603 new_dir = wFooter->prompt(old_dir);
1604 }
1605 if (!new_dir.empty() && new_dir != old_dir)
1606 {
1607 std::string full_old_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + old_dir;
1608 std::string full_new_dir = Config.mpd_music_dir + myTagEditor->CurrentDir() + "/" + new_dir;
1609 if (rename(full_old_dir.c_str(), full_new_dir.c_str()) == 0)
1610 {
1611 const char msg[] = "Directory renamed to \"%1%\"";
1612 Statusbar::printf(msg, wideShorten(new_dir, COLS-const_strlen(msg)));
1613 Mpd.UpdateDirectory(myTagEditor->CurrentDir());
1614 }
1615 else
1616 {
1617 const char msg[] = "Couldn't rename \"%1%\": %2%";
1618 Statusbar::printf(msg, wideShorten(old_dir, COLS-const_strlen(msg)-25), strerror(errno));
1619 }
1620 }
1621 }
1622 # endif // HAVE_TAGLIB_H
1623 }
1624
canBeRun()1625 bool EditPlaylistName::canBeRun()
1626 {
1627 return (myScreen->isActiveWindow(myPlaylistEditor->Playlists)
1628 && !myPlaylistEditor->Playlists.empty())
1629 || (myScreen == myBrowser
1630 && !myBrowser->main().empty()
1631 && myBrowser->main().current()->value().type() == MPD::Item::Type::Playlist);
1632 }
1633
run()1634 void EditPlaylistName::run()
1635 {
1636 using Global::wFooter;
1637 std::string old_name, new_name;
1638 if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
1639 old_name = myPlaylistEditor->Playlists.current()->value().path();
1640 else
1641 old_name = myBrowser->main().current()->value().playlist().path();
1642 {
1643 Statusbar::ScopedLock slock;
1644 Statusbar::put() << NC::Format::Bold << "Playlist: " << NC::Format::NoBold;
1645 new_name = wFooter->prompt(old_name);
1646 }
1647 if (!new_name.empty() && new_name != old_name)
1648 {
1649 Mpd.Rename(old_name, new_name);
1650 const char msg[] = "Playlist renamed to \"%1%\"";
1651 Statusbar::printf(msg, wideShorten(new_name, COLS-const_strlen(msg)));
1652 }
1653 }
1654
canBeRun()1655 bool EditLyrics::canBeRun()
1656 {
1657 return myScreen == myLyrics;
1658 }
1659
run()1660 void EditLyrics::run()
1661 {
1662 myLyrics->edit();
1663 }
1664
canBeRun()1665 bool JumpToBrowser::canBeRun()
1666 {
1667 m_song = currentSong(myScreen);
1668 return m_song != nullptr;
1669 }
1670
run()1671 void JumpToBrowser::run()
1672 {
1673 myBrowser->locateSong(*m_song);
1674 }
1675
canBeRun()1676 bool JumpToMediaLibrary::canBeRun()
1677 {
1678 m_song = currentSong(myScreen);
1679 return m_song != nullptr;
1680 }
1681
run()1682 void JumpToMediaLibrary::run()
1683 {
1684 myLibrary->locateSong(*m_song);
1685 }
1686
canBeRun()1687 bool JumpToPlaylistEditor::canBeRun()
1688 {
1689 return myScreen == myBrowser
1690 && myBrowser->main().current()->value().type() == MPD::Item::Type::Playlist;
1691 }
1692
run()1693 void JumpToPlaylistEditor::run()
1694 {
1695 myPlaylistEditor->locatePlaylist(myBrowser->main().current()->value().playlist());
1696 }
1697
run()1698 void ToggleScreenLock::run()
1699 {
1700 using Global::wFooter;
1701 using Global::myLockedScreen;
1702 const char *msg_unlockable_screen = "Current screen can't be locked";
1703 if (myLockedScreen != nullptr)
1704 {
1705 BaseScreen::unlock();
1706 Actions::setResizeFlags();
1707 myScreen->resize();
1708 Statusbar::print("Screen unlocked");
1709 }
1710 else if (!myScreen->isLockable())
1711 {
1712 Statusbar::print(msg_unlockable_screen);
1713 }
1714 else
1715 {
1716 unsigned part = Config.locked_screen_width_part*100;
1717 if (Config.ask_for_locked_screen_width_part)
1718 {
1719 Statusbar::ScopedLock slock;
1720 Statusbar::put() << "% of the locked screen's width to be reserved (20-80): ";
1721 part = fromString<unsigned>(wFooter->prompt(boost::lexical_cast<std::string>(part)));
1722 }
1723 boundsCheck(part, 20u, 80u);
1724 Config.locked_screen_width_part = part/100.0;
1725 if (myScreen->lock())
1726 Statusbar::printf("Screen locked (with %1%%% width)", part);
1727 else
1728 Statusbar::print(msg_unlockable_screen);
1729 }
1730 }
1731
canBeRun()1732 bool JumpToTagEditor::canBeRun()
1733 {
1734 # ifdef HAVE_TAGLIB_H
1735 m_song = currentSong(myScreen);
1736 return m_song != nullptr && isMPDMusicDirSet();
1737 # else
1738 return false;
1739 # endif // HAVE_TAGLIB_H
1740 }
1741
run()1742 void JumpToTagEditor::run()
1743 {
1744 # ifdef HAVE_TAGLIB_H
1745 myTagEditor->LocateSong(*m_song);
1746 # endif // HAVE_TAGLIB_H
1747 }
1748
canBeRun()1749 bool JumpToPositionInSong::canBeRun()
1750 {
1751 return Status::State::player() != MPD::psStop && Status::State::totalTime() > 0;
1752 }
1753
run()1754 void JumpToPositionInSong::run()
1755 {
1756 using Global::wFooter;
1757
1758 const MPD::Song s = myPlaylist->nowPlayingSong();
1759
1760 std::string spos;
1761 {
1762 Statusbar::ScopedLock slock;
1763 Statusbar::put() << "Position to go (in %/m:ss/seconds(s)): ";
1764 spos = wFooter->prompt();
1765 }
1766
1767 boost::regex rx;
1768 boost::smatch what;
1769 if (boost::regex_match(spos, what, rx.assign("([0-9]+):([0-9]{2})"))) // mm:ss
1770 {
1771 auto mins = fromString<unsigned>(what[1]);
1772 auto secs = fromString<unsigned>(what[2]);
1773 boundsCheck(secs, 0u, 60u);
1774 Mpd.Seek(s.getPosition(), mins * 60 + secs);
1775 }
1776 else if (boost::regex_match(spos, what, rx.assign("([0-9]+)s"))) // position in seconds
1777 {
1778 auto secs = fromString<unsigned>(what[1]);
1779 Mpd.Seek(s.getPosition(), secs);
1780 }
1781 else if (boost::regex_match(spos, what, rx.assign("([0-9]+)[%]{0,1}"))) // position in %
1782 {
1783 auto percent = fromString<unsigned>(what[1]);
1784 boundsCheck(percent, 0u, 100u);
1785 int secs = (percent * s.getDuration()) / 100.0;
1786 Mpd.Seek(s.getPosition(), secs);
1787 }
1788 else
1789 Statusbar::print("Invalid format ([m]:[ss], [s]s, [%]%, [%] accepted)");
1790 }
1791
canBeRun()1792 bool SelectItem::canBeRun()
1793 {
1794 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1795 return m_list != nullptr
1796 && !m_list->empty()
1797 && m_list->currentP()->isSelectable();
1798 }
1799
run()1800 void SelectItem::run()
1801 {
1802 auto current = m_list->currentP();
1803 current->setSelected(!current->isSelected());
1804 }
1805
canBeRun()1806 bool SelectRange::canBeRun()
1807 {
1808 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1809 if (m_list == nullptr)
1810 return false;
1811 m_begin = m_list->beginP();
1812 m_end = m_list->endP();
1813 return findRange(m_begin, m_end);
1814 }
1815
run()1816 void SelectRange::run()
1817 {
1818 for (; m_begin != m_end; ++m_begin)
1819 m_begin->setSelected(true);
1820 Statusbar::print("Range selected");
1821 }
1822
canBeRun()1823 bool ReverseSelection::canBeRun()
1824 {
1825 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1826 return m_list != nullptr;
1827 }
1828
run()1829 void ReverseSelection::run()
1830 {
1831 for (auto &p : *m_list)
1832 p.setSelected(!p.isSelected());
1833 Statusbar::print("Selection reversed");
1834 }
1835
canBeRun()1836 bool RemoveSelection::canBeRun()
1837 {
1838 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1839 return m_list != nullptr;
1840 }
1841
run()1842 void RemoveSelection::run()
1843 {
1844 for (auto &p : *m_list)
1845 p.setSelected(false);
1846 Statusbar::print("Selection removed");
1847 }
1848
canBeRun()1849 bool SelectAlbum::canBeRun()
1850 {
1851 auto *w = myScreen->activeWindow();
1852 if (m_list != static_cast<void *>(w))
1853 m_list = dynamic_cast<NC::List *>(w);
1854 if (m_songs != static_cast<void *>(w))
1855 m_songs = dynamic_cast<SongList *>(w);
1856 return m_list != nullptr && !m_list->empty()
1857 && m_songs != nullptr;
1858 }
1859
run()1860 void SelectAlbum::run()
1861 {
1862 const auto front = m_songs->beginS(), current = m_songs->currentS(), end = m_songs->endS();
1863 if (current->song() == nullptr)
1864 return;
1865 auto get = &MPD::Song::getAlbum;
1866 const std::string tag = current->song()->getTags(get);
1867 // go up
1868 for (auto it = current; it != front;)
1869 {
1870 --it;
1871 if (it->song() == nullptr || it->song()->getTags(get) != tag)
1872 break;
1873 it->properties().setSelected(true);
1874 }
1875 // go down
1876 for (auto it = current;;)
1877 {
1878 it->properties().setSelected(true);
1879 if (++it == end)
1880 break;
1881 if (it->song() == nullptr || it->song()->getTags(get) != tag)
1882 break;
1883 }
1884 Statusbar::print("Album around cursor position selected");
1885 }
1886
canBeRun()1887 bool SelectFoundItems::canBeRun()
1888 {
1889 m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
1890 if (m_list == nullptr || m_list->empty())
1891 return false;
1892 m_searchable = dynamic_cast<Searchable *>(myScreen);
1893 return m_searchable != nullptr && m_searchable->allowsSearching();
1894 }
1895
run()1896 void SelectFoundItems::run()
1897 {
1898 auto current_pos = m_list->choice();
1899 myScreen->activeWindow()->scroll(NC::Scroll::Home);
1900 bool found = m_searchable->search(SearchDirection::Forward, false, false);
1901 if (found)
1902 {
1903 Statusbar::print("Searching for items...");
1904 m_list->currentP()->setSelected(true);
1905 while (m_searchable->search(SearchDirection::Forward, false, true))
1906 m_list->currentP()->setSelected(true);
1907 Statusbar::print("Found items selected");
1908 }
1909 m_list->highlight(current_pos);
1910 }
1911
canBeRun()1912 bool AddSelectedItems::canBeRun()
1913 {
1914 return myScreen != mySelectedItemsAdder;
1915 }
1916
run()1917 void AddSelectedItems::run()
1918 {
1919 mySelectedItemsAdder->switchTo();
1920 }
1921
run()1922 void CropMainPlaylist::run()
1923 {
1924 auto &w = myPlaylist->main();
1925 // cropping doesn't make sense in this case
1926 if (w.size() <= 1)
1927 return;
1928 if (Config.ask_before_clearing_playlists)
1929 confirmAction("Do you really want to crop main playlist?");
1930 Statusbar::print("Cropping playlist...");
1931 selectCurrentIfNoneSelected(w);
1932 reverseSelectionHelper(w.begin(), w.end());
1933 deleteSelectedSongsFromPlaylist(w);
1934 Statusbar::print("Playlist cropped");
1935 }
1936
canBeRun()1937 bool CropPlaylist::canBeRun()
1938 {
1939 return myScreen == myPlaylistEditor;
1940 }
1941
run()1942 void CropPlaylist::run()
1943 {
1944 auto &w = myPlaylistEditor->Content;
1945 // cropping doesn't make sense in this case
1946 if (w.size() <= 1)
1947 return;
1948 assert(!myPlaylistEditor->Playlists.empty());
1949 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
1950 if (Config.ask_before_clearing_playlists)
1951 confirmAction(boost::format("Do you really want to crop playlist \"%1%\"?") % playlist);
1952 selectCurrentIfNoneSelected(w);
1953 Statusbar::printf("Cropping playlist \"%1%\"...", playlist);
1954 cropPlaylist(w, std::bind(&MPD::Connection::PlaylistDelete, ph::_1, playlist, ph::_2));
1955 Statusbar::printf("Playlist \"%1%\" cropped", playlist);
1956 }
1957
run()1958 void ClearMainPlaylist::run()
1959 {
1960 if (!myPlaylist->main().empty() && Config.ask_before_clearing_playlists)
1961 confirmAction("Do you really want to clear main playlist?");
1962 Mpd.ClearMainPlaylist();
1963 Statusbar::print("Playlist cleared");
1964 myPlaylist->main().reset();
1965 }
1966
canBeRun()1967 bool ClearPlaylist::canBeRun()
1968 {
1969 return myScreen == myPlaylistEditor;
1970 }
1971
run()1972 void ClearPlaylist::run()
1973 {
1974 if (myPlaylistEditor->Playlists.empty())
1975 return;
1976 std::string playlist = myPlaylistEditor->Playlists.current()->value().path();
1977 if (Config.ask_before_clearing_playlists)
1978 confirmAction(boost::format("Do you really want to clear playlist \"%1%\"?") % playlist);
1979 Mpd.ClearPlaylist(playlist);
1980 Statusbar::printf("Playlist \"%1%\" cleared", playlist);
1981 }
1982
canBeRun()1983 bool SortPlaylist::canBeRun()
1984 {
1985 if (myScreen != myPlaylist)
1986 return false;
1987 auto first = myPlaylist->main().begin(), last = myPlaylist->main().end();
1988 return findSelectedRangeAndPrintInfoIfNot(first, last);
1989 }
1990
run()1991 void SortPlaylist::run()
1992 {
1993 mySortPlaylistDialog->switchTo();
1994 }
1995
canBeRun()1996 bool ReversePlaylist::canBeRun()
1997 {
1998 if (myScreen != myPlaylist)
1999 return false;
2000 m_begin = myPlaylist->main().begin();
2001 m_end = myPlaylist->main().end();
2002 return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end);
2003 }
2004
run()2005 void ReversePlaylist::run()
2006 {
2007 Statusbar::print("Reversing range...");
2008 Mpd.StartCommandsList();
2009 for (--m_end; m_begin < m_end; ++m_begin, --m_end)
2010 Mpd.Swap(m_begin->value().getPosition(), m_end->value().getPosition());
2011 Mpd.CommitCommandsList();
2012 Statusbar::print("Range reversed");
2013 }
2014
canBeRun()2015 bool ApplyFilter::canBeRun()
2016 {
2017 m_filterable = dynamic_cast<Filterable *>(myScreen);
2018 return m_filterable != nullptr
2019 && m_filterable->allowsFiltering();
2020 }
2021
run()2022 void ApplyFilter::run()
2023 {
2024 using Global::wFooter;
2025
2026 std::string filter = m_filterable->currentFilter();
2027 if (!filter.empty())
2028 {
2029 m_filterable->applyFilter(filter);
2030 myScreen->refreshWindow();
2031 }
2032
2033 try
2034 {
2035 ScopedValue<bool> disabled_autocenter_mode(Config.autocenter_mode, false);
2036 Statusbar::ScopedLock slock;
2037 NC::Window::ScopedPromptHook helper(
2038 *wFooter,
2039 Statusbar::Helpers::ApplyFilterImmediately(m_filterable));
2040 Statusbar::put() << "Apply filter: ";
2041 filter = wFooter->prompt(filter);
2042 }
2043 catch (NC::PromptAborted &)
2044 {
2045 m_filterable->applyFilter(filter);
2046 throw;
2047 }
2048
2049 if (filter.empty())
2050 Statusbar::printf("Filtering disabled");
2051 else
2052 Statusbar::printf("Using filter \"%1%\"", filter);
2053
2054 if (myScreen == myPlaylist)
2055 myPlaylist->reloadTotalLength();
2056
2057 listsChangeFinisher();
2058 }
2059
canBeRun()2060 bool Find::canBeRun()
2061 {
2062 return myScreen == myHelp
2063 || myScreen == myLyrics
2064 || myScreen == myLastfm;
2065 }
2066
run()2067 void Find::run()
2068 {
2069 using Global::wFooter;
2070
2071 std::string token;
2072 {
2073 Statusbar::ScopedLock slock;
2074 Statusbar::put() << "Find: ";
2075 token = wFooter->prompt();
2076 }
2077
2078 Statusbar::print("Searching...");
2079 auto s = static_cast<Screen<NC::Scrollpad> *>(myScreen);
2080 s->main().removeProperties();
2081 if (token.empty() || s->main().setProperties(NC::Format::Reverse, token, NC::Format::NoReverse, Config.regex_type))
2082 Statusbar::print("Done");
2083 else
2084 Statusbar::print("No matching patterns found");
2085 s->main().flush();
2086 }
2087
canBeRun()2088 bool FindItemBackward::canBeRun()
2089 {
2090 auto w = dynamic_cast<Searchable *>(myScreen);
2091 return w && w->allowsSearching();
2092 }
2093
run()2094 void FindItemForward::run()
2095 {
2096 findItem(SearchDirection::Forward);
2097 listsChangeFinisher();
2098 }
2099
canBeRun()2100 bool FindItemForward::canBeRun()
2101 {
2102 auto w = dynamic_cast<Searchable *>(myScreen);
2103 return w && w->allowsSearching();
2104 }
2105
run()2106 void FindItemBackward::run()
2107 {
2108 findItem(SearchDirection::Backward);
2109 listsChangeFinisher();
2110 }
2111
canBeRun()2112 bool NextFoundItem::canBeRun()
2113 {
2114 return dynamic_cast<Searchable *>(myScreen);
2115 }
2116
run()2117 void NextFoundItem::run()
2118 {
2119 Searchable *w = dynamic_cast<Searchable *>(myScreen);
2120 assert(w != nullptr);
2121 w->search(SearchDirection::Forward, Config.wrapped_search, true);
2122 listsChangeFinisher();
2123 }
2124
canBeRun()2125 bool PreviousFoundItem::canBeRun()
2126 {
2127 return dynamic_cast<Searchable *>(myScreen);
2128 }
2129
run()2130 void PreviousFoundItem::run()
2131 {
2132 Searchable *w = dynamic_cast<Searchable *>(myScreen);
2133 assert(w != nullptr);
2134 w->search(SearchDirection::Backward, Config.wrapped_search, true);
2135 listsChangeFinisher();
2136 }
2137
run()2138 void ToggleFindMode::run()
2139 {
2140 Config.wrapped_search = !Config.wrapped_search;
2141 Statusbar::printf("Search mode: %1%",
2142 Config.wrapped_search ? "Wrapped" : "Normal"
2143 );
2144 }
2145
run()2146 void ToggleReplayGainMode::run()
2147 {
2148 using Global::wFooter;
2149
2150 char rgm = 0;
2151 {
2152 Statusbar::ScopedLock slock;
2153 Statusbar::put() << "Replay gain mode? "
2154 << "[" << NC::Format::Bold << 'o' << NC::Format::NoBold << "ff"
2155 << "/" << NC::Format::Bold << 't' << NC::Format::NoBold << "rack"
2156 << "/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "lbum"
2157 << "] ";
2158 rgm = Statusbar::Helpers::promptReturnOneOf({'t', 'a', 'o'});
2159 }
2160 switch (rgm)
2161 {
2162 case 't':
2163 Mpd.SetReplayGainMode(MPD::rgmTrack);
2164 break;
2165 case 'a':
2166 Mpd.SetReplayGainMode(MPD::rgmAlbum);
2167 break;
2168 case 'o':
2169 Mpd.SetReplayGainMode(MPD::rgmOff);
2170 break;
2171 default: // impossible
2172 throw std::runtime_error(
2173 (boost::format("ToggleReplayGainMode: impossible case reached: %1%") % rgm).str()
2174 );
2175 }
2176 Statusbar::printf("Replay gain mode: %1%", Mpd.GetReplayGainMode());
2177 }
2178
run()2179 void ToggleAddMode::run()
2180 {
2181 std::string mode_desc;
2182 switch (Config.space_add_mode)
2183 {
2184 case SpaceAddMode::AddRemove:
2185 Config.space_add_mode = SpaceAddMode::AlwaysAdd;
2186 mode_desc = "always add an item to playlist";
2187 break;
2188 case SpaceAddMode::AlwaysAdd:
2189 Config.space_add_mode = SpaceAddMode::AddRemove;
2190 mode_desc = "add an item to playlist or remove if already added";
2191 break;
2192 }
2193 Statusbar::printf("Add mode: %1%", mode_desc);
2194 }
2195
run()2196 void ToggleMouse::run()
2197 {
2198 Config.mouse_support = !Config.mouse_support;
2199 if (Config.mouse_support)
2200 NC::Mouse::enable();
2201 else
2202 NC::Mouse::disable();
2203 Statusbar::printf("Mouse support %1%",
2204 Config.mouse_support ? "enabled" : "disabled"
2205 );
2206 }
2207
run()2208 void ToggleBitrateVisibility::run()
2209 {
2210 Config.display_bitrate = !Config.display_bitrate;
2211 Statusbar::printf("Bitrate visibility %1%",
2212 Config.display_bitrate ? "enabled" : "disabled"
2213 );
2214 }
2215
run()2216 void AddRandomItems::run()
2217 {
2218 using Global::wFooter;
2219 char rnd_type = 0;
2220 {
2221 Statusbar::ScopedLock slock;
2222 Statusbar::put() << "Add random? "
2223 << "[" << NC::Format::Bold << 's' << NC::Format::NoBold << "ongs"
2224 << "/" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtists"
2225 << "/" << "album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtists"
2226 << "/" << "al" << NC::Format::Bold << 'b' << NC::Format::NoBold << "ums"
2227 << "] ";
2228 rnd_type = Statusbar::Helpers::promptReturnOneOf({'s', 'a', 'A', 'b'});
2229 }
2230
2231 mpd_tag_type tag_type = MPD_TAG_ARTIST;
2232 std::string tag_type_str ;
2233 if (rnd_type != 's')
2234 {
2235 tag_type = charToTagType(rnd_type);
2236 tag_type_str = boost::locale::to_lower(tagTypeToString(tag_type));
2237 }
2238 else
2239 tag_type_str = "song";
2240
2241 unsigned number;
2242 {
2243 Statusbar::ScopedLock slock;
2244 Statusbar::put() << "Number of random " << tag_type_str << "s: ";
2245 number = fromString<unsigned>(wFooter->prompt());
2246 }
2247 if (number > 0)
2248 {
2249 bool success;
2250 if (rnd_type == 's')
2251 success = Mpd.AddRandomSongs(number, Config.random_exclude_pattern, Global::RNG);
2252 else
2253 success = Mpd.AddRandomTag(tag_type, number, Global::RNG);
2254 if (success)
2255 Statusbar::printf("%1% random %2%%3% added to playlist", number, tag_type_str, number == 1 ? "" : "s");
2256 }
2257 }
2258
canBeRun()2259 bool ToggleBrowserSortMode::canBeRun()
2260 {
2261 return myScreen == myBrowser;
2262 }
2263
run()2264 void ToggleBrowserSortMode::run()
2265 {
2266 switch (Config.browser_sort_mode)
2267 {
2268 case SortMode::Type:
2269 Config.browser_sort_mode = SortMode::Name;
2270 Statusbar::print("Sort songs by: name");
2271 break;
2272 case SortMode::Name:
2273 Config.browser_sort_mode = SortMode::ModificationTime;
2274 Statusbar::print("Sort songs by: modification time");
2275 break;
2276 case SortMode::ModificationTime:
2277 Config.browser_sort_mode = SortMode::CustomFormat;
2278 Statusbar::print("Sort songs by: custom format");
2279 break;
2280 case SortMode::CustomFormat:
2281 Config.browser_sort_mode = SortMode::None;
2282 Statusbar::print("Do not sort songs");
2283 break;
2284 case SortMode::None:
2285 Config.browser_sort_mode = SortMode::Type;
2286 Statusbar::print("Sort songs by: type");
2287 }
2288 if (Config.browser_sort_mode != SortMode::None)
2289 {
2290 size_t sort_offset = myBrowser->inRootDirectory() ? 0 : 1;
2291 std::stable_sort(
2292 myBrowser->main().begin()+sort_offset, myBrowser->main().end(),
2293 LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the,
2294 Config.browser_sort_mode));
2295 }
2296 }
2297
canBeRun()2298 bool ToggleLibraryTagType::canBeRun()
2299 {
2300 return (myScreen->isActiveWindow(myLibrary->Tags))
2301 || (myLibrary->columns() == 2 && myScreen->isActiveWindow(myLibrary->Albums));
2302 }
2303
run()2304 void ToggleLibraryTagType::run()
2305 {
2306 using Global::wFooter;
2307
2308 char tag_type = 0;
2309 {
2310 Statusbar::ScopedLock slock;
2311 Statusbar::put() << "Tag type? "
2312 << "[" << NC::Format::Bold << 'a' << NC::Format::NoBold << "rtist"
2313 << "/" << "album" << NC::Format::Bold << 'A' << NC::Format::NoBold << "rtist"
2314 << "/" << NC::Format::Bold << 'y' << NC::Format::NoBold << "ear"
2315 << "/" << NC::Format::Bold << 'g' << NC::Format::NoBold << "enre"
2316 << "/" << NC::Format::Bold << 'c' << NC::Format::NoBold << "omposer"
2317 << "/" << NC::Format::Bold << 'p' << NC::Format::NoBold << "erformer"
2318 << "] ";
2319 tag_type = Statusbar::Helpers::promptReturnOneOf({'a', 'A', 'y', 'g', 'c', 'p'});
2320 }
2321 mpd_tag_type new_tagitem = charToTagType(tag_type);
2322 if (new_tagitem != Config.media_lib_primary_tag)
2323 {
2324 Config.media_lib_primary_tag = new_tagitem;
2325 std::string item_type = tagTypeToString(Config.media_lib_primary_tag);
2326 myLibrary->Tags.setTitle(Config.titles_visibility ? item_type + "s" : "");
2327 myLibrary->Tags.reset();
2328 item_type = boost::locale::to_lower(item_type);
2329 std::string and_mtime = Config.media_library_sort_by_mtime ?
2330 " and mtime" :
2331 "";
2332 if (myLibrary->columns() == 2)
2333 {
2334 myLibrary->Songs.clear();
2335 myLibrary->Albums.reset();
2336 myLibrary->Albums.clear();
2337 myLibrary->Albums.setTitle(Config.titles_visibility ? "Albums (sorted by " + item_type + and_mtime + ")" : "");
2338 myLibrary->Albums.display();
2339 }
2340 else
2341 {
2342 myLibrary->Tags.clear();
2343 myLibrary->Tags.display();
2344 }
2345 Statusbar::printf("Switched to the list of %1%s", item_type);
2346 }
2347 }
2348
canBeRun()2349 bool ToggleMediaLibrarySortMode::canBeRun()
2350 {
2351 return myScreen == myLibrary;
2352 }
2353
run()2354 void ToggleMediaLibrarySortMode::run()
2355 {
2356 myLibrary->toggleSortMode();
2357 }
2358
canBeRun()2359 bool FetchLyricsInBackground::canBeRun()
2360 {
2361 m_hs = dynamic_cast<HasSongs *>(myScreen);
2362 return m_hs != nullptr && m_hs->itemAvailable();
2363 }
2364
run()2365 void FetchLyricsInBackground::run()
2366 {
2367 auto songs = m_hs->getSelectedSongs();
2368 for (const auto &s : songs)
2369 myLyrics->fetchInBackground(s, true);
2370 Statusbar::print("Selected songs queued for lyrics fetching");
2371 }
2372
canBeRun()2373 bool RefetchLyrics::canBeRun()
2374 {
2375 return myScreen == myLyrics;
2376 }
2377
run()2378 void RefetchLyrics::run()
2379 {
2380 myLyrics->refetchCurrent();
2381 }
2382
canBeRun()2383 bool SetSelectedItemsPriority::canBeRun()
2384 {
2385 if (Mpd.Version() < 17)
2386 {
2387 Statusbar::print("Priorities are supported in MPD >= 0.17.0");
2388 return false;
2389 }
2390 return myScreen == myPlaylist && !myPlaylist->main().empty();
2391 }
2392
run()2393 void SetSelectedItemsPriority::run()
2394 {
2395 using Global::wFooter;
2396
2397 unsigned prio;
2398 {
2399 Statusbar::ScopedLock slock;
2400 Statusbar::put() << "Set priority [0-255]: ";
2401 prio = fromString<unsigned>(wFooter->prompt());
2402 boundsCheck(prio, 0u, 255u);
2403 }
2404 myPlaylist->setSelectedItemsPriority(prio);
2405 }
2406
canBeRun()2407 bool ToggleOutput::canBeRun()
2408 {
2409 #ifdef ENABLE_OUTPUTS
2410 return myScreen == myOutputs;
2411 #else
2412 return false;
2413 #endif // ENABLE_OUTPUTS
2414 }
2415
run()2416 void ToggleOutput::run()
2417 {
2418 #ifdef ENABLE_OUTPUTS
2419 myOutputs->toggleOutput();
2420 #endif // ENABLE_OUTPUTS
2421 }
2422
canBeRun()2423 bool ToggleVisualizationType::canBeRun()
2424 {
2425 # ifdef ENABLE_VISUALIZER
2426 return myScreen == myVisualizer;
2427 # else
2428 return false;
2429 # endif // ENABLE_VISUALIZER
2430 }
2431
run()2432 void ToggleVisualizationType::run()
2433 {
2434 # ifdef ENABLE_VISUALIZER
2435 myVisualizer->ToggleVisualizationType();
2436 # endif // ENABLE_VISUALIZER
2437 }
2438
run()2439 void ShowSongInfo::run()
2440 {
2441 mySongInfo->switchTo();
2442 }
2443
canBeRun()2444 bool ShowArtistInfo::canBeRun()
2445 {
2446 return myScreen == myLastfm
2447 || (myScreen->isActiveWindow(myLibrary->Tags)
2448 && !myLibrary->Tags.empty()
2449 && Config.media_lib_primary_tag == MPD_TAG_ARTIST)
2450 || currentSong(myScreen);
2451 }
2452
run()2453 void ShowArtistInfo::run()
2454 {
2455 if (myScreen == myLastfm)
2456 {
2457 myLastfm->switchTo();
2458 return;
2459 }
2460
2461 std::string artist;
2462 if (myScreen->isActiveWindow(myLibrary->Tags))
2463 {
2464 assert(!myLibrary->Tags.empty());
2465 assert(Config.media_lib_primary_tag == MPD_TAG_ARTIST);
2466 artist = myLibrary->Tags.current()->value().tag();
2467 }
2468 else
2469 {
2470 auto s = currentSong(myScreen);
2471 assert(s);
2472 artist = s->getArtist();
2473 }
2474
2475 if (!artist.empty())
2476 {
2477 myLastfm->queueJob(new LastFm::ArtistInfo(artist, Config.lastfm_preferred_language));
2478 if (!isVisible(myLastfm))
2479 myLastfm->switchTo();
2480 }
2481 }
2482
canBeRun()2483 bool ShowLyrics::canBeRun()
2484 {
2485 if (myScreen == myLyrics)
2486 {
2487 m_song = nullptr;
2488 return true;
2489 }
2490 else
2491 {
2492 m_song = currentSong(myScreen);
2493 return m_song != nullptr;
2494 }
2495 }
2496
run()2497 void ShowLyrics::run()
2498 {
2499 if (m_song != nullptr)
2500 myLyrics->fetch(*m_song);
2501 if (myScreen == myLyrics || !isVisible(myLyrics))
2502 myLyrics->switchTo();
2503 }
2504
run()2505 void Quit::run()
2506 {
2507 ExitMainLoop = true;
2508 }
2509
run()2510 void NextScreen::run()
2511 {
2512 if (Config.screen_switcher_previous)
2513 {
2514 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2515 tababble->switchToPreviousScreen();
2516 }
2517 else if (!Config.screen_sequence.empty())
2518 {
2519 auto screen = nextScreenTypeInSequence(
2520 Config.screen_sequence.begin(),
2521 Config.screen_sequence.end(),
2522 myScreen->type());
2523 toScreen(*screen)->switchTo();
2524 }
2525 }
2526
run()2527 void PreviousScreen::run()
2528 {
2529 if (Config.screen_switcher_previous)
2530 {
2531 if (auto tababble = dynamic_cast<Tabbable *>(myScreen))
2532 tababble->switchToPreviousScreen();
2533 }
2534 else if (!Config.screen_sequence.empty())
2535 {
2536 auto screen = nextScreenTypeInSequence(
2537 Config.screen_sequence.rbegin(),
2538 Config.screen_sequence.rend(),
2539 myScreen->type());
2540 toScreen(*screen)->switchTo();
2541 }
2542 }
2543
canBeRun()2544 bool ShowHelp::canBeRun()
2545 {
2546 return myScreen != myHelp
2547 # ifdef HAVE_TAGLIB_H
2548 && myScreen != myTinyTagEditor
2549 # endif // HAVE_TAGLIB_H
2550 ;
2551 }
2552
run()2553 void ShowHelp::run()
2554 {
2555 myHelp->switchTo();
2556 }
2557
canBeRun()2558 bool ShowPlaylist::canBeRun()
2559 {
2560 return myScreen != myPlaylist
2561 # ifdef HAVE_TAGLIB_H
2562 && myScreen != myTinyTagEditor
2563 # endif // HAVE_TAGLIB_H
2564 ;
2565 }
2566
run()2567 void ShowPlaylist::run()
2568 {
2569 myPlaylist->switchTo();
2570 }
2571
canBeRun()2572 bool ShowBrowser::canBeRun()
2573 {
2574 return myScreen != myBrowser
2575 # ifdef HAVE_TAGLIB_H
2576 && myScreen != myTinyTagEditor
2577 # endif // HAVE_TAGLIB_H
2578 ;
2579 }
2580
run()2581 void ShowBrowser::run()
2582 {
2583 myBrowser->switchTo();
2584 }
2585
canBeRun()2586 bool ChangeBrowseMode::canBeRun()
2587 {
2588 return myScreen == myBrowser;
2589 }
2590
run()2591 void ChangeBrowseMode::run()
2592 {
2593 myBrowser->changeBrowseMode();
2594 }
2595
canBeRun()2596 bool ShowSearchEngine::canBeRun()
2597 {
2598 return myScreen != mySearcher
2599 # ifdef HAVE_TAGLIB_H
2600 && myScreen != myTinyTagEditor
2601 # endif // HAVE_TAGLIB_H
2602 ;
2603 }
2604
run()2605 void ShowSearchEngine::run()
2606 {
2607 mySearcher->switchTo();
2608 }
2609
canBeRun()2610 bool ResetSearchEngine::canBeRun()
2611 {
2612 return myScreen == mySearcher;
2613 }
2614
run()2615 void ResetSearchEngine::run()
2616 {
2617 mySearcher->reset();
2618 }
2619
canBeRun()2620 bool ShowMediaLibrary::canBeRun()
2621 {
2622 return myScreen != myLibrary
2623 # ifdef HAVE_TAGLIB_H
2624 && myScreen != myTinyTagEditor
2625 # endif // HAVE_TAGLIB_H
2626 ;
2627 }
2628
run()2629 void ShowMediaLibrary::run()
2630 {
2631 myLibrary->switchTo();
2632 }
2633
canBeRun()2634 bool ToggleMediaLibraryColumnsMode::canBeRun()
2635 {
2636 return myScreen == myLibrary;
2637 }
2638
run()2639 void ToggleMediaLibraryColumnsMode::run()
2640 {
2641 myLibrary->toggleColumnsMode();
2642 myLibrary->refresh();
2643 }
2644
canBeRun()2645 bool ShowPlaylistEditor::canBeRun()
2646 {
2647 return myScreen != myPlaylistEditor
2648 # ifdef HAVE_TAGLIB_H
2649 && myScreen != myTinyTagEditor
2650 # endif // HAVE_TAGLIB_H
2651 ;
2652 }
2653
run()2654 void ShowPlaylistEditor::run()
2655 {
2656 myPlaylistEditor->switchTo();
2657 }
2658
canBeRun()2659 bool ShowTagEditor::canBeRun()
2660 {
2661 # ifdef HAVE_TAGLIB_H
2662 return myScreen != myTagEditor
2663 && myScreen != myTinyTagEditor;
2664 # else
2665 return false;
2666 # endif // HAVE_TAGLIB_H
2667 }
2668
run()2669 void ShowTagEditor::run()
2670 {
2671 # ifdef HAVE_TAGLIB_H
2672 if (isMPDMusicDirSet())
2673 myTagEditor->switchTo();
2674 # endif // HAVE_TAGLIB_H
2675 }
2676
canBeRun()2677 bool ShowOutputs::canBeRun()
2678 {
2679 # ifdef ENABLE_OUTPUTS
2680 return myScreen != myOutputs
2681 # ifdef HAVE_TAGLIB_H
2682 && myScreen != myTinyTagEditor
2683 # endif // HAVE_TAGLIB_H
2684 ;
2685 # else
2686 return false;
2687 # endif // ENABLE_OUTPUTS
2688 }
2689
run()2690 void ShowOutputs::run()
2691 {
2692 # ifdef ENABLE_OUTPUTS
2693 myOutputs->switchTo();
2694 # endif // ENABLE_OUTPUTS
2695 }
2696
canBeRun()2697 bool ShowVisualizer::canBeRun()
2698 {
2699 # ifdef ENABLE_VISUALIZER
2700 return myScreen != myVisualizer
2701 # ifdef HAVE_TAGLIB_H
2702 && myScreen != myTinyTagEditor
2703 # endif // HAVE_TAGLIB_H
2704 ;
2705 # else
2706 return false;
2707 # endif // ENABLE_VISUALIZER
2708 }
2709
run()2710 void ShowVisualizer::run()
2711 {
2712 # ifdef ENABLE_VISUALIZER
2713 myVisualizer->switchTo();
2714 # endif // ENABLE_VISUALIZER
2715 }
2716
canBeRun()2717 bool ShowClock::canBeRun()
2718 {
2719 # ifdef ENABLE_CLOCK
2720 return myScreen != myClock
2721 # ifdef HAVE_TAGLIB_H
2722 && myScreen != myTinyTagEditor
2723 # endif // HAVE_TAGLIB_H
2724 ;
2725 # else
2726 return false;
2727 # endif // ENABLE_CLOCK
2728 }
2729
run()2730 void ShowClock::run()
2731 {
2732 # ifdef ENABLE_CLOCK
2733 myClock->switchTo();
2734 # endif // ENABLE_CLOCK
2735 }
2736
2737 #ifdef HAVE_TAGLIB_H
canBeRun()2738 bool ShowServerInfo::canBeRun()
2739 {
2740 return myScreen != myTinyTagEditor;
2741 }
2742 #endif // HAVE_TAGLIB_H
2743
run()2744 void ShowServerInfo::run()
2745 {
2746 myServerInfo->switchTo();
2747 }
2748
2749 }
2750
2751 namespace {
2752
populateActions()2753 void populateActions()
2754 {
2755 AvailableActions.resize(static_cast<size_t>(Actions::Type::_numberOfActions));
2756 auto insert_action = [](Actions::BaseAction *a) {
2757 AvailableActions.at(static_cast<size_t>(a->type())).reset(a);
2758 };
2759 insert_action(new Actions::Dummy());
2760 insert_action(new Actions::UpdateEnvironment());
2761 insert_action(new Actions::MouseEvent());
2762 insert_action(new Actions::ScrollUp());
2763 insert_action(new Actions::ScrollDown());
2764 insert_action(new Actions::ScrollUpArtist());
2765 insert_action(new Actions::ScrollUpAlbum());
2766 insert_action(new Actions::ScrollDownArtist());
2767 insert_action(new Actions::ScrollDownAlbum());
2768 insert_action(new Actions::PageUp());
2769 insert_action(new Actions::PageDown());
2770 insert_action(new Actions::MoveHome());
2771 insert_action(new Actions::MoveEnd());
2772 insert_action(new Actions::ToggleInterface());
2773 insert_action(new Actions::JumpToParentDirectory());
2774 insert_action(new Actions::RunAction());
2775 insert_action(new Actions::SelectItem());
2776 insert_action(new Actions::SelectRange());
2777 insert_action(new Actions::PreviousColumn());
2778 insert_action(new Actions::NextColumn());
2779 insert_action(new Actions::MasterScreen());
2780 insert_action(new Actions::SlaveScreen());
2781 insert_action(new Actions::VolumeUp());
2782 insert_action(new Actions::VolumeDown());
2783 insert_action(new Actions::AddItemToPlaylist());
2784 insert_action(new Actions::DeletePlaylistItems());
2785 insert_action(new Actions::DeleteStoredPlaylist());
2786 insert_action(new Actions::DeleteBrowserItems());
2787 insert_action(new Actions::ReplaySong());
2788 insert_action(new Actions::PreviousSong());
2789 insert_action(new Actions::NextSong());
2790 insert_action(new Actions::Pause());
2791 insert_action(new Actions::Stop());
2792 insert_action(new Actions::Play());
2793 insert_action(new Actions::ExecuteCommand());
2794 insert_action(new Actions::SavePlaylist());
2795 insert_action(new Actions::MoveSortOrderUp());
2796 insert_action(new Actions::MoveSortOrderDown());
2797 insert_action(new Actions::MoveSelectedItemsUp());
2798 insert_action(new Actions::MoveSelectedItemsDown());
2799 insert_action(new Actions::MoveSelectedItemsTo());
2800 insert_action(new Actions::Add());
2801 insert_action(new Actions::Load());
2802 insert_action(new Actions::PlayItem());
2803 insert_action(new Actions::SeekForward());
2804 insert_action(new Actions::SeekBackward());
2805 insert_action(new Actions::ToggleDisplayMode());
2806 insert_action(new Actions::ToggleSeparatorsBetweenAlbums());
2807 insert_action(new Actions::ToggleLyricsUpdateOnSongChange());
2808 insert_action(new Actions::ToggleLyricsFetcher());
2809 insert_action(new Actions::ToggleFetchingLyricsInBackground());
2810 insert_action(new Actions::TogglePlayingSongCentering());
2811 insert_action(new Actions::UpdateDatabase());
2812 insert_action(new Actions::JumpToPlayingSong());
2813 insert_action(new Actions::ToggleRepeat());
2814 insert_action(new Actions::Shuffle());
2815 insert_action(new Actions::ToggleRandom());
2816 insert_action(new Actions::StartSearching());
2817 insert_action(new Actions::SaveTagChanges());
2818 insert_action(new Actions::ToggleSingle());
2819 insert_action(new Actions::ToggleConsume());
2820 insert_action(new Actions::ToggleCrossfade());
2821 insert_action(new Actions::SetCrossfade());
2822 insert_action(new Actions::SetVolume());
2823 insert_action(new Actions::EnterDirectory());
2824 insert_action(new Actions::EditSong());
2825 insert_action(new Actions::EditLibraryTag());
2826 insert_action(new Actions::EditLibraryAlbum());
2827 insert_action(new Actions::EditDirectoryName());
2828 insert_action(new Actions::EditPlaylistName());
2829 insert_action(new Actions::EditLyrics());
2830 insert_action(new Actions::JumpToBrowser());
2831 insert_action(new Actions::JumpToMediaLibrary());
2832 insert_action(new Actions::JumpToPlaylistEditor());
2833 insert_action(new Actions::ToggleScreenLock());
2834 insert_action(new Actions::JumpToTagEditor());
2835 insert_action(new Actions::JumpToPositionInSong());
2836 insert_action(new Actions::ReverseSelection());
2837 insert_action(new Actions::RemoveSelection());
2838 insert_action(new Actions::SelectAlbum());
2839 insert_action(new Actions::SelectFoundItems());
2840 insert_action(new Actions::AddSelectedItems());
2841 insert_action(new Actions::CropMainPlaylist());
2842 insert_action(new Actions::CropPlaylist());
2843 insert_action(new Actions::ClearMainPlaylist());
2844 insert_action(new Actions::ClearPlaylist());
2845 insert_action(new Actions::SortPlaylist());
2846 insert_action(new Actions::ReversePlaylist());
2847 insert_action(new Actions::ApplyFilter());
2848 insert_action(new Actions::Find());
2849 insert_action(new Actions::FindItemForward());
2850 insert_action(new Actions::FindItemBackward());
2851 insert_action(new Actions::NextFoundItem());
2852 insert_action(new Actions::PreviousFoundItem());
2853 insert_action(new Actions::ToggleFindMode());
2854 insert_action(new Actions::ToggleReplayGainMode());
2855 insert_action(new Actions::ToggleAddMode());
2856 insert_action(new Actions::ToggleMouse());
2857 insert_action(new Actions::ToggleBitrateVisibility());
2858 insert_action(new Actions::AddRandomItems());
2859 insert_action(new Actions::ToggleBrowserSortMode());
2860 insert_action(new Actions::ToggleLibraryTagType());
2861 insert_action(new Actions::ToggleMediaLibrarySortMode());
2862 insert_action(new Actions::FetchLyricsInBackground());
2863 insert_action(new Actions::RefetchLyrics());
2864 insert_action(new Actions::SetSelectedItemsPriority());
2865 insert_action(new Actions::ToggleOutput());
2866 insert_action(new Actions::ToggleVisualizationType());
2867 insert_action(new Actions::ShowSongInfo());
2868 insert_action(new Actions::ShowArtistInfo());
2869 insert_action(new Actions::ShowLyrics());
2870 insert_action(new Actions::Quit());
2871 insert_action(new Actions::NextScreen());
2872 insert_action(new Actions::PreviousScreen());
2873 insert_action(new Actions::ShowHelp());
2874 insert_action(new Actions::ShowPlaylist());
2875 insert_action(new Actions::ShowBrowser());
2876 insert_action(new Actions::ChangeBrowseMode());
2877 insert_action(new Actions::ShowSearchEngine());
2878 insert_action(new Actions::ResetSearchEngine());
2879 insert_action(new Actions::ShowMediaLibrary());
2880 insert_action(new Actions::ToggleMediaLibraryColumnsMode());
2881 insert_action(new Actions::ShowPlaylistEditor());
2882 insert_action(new Actions::ShowTagEditor());
2883 insert_action(new Actions::ShowOutputs());
2884 insert_action(new Actions::ShowVisualizer());
2885 insert_action(new Actions::ShowClock());
2886 insert_action(new Actions::ShowServerInfo());
2887 for (size_t i = 0; i < AvailableActions.size(); ++i)
2888 {
2889 if (AvailableActions[i] == nullptr)
2890 throw std::logic_error("undefined action at position "
2891 + boost::lexical_cast<std::string>(i));
2892 }
2893 }
2894
scrollTagCanBeRun(NC::List * & list,const SongList * & songs)2895 bool scrollTagCanBeRun(NC::List *&list, const SongList *&songs)
2896 {
2897 auto w = myScreen->activeWindow();
2898 if (list != static_cast<void *>(w))
2899 list = dynamic_cast<NC::List *>(w);
2900 if (songs != static_cast<void *>(w))
2901 songs = dynamic_cast<SongList *>(w);
2902 return list != nullptr && !list->empty()
2903 && songs != nullptr;
2904 }
2905
scrollTagUpRun(NC::List * list,const SongList * songs,MPD::Song::GetFunction get)2906 void scrollTagUpRun(NC::List *list, const SongList *songs, MPD::Song::GetFunction get)
2907 {
2908 const auto front = songs->beginS();
2909 auto it = songs->currentS();
2910 if (it->song() != nullptr)
2911 {
2912 const std::string tag = it->song()->getTags(get);
2913 while (it != front)
2914 {
2915 --it;
2916 if (it->song() == nullptr || it->song()->getTags(get) != tag)
2917 break;
2918 }
2919 list->highlight(it-front);
2920 }
2921 }
2922
scrollTagDownRun(NC::List * list,const SongList * songs,MPD::Song::GetFunction get)2923 void scrollTagDownRun(NC::List *list, const SongList *songs, MPD::Song::GetFunction get)
2924 {
2925 const auto front = songs->beginS(), back = --songs->endS();
2926 auto it = songs->currentS();
2927 if (it->song() != nullptr)
2928 {
2929 const std::string tag = it->song()->getTags(get);
2930 while (it != back)
2931 {
2932 ++it;
2933 if (it->song() == nullptr || it->song()->getTags(get) != tag)
2934 break;
2935 }
2936 list->highlight(it-front);
2937 }
2938 }
2939
seek(SearchDirection sd)2940 void seek(SearchDirection sd)
2941 {
2942 using Global::wHeader;
2943 using Global::wFooter;
2944 using Global::Timer;
2945 using Global::SeekingInProgress;
2946
2947 if (!Status::State::totalTime())
2948 {
2949 Statusbar::print("Unknown item length");
2950 return;
2951 }
2952
2953 Progressbar::ScopedLock progressbar_lock;
2954 Statusbar::ScopedLock statusbar_lock;
2955
2956 unsigned songpos = Status::State::elapsedTime();
2957 auto t = Timer;
2958
2959 NC::Window::ScopedTimeout stimeout{*wFooter, BaseScreen::defaultWindowTimeout};
2960
2961 // Accept single action of a given type or action chain for which all actions
2962 // can be run and one of them is of the given type. This will still not work
2963 // in some contrived cases, but allows for more flexibility than accepting
2964 // single actions only.
2965 auto hasRunnableAction = [](BindingsConfiguration::BindingIteratorPair &bindings,
2966 Actions::Type type) {
2967 bool success = false;
2968 for (auto binding = bindings.first; binding != bindings.second; ++binding)
2969 {
2970 auto &actions = binding->actions();
2971 for (const auto &action : actions)
2972 {
2973 if (action->canBeRun())
2974 {
2975 if (action->type() == type)
2976 success = true;
2977 }
2978 else
2979 {
2980 success = false;
2981 break;
2982 }
2983 }
2984 if (success)
2985 break;
2986 }
2987 return success;
2988 };
2989
2990 SeekingInProgress = true;
2991 while (true)
2992 {
2993 Status::trace();
2994
2995 unsigned howmuch = Config.incremental_seeking
2996 ? (Timer-t).total_seconds()/2+Config.seek_time
2997 : Config.seek_time;
2998
2999 NC::Key::Type input = readKey(*wFooter);
3000
3001 switch (sd)
3002 {
3003 case SearchDirection::Backward:
3004 if (songpos > 0)
3005 {
3006 if (songpos < howmuch)
3007 songpos = 0;
3008 else
3009 songpos -= howmuch;
3010 }
3011 break;
3012 case SearchDirection::Forward:
3013 if (songpos < Status::State::totalTime())
3014 songpos = std::min(songpos + howmuch, Status::State::totalTime());
3015 break;
3016 };
3017
3018 std::string tracklength;
3019 // FIXME: merge this with the code in status.cpp
3020 switch (Config.design)
3021 {
3022 case Design::Classic:
3023 tracklength = " [";
3024 if (Config.display_remaining_time)
3025 {
3026 tracklength += "-";
3027 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
3028 }
3029 else
3030 tracklength += MPD::Song::ShowTime(songpos);
3031 tracklength += "/";
3032 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
3033 tracklength += "]";
3034 *wFooter << NC::XY(wFooter->getWidth()-tracklength.length(), 1)
3035 << Config.statusbar_time_color
3036 << tracklength
3037 << NC::FormattedColor::End<>(Config.statusbar_time_color);
3038 break;
3039 case Design::Alternative:
3040 if (Config.display_remaining_time)
3041 {
3042 tracklength = "-";
3043 tracklength += MPD::Song::ShowTime(Status::State::totalTime()-songpos);
3044 }
3045 else
3046 tracklength = MPD::Song::ShowTime(songpos);
3047 tracklength += "/";
3048 tracklength += MPD::Song::ShowTime(Status::State::totalTime());
3049 *wHeader << NC::XY(0, 0)
3050 << Config.statusbar_time_color
3051 << tracklength
3052 << NC::FormattedColor::End<>(Config.statusbar_time_color)
3053 << " ";
3054 wHeader->refresh();
3055 break;
3056 }
3057 Progressbar::draw(songpos, Status::State::totalTime());
3058 wFooter->refresh();
3059
3060 auto k = Bindings.get(input);
3061 if (hasRunnableAction(k, Actions::Type::SeekBackward))
3062 sd = SearchDirection::Backward;
3063 else if (hasRunnableAction(k, Actions::Type::SeekForward))
3064 sd = SearchDirection::Forward;
3065 else
3066 break;
3067 }
3068 SeekingInProgress = false;
3069 Mpd.Seek(Status::State::currentSongPosition(), songpos);
3070 }
3071
findItem(const SearchDirection direction)3072 void findItem(const SearchDirection direction)
3073 {
3074 using Global::wFooter;
3075
3076 Searchable *w = dynamic_cast<Searchable *>(myScreen);
3077 assert(w != nullptr);
3078 assert(w->allowsSearching());
3079
3080 std::string constraint;
3081 try
3082 {
3083 ScopedValue<bool> disabled_autocenter_mode(Config.autocenter_mode, false);
3084 Statusbar::ScopedLock slock;
3085 NC::Window::ScopedPromptHook prompt_hook(
3086 *wFooter,
3087 Statusbar::Helpers::FindImmediately(w, direction));
3088 Statusbar::put() << (boost::format("Find %1%: ") % direction).str();
3089 constraint = wFooter->prompt(constraint);
3090 }
3091 catch (NC::PromptAborted &)
3092 {
3093 w->setSearchConstraint(constraint);
3094 w->search(direction, Config.wrapped_search, false);
3095 throw;
3096 }
3097
3098 if (constraint.empty())
3099 {
3100 Statusbar::printf("Constraint unset");
3101 w->clearSearchConstraint();
3102 }
3103 else
3104 Statusbar::printf("Using constraint \"%1%\"", constraint);
3105 }
3106
listsChangeFinisher()3107 void listsChangeFinisher()
3108 {
3109 if (myScreen == myLibrary
3110 || myScreen == myPlaylistEditor
3111 # ifdef HAVE_TAGLIB_H
3112 || myScreen == myTagEditor
3113 # endif // HAVE_TAGLIB_H
3114 )
3115 {
3116 if (myScreen->activeWindow() == &myLibrary->Tags)
3117 {
3118 myLibrary->Albums.clear();
3119 myLibrary->Albums.refresh();
3120 myLibrary->Songs.clear();
3121 myLibrary->Songs.refresh();
3122 myLibrary->updateTimer();
3123 }
3124 else if (myScreen->activeWindow() == &myLibrary->Albums)
3125 {
3126 myLibrary->Songs.clear();
3127 myLibrary->Songs.refresh();
3128 myLibrary->updateTimer();
3129 }
3130 else if (myScreen->isActiveWindow(myPlaylistEditor->Playlists))
3131 {
3132 myPlaylistEditor->Content.clear();
3133 myPlaylistEditor->Content.refresh();
3134 myPlaylistEditor->updateTimer();
3135 }
3136 # ifdef HAVE_TAGLIB_H
3137 else if (myScreen->activeWindow() == myTagEditor->Dirs)
3138 {
3139 myTagEditor->Tags->clear();
3140 }
3141 # endif // HAVE_TAGLIB_H
3142 }
3143 }
3144
3145 }
3146