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