/*
 * Cantata
 *
 * Copyright (c) 2011-2022 Craig Drummond <craig.p.drummond@gmail.com>
 *
 */
/*
 * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and
 *                    Roeland Douma (roeland AT rullzer DOT com)
 *
 * This file is part of QtMPC.
 *
 * QtMPC is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * QtMPC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with QtMPC.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "mainwindow.h"
#include "application.h"
#include "coverdialog.h"
#include "covers.h"
#include "currentcover.h"
#include "knotification.h"
#include "models/playlistsmodel.h"
#include "mpd-interface/mpdparseutils.h"
#include "mpd-interface/mpdstats.h"
#include "preferencesdialog.h"
#include "support/inputdialog.h"
#include "support/messagebox.h"
#include "support/thread.h"
#include "trayitem.h"
#ifdef ENABLE_SIMPLE_MPD_SUPPORT
#include "mpd-interface/mpduser.h"
#endif
#include "apikeys.h"
#include "customactions.h"
#include "folderpage.h"
#include "librarypage.h"
#include "models/mpdlibrarymodel.h"
#include "models/musiclibraryitemalbum.h"
#include "models/musiclibraryitemartist.h"
#include "searchpage.h"
#include "settings.h"
#include "streams/streamdialog.h"
#include "support/gtkstyle.h"
#include "support/utils.h"
#include "widgets/mirrormenu.h"
#ifdef ENABLE_DEVICES_SUPPORT
#include "devices/actiondialog.h"
#include "devices/devicespage.h"
#include "devices/filejob.h"
#include "devices/syncdialog.h"
#include "models/devicesmodel.h"
#if defined CDDB_FOUND || defined MusicBrainz5_FOUND
#include "devices/audiocddevice.h"
#endif
#endif
#include "http/httpserver.h"
#include "online/onlineservicespage.h"
#ifdef TagLib_FOUND
#include "tags/tageditor.h"
#include "tags/tags.h"
#include "tags/trackorganiser.h"
#ifdef ENABLE_REPLAYGAIN_SUPPORT
#include "replaygain/rgdialog.h"
#endif
#endif
#include "models/streamsmodel.h"
#include "playlists/playlistspage.h"
#include "support/fancytabwidget.h"
#ifdef QT_QTDBUS_FOUND
#include "cantataadaptor.h"
#include "dbus/mpris.h"
#ifdef Q_OS_LINUX
#include "dbus/powermanagement.h"
#endif
#endif
#if !defined Q_OS_WIN && !defined Q_OS_MAC
#include "devices/mountpoints.h"
#endif
#ifdef Q_OS_MAC
#include "mac/dockmenu.h"
#include "support/osxstyle.h"
#include "support/windowmanager.h"
#ifdef MAC_MEDIAPLAYER_FOUND
#include "mac/macnowplaying.h"
#endif
#ifdef IOKIT_FOUND
#include "mac/powermanagement.h"
#endif
#endif
#include "playlists/dynamicplaylists.h"
#include "stdactions.h"
#include "support/action.h"
#include "support/actioncollection.h"
#include "support/messagewidget.h"
#include "widgets/actionitemdelegate.h"
#include "widgets/groupedview.h"
#include "widgets/icons.h"
#include "widgets/volumecontrol.h"
#ifdef ENABLE_HTTP_STREAM_PLAYBACK
#include "mpd-interface/httpstream.h"
#endif
#include <QProcess>
#include <QSet>
#include <QString>
#include <QTimer>
#include <QToolBar>
#if defined(Q_OS_WIN) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include "windows/thumbnailtoolbar.h"
#endif
#include "mediakeys.h"
#include <QActionGroup>
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QKeyEvent>
#include <QKeySequence>
#include <QLocale>
#include <QMenuBar>
#include <QSpacerItem>
#include <QStringList>
#include <algorithm>
#include <cstdlib>

static Qt::Key nextKey(Qt::Key& key)
{
	auto k = static_cast<Qt::Key>(key);
	key = static_cast<Qt::Key>(k + 1);
	if (Qt::Key_0 == key) {
		key = Qt::Key_A;
	}
	else if (Qt::Key_Colon == key) {
		key = Qt::Key_0;
	}
	return k;
}

static const char* constRatingKey = "rating";
static const char* constUserSettingProp = "user-setting-1";
static const char* constUserSetting2Prop = "user-setting-2";

MainWindow::MainWindow(QWidget* parent)
	: QMainWindow(parent), prevPage(-1), lastState(MPDState_Inactive), lastSongId(-1), autoScrollPlayQueue(true), singlePane(false), shown(false), currentPage(nullptr)
#ifdef QT_QTDBUS_FOUND
	  ,
	  mpris(nullptr)
#endif
	  ,
	  statusTimer(nullptr), playQueueSearchTimer(nullptr)
#if !defined Q_OS_WIN && !defined Q_OS_MAC
	  ,
	  mpdAccessibilityTimer(nullptr), showMenubarAction(nullptr)
#endif
	  ,
	  contextTimer(nullptr), contextSwitchTime(0), connectedState(CS_Init), stopAfterCurrent(false), responsiveSidebar(false)
#if defined(Q_OS_WIN) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
	  ,
	  thumbnailTooolbar(0)
#endif
{
	stopTrackButton = nullptr;
	coverWidget = nullptr;
	tabWidget = nullptr;
	savePlayQueueButton = nullptr;
	centerPlayQueueButton = nullptr;
	midSpacer = nullptr;

	QPoint p = pos();
	ActionCollection::setMainWidget(this);
	trayItem = new TrayItem(this);
#ifdef QT_QTDBUS_FOUND
	new CantataAdaptor(this);
	QDBusConnection::sessionBus().registerObject("/cantata", this);
#endif
	setMinimumHeight(Utils::scaleForDpi(480));
	setMinimumWidth(Utils::scaleForDpi(400));
	QWidget* widget = new QWidget(this);
	setupUi(widget);
	setCentralWidget(widget);
	messageWidget->hide();

	// Need to set these values here, as used in library/device loading...
	Song::setComposerGenres(Settings::self()->composerGenres());
	Song::setUseOriginalYear(Settings::self()->useOriginalYear());

	int hSpace = Utils::layoutSpacing(this);
	int vSpace = Utils::scaleForDpi(2);
	toolbarLayout->setContentsMargins(hSpace, vSpace, hSpace, vSpace);
	toolbar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
	setWindowTitle("Cantata");
	QWidget* tb = toolbar;
#ifdef Q_OS_MAC
	setUnifiedTitleAndToolBarOnMac(true);
	QToolBar* topToolBar = addToolBar("ToolBar");
	WindowManager* wm = new WindowManager(topToolBar);
	wm->initialize(WindowManager::WM_DRAG_MENU_AND_TOOLBAR);
	wm->registerWidgetAndChildren(topToolBar);
	topToolBar->setObjectName("MainToolBar");
	topToolBar->addWidget(toolbar);
	topToolBar->setMovable(false);
	topToolBar->setContextMenuPolicy(Qt::PreventContextMenu);

	topToolBar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
	topToolBar->setContentsMargins(0, 0, 0, 0);
	QLayout* l = topToolBar->layout();
	if (l) {
		l->setContentsMargins(0, 0, 0, 0);
		l->setSpacing(0);
	}
	topToolBar->ensurePolished();
	toolbar = topToolBar;
#elif !defined Q_OS_WIN
	QProxyStyle* proxy = qobject_cast<QProxyStyle*>(style());
	QStyle* check = proxy && proxy->baseStyle() ? proxy->baseStyle() : style();
	if (check->inherits("Kvantum::Style")) {
		QToolBar* topToolBar = addToolBar("ToolBar");
		topToolBar->setObjectName("MainToolBar");
		topToolBar->addWidget(toolbar);
		topToolBar->setMovable(false);
		topToolBar->setContextMenuPolicy(Qt::PreventContextMenu);
		topToolBar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
		topToolBar->setContentsMargins(0, 0, 0, 0);
		QLayout* l = topToolBar->layout();
		if (l) {
			l->setContentsMargins(0, 0, 0, 0);
			l->setSpacing(0);
		}
		topToolBar->ensurePolished();
		toolbar = topToolBar;
	}
	else {
		toolbar->setFixedHeight(qMax(54, (int)(fontMetrics().height() * 3.25) + (toolbarLayout->spacing() * 3) + (vSpace * 2)));
	}
#endif

	toolbar->ensurePolished();
	toolbar->adjustSize();
	coverWidget->setSize(toolbar->height() - (vSpace * 2));
	tb->setMinimumHeight(toolbar->height());
	nowPlaying->initColors();
	nowPlaying->adjustSize();
	nowPlaying->setFixedHeight(nowPlaying->height());
	volumeSlider->setColor(QApplication::palette().color(QPalette::Normal, QPalette::Text));
	Icons::self()->initIcons();

	setWindowIcon(Icons::self()->appIcon);

	prefAction = ActionCollection::get()->createAction("configure", Utils::KDE == Utils::currentDe() ? tr("Configure Cantata...") : tr("Preferences..."),
	                                                   Icons::self()->configureIcon);
	connect(prefAction, SIGNAL(triggered()), this, SLOT(showPreferencesDialog()));
	quitAction = ActionCollection::get()->createAction("quit", tr("Quit"), Icons::self()->quitIcon);
	connect(quitAction, SIGNAL(triggered()), this, SLOT(quit()));
	quitAction->setShortcut(QKeySequence::Quit);
	Action* aboutAction = ActionCollection::get()->createAction("about", tr("About Cantata..."), Icons::self()->appIcon);
	connect(aboutAction, SIGNAL(triggered()), this, SLOT(showAboutDialog()));
#ifdef Q_OS_MAC
	prefAction->setMenuRole(QAction::PreferencesRole);
	quitAction->setMenuRole(QAction::QuitRole);
	aboutAction->setMenuRole(QAction::AboutRole);
#endif
	restoreAction = new Action(tr("Show Window"), this);
	connect(restoreAction, SIGNAL(triggered()), this, SLOT(restoreWindow()));

	serverInfoAction = ActionCollection::get()->createAction("mpdinfo", tr("Server information..."), Icon::fa()->icon(fa::fa_solid, fa::fa_server));
	connect(serverInfoAction, SIGNAL(triggered()), this, SLOT(showServerInfo()));
	serverInfoAction->setEnabled(Settings::self()->firstRun());
	refreshDbAction = ActionCollection::get()->createAction("refresh", tr("Refresh Database"), Icons::self()->refreshIcon);
	doDbRefreshAction = new Action(refreshDbAction->icon(), tr("Refresh"), this);
	refreshDbAction->setEnabled(false);
	connectAction = new Action(Icons::self()->connectIcon, tr("Connect"), this);
	connectionsAction = new Action(Icon::fa(fa::fa_solid, fa::fa_server), tr("Collection"), this);
	partitionsAction = new Action(Icon::fa(fa::fa_solid, fa::fa_table_columns), tr("Partitions"), this);
	outputsAction = new Action(Icon::fa(fa::fa_solid, fa::fa_volume_high), tr("Outputs"), this);
	stopAfterTrackAction = ActionCollection::get()->createAction("stopaftertrack", tr("Stop After Track"), Icons::self()->toolbarStopIcon);

	QList<int> seeks = QList<int>() << 5 << 30 << 60;
	QList<int> seekShortcuts = QList<int>() << (int)Qt::ControlModifier << (int)Qt::ShiftModifier << (int)(Qt::ControlModifier | Qt::ShiftModifier);
	for (int i = 0; i < seeks.length(); ++i) {
		int seek = seeks.at(i);
		Action* fwdAction = ActionCollection::get()->createAction("seekfwd" + QString::number(seek), tr("Seek forward (%1 seconds)").arg(seek));
		Action* revAction = ActionCollection::get()->createAction("seekrev" + QString::number(seek), tr("Seek backward (%1 seconds)").arg(seek));
		fwdAction->setProperty("offset", seek);
		revAction->setProperty("offset", -1 * seek);
		connect(fwdAction, SIGNAL(triggered()), MPDConnection::self(), SLOT(seek()));
		connect(revAction, SIGNAL(triggered()), MPDConnection::self(), SLOT(seek()));
		addAction(fwdAction);
		addAction(revAction);
		if (i < seekShortcuts.length()) {
			fwdAction->setShortcut((Qt::RightToLeft == layoutDirection() ? Qt::Key_Left : Qt::Key_Right) | seekShortcuts.at(i));
			revAction->setShortcut((Qt::RightToLeft == layoutDirection() ? Qt::Key_Right : Qt::Key_Left) | seekShortcuts.at(i));
		}
	}

	addPlayQueueToStoredPlaylistAction = new Action(Icons::self()->playlistListIcon, tr("Add To Stored Playlist"), this);
#ifdef ENABLE_DEVICES_SUPPORT
	copyToDeviceAction = new Action(StdActions::self()->copyToDeviceAction->icon(), Utils::strippedText(StdActions::self()->copyToDeviceAction->text()), this);
	copyToDeviceAction->setMenu(DevicesModel::self()->menu()->duplicate(nullptr));
#endif
	cropPlayQueueAction = ActionCollection::get()->createAction("cropplaylist", tr("Crop Others"));
	addStreamToPlayQueueAction = ActionCollection::get()->createAction("addstreamtoplayqueue", tr("Add Stream URL"));
	addLocalFilesToPlayQueueAction = ActionCollection::get()->createAction("addlocalfiles", tr("Add Local Files"));
	clearPlayQueueAction = ActionCollection::get()->createAction("clearplaylist", tr("Clear"), Icons::self()->clearIcon);
	clearPlayQueueAction->setShortcut(QKeyCombination(Qt::ControlModifier, Qt::Key_K));
	centerPlayQueueAction = ActionCollection::get()->createAction("centerplaylist", tr("Center On Current Track"), Icons::self()->centrePlayQueueOnTrackIcon);
	expandInterfaceAction = ActionCollection::get()->createAction("expandinterface", tr("Expanded Interface"), Icon::fa(fa::fa_solid, fa::fa_expand));
	expandInterfaceAction->setCheckable(true);
	songInfoAction = ActionCollection::get()->createAction("showsonginfo", tr("Show Current Song Information"), Icons::self()->infoIcon);
	songInfoAction->setShortcut(Qt::Key_F12);
	songInfoAction->setCheckable(true);
	fullScreenAction = ActionCollection::get()->createAction("fullScreen", tr("Full Screen"), Icon::fa(fa::fa_solid, fa::fa_maximize));
#ifndef Q_OS_MAC
	fullScreenAction->setShortcut(Qt::Key_F11);
#endif
	randomPlayQueueAction = ActionCollection::get()->createAction("randomplaylist", tr("Random"), Icons::self()->shuffleIcon);
	repeatPlayQueueAction = ActionCollection::get()->createAction("repeatplaylist", tr("Repeat"), Icons::self()->repeatIcon);
	singlePlayQueueAction = ActionCollection::get()->createAction("singleplaylist", tr("Single"), Icons::self()->singleIcon, tr("When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled."));
	consumePlayQueueAction = ActionCollection::get()->createAction("consumeplaylist", tr("Consume"), Icons::self()->consumeIcon, tr("When consume is activated, a song is removed from the play queue after it has been played."));
	searchPlayQueueAction = ActionCollection::get()->createAction("searchplaylist", tr("Find in Play Queue"), Icons::self()->searchIcon);
	addAction(searchPlayQueueAction);

	searchPlayQueueAction->setShortcut(QKeyCombination(Qt::ControlModifier | Qt::ShiftModifier, Qt::Key_F));
#ifdef ENABLE_HTTP_STREAM_PLAYBACK
	streamPlayAction = ActionCollection::get()->createAction("streamplay", tr("Play HTTP Output Stream"), Icons::self()->httpStreamIcon);
	streamPlayAction->setCheckable(true);
	streamPlayAction->setChecked(false);
	streamPlayAction->setVisible(false);
#endif

	locateAction = new Action(Icons::self()->searchIcon, tr("Locate In Library"), this);
	locateArtistAction = ActionCollection::get()->createAction("locateartist", tr("Artist"));
	locateAlbumAction = ActionCollection::get()->createAction("locatealbum", tr("Album"));
	locateTrackAction = ActionCollection::get()->createAction("locatetrack", tr("Track"));
	locateArtistAction->setSettingsText(locateAction);
	locateAlbumAction->setSettingsText(locateAction);
	locateTrackAction->setSettingsText(locateAction);
	addAction(locateAction);

	QMenu* locateMenu = new QMenu();
	locateMenu->addAction(locateArtistAction);
	locateMenu->addAction(locateAlbumAction);
	locateMenu->addAction(locateTrackAction);
	locateAction->setMenu(locateMenu);

	playNextAction = ActionCollection::get()->createAction("playnext", tr("Play Next"));
	moveToBeginning = ActionCollection::get()->createAction("movetobeginning", tr("Move To Beginning"));
	moveToEnd = ActionCollection::get()->createAction("movetoend", tr("Move To End"));
#ifdef TagLib_FOUND
	editPlayQueueTagsAction = ActionCollection::get()->createAction("editpqtags", Utils::strippedText(StdActions::self()->editTagsAction->text()), StdActions::self()->editTagsAction->icon());
	editPlayQueueTagsAction->setSettingsText(tr("Edit Track Information (Play Queue)"));
#endif
	addAction(expandAllAction = ActionCollection::get()->createAction("expandall", tr("Expand All")));
	expandAllAction->setShortcut(QKeyCombination(Qt::ControlModifier, Qt::Key_Down));
	addAction(collapseAllAction = ActionCollection::get()->createAction("collapseall", tr("Collapse All")));
	collapseAllAction->setShortcut(QKeyCombination(Qt::ControlModifier, Qt::Key_Up));
	cancelAction = ActionCollection::get()->createAction("cancel", tr("Cancel"), Icons::self()->cancelIcon);
	cancelAction->setShortcut(QKeyCombination(Qt::AltModifier, Qt::Key_Escape));
	connect(cancelAction, SIGNAL(triggered()), messageWidget, SLOT(animatedHide()));

	StdActions::self()->playPauseTrackAction->setEnabled(false);
	StdActions::self()->nextTrackAction->setEnabled(false);
	updateNextTrack(-1);
	StdActions::self()->prevTrackAction->setEnabled(false);
	enableStopActions(false);
	volumeSlider->initActions();

	connectionsAction->setMenu(new QMenu(this));
	connectionsGroup = new QActionGroup(connectionsAction->menu());
	partitionsAction->setMenu(new QMenu(this));
	partitionsGroup = new QActionGroup(partitionsAction->menu());
	outputsAction->setMenu(new QMenu(this));
	outputsAction->setVisible(false);
	addPlayQueueToStoredPlaylistAction->setMenu(PlaylistsModel::self()->menu()->duplicate(nullptr));

	playPauseTrackButton->setDefaultAction(StdActions::self()->playPauseTrackAction);
	stopTrackButton->setDefaultAction(StdActions::self()->stopPlaybackAction);
	nextTrackButton->setDefaultAction(StdActions::self()->nextTrackAction);
	prevTrackButton->setDefaultAction(StdActions::self()->prevTrackAction);

	QMenu* stopMenu = new QMenu(this);
	stopMenu->addAction(StdActions::self()->stopPlaybackAction);
	stopMenu->addAction(StdActions::self()->stopAfterCurrentTrackAction);
	stopTrackButton->setMenu(stopMenu);
	stopTrackButton->setPopupMode(QToolButton::DelayedPopup);

	clearPlayQueueAction->setEnabled(false);
	centerPlayQueueAction->setEnabled(false);
	StdActions::self()->savePlayQueueAction->setEnabled(false);
	addStreamToPlayQueueAction->setEnabled(false);
	addLocalFilesToPlayQueueAction->setEnabled(false);
	clearPlayQueueButton->setDefaultAction(clearPlayQueueAction);
	savePlayQueueButton->setDefaultAction(StdActions::self()->savePlayQueueAction);
	centerPlayQueueButton->setDefaultAction(centerPlayQueueAction);
	randomButton->setDefaultAction(randomPlayQueueAction);
	repeatButton->setDefaultAction(repeatPlayQueueAction);
	singleButton->setDefaultAction(singlePlayQueueAction);
	consumeButton->setDefaultAction(consumePlayQueueAction);

	QStringList hiddenPages = Settings::self()->hiddenPages();
	playQueuePage = new PlayQueuePage(this);
	contextPage = new ContextPage(this);
	QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, playQueuePage);
	layout->setContentsMargins(0, 0, 0, 0);
	bool playQueueInSidebar = !hiddenPages.contains(playQueuePage->metaObject()->className());
	bool contextInSidebar = !hiddenPages.contains(contextPage->metaObject()->className());
	layout = new QBoxLayout(QBoxLayout::TopToBottom, contextPage);
	layout->setContentsMargins(0, 0, 0, 0);

// Build sidebar...
#define TAB_ACTION(A) A->icon(), A->text(), A->text()
	Qt::Key sidebarPageShortcutKey = Qt::Key_1;
	addAction(showPlayQueueAction = ActionCollection::get()->createAction("showplayqueue", tr("Play Queue"), Icons::self()->playqueueIcon));
	showPlayQueueAction->setShortcut(QKeyCombination(Qt::ControlModifier | Qt::ShiftModifier, Qt::Key_Q));
	tabWidget->addTab(playQueuePage, TAB_ACTION(showPlayQueueAction), playQueueInSidebar);
	connect(showPlayQueueAction, SIGNAL(triggered()), this, SLOT(showPlayQueue()));
	libraryPage = new LibraryPage(this);
	addAction(libraryTabAction = ActionCollection::get()->createAction("showlibrarytab", tr("Library"), Icons::self()->libraryIcon));

	libraryTabAction->setShortcut(QKeyCombination(Qt::ControlModifier | Qt::ShiftModifier, nextKey(sidebarPageShortcutKey)));
	tabWidget->addTab(libraryPage, TAB_ACTION(libraryTabAction), !hiddenPages.contains(libraryPage->metaObject()->className()));
	connect(libraryTabAction, SIGNAL(triggered()), this, SLOT(showLibraryTab()));
	folderPage = new FolderPage(this);
	addAction(foldersTabAction = ActionCollection::get()->createAction("showfolderstab", tr("Folders"), Icons::self()->foldersIcon));
	foldersTabAction->setShortcut(QKeyCombination(Qt::ControlModifier | Qt::ShiftModifier, nextKey(sidebarPageShortcutKey)));
	tabWidget->addTab(folderPage, TAB_ACTION(foldersTabAction), !hiddenPages.contains(folderPage->metaObject()->className()));
	connect(foldersTabAction, SIGNAL(triggered()), this, SLOT(showFoldersTab()));
	folderPage->setEnabled(!hiddenPages.contains(folderPage->metaObject()->className()));
	playlistsPage = new PlaylistsPage(this);
	addAction(playlistsTabAction = ActionCollection::get()->createAction("showplayliststab", tr("Playlists"), Icons::self()->playlistsIcon));
	playlistsTabAction->setShortcut(QKeyCombination(Qt::ControlModifier | Qt::ShiftModifier, nextKey(sidebarPageShortcutKey)));
	tabWidget->addTab(playlistsPage, TAB_ACTION(playlistsTabAction), !hiddenPages.contains(playlistsPage->metaObject()->className()));
	connect(playlistsTabAction, SIGNAL(triggered()), this, SLOT(showPlaylistsTab()));
	connect(playlistsPage, SIGNAL(error(const QString&)), SLOT(showError(const QString&)));
	connect(DynamicPlaylists::self(), SIGNAL(error(const QString&)), SLOT(showError(const QString&)));
	connect(DynamicPlaylists::self(), SIGNAL(running(bool)), dynamicLabel, SLOT(setVisible(bool)));
	connect(DynamicPlaylists::self(), SIGNAL(running(bool)), this, SLOT(controlDynamicButton()));
	stopDynamicButton->setDefaultAction(DynamicPlaylists::self()->stopAct());
	onlinePage = new OnlineServicesPage(this);
	addAction(onlineTabAction = ActionCollection::get()->createAction("showonlinetab", tr("Internet"), Icons::self()->onlineIcon));

	onlineTabAction->setShortcut(QKeyCombination(Qt::ControlModifier | Qt::ShiftModifier, nextKey(sidebarPageShortcutKey)));
	tabWidget->addTab(onlinePage, TAB_ACTION(onlineTabAction), !hiddenPages.contains(onlinePage->metaObject()->className()));
	onlinePage->setEnabled(!hiddenPages.contains(onlinePage->metaObject()->className()));
	connect(onlineTabAction, SIGNAL(triggered()), this, SLOT(showOnlineTab()));
	//    connect(onlinePage, SIGNAL(addToDevice(const QString &, const QString &, const QList<Song> &)), SLOT(copyToDevice(const QString &, const QString &, const QList<Song> &)));
	connect(onlinePage, SIGNAL(error(const QString&)), this, SLOT(showError(const QString&)));
#ifdef ENABLE_DEVICES_SUPPORT
	devicesPage = new DevicesPage(this);
	addAction(devicesTabAction = ActionCollection::get()->createAction("showdevicestab", tr("Devices"), Icons::self()->devicesIcon));
	devicesTabAction->setShortcut(QKeyCombination(Qt::ControlModifier | Qt::ShiftModifier, nextKey(sidebarPageShortcutKey)));
	tabWidget->addTab(devicesPage, TAB_ACTION(devicesTabAction), !hiddenPages.contains(devicesPage->metaObject()->className()));
	DevicesModel::self()->setEnabled(!hiddenPages.contains(devicesPage->metaObject()->className()));
	connect(devicesTabAction, SIGNAL(triggered()), this, SLOT(showDevicesTab()));
#endif
	searchPage = new SearchPage(this);
	addAction(searchTabAction = ActionCollection::get()->createAction("showsearchtab", tr("Search"), Icons::self()->searchTabIcon));
	searchTabAction->setShortcut(QKeyCombination(Qt::ControlModifier | Qt::ShiftModifier, nextKey(sidebarPageShortcutKey)));
	connect(searchTabAction, SIGNAL(triggered()), this, SLOT(showSearchTab()));
	connect(searchPage, SIGNAL(locate(QList<Song>)), this, SLOT(locateTracks(QList<Song>)));
	tabWidget->addTab(searchPage, TAB_ACTION(searchTabAction), !hiddenPages.contains(searchPage->metaObject()->className()));
	tabWidget->addTab(contextPage, Icons::self()->infoSidebarIcon, tr("Info"), songInfoAction->text(),
	                  !hiddenPages.contains(contextPage->metaObject()->className()));
	tabWidget->setStyle(Settings::self()->sidebar());

	if (playQueueInSidebar) {
		tabToggled(PAGE_PLAYQUEUE);
	}
	else {
		tabWidget->setCurrentIndex(PAGE_LIBRARY);
	}

	if (contextInSidebar) {
		tabToggled(PAGE_CONTEXT);
	}
	else {
		tabWidget->toggleTab(PAGE_CONTEXT, false);
	}

	expandInterfaceAction->setChecked(Settings::self()->showPlaylist());
	fullScreenAction->setEnabled(expandInterfaceAction->isChecked());
	if (fullScreenAction->isEnabled()) {
		fullScreenAction->setChecked(Settings::self()->showFullScreen());
	}
	randomPlayQueueAction->setCheckable(true);
	repeatPlayQueueAction->setCheckable(true);
	singlePlayQueueAction->setCheckable(true);
	consumePlayQueueAction->setCheckable(true);

	songInfoButton->setDefaultAction(songInfoAction);
	fullScreenLabel->setVisible(false);
	connect(fullScreenLabel, SIGNAL(leftClickedUrl()), fullScreenAction, SIGNAL(triggered()));
	connect(playQueueSearchWidget, SIGNAL(active(bool)), playQueue, SLOT(searchActive(bool)));
	if (Configuration(playQueuePage->metaObject()->className()).get(ItemView::constSearchActiveKey, false)) {
		playQueueSearchWidget->activate();
	}
	else {
		playQueueSearchWidget->setVisible(false);
	}
	playQueueSearchWidget->setToolTip(tr("<p>Enter a string to search artist, album, title, etc. To filter based on year, add <i>#year-range</i> to search string - e.g.</p><ul>"
	                                     "<li><b><i>#2000</i></b> return tracks from 2000</li>"
	                                     "<li><b><i>#1980-1989</i></b> return tracks from the 80's</li>"
	                                     "<li><b><i>Blah #2000</i></b> to search for string <i>Blah</i> and only return tracks from 2000</li>"
	                                     "</ul></p>"));
	QList<QToolButton*> playbackBtns = QList<QToolButton*>() << prevTrackButton << stopTrackButton << playPauseTrackButton << nextTrackButton;
	QList<QToolButton*> controlBtns = QList<QToolButton*>() << menuButton << songInfoButton;
	int playbackIconSizeNonScaled = 24 == Icons::self()->toolbarPlayIcon.actualSize(QSize(24, 24)).width() ? 24 : 28;
	int playbackIconSize = Utils::scaleForDpi(playbackIconSizeNonScaled);
	int playPauseIconSize = Utils::scaleForDpi(32);
	int controlIconSize = Utils::scaleForDpi(22);
	int controlButtonSize = Utils::scaleForDpi(32);
	int playbackButtonSize = 28 == playbackIconSizeNonScaled ? Utils::scaleForDpi(34) : controlButtonSize;

	for (QToolButton* b : controlBtns) {
		b->setAutoRaise(true);
		b->setToolButtonStyle(Qt::ToolButtonIconOnly);
		b->setFixedSize(QSize(controlButtonSize, controlButtonSize));
		b->setIconSize(QSize(controlIconSize, controlIconSize));
	}
	for (QToolButton* b : playbackBtns) {
		b->setAutoRaise(true);
		b->setToolButtonStyle(Qt::ToolButtonIconOnly);
		b->setFixedSize(QSize(playbackButtonSize, playbackButtonSize));
		b->setIconSize(QSize(playbackIconSize, playbackIconSize));
	}

	playPauseTrackButton->setIconSize(QSize(playPauseIconSize, playPauseIconSize));
	playPauseTrackButton->setFixedSize(QSize(playPauseIconSize + 6, playPauseIconSize + 6));

	QList<QWidget*> pqWidgets = QList<QWidget*>() << stopDynamicButton << dynamicLabel << playQueueStatsLabel << fullScreenLabel
												  << repeatButton << singleButton << randomButton << consumeButton << midSpacer
												  << centerPlayQueueButton << savePlayQueueButton << clearPlayQueueButton << sizeGrip;
	for (const auto& item : pqWidgets) {
		Application::fixSize(item);
	}

	if (fullScreenAction->isEnabled()) {
		fullScreenAction->setChecked(Settings::self()->showFullScreen());
	}

	randomPlayQueueAction->setChecked(false);
	repeatPlayQueueAction->setChecked(false);
	singlePlayQueueAction->setChecked(false);
	consumePlayQueueAction->setChecked(false);

	expandedSize = Settings::self()->mainWindowSize();
	collapsedSize = Settings::self()->mainWindowCollapsedSize();

	int width = playPauseTrackButton->width() * 25;
	QSize defaultSize(playPauseTrackButton->width() * 25, playPauseTrackButton->height() * 18);

	if (Settings::self()->firstRun() || (expandInterfaceAction->isChecked() && expandedSize.isEmpty())) {
		resize(defaultSize);
		splitter->setSizes(QList<int>() << width * 0.4 << width * 0.6);
	}
	else {
		if (expandInterfaceAction->isChecked()) {
			if (!expandedSize.isEmpty()) {
				if (!Settings::self()->maximized() && expandedSize.width() > 1) {
					resize(expandedSize);
					expandOrCollapse(false);
				}
				else {
					resize(expandedSize.width() > 1 ? expandedSize : defaultSize);
					// Issue #1137 Under Windows, Cantata does not restore maximized correctly if context is in sidebar
					// ...so, set maximized after shown
					QTimer::singleShot(0, this, SLOT(showMaximized()));
					if (expandedSize.width() <= 1) {
						expandedSize = defaultSize;
					}
				}
			}
		}
		else {
			if (!collapsedSize.isEmpty() && collapsedSize.width() > 1) {
				resize(collapsedSize);
				expandOrCollapse(false);
			}
		}

		if (!playQueueInSidebar) {
			QByteArray state = Settings::self()->splitterState();
			if (state.isEmpty()) {
				int width = playPauseTrackButton->width() * 25;
				splitter->setSizes(QList<int>() << width * 0.4 << width * 0.6);
			}
			else {
				splitter->restoreState(Settings::self()->splitterState());
			}
		}
		if (fullScreenAction->isChecked()) {
			fullScreen();
		}
		else {
			QPoint p = Settings::self()->mainWindowPos();
			if (!p.isNull()) {
				move(p);
			}
		}
	}

#ifndef Q_OS_WIN
#ifdef Q_OS_MAC
	bool showMenubar = true;
#else
	bool showMenubar = Utils::Gnome != Utils::currentDe();
#endif
	if (showMenubar) {
#ifdef Q_OS_MAC
		menuButton->setVisible(false);
#else
		showMenubarAction = ActionCollection::get()->createAction("showmenubar", tr("Show Menubar"));
		showMenubarAction->setShortcut(QKeyCombination(Qt::ControlModifier, Qt::Key_M));
		showMenubarAction->setCheckable(true);
		connect(showMenubarAction, SIGNAL(toggled(bool)), this, SLOT(toggleMenubar()));
#endif

		QMenu* menu = new QMenu(tr("&Music"), this);
		addMenuAction(menu, refreshDbAction);
		menu->addSeparator();
		addMenuAction(menu, connectionsAction);
		addMenuAction(menu, partitionsAction);
		addMenuAction(menu, outputsAction);
#ifdef ENABLE_HTTP_STREAM_PLAYBACK
		addMenuAction(menu, streamPlayAction);
#endif
		menu->addSeparator();
		addMenuAction(menu, quitAction);
		menuBar()->addMenu(menu);
		menu = new QMenu(tr("&Edit"), this);
		addMenuAction(menu, PlayQueueModel::self()->undoAct());
		addMenuAction(menu, PlayQueueModel::self()->redoAct());
		menu->addSeparator();
		addMenuAction(menu, StdActions::self()->searchAction);
		addMenuAction(menu, searchPlayQueueAction);
		if (Utils::KDE != Utils::currentDe()) {
			menu->addSeparator();
			addMenuAction(menu, prefAction);
		}
		menuBar()->addMenu(menu);
		if (Utils::KDE != Utils::currentDe()) {
			menu = new QMenu(tr("&View"), this);
#ifndef Q_OS_MAC
			if (showMenubarAction) {
				addMenuAction(menu, showMenubarAction);
				menu->addSeparator();
			}
#endif
			addMenuAction(menu, expandInterfaceAction);
			addMenuAction(menu, fullScreenAction);
			//addMenuAction(menu, songInfoAction);
			menuBar()->addMenu(menu);
		}
		menu = new QMenu(tr("&Queue"), this);
		addMenuAction(menu, clearPlayQueueAction);
		addMenuAction(menu, StdActions::self()->savePlayQueueAction);
		addMenuAction(menu, addStreamToPlayQueueAction);
		addMenuAction(menu, addLocalFilesToPlayQueueAction);
		menu->addSeparator();
		addMenuAction(menu, PlayQueueModel::self()->shuffleAct());
		addMenuAction(menu, PlayQueueModel::self()->sortAct());
		menuBar()->addMenu(menu);
		if (Utils::KDE == Utils::currentDe()) {
			menu = new QMenu(tr("&Settings"), this);
#ifndef Q_OS_MAC
			if (showMenubarAction) {
				addMenuAction(menu, showMenubarAction);
			}
#endif
			addMenuAction(menu, expandInterfaceAction);
			addMenuAction(menu, fullScreenAction);
			//addMenuAction(menu, songInfoAction);
			menu->addSeparator();
			addMenuAction(menu, prefAction);
			menuBar()->addMenu(menu);
		}
#ifdef Q_OS_MAC
		OSXStyle::self()->initWindowMenu(this);
#endif
		menu = new QMenu(tr("&Help"), this);
		addMenuAction(menu, serverInfoAction);
		addMenuAction(menu, aboutAction);
		menuBar()->addMenu(menu);
	}
#endif// infdef Q_OS_WIN

#ifndef Q_OS_MAC
	QMenu* mainMenu = new QMenu(this);
	mainMenu->addAction(expandInterfaceAction);
#ifndef Q_OS_WIN
	if (showMenubarAction) {
		mainMenu->addAction(showMenubarAction);
		showMenubarAction->setChecked(Settings::self()->showMenubar());
		toggleMenubar();
	}
#endif
	mainMenu->addAction(fullScreenAction);
	mainMenu->addAction(connectionsAction);
	mainMenu->addAction(partitionsAction);
	mainMenu->addAction(outputsAction);
#ifdef ENABLE_HTTP_STREAM_PLAYBACK
	mainMenu->addAction(streamPlayAction);
#endif
	mainMenu->addAction(prefAction);
	mainMenu->addAction(refreshDbAction);
	mainMenu->addSeparator();
	mainMenu->addAction(StdActions::self()->searchAction);
	mainMenu->addAction(searchPlayQueueAction);
	mainMenu->addSeparator();
	mainMenu->addAction(serverInfoAction);
	mainMenu->addAction(aboutAction);
	mainMenu->addSeparator();
	mainMenu->addAction(quitAction);
	menuButton->setIcon(Icons::self()->toolbarMenuIcon);
	menuButton->setAlignedMenu(mainMenu);
#endif// ifndef Q_OS_MAC

	dynamicLabel->setVisible(false);
	stopDynamicButton->setVisible(false);
	StdActions::self()->addWithPriorityAction->setVisible(false);
	StdActions::self()->setPriorityAction->setVisible(false);

	playQueueProxyModel.setSourceModel(PlayQueueModel::self());
	playQueue->setModel(&playQueueProxyModel);
	playQueue->addAction(playQueue->removeFromAct());
	ratingAction = new Action(tr("Set Rating"), this);
	ratingAction->setMenu(new QMenu(nullptr));
	for (int i = 0; i < ((Song::Rating_Max / Song::Rating_Step) + 1); ++i) {
		QString text;
		if (0 == i) {
			text = tr("No Rating");
		}
		else {
			for (int s = 0; s < i; ++s) {
				text += QChar(0x2605);
			}
		}
		Action* action = ActionCollection::get()->createAction(QLatin1String("rating") + QString::number(i), text);
		action->setProperty(constRatingKey, i * Song::Rating_Step);
		action->setShortcut(QKeyCombination(Qt::AltModifier, static_cast<Qt::Key>(Qt::Key_0 + i)));
		action->setSettingsText(ratingAction);
		ratingAction->menu()->addAction(action);
		connect(action, SIGNAL(triggered()), SLOT(setRating()));
	}
	playQueue->addAction(ratingAction);
	playQueue->addAction(StdActions::self()->setPriorityAction);
	playQueue->addAction(stopAfterTrackAction);
	playQueue->addAction(locateAction);
#ifdef TagLib_FOUND
	playQueue->addAction(editPlayQueueTagsAction);
#endif
	playQueue->addAction(playNextAction);
	playQueue->addAction(moveToBeginning);
	playQueue->addAction(moveToEnd);
	Action* sep = new Action(this);
	sep->setSeparator(true);
	playQueue->addAction(sep);
	playQueue->addAction(PlayQueueModel::self()->removeDuplicatesAct());
	playQueue->addAction(clearPlayQueueAction);
	playQueue->addAction(cropPlayQueueAction);
	playQueue->addAction(StdActions::self()->savePlayQueueAction);
	playQueue->addAction(addStreamToPlayQueueAction);
	playQueue->addAction(addLocalFilesToPlayQueueAction);
	playQueue->addAction(addPlayQueueToStoredPlaylistAction);
#ifdef ENABLE_DEVICES_SUPPORT
	playQueue->addAction(copyToDeviceAction);
#endif
	playQueue->addAction(PlayQueueModel::self()->shuffleAct());
	playQueue->addAction(PlayQueueModel::self()->sortAct());
	playQueue->addAction(PlayQueueModel::self()->undoAct());
	playQueue->addAction(PlayQueueModel::self()->redoAct());
	playQueue->readConfig();

#ifdef ENABLE_DEVICES_SUPPORT
	connect(DevicesModel::self(), SIGNAL(addToDevice(const QString&)), this, SLOT(addToDevice(const QString&)));
	connect(DevicesModel::self(), SIGNAL(error(const QString&)), this, SLOT(showError(const QString&)));
	connect(libraryPage, SIGNAL(addToDevice(const QString&, const QString&, const QList<Song>&)), SLOT(copyToDevice(const QString&, const QString&, const QList<Song>&)));
	connect(folderPage->mpd(), SIGNAL(addToDevice(const QString&, const QString&, const QList<Song>&)), SLOT(copyToDevice(const QString&, const QString&, const QList<Song>&)));
	connect(playlistsPage, SIGNAL(addToDevice(const QString&, const QString&, const QList<Song>&)), SLOT(copyToDevice(const QString&, const QString&, const QList<Song>&)));
	connect(devicesPage, SIGNAL(addToDevice(const QString&, const QString&, const QList<Song>&)), SLOT(copyToDevice(const QString&, const QString&, const QList<Song>&)));
	connect(searchPage, SIGNAL(addToDevice(const QString&, const QString&, const QList<Song>&)), SLOT(copyToDevice(const QString&, const QString&, const QList<Song>&)));
	connect(StdActions::self()->deleteSongsAction, SIGNAL(triggered()), SLOT(deleteSongs()));
	connect(devicesPage, SIGNAL(deleteSongs(const QString&, const QList<Song>&)), SLOT(deleteSongs(const QString&, const QList<Song>&)));
	connect(libraryPage, SIGNAL(deleteSongs(const QString&, const QList<Song>&)), SLOT(deleteSongs(const QString&, const QList<Song>&)));
	connect(folderPage->mpd(), SIGNAL(deleteSongs(const QString&, const QList<Song>&)), SLOT(deleteSongs(const QString&, const QList<Song>&)));
	connect(searchPage, SIGNAL(deleteSongs(const QString&, const QList<Song>&)), SLOT(deleteSongs(const QString&, const QList<Song>&)));
#endif
	for (QAction* act : StdActions::self()->setPriorityAction->menu()->actions()) {
		connect(act, SIGNAL(triggered()), this, SLOT(addWithPriority()));
	}
	for (QAction* act : StdActions::self()->addWithPriorityAction->menu()->actions()) {
		connect(act, SIGNAL(triggered()), this, SLOT(addWithPriority()));
	}
	connect(StdActions::self()->appendToPlayQueueAndPlayAction, SIGNAL(triggered()), this, SLOT(appendToPlayQueueAndPlay()));
	connect(StdActions::self()->addToPlayQueueAndPlayAction, SIGNAL(triggered()), this, SLOT(addToPlayQueueAndPlay()));
	connect(StdActions::self()->insertAfterCurrentAction, SIGNAL(triggered()), this, SLOT(insertIntoPlayQueue()));
	connect(MPDConnection::self(), SIGNAL(partitionsUpdated(const QList<Partition>&)), this, SLOT(partitionsUpdated(const QList<Partition>&)));
	connect(MPDConnection::self(), SIGNAL(outputsUpdated(const QList<Output>&)), this, SLOT(outputsUpdated(const QList<Output>&)));
	connect(this, SIGNAL(changePartition(QString)), MPDConnection::self(), SLOT(changePartition(QString)));
	connect(this, SIGNAL(newPartition(QString)), MPDConnection::self(), SLOT(newPartition(QString)));
	connect(this, SIGNAL(delPartition(QString)), MPDConnection::self(), SLOT(delPartition(QString)));
	connect(this, SIGNAL(enableOutput(quint32, bool)), MPDConnection::self(), SLOT(enableOutput(quint32, bool)));
	connect(this, SIGNAL(moveOutput(QString)), MPDConnection::self(), SLOT(moveOutput(QString)));
	connect(this, SIGNAL(outputs()), MPDConnection::self(), SLOT(outputs()));
	connect(this, SIGNAL(pause(bool)), MPDConnection::self(), SLOT(setPause(bool)));
	connect(this, SIGNAL(play()), MPDConnection::self(), SLOT(play()));
	connect(this, SIGNAL(stop(bool)), MPDConnection::self(), SLOT(stopPlaying(bool)));
	connect(this, SIGNAL(terminating()), MPDConnection::self(), SLOT(stop()));
	connect(this, SIGNAL(getStatus()), MPDConnection::self(), SLOT(getStatus()));
	connect(this, SIGNAL(playListInfo()), MPDConnection::self(), SLOT(playListInfo()));
	connect(this, SIGNAL(currentSong()), MPDConnection::self(), SLOT(currentSong()));
	connect(this, SIGNAL(setSeekId(qint32, quint32)), MPDConnection::self(), SLOT(setSeekId(qint32, quint32)));
	connect(this, SIGNAL(startPlayingSongId(qint32)), MPDConnection::self(), SLOT(startPlayingSongId(qint32)));
	connect(this, SIGNAL(setDetails(const MPDConnectionDetails&)), MPDConnection::self(), SLOT(setDetails(const MPDConnectionDetails&)));
	connect(this, SIGNAL(setPriority(const QList<qint32>&, quint8, bool)), MPDConnection::self(), SLOT(setPriority(const QList<qint32>&, quint8, bool)));
	connect(this, SIGNAL(addSongsToPlaylist(const QString&, const QStringList&)), MPDConnection::self(), SLOT(addToPlaylist(const QString&, const QStringList&)));
	connect(PlayQueueModel::self(), SIGNAL(statsUpdated(int, quint32)), this, SLOT(updatePlayQueueStats(int, quint32)));
	connect(PlayQueueModel::self(), SIGNAL(fetchingStreams()), playQueue, SLOT(showSpinner()));
	connect(PlayQueueModel::self(), SIGNAL(streamsFetched()), playQueue, SLOT(hideSpinner()));
	connect(PlayQueueModel::self(), SIGNAL(updateCurrent(Song)), SLOT(updateCurrentSong(Song)));
	connect(PlayQueueModel::self(), SIGNAL(streamFetchStatus(QString)), playQueue, SLOT(streamFetchStatus(QString)));
	connect(PlayQueueModel::self(), SIGNAL(error(QString)), SLOT(showError(QString)));
	connect(playQueue, SIGNAL(cancelStreamFetch()), PlayQueueModel::self(), SLOT(cancelStreamFetch()));
	connect(playQueue, SIGNAL(itemsSelected(bool)), SLOT(playQueueItemsSelected(bool)));
	connect(MPDStatus::self(), SIGNAL(updated()), this, SLOT(updateStatus()));
	connect(MPDConnection::self(), SIGNAL(playlistUpdated(const QList<Song>&, bool)), this, SLOT(updatePlayQueue(const QList<Song>&, bool)));
	connect(MPDConnection::self(), SIGNAL(currentSongUpdated(Song)), this, SLOT(updateCurrentSong(Song)));
	connect(MPDConnection::self(), SIGNAL(stateChanged(bool)), SLOT(mpdConnectionStateChanged(bool)));
	connect(MPDConnection::self(), SIGNAL(error(const QString&, bool)), SLOT(showError(const QString&, bool)));
	connect(MPDConnection::self(), SIGNAL(info(const QString&)), SLOT(showInformation(const QString&)));
	connect(MPDConnection::self(), SIGNAL(dirChanged()), SLOT(checkMpdDir()));
	connect(MPDConnection::self(), SIGNAL(connectionNotChanged(QString)), SLOT(mpdConnectionName(QString)));
	connect(MpdLibraryModel::self(), SIGNAL(error(QString)), SLOT(showError(QString)));
	connect(ApiKeys::self(), SIGNAL(error(const QString&)), SLOT(showError(const QString&)));
	connect(refreshDbAction, SIGNAL(triggered()), this, SLOT(refreshDbPromp()));
	connect(doDbRefreshAction, SIGNAL(triggered()), MpdLibraryModel::self(), SLOT(clearDb()));
	connect(doDbRefreshAction, SIGNAL(triggered()), MPDConnection::self(), SLOT(update()));
	connect(doDbRefreshAction, SIGNAL(triggered()), messageWidget, SLOT(animatedHide()));
	connect(connectAction, SIGNAL(triggered()), this, SLOT(connectToMpd()));
	connect(StdActions::self()->prevTrackAction, SIGNAL(triggered()), MPDConnection::self(), SLOT(goToPrevious()));
	connect(StdActions::self()->nextTrackAction, SIGNAL(triggered()), MPDConnection::self(), SLOT(goToNext()));
	connect(StdActions::self()->playPauseTrackAction, SIGNAL(triggered()), this, SLOT(playPauseTrack()));
	connect(StdActions::self()->stopPlaybackAction, SIGNAL(triggered()), this, SLOT(stopPlayback()));
	connect(StdActions::self()->stopAfterCurrentTrackAction, SIGNAL(triggered()), this, SLOT(stopAfterCurrentTrack()));
	connect(stopAfterTrackAction, SIGNAL(triggered()), this, SLOT(stopAfterTrack()));
	connect(this, SIGNAL(setVolume(int)), MPDConnection::self(), SLOT(setVolume(int)));
	connect(nowPlaying, SIGNAL(sliderReleased()), this, SLOT(setPosition()));
	connect(PlayQueueModel::self(), SIGNAL(currentSongRating(QString, quint8)), nowPlaying, SLOT(rating(QString, quint8)));
	connect(randomPlayQueueAction, SIGNAL(triggered(bool)), MPDConnection::self(), SLOT(setRandom(bool)));
	connect(repeatPlayQueueAction, SIGNAL(triggered(bool)), MPDConnection::self(), SLOT(setRepeat(bool)));
	connect(singlePlayQueueAction, SIGNAL(triggered(bool)), MPDConnection::self(), SLOT(setSingle(bool)));
	connect(consumePlayQueueAction, SIGNAL(triggered(bool)), MPDConnection::self(), SLOT(setConsume(bool)));
#ifdef ENABLE_HTTP_STREAM_PLAYBACK
	connect(streamPlayAction, SIGNAL(triggered(bool)), HttpStream::self(), SLOT(setEnabled(bool)));
	connect(MPDConnection::self(), SIGNAL(streamUrl(QString)), SLOT(streamUrl(QString)));
#endif
	connect(playQueueSearchWidget, SIGNAL(returnPressed()), this, SLOT(searchPlayQueue()));
	connect(playQueueSearchWidget, SIGNAL(textChanged(const QString)), this, SLOT(searchPlayQueue()));
	connect(playQueueSearchWidget, SIGNAL(active(bool)), this, SLOT(playQueueSearchActivated(bool)));
	connect(playQueue, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(playQueueItemActivated(const QModelIndex&)));
	connect(StdActions::self()->removeAction, SIGNAL(triggered()), this, SLOT(removeItems()));
	connect(StdActions::self()->appendToPlayQueueAction, SIGNAL(triggered()), this, SLOT(appendToPlayQueue()));
	connect(StdActions::self()->replacePlayQueueAction, SIGNAL(triggered()), this, SLOT(replacePlayQueue()));
	connect(playQueue->removeFromAct(), SIGNAL(triggered()), this, SLOT(removeFromPlayQueue()));
	connect(clearPlayQueueAction, SIGNAL(triggered()), playQueueSearchWidget, SLOT(clear()));
	connect(clearPlayQueueAction, SIGNAL(triggered()), this, SLOT(clearPlayQueue()));
	connect(centerPlayQueueAction, SIGNAL(triggered()), this, SLOT(centerPlayQueue()));
	connect(cropPlayQueueAction, SIGNAL(triggered()), this, SLOT(cropPlayQueue()));
	connect(songInfoAction, SIGNAL(triggered()), this, SLOT(showSongInfo()));
	connect(expandInterfaceAction, SIGNAL(triggered()), this, SLOT(expandOrCollapse()));
	connect(fullScreenAction, SIGNAL(triggered()), this, SLOT(fullScreen()));
#ifdef TagLib_FOUND
	connect(StdActions::self()->editTagsAction, SIGNAL(triggered()), this, SLOT(editTags()));
	connect(editPlayQueueTagsAction, SIGNAL(triggered()), this, SLOT(editTags()));
	connect(StdActions::self()->organiseFilesAction, SIGNAL(triggered()), SLOT(organiseFiles()));
#endif
	connect(context, SIGNAL(findArtist(QString)), this, SLOT(locateArtist(QString)));
	connect(context, SIGNAL(findAlbum(QString, QString)), this, SLOT(locateAlbum(QString, QString)));
	connect(context, SIGNAL(playSong(QString)), PlayQueueModel::self(), SLOT(playSong(QString)));
	connect(locateTrackAction, SIGNAL(triggered()), this, SLOT(locateTrack()));
	connect(locateAlbumAction, SIGNAL(triggered()), this, SLOT(locateTrack()));
	connect(locateArtistAction, SIGNAL(triggered()), this, SLOT(locateTrack()));
	connect(this, SIGNAL(playNext(QList<quint32>, quint32, quint32)), MPDConnection::self(), SLOT(move(QList<quint32>, quint32, quint32)));
	connect(playNextAction, SIGNAL(triggered()), this, SLOT(moveSelectionAfterCurrentSong()));
	connect(moveToBeginning, SIGNAL(triggered()), this, SLOT(moveSelectionAtQueueStart()));
	connect(moveToEnd, SIGNAL(triggered()), this, SLOT(moveSelectionAtQueueEnd()));
	connect(qApp, SIGNAL(paletteChanged(const QPalette&)), this, SLOT(paletteChanged()));

	connect(StdActions::self()->searchAction, SIGNAL(triggered()), SLOT(showSearch()));
	connect(searchPlayQueueAction, SIGNAL(triggered()), this, SLOT(showPlayQueueSearch()));
	connect(playQueue, SIGNAL(focusSearch(QString)), playQueueSearchWidget, SLOT(activate(QString)));
	connect(expandAllAction, SIGNAL(triggered()), this, SLOT(expandAll()));
	connect(collapseAllAction, SIGNAL(triggered()), this, SLOT(collapseAll()));
	connect(addStreamToPlayQueueAction, SIGNAL(triggered()), this, SLOT(addStreamToPlayQueue()));
	connect(addLocalFilesToPlayQueueAction, SIGNAL(triggered()), this, SLOT(addLocalFilesToPlayQueue()));
	connect(splitter, SIGNAL(splitterMoved(int, int)), this, SLOT(controlPlayQueueButtons()));
	connect(StdActions::self()->setCoverAction, SIGNAL(triggered()), SLOT(setCover()));
#ifdef ENABLE_REPLAYGAIN_SUPPORT
	connect(StdActions::self()->replaygainAction, SIGNAL(triggered()), SLOT(replayGain()));
#endif
	connect(PlaylistsModel::self(), SIGNAL(addToNew()), this, SLOT(addToNewStoredPlaylist()));
	connect(PlaylistsModel::self(), SIGNAL(addToExisting(const QString&)), this, SLOT(addToExistingStoredPlaylist(const QString&)));
	// TODO: Why is this here???
	//    connect(playlistsPage, SIGNAL(add(const QStringList &, bool, quint8)), PlayQueueModel::self(), SLOT(addItems(const QStringList &, bool, quint8)));
	connect(coverWidget, SIGNAL(clicked()), expandInterfaceAction, SLOT(trigger()));
#if !defined Q_OS_WIN && !defined Q_OS_MAC
	connect(MountPoints::self(), SIGNAL(updated()), SLOT(checkMpdAccessibility()));
	connect(qApp, SIGNAL(commitDataRequest(QSessionManager&)), SLOT(commitDataRequest(QSessionManager&)));
#endif
	playQueueItemsSelected(false);
	playQueue->setFocus();

	MPDConnection::self()->start();
	connectToMpd();

	QString page = Settings::self()->page();
	for (int i = 0; i < tabWidget->count(); ++i) {
		if (tabWidget->widget(i)->metaObject()->className() == page) {
			tabWidget->setCurrentIndex(i);
			break;
		}
	}

	connect(tabWidget, SIGNAL(currentChanged(int)), this, SLOT(currentTabChanged(int)));
	connect(tabWidget, SIGNAL(tabToggled(int)), this, SLOT(tabToggled(int)));
	connect(tabWidget, SIGNAL(styleChanged(int)), this, SLOT(sidebarModeChanged()));

	readSettings();
	updateConnectionsMenu();
	ActionCollection::get()->readSettings();

	if (testAttribute(Qt::WA_TranslucentBackground)) {
		// BUG: 146 - Work-around non-showing main window on start-up with transparent QtCurve windows.
		move(p.isNull() ? QPoint(96, 96) : p);
	}

	currentTabChanged(tabWidget->currentIndex());

	if (Settings::self()->firstRun() && MPDConnection::self()->isConnected()) {
		mpdConnectionStateChanged(true);
	}
	MediaKeys::self()->start();
#ifdef Q_OS_MAC
	dockMenu = new DockMenu(this);
#endif
#ifdef MAC_MEDIAPLAYER_FOUND
	macNowPlaying = new MacNowPlaying(this);
#endif
	updateActionToolTips();
	CustomActions::self()->setMainWindow(this);

	if (Utils::useSystemTray() && Settings::self()->startHidden()) {
		hide();
	}
	else {
		show();
	}
	QTimer::singleShot(0, this, SLOT(controlPlayQueueButtons()));
}

MainWindow::~MainWindow()
{
#if !defined Q_OS_WIN && !defined Q_OS_MAC
	if (showMenubarAction) {
		Settings::self()->saveShowMenubar(showMenubarAction->isChecked());
	}
#endif
#ifdef ENABLE_HTTP_STREAM_PLAYBACK
	HttpStream::self()->save();
#endif
	bool hadCantataStreams = PlayQueueModel::self()->removeCantataStreams();
	Settings::self()->saveShowFullScreen(fullScreenAction->isChecked());
	if (!fullScreenAction->isChecked()) {
		if (expandInterfaceAction->isChecked()) {
			Settings::self()->saveMaximized(isMaximized());
			Settings::self()->saveMainWindowSize(isMaximized() ? previousSize : size());
		}
		else {
			Settings::self()->saveMaximized(false);
			Settings::self()->saveMainWindowSize(expandedSize);
		}
		Settings::self()->saveMainWindowCollapsedSize(expandInterfaceAction->isChecked() ? collapsedSize : size());
		Settings::self()->saveShowPlaylist(expandInterfaceAction->isChecked());
		Settings::self()->saveMainWindowPos(pos());
	}
#ifdef ENABLE_HTTP_STREAM_PLAYBACK
	Settings::self()->savePlayStream(streamPlayAction->isVisible() && streamPlayAction->isChecked());
#endif
	if (!fullScreenAction->isChecked()) {
		if (!tabWidget->isEnabled(PAGE_PLAYQUEUE)) {
			Settings::self()->saveSplitterState(splitter->saveState());
		}
	}
	Settings::self()->savePage(tabWidget->currentWidget()->metaObject()->className());
	playQueue->saveConfig();
	//    playlistsPage->saveConfig();
	context->saveConfig();
	StreamsModel::self()->save();
	nowPlaying->saveConfig();
	Settings::self()->saveForceSingleClick(TreeView::getForceSingleClick());
	if (Utils::useSystemTray()) {
		Settings::StartupState startupState = Settings::self()->startupState();
		Settings::self()->saveStartHidden(trayItem->isActive() && Settings::self()->minimiseOnClose() && ((isHidden() && Settings::SS_ShowMainWindow != startupState) || (Settings::SS_HideMainWindow == startupState)));
	}
	Settings::self()->save();
	disconnect(MPDConnection::self(), nullptr, nullptr, nullptr);
	if (Settings::self()->stopOnExit()) {
		DynamicPlaylists::self()->stop();
		emit terminating();
		Utils::sleep();// Allow time for stop to be sent...
#ifdef ENABLE_SIMPLE_MPD_SUPPORT
		// Hide this waindow, as we /might/ delay for up to 2 seconds waiting
		// for MPD to terminate...
		setVisible(false);
		MPDUser::self()->stop();
#endif
	}
	else if (hadCantataStreams) {
		Utils::sleep();// Allow time for removal of cantata streams...
	}
#ifdef ENABLE_DEVICES_SUPPORT
	DevicesModel::self()->stop();
#if defined CDDB_FOUND || defined MusicBrainz5_FOUND
	Covers::self()->cleanCdda();
#endif
#endif
	MediaKeys::self()->stop();
#ifdef TagLib_FOUND
	Tags::stop();
#endif
	ThreadCleaner::self()->stopAll();
	Configuration(playQueuePage->metaObject()->className()).set(ItemView::constSearchActiveKey, playQueueSearchWidget->isActive());
}

QList<Song> MainWindow::selectedSongs() const
{
	return currentPage ? currentPage->selectedSongs() : QList<Song>();
}

QStringList MainWindow::listActions() const
{
	QStringList allActions;
	for (int i = 0; i < ActionCollection::get()->actions().count(); i++) {
		Action* action = qobject_cast<Action*>(ActionCollection::get()->actions().at(i));
		if (!action) {
			continue;
		}
		allActions.append(action->objectName() + " - " + Action::settingsText(action));
	}
	return allActions;
}

void MainWindow::triggerAction(const QString& name)
{
	QAction* act = ActionCollection::get()->action(name);
	if (act) {
		act->trigger();
	}
}

#if !defined Q_OS_WIN
void MainWindow::addMenuAction(QMenu* menu, QAction* action)
{
	menu->addAction(action);
	addAction(action);// Bind action to window, so that it works when fullscreen!
}
#endif

void MainWindow::showError(const QString& message, bool showActions)
{
	if (!message.isEmpty()) {
		if (!isVisible()) {
			show();
		}
		expand();
	}

	if (QLatin1String("NO_SONGS") == message) {
		messageWidget->setError(tr("Failed to locate any songs matching the dynamic playlist rules."));
	}
	else {
		messageWidget->setError(message);
	}
	if (showActions) {
		messageWidget->setActions(QList<QAction*>() << prefAction << connectAction);
	}
	else {
		messageWidget->removeAllActions();
	}
	QApplication::alert(this);
}

void MainWindow::showInformation(const QString& message)
{
	if (!messageWidget->showingError()) {
		messageWidget->setInformation(message);
		messageWidget->removeAllActions();
	}
}

void MainWindow::mpdConnectionStateChanged(bool connected)
{
	serverInfoAction->setEnabled(connected && !MPDConnection::self()->isMopidy());
	refreshDbAction->setEnabled(connected);
	addStreamToPlayQueueAction->setEnabled(connected);
	addLocalFilesToPlayQueueAction->setEnabled(connected && (HttpServer::self()->isAlive() || MPDConnection::self()->localFilePlaybackSupported()));
	partitionsAction->setVisible(connected && MPDConnection::self()->canUsePartitions());

	if (connected) {
		//if (!messageWidget->showingError()) {
		messageWidget->hide();
		//}
		if (CS_Connected != connectedState) {
			emit playListInfo();
			emit outputs();
			MpdLibraryModel::self()->reload();
			if (CS_Init != connectedState) {
				currentTabChanged(tabWidget->currentIndex());
			}
			connectedState = CS_Connected;
			StdActions::self()->addWithPriorityAction->setVisible(MPDConnection::self()->canUsePriority());
			StdActions::self()->setPriorityAction->setVisible(MPDConnection::self()->canUsePriority());
		}
	}
	else {
		libraryPage->clear();
		folderPage->mpd()->clear();
		PlaylistsModel::self()->clear();
		PlayQueueModel::self()->clear();
		searchPage->clear();
		connectedState = CS_Disconnected;
		outputsAction->setVisible(false);
		MPDStatus dummyStatus;
		updateStatus(&dummyStatus);
	}
	updateWindowTitle();
}

void MainWindow::keyPressEvent(QKeyEvent* event)
{
	if ((Qt::Key_Enter == event->key() || Qt::Key_Return == event->key()) && playQueue->hasFocus()) {
		QModelIndexList selection = playQueue->selectedIndexes();
		if (!selection.isEmpty()) {
			//play the first selected song
			playQueueItemActivated(selection.first());
		}
	}
}

void MainWindow::showEvent(QShowEvent* event)
{
#if defined(Q_OS_WIN) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
	if (!thumbnailTooolbar) {
		thumbnailTooolbar = new ThumbnailToolBar(this);
	}
#endif
	QMainWindow::showEvent(event);
	controlView();
	if (!shown) {
		shown = true;
		// Work-around for qt5ct palette issues...
		QTimer::singleShot(0, this, SLOT(paletteChanged()));
	}
}

void MainWindow::closeEvent(QCloseEvent* event)
{
	if (trayItem->isActive() && Settings::self()->minimiseOnClose() && event->spontaneous()) {
		lastPos = pos();
		hide();
		event->ignore();
	}
	else if (canClose()) {
		QMainWindow::closeEvent(event);
	}
}

void MainWindow::resizeEvent(QResizeEvent* event)
{
	previousSize = event->oldSize();
	QMainWindow::resizeEvent(event);
	controlView();
}

void MainWindow::playQueueItemsSelected(bool s)
{
	int rc = playQueue->model() ? playQueue->model()->rowCount() : 0;
	bool haveItems = rc > 0;
	bool singleSelection = 1 == playQueue->selectedIndexes(false).count();// Dont need sorted selection here...
	playQueue->removeFromAct()->setEnabled(s && haveItems);
	StdActions::self()->setPriorityAction->setEnabled(s && haveItems);
	locateAction->setEnabled(singleSelection);
	cropPlayQueueAction->setEnabled(playQueue->haveUnSelectedItems() && haveItems);
#ifdef TagLib_FOUND
	editPlayQueueTagsAction->setEnabled(s && haveItems && MPDConnection::self()->getDetails().dirReadable);
#endif
	addPlayQueueToStoredPlaylistAction->setEnabled(haveItems);
#ifdef ENABLE_DEVICES_SUPPORT
	copyToDeviceAction->setEnabled(editPlayQueueTagsAction->isEnabled());
#endif
	stopAfterTrackAction->setEnabled(singleSelection);
	ratingAction->setEnabled(s && haveItems);
}

void MainWindow::connectToMpd(const MPDConnectionDetails& details)
{
	if (!MPDConnection::self()->isConnected() || details != MPDConnection::self()->getDetails()) {
		libraryPage->clear();
		folderPage->mpd()->clear();
		PlaylistsModel::self()->clear();
		PlayQueueModel::self()->clear();
		searchPage->clear();
		if (!MPDConnection::self()->getDetails().isEmpty() && details != MPDConnection::self()->getDetails()) {
			DynamicPlaylists::self()->stop();
		}
		showInformation(tr("Connecting to %1").arg(details.description()));
		outputsAction->setVisible(false);
		if (CS_Init != connectedState) {
			connectedState = CS_Disconnected;
		}
	}
	emit setDetails(details);
}

void MainWindow::connectToMpd()
{
	messageWidget->hide();
	connectToMpd(Settings::self()->connectionDetails());
}

void MainWindow::streamUrl(const QString& u)
{
#ifdef ENABLE_HTTP_STREAM_PLAYBACK
	streamPlayAction->setVisible(!u.isEmpty());
	streamPlayAction->setChecked(streamPlayAction->isVisible() && Settings::self()->playStream());
	HttpStream::self()->setEnabled(streamPlayAction->isChecked());
#else
	Q_UNUSED(u)
#endif
}

void MainWindow::refreshDbPromp()
{
	int btnLayout = style()->styleHint(QStyle::SH_DialogButtonLayout);
	if (QDialogButtonBox::GnomeLayout == btnLayout || QDialogButtonBox::MacLayout == btnLayout) {
		messageWidget->setActions(QList<QAction*>() << cancelAction << doDbRefreshAction);
	}
	else {
		messageWidget->setActions(QList<QAction*>() << doDbRefreshAction << cancelAction);
	}
	messageWidget->setWarning(tr("Refresh MPD Database?"), false);
	expand();
}

void MainWindow::showAboutDialog()
{
	QMessageBox::about(this, tr("About Cantata"),
	                   tr("<b>Cantata %1</b><br/><br/>MPD client.<br/><br/>"
	                      "&copy; 2011-2022 Craig Drummond, 2024 Cantata Contributors<br/>Released under the <a href=\"http://www.gnu.org/licenses/gpl.html\">GPLv3</a>")
	                                   .arg(PACKAGE_VERSION_STRING)
	                           + QLatin1String("<br/><br/>") + tr("Please refer to <a href=\"https://github.com/nullobsi/cantata/issues\">Cantata's issue tracker</a> for a list of known issues, and to report new issues.") + QLatin1String("<br/><br/><i><small>") + tr("Based upon <a href=\"http://lowblog.nl\">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/>") + tr("Context view backdrops courtesy of <a href=\"http://www.fanart.tv\">FanArt.tv</a>") + QLatin1String("<br/>") + tr("Context view metadata courtesy of <a href=\"http://www.wikipedia.org\">Wikipedia</a> and <a href=\"http://www.last.fm\">Last.fm</a>") + QLatin1String("<br/><br/>") + tr("Please consider uploading your own music fan-art to <a href=\"http://www.fanart.tv\">FanArt.tv</a>") + QLatin1String("</small></i>"));
}

bool MainWindow::canClose()
{
	if (onlinePage->isDownloading() && MessageBox::No == MessageBox::warningYesNo(this, tr("A Podcast is currently being downloaded\n\nQuitting now will abort the download."), QString(), GuiItem(tr("Abort download and quit")), GuiItem("Do not quit just yet"))) {
		return false;
	}
	onlinePage->cancelAll();
	return true;
}

void MainWindow::expand()
{
	if (!expandInterfaceAction->isChecked()) {
		expandInterfaceAction->setChecked(true);
		expandOrCollapse();
	}
}

bool MainWindow::canShowDialog()
{
	if (PreferencesDialog::instanceCount() || CoverDialog::instanceCount()
#ifdef TagLib_FOUND
	    || TagEditor::instanceCount() || TrackOrganiser::instanceCount()
#endif
#ifdef ENABLE_DEVICES_SUPPORT
	    || ActionDialog::instanceCount() || SyncDialog::instanceCount()
#endif
#ifdef ENABLE_REPLAYGAIN_SUPPORT
	    || RgDialog::instanceCount()
#endif
	) {
		MessageBox::error(this, tr("Please close other dialogs first."));
		return false;
	}
	return true;
}

void MainWindow::showPreferencesDialog(const QString& page)
{
	if (PreferencesDialog::instanceCount()) {
		emit showPreferencesPage(page.isEmpty() ? "collection" : page);
	}
	if (PreferencesDialog::instanceCount() || !canShowDialog()) {
		return;
	}
	PreferencesDialog* pref = new PreferencesDialog(this);
	controlConnectionsMenu(false);
	connect(pref, SIGNAL(settingsSaved()), this, SLOT(updateSettings()));
	connect(pref, SIGNAL(destroyed()), SLOT(controlConnectionsMenu()));
	connect(this, SIGNAL(showPreferencesPage(QString)), pref, SLOT(showPage(QString)));
	if (!page.isEmpty()) {
		pref->showPage(page);
	}
	pref->show();
}

void MainWindow::quit()
{
	if (!canClose()) {
		return;
	}
#ifdef ENABLE_REPLAYGAIN_SUPPORT
	if (RgDialog::instanceCount()) {
		return;
	}
#endif
#ifdef TagLib_FOUND
	if (TagEditor::instanceCount() || 0 != TrackOrganiser::instanceCount()) {
		return;
	}
#endif
#ifdef ENABLE_DEVICES_SUPPORT
	if (ActionDialog::instanceCount() || SyncDialog::instanceCount()) {
		return;
	}
#endif
	qApp->quit();
}

void MainWindow::commitDataRequest(QSessionManager& mgr)
{
	Q_UNUSED(mgr)
#if !defined Q_OS_WIN && !defined Q_OS_MAC
	if (isVisible() && trayItem->isActive() && Settings::self()->minimiseOnClose()) {
		// Issue 1183 If We are using a tray item, and have window open - then cantata intercepts close events
		// and hides the window. This seems to prevent the logout dialog appwaring under GNOME Shell.
		// To work-around this if qApp emits commitDataRequest and our window is open, them close.
		QMainWindow::close();
	}
#endif
}

void MainWindow::checkMpdDir()
{
#ifdef Q_OS_LINUX
	if (mpdAccessibilityTimer) {
		mpdAccessibilityTimer->stop();
	}
	MPDConnection::self()->setDirReadable();
#endif

#ifdef TagLib_FOUND
	if (StdActions::self()->editTagsAction->isEnabled()) {
		StdActions::self()->editTagsAction->setEnabled(MPDConnection::self()->getDetails().dirReadable);
	}
#endif
	if (currentPage) {
		currentPage->controlActions();
	}
}

void MainWindow::partitionsUpdated(const QList<Partition>& partitions)
{
	QMenu* menu = partitionsAction->menu();
	menu->clear();

	const QString& current = MPDStatus::self()->partition();
	QList<Partition> sortedPartitions = partitions;
	std::sort(sortedPartitions.begin(), sortedPartitions.end());
	for (const Partition& p : sortedPartitions) {
		QAction* act = menu->addAction(Utils::escapeActionText(p.name), this, SLOT(selectPartition()));
		act->setData(p.name);
		act->setCheckable(true);
		act->setChecked(p.name == current);
		act->setActionGroup(partitionsGroup);
	}
	menu->addSeparator();
	menu->addAction(tr("Create new partition"), this, SLOT(createNewPartition()));
	QMenu* deleteMenu = menu->addMenu(tr("Remove partition"));
	deleteMenu->menuAction()->setData("deletepartition");
	for (const Partition& p : sortedPartitions) {
		QAction* act = deleteMenu->addAction(Utils::escapeActionText(p.name), this, SLOT(deleteAPartition()));
		act->setData(p.name);
		act->setVisible(p.name != current);
	}
	deleteMenu->menuAction()->setVisible(deleteMenu->actions().count() > 1);

	partitionsAction->setVisible(partitions.count() > 0);
	trayItem->updatePartitions();
}

void MainWindow::outputsUpdated(const QList<Output>& outputs)
{
	const char* constMpdConName = "mpd-name";
	const char* constMpdPartitionName = "mpd-partition";
	const char* constMpdEnabledOuptuts = "mpd-outputs";
	QString lastConn = property(constMpdConName).toString();
	QString lastPart = property(constMpdPartitionName).toString();
	QString newConn = MPDConnection::self()->getDetails().name;
	QString newPart = MPDConnection::self()->getDetails().partition;
	setProperty(constMpdConName, newConn);
	setProperty(constMpdPartitionName, newPart);
	outputsAction->setVisible(true);
	QSet<QString> enabledMpd;
	QSet<QString> inCurrentPartitionMpd;
	QSet<QString> inOtherPartitionMpd;
	QSet<QString> lastEnabledMpd = Utils::listToSet(property(constMpdEnabledOuptuts).toStringList());
	QSet<QString> menuItems;
	QSet<QString> menuMoveableItems;
	QMenu* menu = outputsAction->menu();
	for (const Output& o : outputs) {
		if (o.inCurrentPartition) {
			if (o.enabled) {
				enabledMpd.insert(o.name);
			}
			inCurrentPartitionMpd.insert(o.name);
		}
		else {
			inOtherPartitionMpd.insert(o.name);
		}
	}

	for (QAction* act : menu->actions()) {
		if (act->isCheckable()) {
			menuItems.insert(act->data().toString());
		}
		else if (act->menu() && act->data().toString() == "moveoutput") {
			for (QAction* subAct : act->menu()->actions()) {
				menuMoveableItems.insert(subAct->data().toString());
			}
		}
	}

	if (menuItems != inCurrentPartitionMpd || menuMoveableItems != inOtherPartitionMpd) {
		menu->clear();
		QList<Output> out = outputs;
		std::sort(out.begin(), out.end());
		Qt::Key i = Qt::Key_1;
		for (const Output& o : out) {
			if (!o.inCurrentPartition) {
				continue;
			}
			QAction* act = menu->addAction(Utils::escapeActionText(o.name), this, SLOT(toggleOutput()));
			act->setData(o.id);
			act->setCheckable(true);
			act->setChecked(o.enabled);
			act->setShortcut(QKeyCombination(Qt::ControlModifier | Qt::AltModifier, nextKey(i)));
		}
		menu->addSeparator();
		QMenu* moveMenu = menu->addMenu(tr("Move output to this partition"));
		moveMenu->menuAction()->setData("moveoutput");
		moveMenu->menuAction()->setVisible(inOtherPartitionMpd.count() > 0);
		for (const Output& o : out) {
			if (o.inCurrentPartition) {
				continue;
			}
			QAction* act = moveMenu->addAction(Utils::escapeActionText(o.name), this, SLOT(moveOutputToThisPartition()));
			act->setData(o.name);
		}
	}
	else {
		for (const Output& o : outputs) {
			if (!o.inCurrentPartition) {
				continue;
			}
			for (QAction* act : menu->actions()) {
				if (act->isCheckable() && Utils::strippedText(act->text()) == o.name) {
					act->setChecked(o.enabled);
					break;
				}
			}
		}
	}

	if (newConn == lastConn && newPart == lastPart && enabledMpd != lastEnabledMpd && !menuItems.isEmpty()) {
		QSet<QString> switchedOn = enabledMpd - lastEnabledMpd;
		QSet<QString> switchedOff = lastEnabledMpd - enabledMpd;

		KNotification* notif = new KNotification("changeOutput");
		notif->setTitle(tr("Outputs"));

		if (!switchedOn.isEmpty() && switchedOff.isEmpty()) {
			QStringList names = switchedOn.values();
			std::sort(names.begin(), names.end());
			notif->setText(tr("Enabled: %1").arg(names.join(QLatin1String(", "))));
		}
		else if (!switchedOff.isEmpty() && switchedOn.isEmpty()) {
			QStringList names = switchedOff.values();
			std::sort(names.begin(), names.end());
			notif->setText(tr("Disabled: %1").arg(names.join(QLatin1String(", "))));
		}
		else if (!switchedOn.isEmpty() && !switchedOff.isEmpty()) {
			QStringList on = switchedOn.values();
			std::sort(on.begin(), on.end());
			QStringList off = switchedOff.values();
			std::sort(off.begin(), off.end());
			notif->setText(tr("Enabled: %1").arg(on.join(QLatin1String(", "))) + QLatin1Char('\n') + tr("Disabled: %1").arg(off.join(QLatin1String(", "))));
		}
		notif->sendEvent();
	}
	setProperty(constMpdEnabledOuptuts, QStringList() << enabledMpd.values());
	outputsAction->setVisible(outputs.count() > (MPDConnection::self()->canUsePartitions() ? 0 : 1));
	trayItem->updateOutputs();
}

void MainWindow::updateConnectionsMenu()
{
	QList<MPDConnectionDetails> connections = Settings::self()->allConnections();
	if (connections.count() < 2) {
		connectionsAction->setVisible(false);
	}
	else {
		connectionsAction->setVisible(true);
		QString currentConn = Settings::self()->currentConnection();
		QSet<QString> cfg;
		QSet<QString> menuItems;
		QMenu* menu = connectionsAction->menu();
		for (const MPDConnectionDetails& d : connections) {
			cfg.insert(d.name);
		}

		for (QAction* act : menu->actions()) {
			menuItems.insert(act->data().toString());
			act->setChecked(act->data().toString() == currentConn);
		}

		if (menuItems != cfg) {
			menu->clear();
			std::sort(connections.begin(), connections.end());
			Qt::Key i = Qt::Key_1;
			for (const MPDConnectionDetails& d : connections) {
				QAction* act = menu->addAction(Utils::escapeActionText(d.getName()), this, SLOT(changeConnection()));
				act->setData(d.name);
				act->setCheckable(true);
				act->setChecked(d.name == currentConn);
				act->setActionGroup(connectionsGroup);
				act->setShortcut(QKeyCombination(Qt::ControlModifier, nextKey(i)));
			}
		}
	}
	trayItem->updateConnections();
}

void MainWindow::controlConnectionsMenu(bool enable)
{
	if (enable) {
		updateConnectionsMenu();
	}
	for (QAction* act : connectionsAction->menu()->actions()) {
		act->setEnabled(enable);
	}
}

void MainWindow::controlDynamicButton()
{
	stopDynamicButton->setVisible(DynamicPlaylists::self()->isRunning());
	PlayQueueModel::self()->enableUndo(!DynamicPlaylists::self()->isRunning());
}

void MainWindow::setRating()
{
	Action* act = qobject_cast<Action*>(sender());
	if (act) {
		PlayQueueModel::self()->setRating(playQueueProxyModel.mapToSourceRows(playQueue->selectedIndexes()), act->property(constRatingKey).toUInt());
	}
}

void MainWindow::initMpris()
{
#ifdef QT_QTDBUS_FOUND
	if (Settings::self()->mpris()) {
		if (!mpris) {
			mpris = new Mpris(this);
			connect(mpris, SIGNAL(showMainWindow()), this, SLOT(restoreWindow()));
		}
	}
	else if (mpris) {
		disconnect(mpris, SIGNAL(showMainWindow()), this, SLOT(restoreWindow()));
		mpris->deleteLater();
		mpris = nullptr;
	}
	CurrentCover::self()->setEnabled(mpris || Settings::self()->showPopups() || 0 != Settings::self()->playQueueBackground() || Settings::self()->showCoverWidget());
#endif
}

void MainWindow::toggleMenubar()
{
#if !defined Q_OS_WIN && !defined Q_OS_MAC
	if (showMenubarAction) {
		menuButton->setVisible(!showMenubarAction->isChecked());
		menuBar()->setVisible(showMenubarAction->isChecked());
		setCollapsedSize();
	}
#endif
}

void MainWindow::paletteChanged()
{
	QColor textColor = QApplication::palette().color(QPalette::Normal, QPalette::Text);

	nowPlaying->initColors();
	volumeSlider->setColor(textColor);

	Icons::self()->initIcons();

	StdActions::self()->prevTrackAction->setIcon(Icons::self()->toolbarPrevIcon);
	StdActions::self()->nextTrackAction->setIcon(Icons::self()->toolbarNextIcon);
	StdActions::self()->playPauseTrackAction->setIcon(MPDState_Playing == MPDStatus::self()->state() ? Icons::self()->toolbarPauseIcon : Icons::self()->toolbarPlayIcon);
	StdActions::self()->stopPlaybackAction->setIcon(Icons::self()->toolbarStopIcon);
	StdActions::self()->stopAfterCurrentTrackAction->setIcon(Icons::self()->toolbarStopIcon);
	StdActions::self()->stopAfterTrackAction->setIcon(Icons::self()->toolbarStopIcon);
	songInfoAction->setIcon(Icons::self()->infoIcon);
	menuButton->setIcon(Icons::self()->toolbarMenuIcon);
}

void MainWindow::readSettings()
{
#ifdef QT_QTDBUS_FOUND
	// It appears as if the KDE MPRIS code does not like the MPRIS interface to be setup before the window is visible.
	// to work-around this, initMpris in the next event loop iteration.
	// See #863
	QTimer::singleShot(0, this, SLOT(initMpris()));
#else
	CurrentCover::self()->setEnabled(Settings::self()->showPopups() || 0 != Settings::self()->playQueueBackground() || Settings::self()->showCoverWidget());
#endif

	checkMpdDir();
	Song::setIgnorePrefixes(Settings::self()->ignorePrefixes());
	Covers::self()->readConfig();
	HttpServer::self()->readConfig();
#ifdef ENABLE_DEVICES_SUPPORT
	StdActions::self()->deleteSongsAction->setVisible(Settings::self()->showDeleteAction());
#endif
	Song::setComposerGenres(Settings::self()->composerGenres());
	trayItem->setup();
	autoScrollPlayQueue = Settings::self()->playQueueScroll();
	TreeView::setForceSingleClick(Settings::self()->forceSingleClick());
#if (defined Q_OS_LINUX && defined QT_QTDBUS_FOUND) || (defined Q_OS_MAC && defined IOKIT_FOUND)
	PowerManagement::self()->setInhibitSuspend(Settings::self()->inhibitSuspend());
#endif
	context->readConfig();
	tabWidget->setProperty(constUserSettingProp, Settings::self()->hiddenPages());
	tabWidget->setProperty(constUserSetting2Prop, Settings::self()->sidebar());
	coverWidget->setProperty(constUserSettingProp, Settings::self()->showCoverWidget());
	stopTrackButton->setProperty(constUserSettingProp, Settings::self()->showStopButton());
	responsiveSidebar = Settings::self()->responsiveSidebar();
	controlView(true);
#if defined(Q_OS_WIN) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
	if (thumbnailTooolbar) {
		thumbnailTooolbar->readSettings();
	}
#endif
	searchPlayQueueAction->setEnabled(Settings::self()->playQueueSearch());
	searchPlayQueueAction->setVisible(Settings::self()->playQueueSearch());
	nowPlaying->readConfig();
	setCollapsedSize();
	toggleSplitterAutoHide();
	if (contextSwitchTime != Settings::self()->contextSwitchTime()) {
		contextSwitchTime = Settings::self()->contextSwitchTime();
		if (0 == contextSwitchTime && contextTimer) {
			contextTimer->stop();
			contextTimer->deleteLater();
			contextTimer = nullptr;
		}
	}
	MPDConnection::self()->setVolumeFadeDuration(Settings::self()->stopFadeDuration());
	MPDParseUtils::setSingleTracksFolders(Settings::self()->singleTracksFolders());
	MPDParseUtils::setCueFileSupport(Settings::self()->cueSupport());
	volumeSlider->setPageStep(Settings::self()->volumeStep());
}

void MainWindow::updateSettings()
{
	connectToMpd();
	Settings::self()->save();
	readSettings();

	bool wasAutoExpand = playQueue->isAutoExpand();
	bool wasStartClosed = playQueue->isStartClosed();
	bool wasGrouped = playQueue->isGrouped();
	playQueue->readConfig();

	if (wasGrouped != playQueue->isGrouped() || (playQueue->isGrouped() && (wasAutoExpand != playQueue->isAutoExpand() || wasStartClosed != playQueue->isStartClosed()))) {
		QModelIndex idx = playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(PlayQueueModel::self()->currentSongRow(), 0));
		playQueue->updateRows(idx.row(), current.key, autoScrollPlayQueue && playQueueProxyModel.isEmpty() && MPDState_Playing == MPDStatus::self()->state());
	}

	updateActionToolTips();
}

void MainWindow::toggleOutput()
{
	QAction* act = qobject_cast<QAction*>(sender());
	if (act) {
		emit enableOutput(act->data().toUInt(), act->isChecked());
	}
}

void MainWindow::moveOutputToThisPartition()
{
	QAction* act = qobject_cast<QAction*>(sender());
	if (act) {
		emit moveOutput(act->data().toString());
	}
}

void MainWindow::selectPartition()
{
	QAction* act = qobject_cast<QAction*>(sender());
	if (act) {
		emit changePartition(act->data().toString());
	}
}

void MainWindow::createNewPartition()
{
	QString name = InputDialog::getText(tr("Partition Name"), tr("Enter a name for the partition:"), QString(), nullptr, this);
	if (!name.isEmpty()) {
		emit newPartition(name);
	}
}

void MainWindow::deleteAPartition()
{
	QAction* act = qobject_cast<QAction*>(sender());
	if (act) {
		QString name = act->data().toString();
		if (MessageBox::Yes == MessageBox::warningYesNo(this, tr("Are you sure you wish to remove partition \"%1\"?\n\nThis cannot be undone.").arg(name), tr("Remove Partition"), StdGuiItem::remove(), StdGuiItem::cancel())) {
			emit delPartition(name);
		}
	}
}

void MainWindow::changeConnection()
{
	QAction* act = qobject_cast<QAction*>(sender());
	if (act) {
		Settings::self()->saveCurrentConnection(act->data().toString());
		connectToMpd();
	}
}

void MainWindow::showServerInfo()
{
	QStringList handlers = MPDConnection::self()->urlHandlers().values();
	QStringList tags = MPDConnection::self()->tags().values();
	std::sort(handlers.begin(), handlers.end());
	std::sort(tags.begin(), tags.end());
	long version = MPDConnection::self()->version();
	QDateTime dbUpdate;
	dbUpdate.setSecsSinceEpoch(MPDStats::self()->dbUpdate());
	MessageBox::information(this,
#ifdef Q_OS_MAC
	                        tr("Server Information") + QLatin1String("<br/><br/>") +
#endif
	                                QLatin1String("<p><table>") + tr("<tr><td colspan=\"2\"><b>Server</b></td></tr>"
	                                                                 "<tr><td align=\"right\">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr>"
	                                                                 "<tr><td align=\"right\">Uptime:&nbsp;</td><td>%4</td></tr>"
	                                                                 "<tr><td align=\"right\">Playing:&nbsp;</td><td>%5</td></tr>"
	                                                                 "<tr><td align=\"right\">Handlers:&nbsp;</td><td>%6</td></tr>"
	                                                                 "<tr><td align=\"right\">Tags:&nbsp;</td><td>%7</td></tr>")
	                                                                      .arg((version >> 16) & 0xFF)
	                                                                      .arg((version >> 8) & 0xFF)
	                                                                      .arg(version & 0xFF)
	                                                                      .arg(Utils::formatDuration(MPDStats::self()->uptime()))
	                                                                      .arg(Utils::formatDuration(MPDStats::self()->playtime()))
	                                                                      .arg(handlers.join(", "))
	                                                                      .arg(tags.join(", "))
	                                + QLatin1String("<tr/>") + tr("<tr><td colspan=\"2\"><b>Database</b></td></tr>"
	                                                              "<tr><td align=\"right\">Artists:&nbsp;</td><td>%1</td></tr>"
	                                                              "<tr><td align=\"right\">Albums:&nbsp;</td><td>%2</td></tr>"
	                                                              "<tr><td align=\"right\">Songs:&nbsp;</td><td>%3</td></tr>"
	                                                              "<tr><td align=\"right\">Duration:&nbsp;</td><td>%4</td></tr>"
	                                                              "<tr><td align=\"right\">Updated:&nbsp;</td><td>%5</td></tr>")
	                                                                   .arg(MPDStats::self()->artists())
	                                                                   .arg(MPDStats::self()->albums())
	                                                                   .arg(MPDStats::self()->songs())
	                                                                   .arg(Utils::formatDuration(MPDStats::self()->dbPlaytime()))
	                                                                   .arg(QLocale().toString(dbUpdate, QLocale::ShortFormat))
	                                + QLatin1String("</table></p>"),
	                        tr("Server Information"));
}

void MainWindow::enableStopActions(bool enable)
{
	StdActions::self()->stopAfterCurrentTrackAction->setEnabled(enable);
	StdActions::self()->stopPlaybackAction->setEnabled(enable);
}

void MainWindow::stopPlayback()
{
	emit stop();
	enableStopActions(false);
	StdActions::self()->nextTrackAction->setEnabled(false);
	StdActions::self()->prevTrackAction->setEnabled(false);
}

void MainWindow::stopAfterCurrentTrack()
{
	PlayQueueModel::self()->clearStopAfterTrack();
	emit stop(true);
}

void MainWindow::stopAfterTrack()
{
	QModelIndexList selected = playQueue->selectedIndexes(false);// Dont need sorted selection here...
	if (1 == selected.count()) {
		QModelIndex idx = playQueueProxyModel.mapToSource(selected.first());
		PlayQueueModel::self()->setStopAfterTrack(PlayQueueModel::self()->getIdByRow(idx.row()));
	}
}

void MainWindow::playPauseTrack()
{
	switch (MPDStatus::self()->state()) {
	case MPDState_Playing:
		emit pause(true);
		break;
	case MPDState_Paused:
		emit pause(false);
		break;
	default:
		if (PlayQueueModel::self()->rowCount() > 0) {
			if (-1 != PlayQueueModel::self()->currentSong() && -1 != PlayQueueModel::self()->currentSongRow()) {
				emit startPlayingSongId(PlayQueueModel::self()->currentSong());
			}
			else {
				emit play();
			}
		}
	}
}

void MainWindow::setPosition()
{
	emit setSeekId(MPDStatus::self()->songId(), nowPlaying->value());
}

void MainWindow::searchPlayQueue()
{
	if (playQueueSearchWidget->text().isEmpty()) {
		if (playQueueSearchTimer) {
			playQueueSearchTimer->stop();
		}
		realSearchPlayQueue();
	}
	else {
		if (!playQueueSearchTimer) {
			playQueueSearchTimer = new QTimer(this);
			playQueueSearchTimer->setSingleShot(true);
			connect(playQueueSearchTimer, SIGNAL(timeout()), SLOT(realSearchPlayQueue()));
		}
		playQueueSearchTimer->start(250);
	}
}

void MainWindow::realSearchPlayQueue()
{
	if (playQueueSearchTimer) {
		playQueueSearchTimer->stop();
	}
	QString filter = playQueueSearchWidget->text().trimmed();
	if (filter.length() < 2) {
		filter = QString();
	}

	if (filter != playQueueProxyModel.filterText()) {
		playQueue->setFilterActive(!filter.isEmpty());
		playQueue->clearSelection();
		playQueueProxyModel.update(filter);
		QModelIndex idx = playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(PlayQueueModel::self()->currentSongRow(), 0));
		playQueue->updateRows(idx.row(), current.key, autoScrollPlayQueue && playQueueProxyModel.isEmpty() && MPDState_Playing == MPDStatus::self()->state());
		scrollPlayQueue();
	}
}

void MainWindow::playQueueSearchActivated(bool a)
{
	if (!a && playQueue->isVisible()) {
		playQueue->setFocus();
	}
}

void MainWindow::updatePlayQueue(const QList<Song>& songs, bool isComplete)
{
	StdActions::self()->playPauseTrackAction->setEnabled(!songs.isEmpty());
	StdActions::self()->nextTrackAction->setEnabled(StdActions::self()->stopPlaybackAction->isEnabled() && !songs.isEmpty() && MPDStatus::self()->nextSongId() != -1);
	StdActions::self()->prevTrackAction->setEnabled(StdActions::self()->stopPlaybackAction->isEnabled() && !songs.isEmpty() && (songs.count() > 1 || current.time > 5));
	StdActions::self()->savePlayQueueAction->setEnabled(!songs.isEmpty());
	clearPlayQueueAction->setEnabled(!songs.isEmpty());

	int topRow = -1;
	QModelIndex topIndex = PlayQueueModel::self()->lastCommandWasUnodOrRedo() ? playQueue->indexAt(QPoint(0, 0)) : QModelIndex();
	if (topIndex.isValid()) {
		topRow = playQueueProxyModel.mapToSource(topIndex).row();
	}
	bool wasEmpty = 0 == PlayQueueModel::self()->rowCount();
	bool songChanged = false;

	PlayQueueModel::self()->update(songs, isComplete);

	if (songs.isEmpty()) {
		updateCurrentSong(Song(), wasEmpty);
	}
	else {
		// Check to see if it has been updated...
		Song pqSong = PlayQueueModel::self()->getSongByRow(PlayQueueModel::self()->currentSongRow());
		if (wasEmpty || pqSong.isDifferent(current)) {
			updateCurrentSong(pqSong, wasEmpty);
			songChanged = true;
		}
	}

	if (songChanged) {
		StdActions::self()->prevTrackAction->setEnabled(StdActions::self()->stopPlaybackAction->isEnabled() && !songs.isEmpty() && (songs.count() > 1 || current.time > 5));
	}

	QModelIndex idx = playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(PlayQueueModel::self()->currentSongRow(), 0));
	bool scroll = songChanged && autoScrollPlayQueue && playQueueProxyModel.isEmpty() && (wasEmpty || MPDState_Playing == MPDStatus::self()->state());
	playQueue->updateRows(idx.row(), current.key, scroll, wasEmpty);
	if (!scroll && topRow > 0 && topRow < PlayQueueModel::self()->rowCount()) {
		playQueue->scrollTo(playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(topRow, PlayQueueModel::COL_TITLE)), QAbstractItemView::PositionAtTop);
	}

	playQueueItemsSelected(playQueue->haveSelectedItems());
}

void MainWindow::updateWindowTitle()
{
	bool multipleConnections = connectionsAction->isVisible();
	QString connection = MPDConnection::self()->getDetails().getName();
	QString partition = MPDStatus::self()->partition();
	setWindowTitle((multipleConnections && !partition.isEmpty())
	                       ? tr("Cantata (%1) [%2]").arg(connection, partition)
	                       : multipleConnections
	                       ? tr("Cantata (%1)").arg(connection)
	                       : !partition.isEmpty()
	                       ? tr("Cantata [%1]").arg(partition)
	                       : "Cantata");
}

void MainWindow::updateCurrentSong(Song song, bool wasEmpty)
{
	if (song.isCdda()) {
		emit getStatus();
		if (song.isUnknownAlbum()) {
			Song pqSong = PlayQueueModel::self()->getSongById(song.id);
			if (!pqSong.isEmpty()) {
				song = pqSong;
			}
		}
	}

	//    if (song.isCantataStream()) {
	//        Song mod=HttpServer::self()->decodeUrl(song.file);
	//        if (!mod.title.isEmpty()) {
	//            mod.id=song.id;
	//            song=mod;
	//        }
	//    }

	bool diffSong = song.isDifferent(current);
	current = song;

	CurrentCover::self()->update(current);
	if (current.time < 5 && MPDStatus::self()->songId() == current.id && MPDStatus::self()->timeTotal() > 5) {
		current.time = MPDStatus::self()->timeTotal();
	}
	nowPlaying->setEnabled(-1 != current.id && !current.isCdda() && (!currentIsStream() || current.time > 5));
	nowPlaying->update(current);
	auto mpdStatus = MPDStatus::self()->state();
	// MPDState_Paused is a workaround for owntone mpd server; on forcing next
	// track, MPD state in changed to pause, resulting in no notification
	bool isPlaying = MPDState_Playing == mpdStatus || MPDState_Paused == mpdStatus;
	PlayQueueModel::self()->updateCurrentSong(current.id);
	QModelIndex idx = playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(PlayQueueModel::self()->currentSongRow(), 0));
	playQueue->updateRows(idx.row(), current.key, autoScrollPlayQueue && playQueueProxyModel.isEmpty() && isPlaying, wasEmpty);
	scrollPlayQueue(wasEmpty);
	if (diffSong) {
#ifdef QT_QTDBUS_FOUND
		if (mpris) {
			mpris->updateCurrentSong(current);
		}
#endif
#if defined(Q_OS_WIN) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
		if (thumbnailTooolbar) {
			thumbnailTooolbar->updateCurrentSong(current);
		}
#endif
#ifdef MAC_MEDIAPLAYER_FOUND
		macNowPlaying->updateCurrentSong(current);
#endif
		context->update(current);
		trayItem->songChanged(song, isPlaying);
	}
	centerPlayQueueAction->setEnabled(!song.isEmpty());
}

void MainWindow::scrollPlayQueue(bool wasEmpty)
{
	if (autoScrollPlayQueue && (wasEmpty || MPDState_Playing == MPDStatus::self()->state()) && !playQueue->isGrouped()) {
		qint32 row = PlayQueueModel::self()->currentSongRow();
		if (row >= 0) {
			playQueue->scrollTo(playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(row, 0)), QAbstractItemView::PositionAtCenter);
		}
	}
}

void MainWindow::updateStatus()
{
	updateStatus(MPDStatus::self());
}

void MainWindow::updateStatus(MPDStatus* const status)
{
	if (!status->error().isEmpty()) {
		showError(tr("MPD reported the following error: %1").arg(status->error()));
	}

	if (status->partition() != lastPartition) {
		updateWindowTitle();

		for (QAction* act : partitionsAction->menu()->actions()) {
			if (act->isCheckable()) {
				if (act->data().toString() == status->partition()) {
					act->setChecked(true);
				}
			}
			else if (act->menu() && act->data().toString() == "deletepartition") {
				for (QAction* subAct : act->menu()->actions()) {
					subAct->setVisible(subAct->data().toString() != status->partition());
				}
			}
		}

		// SongIDs are not unique between partitions, so reset the play queue's state.
		PlayQueueModel::self()->clear();
	}

	if (MPDState_Stopped == status->state() || MPDState_Inactive == status->state()) {
		nowPlaying->clearTimes();
		PlayQueueModel::self()->clearStopAfterTrack();
		if (statusTimer) {
			statusTimer->stop();
			statusTimer->setProperty("count", 0);
		}
	}
	else {
		nowPlaying->setRange(0, 0 == status->timeTotal() && 0 != current.time && (current.isCdda() || current.isCantataStream()) ? current.time : status->timeTotal());
		nowPlaying->setValue(status->timeElapsed());
		if (0 == status->timeTotal() && 0 == status->timeElapsed()) {
			if (!statusTimer) {
				statusTimer = new QTimer(this);
				statusTimer->setSingleShot(true);
				connect(statusTimer, SIGNAL(timeout()), SIGNAL(getStatus()));
			}
			QVariant id = statusTimer->property("id");
			if (!id.isValid() || id.toInt() != current.id) {
				statusTimer->setProperty("id", current.id);
				statusTimer->setProperty("count", 0);
				statusTimer->start(250);
			}
			else if (statusTimer->property("count").toInt() < 12) {
				statusTimer->setProperty("count", statusTimer->property("count").toInt() + 1);
				statusTimer->start(250);
			}
		}
		else if (!nowPlaying->isEnabled()) {
			nowPlaying->setEnabled(-1 != current.id && !current.isCdda() && (!currentIsStream() || status->timeTotal() > 5));
		}
	}

	randomPlayQueueAction->setChecked(status->random());
	repeatPlayQueueAction->setChecked(status->repeat());
	singlePlayQueueAction->setChecked(status->single());
	consumePlayQueueAction->setChecked(status->consume());
	updateNextTrack(status->nextSongId());

	if (status->timeElapsed() < 64800 && (!currentIsStream() || (status->timeTotal() > 0 && status->timeElapsed() <= status->timeTotal()))) {
		if (status->state() == MPDState_Stopped || status->state() == MPDState_Inactive) {
			nowPlaying->setRange(0, 0);
		}
		else {
			nowPlaying->setValue(status->timeElapsed());
		}
	}

	PlayQueueModel::self()->setState(status->state());
	StdActions::self()->playPauseTrackAction->setEnabled(status->playlistLength() > 0);
	switch (status->state()) {
	case MPDState_Playing:
		StdActions::self()->playPauseTrackAction->setIcon(Icons::self()->toolbarPauseIcon);
		enableStopActions(true);
		StdActions::self()->nextTrackAction->setEnabled(status->nextSongId() != -1);
		StdActions::self()->prevTrackAction->setEnabled(status->playlistLength() > 1 || status->timeTotal() > 5 || current.time > 5);
		nowPlaying->startTimer();
		break;
	case MPDState_Inactive:
	case MPDState_Stopped:
		StdActions::self()->playPauseTrackAction->setIcon(Icons::self()->toolbarPlayIcon);
		enableStopActions(false);
		StdActions::self()->nextTrackAction->setEnabled(false);
		StdActions::self()->prevTrackAction->setEnabled(false);
		if (!StdActions::self()->playPauseTrackAction->isEnabled()) {
			current = Song();
			nowPlaying->update(current);
			CurrentCover::self()->update(current);
			context->update(current);
		}
		current.id = 0;
		trayItem->setToolTip("cantata", tr("Cantata"), QLatin1String("<i>") + tr("Playback stopped") + QLatin1String("</i>"));
		nowPlaying->stopTimer();
		break;
	case MPDState_Paused:
		StdActions::self()->playPauseTrackAction->setIcon(Icons::self()->toolbarPlayIcon);
		enableStopActions(0 != status->playlistLength());
		StdActions::self()->nextTrackAction->setEnabled(status->nextSongId() != -1);
		StdActions::self()->prevTrackAction->setEnabled(status->playlistLength() > 1 || status->timeTotal() > 5 || current.time > 5);
		nowPlaying->stopTimer();
	default:
		break;
	}

	// Check if song has changed or we're playing again after being stopped, and update song info if needed
	if (MPDState_Inactive != status->state() && (MPDState_Inactive == lastState || (MPDState_Stopped == lastState && MPDState_Playing == status->state()) || lastSongId != status->songId())) {
		emit currentSong();
	}
	if (status->state() != lastState && (MPDState_Playing == status->state() || MPDState_Stopped == status->state())) {
		startContextTimer();
	}

	// Update status info
	lastState = status->state();
	lastSongId = status->songId();
	lastPartition = status->partition();
#ifdef QT_QTDBUS_FOUND
	if (mpris) {
		mpris->updateStatus(status);
	}
#endif
#if defined(Q_OS_WIN) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
	if (thumbnailTooolbar) {
		thumbnailTooolbar->updateStatus(status);
	}
#endif
#ifdef Q_OS_MAC
	dockMenu->update(status);
#endif
#ifdef MAC_MEDIAPLAYER_FOUND
	macNowPlaying->updateStatus(status);
#endif
}

void MainWindow::playQueueItemActivated(const QModelIndex& index)
{
	if (index.isValid()) {
		emit startPlayingSongId(PlayQueueModel::self()->getIdByRow(playQueueProxyModel.mapToSource(index).row()));
	}
}

void MainWindow::clearPlayQueue()
{
	if (!Settings::self()->playQueueConfirmClear() || MessageBox::Yes == MessageBox::questionYesNo(this, tr("Remove all songs from play queue?"))) {
		if (dynamicLabel->isVisible()) {
			DynamicPlaylists::self()->stop(true);
		}
		else {
			PlayQueueModel::self()->removeAll();
		}
	}
}

void MainWindow::centerPlayQueue()
{
	QModelIndex idx = playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(PlayQueueModel::self()->currentSongRow(),
	                                                                                  playQueue->isGrouped() ? 0 : PlayQueueModel::COL_TITLE));
	if (idx.isValid()) {
		if (playQueue->isGrouped()) {
			playQueue->updateRows(idx.row(), current.key, true, true);
		}
		else {
			playQueue->scrollTo(idx, QAbstractItemView::PositionAtCenter);
		}
	}
}

void MainWindow::appendToPlayQueue(int action, quint8 priority, bool decreasePriority)
{
	playQueueSearchWidget->clear();
	if (currentPage) {
		currentPage->addSelectionToPlaylist(QString(), action, priority, decreasePriority);
	}
}

void MainWindow::addWithPriority()
{
	QAction* act = qobject_cast<QAction*>(sender());

	if (!act || !MPDConnection::self()->canUsePriority()) {
		return;
	}

	int prio = act->data().toInt();
	bool isPlayQueue = playQueue->hasFocus();
	bool decreasePriority = false;
	QModelIndexList pqItems;

	if (isPlayQueue) {
		pqItems = playQueue->selectedIndexes();
		if (pqItems.isEmpty()) {
			return;
		}
	}

	if (-1 == prio) {
		InputDialog dlg(tr("Priority"), tr("Enter priority (0..255):"), 150, 0, 255, 5, this);
		QCheckBox* dec = new QCheckBox(tr("Decrease priority for each subsequent track"), this);
		dec->setChecked(false);
		dlg.addExtraWidget(dec);
		if (QDialog::Accepted != dlg.exec()) {
			return;
		}
		prio = dlg.spinBox()->value();
		decreasePriority = dec->isChecked();
	}

	if (prio >= 0 && prio <= 255) {
		if (isPlayQueue) {
			QList<qint32> ids;
			for (const QModelIndex& idx : pqItems) {
				ids.append(PlayQueueModel::self()->getIdByRow(playQueueProxyModel.mapToSource(idx).row()));
			}
			emit setPriority(ids, prio, decreasePriority);
		}
		else {
			appendToPlayQueue(false, prio, decreasePriority);
		}
	}
}

void MainWindow::addToNewStoredPlaylist()
{
	bool pq = playQueue->hasFocus();
	for (;;) {
		QString name = InputDialog::getText(tr("Playlist Name"), tr("Enter a name for the playlist:"), QString(), nullptr, this);
		if (name.isEmpty()) {
			return;
		}

		if (name == MPDConnection::constStreamsPlayListName) {
			MessageBox::error(this, tr("'%1' is used to store favorite streams, please choose another name.").arg(name));
			continue;
		}
		if (PlaylistsModel::self()->exists(name)) {
			switch (MessageBox::warningYesNoCancel(this, tr("A playlist named '%1' already exists!\n\nAdd to that playlist?").arg(name),
			                                       tr("Existing Playlist"))) {
			case MessageBox::Cancel: return;
			case MessageBox::Yes: break;
			case MessageBox::No:
			default: continue;
			}
		}

		if (!name.isEmpty()) {
			addToExistingStoredPlaylist(name, pq);
		}
		break;
	}
}

void MainWindow::addToExistingStoredPlaylist(const QString& name, bool pq)
{
	if (pq) {
		QModelIndexList items = playQueue->selectedIndexes();
		QStringList files;
		if (items.isEmpty()) {
			files = PlayQueueModel::self()->filenames();
		}
		else {
			for (const QModelIndex& idx : items) {
				Song s = PlayQueueModel::self()->getSongByRow(playQueueProxyModel.mapToSource(idx).row());
				if (!s.file.isEmpty()) {
					files.append(s.file);
				}
			}
		}
		if (!files.isEmpty()) {
			emit addSongsToPlaylist(name, files);
		}
	}
	else if (currentPage) {
		currentPage->addSelectionToPlaylist(name);
	}
}

void MainWindow::addStreamToPlayQueue()
{
	StreamDialog dlg(this, true);

	if (QDialog::Accepted == dlg.exec()) {
		QString url = dlg.url();

		if (dlg.save()) {
			StreamsModel::self()->addToFavourites(url, dlg.name());
		}
		PlayQueueModel::self()->addItems(QStringList() << StreamsModel::modifyUrl(url, true, dlg.name()), MPDConnection::Append, 0, false);
	}
}

void MainWindow::addLocalFilesToPlayQueue()
{
	QString extensions;
	for (const auto& ext : PlayQueueModel::constFileExtensions) {
		if (!extensions.isEmpty()) {
			extensions += " ";
		}
		extensions += "*" + ext;
	}
	QStringList files = QFileDialog::getOpenFileNames(this, tr("Select Music Files"), QString(), tr("Music Files ") + "(" + extensions + ")");
	if (!files.isEmpty()) {
		PlayQueueModel::self()->load(files);
	}
}

void MainWindow::removeItems()
{
	if (currentPage) {
		currentPage->removeItems();
	}
}

void MainWindow::checkMpdAccessibility()
{
#ifdef Q_OS_LINUX
	if (!mpdAccessibilityTimer) {
		mpdAccessibilityTimer = new QTimer(this);
		connect(mpdAccessibilityTimer, SIGNAL(timeout()), SLOT(checkMpdDir()));
	}
	mpdAccessibilityTimer->start(500);
#endif
}

void MainWindow::updatePlayQueueStats(int songs, quint32 time)
{
	if (0 == songs) {
		playQueueStatsLabel->setText(QString());
	}
	else if (0 == time) {
		playQueueStatsLabel->setText(tr("%n Track(s)", "", songs));
	}
	else {
		playQueueStatsLabel->setText(tr("%n Tracks (%1)", "", songs).arg(Utils::formatDuration(time)));
	}
}

void MainWindow::showSongInfo()
{
	if (songInfoAction->isCheckable()) {
		stack->setCurrentWidget(songInfoAction->isChecked() ? (QWidget*)context : (QWidget*)splitter);
	}
	else {
		showTab(PAGE_CONTEXT);
	}
}

void MainWindow::fullScreen()
{
	if (expandInterfaceAction->isChecked()) {
#ifndef Q_OS_MAC
		fullScreenLabel->setVisible(!isFullScreen());
#endif
		expandInterfaceAction->setEnabled(isFullScreen());
		if (isFullScreen()) {
			showNormal();
		}
		else {
			showFullScreen();
		}
	}
	else {
		fullScreenAction->setChecked(false);
	}
}

void MainWindow::sidebarModeChanged()
{
	if (expandInterfaceAction->isChecked()) {
		setMinimumHeight(calcMinHeight());
	}
}

void MainWindow::currentTabChanged(int index)
{
	prevPage = index;
	controlDynamicButton();
	switch (index) {
	case PAGE_LIBRARY: currentPage = libraryPage; break;
	case PAGE_FOLDERS: currentPage = folderPage; break;
	case PAGE_PLAYLISTS: currentPage = playlistsPage; break;
	case PAGE_ONLINE: currentPage = onlinePage; break;
#ifdef ENABLE_DEVICES_SUPPORT
	case PAGE_DEVICES: currentPage = devicesPage; break;
#endif
	case PAGE_SEARCH: currentPage = searchPage; break;
	default: currentPage = nullptr; break;
	}
	if (currentPage) {
		currentPage->controlActions();
	}
}

void MainWindow::tabToggled(int index)
{
	switch (index) {
	case PAGE_PLAYQUEUE:
		if (tabWidget->isEnabled(index)) {
			splitter->setAutohidable(0, Settings::self()->splitterAutoHide() && !tabWidget->isEnabled(PAGE_PLAYQUEUE));
			playQueueWidget->setParent(playQueuePage);
			playQueuePage->layout()->addWidget(playQueueWidget);
			playQueueWidget->setVisible(true);
		}
		else {
			playQueuePage->layout()->removeWidget(playQueueWidget);
			playQueueWidget->setParent(splitter);
			playQueueWidget->setVisible(true);
			splitter->setAutohidable(0, Settings::self()->splitterAutoHide() && !tabWidget->isEnabled(PAGE_PLAYQUEUE));
		}
		playQueue->updatePalette();
		break;
	case PAGE_CONTEXT:
		if (tabWidget->isEnabled(index) && songInfoAction->isCheckable()) {
			context->setParent(contextPage);
			contextPage->layout()->addWidget(context);
			context->setVisible(true);
			songInfoButton->setVisible(false);
			songInfoAction->setCheckable(false);
		}
		else if (!songInfoAction->isCheckable()) {
			contextPage->layout()->removeWidget(context);
			stack->addWidget(context);
			songInfoButton->setVisible(true);
			songInfoAction->setCheckable(true);
		}
		break;
	case PAGE_LIBRARY:
		locateAction->setVisible(tabWidget->isEnabled(index));
		break;
	case PAGE_FOLDERS:
		folderPage->setEnabled(!folderPage->isEnabled());
		break;
	case PAGE_PLAYLISTS:
		break;
	case PAGE_ONLINE:
		onlinePage->setEnabled(onlinePage->isEnabled());
		break;
#ifdef ENABLE_DEVICES_SUPPORT
	case PAGE_DEVICES:
		DevicesModel::self()->setEnabled(!DevicesModel::self()->isEnabled());
		StdActions::self()->copyToDeviceAction->setVisible(DevicesModel::self()->isEnabled());
		break;
#endif
	default:
		break;
	}
	sidebarModeChanged();
}

void MainWindow::toggleSplitterAutoHide()
{
	bool ah = Settings::self()->splitterAutoHide();
	if (splitter->isAutoHideEnabled() != ah) {
		splitter->setAutoHideEnabled(ah);
		splitter->setAutohidable(0, ah);
	}
}

void MainWindow::locateTracks(const QList<Song>& songs)
{
	if (!songs.isEmpty() && tabWidget->isEnabled(PAGE_LIBRARY)) {
		showLibraryTab();
		libraryPage->showSongs(songs);
	}
}

void MainWindow::locateTrack()
{
	if (!locateAction->isVisible() || !locateAction->isEnabled()) {
		return;
	}
	Action* act = qobject_cast<Action*>(sender());
	if (!act) {
		return;
	}

	QList<Song> songs = playQueue->selectedSongs();
	if (1 != songs.count() || !tabWidget->isEnabled(PAGE_LIBRARY)) {
		return;
	}
	showLibraryTab();
	if (locateTrackAction == act) {
		libraryPage->showSongs(songs);
	}
	Song s = songs.first();
	if (locateAlbumAction == act) {
		libraryPage->showAlbum(s.albumArtist(), s.albumId());
	}
	if (locateArtistAction == act) {
		libraryPage->showArtist(s.albumArtist());
	}
}

void MainWindow::moveSelectionAfterCurrentSong()
{
	QList<int> selectedRows = playQueueProxyModel.mapToSourceRows(playQueue->selectedIndexes());
	int currentSongIdx = PlayQueueModel::self()->currentSongRow();
	QList<quint32> selectedSongIds;

	for (int row : selectedRows) {
		if (currentSongIdx != row) {
			selectedSongIds.append((quint32)row);
		}
	}

	if (!selectedSongIds.empty()) {
		emit playNext(selectedSongIds, currentSongIdx + 1, PlayQueueModel::self()->rowCount());
	}
}

void MainWindow::moveSelection(int position)
{
	QList<int> selectedRows = playQueueProxyModel.mapToSourceRows(playQueue->selectedIndexes());
	QList<quint32> selectedSongIds;

	for (int row : selectedRows) {
		if (position != row) {
			selectedSongIds.append((quint32)row);
		}
	}

	if (!selectedSongIds.empty()) {
		emit playNext(selectedSongIds, position, PlayQueueModel::self()->rowCount());
	}
}

void MainWindow::moveSelectionAtQueueStart()
{
	moveSelection(0);
}

void MainWindow::moveSelectionAtQueueEnd()
{
	int newPosition = PlayQueueModel::self()->rowCount();
	moveSelection(newPosition);
}

void MainWindow::locateArtist(const QString& artist)
{
	if (songInfoAction->isCheckable()) {
		songInfoAction->setChecked(false);
		showSongInfo();
	}
	showLibraryTab();
	libraryPage->showArtist(artist);
}

void MainWindow::locateAlbum(const QString& artist, const QString& album)
{
	if (songInfoAction->isCheckable()) {
		songInfoAction->setChecked(false);
		showSongInfo();
	}
	showLibraryTab();
	libraryPage->showAlbum(artist, album);
}

void MainWindow::dynamicStatus(const QString& message)
{
	DynamicPlaylists::self()->helperMessage(message);
}

void MainWindow::setCollection(const QString& collection)
{
	if (!connectionsAction->isVisible()) {
		return;
	}
	for (QAction* act : connectionsAction->menu()->actions()) {
		if (Utils::strippedText(act->text()) == collection) {
			if (!act->isChecked()) {
				act->trigger();
			}
			break;
		}
	}
}

void MainWindow::mpdConnectionName(const QString& name)
{
	for (QAction* act : connectionsAction->menu()->actions()) {
		if (Utils::strippedText(act->text()) == name) {
			if (!act->isChecked()) {
				act->setChecked(true);
			}
			break;
		}
	}
}

void MainWindow::showPlayQueueSearch()
{
	playQueueSearchWidget->activate();
}

void MainWindow::showSearch()
{
	if (!searchPlayQueueAction->isEnabled() && playQueue->hasFocus()) {
		playQueueSearchWidget->activate();
	}
	else if (context->isVisible()) {
		context->search();
	}
	else if (currentPage && splitter->sizes().at(0) > 0) {
		if (currentPage == folderPage && folderPage->mpd()->isVisible()) {
			showTab(PAGE_SEARCH);
			if (PAGE_SEARCH == tabWidget->currentIndex()) {
				searchPage->setSearchCategory("file");
			}
		}
		else {
			currentPage->focusSearch();
		}
	}
	else if (!searchPlayQueueAction->isEnabled() && (playQueuePage->isVisible() || playQueue->isVisible())) {
		playQueueSearchWidget->activate();
	}
}

void MainWindow::expandAll()
{
	QWidget* f = QApplication::focusWidget();
	if (f && qobject_cast<TreeView*>(f) && !qobject_cast<GroupedView*>(f)) {
		static_cast<TreeView*>(f)->expandAll(QModelIndex(), true);
	}
}

void MainWindow::collapseAll()
{
	QWidget* f = QApplication::focusWidget();
	if (f && qobject_cast<QTreeView*>(f) && !qobject_cast<GroupedView*>(f)) {
		static_cast<QTreeView*>(f)->collapseAll();
	}
}

void MainWindow::editTags()
{
#ifdef TagLib_FOUND
	if (TagEditor::instanceCount() || !canShowDialog()) {
		return;
	}
	QList<Song> songs;
	bool isPlayQueue = playQueue->hasFocus();
	if (isPlayQueue) {
		songs = playQueue->selectedSongs();
	}
	else if (currentPage) {
		songs = currentPage->selectedSongs();
	}
	if (songs.isEmpty()) {
		return;
	}
	QSet<QString> artists, albumArtists, composers, albums, genres;
	QString udi;
#ifdef ENABLE_DEVICES_SUPPORT
	if (!isPlayQueue && currentPage == devicesPage) {
		DevicesModel::self()->getDetails(artists, albumArtists, composers, albums, genres);
		udi = devicesPage->activeFsDeviceUdi();
		if (udi.isEmpty()) {
			return;
		}
	}
#endif
	MpdLibraryModel::self()->getDetails(artists, albumArtists, composers, albums, genres);
	TagEditor* dlg = new TagEditor(this, songs, artists, albumArtists, composers, albums, genres, udi);
	dlg->show();
#endif
}

void MainWindow::organiseFiles()
{
#ifdef TagLib_FOUND
	if (TrackOrganiser::instanceCount() || !canShowDialog()) {
		return;
	}

	QList<Song> songs;
	if (currentPage) {
		songs = currentPage->selectedSongs();
	}

	if (!songs.isEmpty()) {
		QString udi;
#ifdef ENABLE_DEVICES_SUPPORT
		if (currentPage == devicesPage) {
			udi = devicesPage->activeFsDeviceUdi();
			if (udi.isEmpty()) {
				return;
			}
		}
#endif

		TrackOrganiser* dlg = new TrackOrganiser(this);
		dlg->show(songs, udi);
	}
#endif
}

void MainWindow::addToDevice(const QString& udi)
{
#ifdef ENABLE_DEVICES_SUPPORT
	if (playQueue->hasFocus()) {
		copyToDevice(QString(), udi, playQueue->selectedSongs());
	}
	else if (currentPage) {
		currentPage->addSelectionToDevice(udi);
	}
#else
	Q_UNUSED(udi)
#endif
}

void MainWindow::deleteSongs()
{
#ifdef ENABLE_DEVICES_SUPPORT
	if (!StdActions::self()->deleteSongsAction->isVisible()) {
		return;
	}
	if (currentPage) {
		currentPage->deleteSongs();
	}
#endif
}

void MainWindow::copyToDevice(const QString& from, const QString& to, const QList<Song>& songs)
{
#ifdef ENABLE_DEVICES_SUPPORT
	if (songs.isEmpty() || ActionDialog::instanceCount() || !canShowDialog()) {
		return;
	}
	ActionDialog* dlg = new ActionDialog(this);
	dlg->copy(from, to, songs);
#else
	Q_UNUSED(from)
	Q_UNUSED(to)
	Q_UNUSED(songs)
#endif
}

void MainWindow::deleteSongs(const QString& from, const QList<Song>& songs)
{
#ifdef ENABLE_DEVICES_SUPPORT
	if (songs.isEmpty() || ActionDialog::instanceCount() || !canShowDialog()) {
		return;
	}
	ActionDialog* dlg = new ActionDialog(this);
	dlg->remove(from, songs);
#else
	Q_UNUSED(from)
	Q_UNUSED(songs)
#endif
}

void MainWindow::replayGain()
{
#ifdef ENABLE_REPLAYGAIN_SUPPORT
	if (RgDialog::instanceCount() || !canShowDialog()) {
		return;
	}

	QList<Song> songs;
	if (currentPage) {
		songs = currentPage->selectedSongs();
	}

	if (!songs.isEmpty()) {
		QString udi;
#ifdef ENABLE_DEVICES_SUPPORT
		if (currentPage == devicesPage) {
			udi = devicesPage->activeFsDeviceUdi();
			if (udi.isEmpty()) {
				return;
			}
		}
#endif
		RgDialog* dlg = new RgDialog(this);
		dlg->show(songs, udi);
	}
#endif
}

void MainWindow::setCover()
{
	if (CoverDialog::instanceCount() || !canShowDialog()) {
		return;
	}

	Song song;
	if (currentPage) {
		song = currentPage->coverRequest();
	}

	if (!song.isEmpty()) {
		CoverDialog* dlg = new CoverDialog(this);
		dlg->show(song);
	}
}

void MainWindow::updateNextTrack(int nextTrackId)
{
	if (-1 != nextTrackId && MPDState_Stopped == MPDStatus::self()->state()) {
		nextTrackId = -1;// nextSongId is not accurate if we are stopped.
	}
	QString tt = StdActions::self()->nextTrackAction->property("tooltip").toString();
	if (-1 == nextTrackId && tt.isEmpty()) {
		StdActions::self()->nextTrackAction->setProperty("tooltip", StdActions::self()->nextTrackAction->toolTip());
	}
	else if (-1 == nextTrackId) {
		StdActions::self()->nextTrackAction->setToolTip(tt);
		StdActions::self()->nextTrackAction->setProperty("trackid", nextTrackId);
	}
	else if (nextTrackId != StdActions::self()->nextTrackAction->property("trackid").toInt()) {
		Song s = PlayQueueModel::self()->getSongByRow(PlayQueueModel::self()->getRowById(nextTrackId));
		if (!s.artist.isEmpty() && !s.title.isEmpty()) {
			tt += QLatin1String("<br/><i><small>") + s.artistSong() + QLatin1String("</small></i>");
		}
		else {
			nextTrackId = -1;
		}
		StdActions::self()->nextTrackAction->setToolTip(tt);
		StdActions::self()->nextTrackAction->setProperty("trackid", nextTrackId);
	}
}

void MainWindow::updateActionToolTips()
{
	ActionCollection::get()->updateToolTips();
	tabWidget->setToolTip(PAGE_PLAYQUEUE, showPlayQueueAction->toolTip());
	tabWidget->setToolTip(PAGE_LIBRARY, libraryTabAction->toolTip());
	tabWidget->setToolTip(PAGE_FOLDERS, foldersTabAction->toolTip());
	tabWidget->setToolTip(PAGE_PLAYLISTS, playlistsTabAction->toolTip());
	tabWidget->setToolTip(PAGE_ONLINE, onlineTabAction->toolTip());
#ifdef ENABLE_DEVICES_SUPPORT
	tabWidget->setToolTip(PAGE_DEVICES, devicesTabAction->toolTip());
#endif
	tabWidget->setToolTip(PAGE_SEARCH, searchTabAction->toolTip());
	tabWidget->setToolTip(PAGE_CONTEXT, songInfoAction->toolTip());
}

void MainWindow::startContextTimer()
{
	if (!contextSwitchTime || current.isStandardStream()) {
		return;
	}
	if (!contextTimer) {
		contextTimer = new QTimer(this);
		contextTimer->setSingleShot(true);
		connect(contextTimer, SIGNAL(timeout()), this, SLOT(toggleContext()));
	}
	contextTimer->start(contextSwitchTime);
}

int MainWindow::calcMinHeight()
{
	return tabWidget->style() & FancyTabWidget::Side && tabWidget->style() & FancyTabWidget::Large
			? calcCollapsedSize() + (tabWidget->visibleCount() * tabWidget->tabSize().height())
			: Utils::scaleForDpi(256);
}

int MainWindow::calcCollapsedSize()
{
#if !defined Q_OS_MAC && !defined Q_OS_WIN
	return toolbar->height() + (showMenubarAction && menuBar() && menuBar()->isVisible() ? menuBar()->height() : 0);
#else
	return toolbar->height();
#endif
}

void MainWindow::setCollapsedSize()
{
	if (!expandInterfaceAction->isChecked()) {
		int w = width();
		adjustSize();
		collapsedSize = QSize(w, calcCollapsedSize());
		resize(collapsedSize);
		setFixedHeight(collapsedSize.height());
	}
}

void MainWindow::controlView(bool forceUpdate)
{
	if (!stopTrackButton || !coverWidget || !tabWidget) {
		controlPlayQueueButtons();
		return;
	}

	static int lastWidth = -1;

	if (forceUpdate || -1 == lastWidth || qAbs(lastWidth - width()) > 20) {
		bool stopEnabled = stopTrackButton->property(constUserSettingProp).toBool();
		bool coverWidgetEnabled = coverWidget->property(constUserSettingProp).toBool();
		int tabWidgetStyle = tabWidget->property(constUserSetting2Prop).toInt();
		QStringList tabWidgetPages = tabWidget->property(constUserSettingProp).toStringList();

		if ((!stopEnabled && !coverWidgetEnabled) || width() < Utils::scaleForDpi(450)) {
			stopTrackButton->setVisible(false);
			coverWidget->setEnabled(false);
		}
		else if (stopEnabled && coverWidgetEnabled) {
			if (width() > Utils::scaleForDpi(550)) {
				stopTrackButton->setVisible(true);
				coverWidget->setEnabled(true);
			}
			else if (width() > Utils::scaleForDpi(450)) {
				stopTrackButton->setVisible(false);
				coverWidget->setEnabled(true);
			}
		}
		else {
			coverWidget->setEnabled(coverWidgetEnabled);
			stopTrackButton->setVisible(stopEnabled);
		}

		if (expandInterfaceAction->isChecked() && (responsiveSidebar || forceUpdate)) {
			if (!responsiveSidebar || width() > Utils::scaleForDpi(450)) {
				if (forceUpdate || singlePane) {
					int index = tabWidget->currentIndex();
					if (forceUpdate || tabWidget->style() != tabWidgetStyle) {
						tabWidget->setStyle(tabWidgetStyle);
					}
					tabWidget->setHiddenPages(tabWidgetPages);
					tabWidget->setCurrentIndex(index);
				}
				singlePane = false;
			}
			else if (responsiveSidebar) {
				if (forceUpdate || !singlePane) {
					int index = tabWidget->currentIndex();
					int smallStyle = FancyTabWidget::Small | FancyTabWidget::Top | FancyTabWidget::IconOnly;
					if (forceUpdate || tabWidget->style() != smallStyle) {
						tabWidget->setStyle(smallStyle);
					}
					tabWidgetPages.removeAll(QLatin1String("PlayQueuePage"));
					tabWidgetPages.removeAll(QLatin1String("ContextPage"));
					tabWidget->setHiddenPages(tabWidgetPages);

					if (forceUpdate && !isVisible() && PAGE_PLAYQUEUE != index && PAGE_CONTEXT != index) {
						QString page = Settings::self()->page();
						for (int i = 0; i < tabWidget->count(); ++i) {
							if (tabWidget->widget(i)->metaObject()->className() == page) {
								index = i;
								break;
							}
						}
					}

					tabWidget->setCurrentIndex(index);
				}
				singlePane = true;
			}
		}
	}
	controlPlayQueueButtons();
}

void MainWindow::controlPlayQueueButtons()
{
	if (!savePlayQueueButton || !centerPlayQueueButton || !midSpacer) {
		return;
	}

	savePlayQueueButton->setVisible(singlePane || playQueueWidget->width() > Utils::scaleForDpi(320));
	centerPlayQueueButton->setVisible(singlePane || playQueueWidget->width() > (Utils::scaleForDpi(320) + centerPlayQueueButton->width()));
	midSpacer->setVisible(singlePane || centerPlayQueueButton->isVisible());
}

void MainWindow::expandOrCollapse(bool saveCurrentSize)
{
	if (!expandInterfaceAction->isChecked() && (isFullScreen() || isMaximized() || messageWidget->isVisible())) {
		expandInterfaceAction->setChecked(true);
		return;
	}

	static bool lastMax = false;

	bool showing = expandInterfaceAction->isChecked();
	QPoint p(isVisible() ? pos() : QPoint());

	if (!showing) {
		setMinimumHeight(0);
		lastMax = isMaximized();
		if (saveCurrentSize) {
			expandedSize = size();
		}
	}
	else {
		if (saveCurrentSize) {
			collapsedSize = size();
		}
		setMinimumHeight(calcMinHeight());
		setMaximumHeight(QWIDGETSIZE_MAX);
	}
	int prevWidth = size().width();
	stack->setVisible(showing);
	if (!showing) {
		setWindowState(windowState() & ~Qt::WindowMaximized);
	}
	adjustSize();

	if (showing) {
		resize(qMax(prevWidth, expandedSize.width()), expandedSize.height());
		if (lastMax) {
			showMaximized();
		}
		controlView();
	}
	else {
		// Width also sometimes expands, so make sure this is no larger than it was before...
		collapsedSize = QSize(collapsedSize.isValid() ? qMin(collapsedSize.width(), prevWidth) : prevWidth, calcCollapsedSize());
		resize(collapsedSize);
		setFixedHeight(size().height());
	}
	if (!p.isNull()) {
		move(p);
	}

	fullScreenAction->setEnabled(showing);
	songInfoButton->setVisible(songInfoAction->isCheckable() && showing);
	songInfoAction->setEnabled(showing);
}

void MainWindow::toggleContext()
{
	if (songInfoButton->isVisible()) {
		if ((MPDState_Playing == MPDStatus::self()->state() && !songInfoAction->isChecked()) || (MPDState_Stopped == MPDStatus::self()->state() && songInfoAction->isChecked())) {
			songInfoAction->trigger();
		}
	}
	else if (MPDState_Playing == MPDStatus::self()->state() && PAGE_CONTEXT != tabWidget->currentIndex()) {
		int pp = prevPage;
		showTab(PAGE_CONTEXT);
		prevPage = pp;
	}
	else if (MPDState_Stopped == MPDStatus::self()->state() && PAGE_CONTEXT == tabWidget->currentIndex() && -1 != prevPage) {
		showTab(prevPage);
	}
}

void MainWindow::hideWindow()
{
	lastPos = pos();
	hide();
}

void MainWindow::restoreWindow()
{
	bool wasHidden = isHidden();
	Utils::raiseWindow(this);
	if (wasHidden && !lastPos.isNull()) {
		move(lastPos);
	}
}

#include "moc_mainwindow.cpp"
