1 /*
2  * Cantata
3  *
4  * Copyright (c) 2011-2020 Craig Drummond <craig.p.drummond@gmail.com>
5  *
6  */
7 /*
8  * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and
9  *                    Roeland Douma (roeland AT rullzer DOT com)
10  *
11  * This file is part of QtMPC.
12  *
13  * QtMPC is free software: you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation, either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * QtMPC is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with QtMPC.  If not, see <http://www.gnu.org/licenses/>.
25  */
26 
27 #include "mainwindow.h"
28 #include "application.h"
29 #include "support/thread.h"
30 #include "trayitem.h"
31 #include "support/messagebox.h"
32 #include "support/inputdialog.h"
33 #include "models/playlistsmodel.h"
34 #include "covers.h"
35 #include "coverdialog.h"
36 #include "currentcover.h"
37 #include "preferencesdialog.h"
38 #include "mpd-interface/mpdstats.h"
39 #include "mpd-interface/mpdparseutils.h"
40 #include "settings.h"
41 #include "support/utils.h"
42 #include "models/musiclibraryitemartist.h"
43 #include "models/musiclibraryitemalbum.h"
44 #include "models/mpdlibrarymodel.h"
45 #include "librarypage.h"
46 #include "folderpage.h"
47 #include "streams/streamdialog.h"
48 #include "searchpage.h"
49 #include "customactions.h"
50 #include "apikeys.h"
51 #include "support/gtkstyle.h"
52 #include "widgets/mirrormenu.h"
53 #ifdef ENABLE_DEVICES_SUPPORT
54 #include "devices/filejob.h"
55 #include "devices/devicespage.h"
56 #include "models/devicesmodel.h"
57 #include "devices/actiondialog.h"
58 #include "devices/syncdialog.h"
59 #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND
60 #include "devices/audiocddevice.h"
61 #endif
62 #endif
63 #include "online/onlineservicespage.h"
64 #include "http/httpserver.h"
65 #ifdef TAGLIB_FOUND
66 #include "tags/trackorganiser.h"
67 #include "tags/tageditor.h"
68 #include "tags/tags.h"
69 #ifdef ENABLE_REPLAYGAIN_SUPPORT
70 #include "replaygain/rgdialog.h"
71 #endif
72 #endif
73 #include "models/streamsmodel.h"
74 #include "playlists/playlistspage.h"
75 #include "support/fancytabwidget.h"
76 #include "support/monoicon.h"
77 #ifdef QT_QTDBUS_FOUND
78 #include "dbus/mpris.h"
79 #include "cantataadaptor.h"
80 #ifdef Q_OS_LINUX
81 #include "dbus/powermanagement.h"
82 #endif
83 #endif
84 #if !defined Q_OS_WIN && !defined Q_OS_MAC
85 #include "devices/mountpoints.h"
86 #endif
87 #ifdef Q_OS_MAC
88 #include "support/windowmanager.h"
89 #include "support/osxstyle.h"
90 #include "mac/dockmenu.h"
91 #ifdef IOKIT_FOUND
92 #include "mac/powermanagement.h"
93 #endif
94 #endif
95 #include "playlists/dynamicplaylists.h"
96 #include "support/messagewidget.h"
97 #include "widgets/groupedview.h"
98 #include "widgets/actionitemdelegate.h"
99 #include "widgets/icons.h"
100 #include "widgets/volumecontrol.h"
101 #include "support/action.h"
102 #include "support/actioncollection.h"
103 #include "stdactions.h"
104 #ifdef ENABLE_HTTP_STREAM_PLAYBACK
105 #include "mpd-interface/httpstream.h"
106 #endif
107 #include <QSet>
108 #include <QString>
109 #include <QTimer>
110 #include <QToolBar>
111 #include <QProcess>
112 #ifdef Q_OS_WIN
113 #include "windows/thumbnailtoolbar.h"
114 #endif
115 #include <QDialogButtonBox>
116 #include <QKeyEvent>
117 #include <QSpacerItem>
118 #include <QMenuBar>
119 #include <QFileDialog>
120 #include "mediakeys.h"
121 #include <cstdlib>
122 #include <algorithm>
123 
nextKey(int & key)124 static int nextKey(int &key)
125 {
126     int k=key;
127     if (Qt::Key_0==key) {
128         key=Qt::Key_A;
129     } else if (Qt::Key_Colon==++key) {
130         key=Qt::Key_0;
131     }
132     return k;
133 }
134 
135 static const char * constRatingKey="rating";
136 static const char * constUserSettingProp = "user-setting-1";
137 static const char * constUserSetting2Prop = "user-setting-2";
138 
MainWindow(QWidget * parent)139 MainWindow::MainWindow(QWidget *parent)
140     : QMainWindow(parent)
141     , prevPage(-1)
142     , lastState(MPDState_Inactive)
143     , lastSongId(-1)
144     , autoScrollPlayQueue(true)
145     , singlePane(false)
146     , shown(false)
147     , currentPage(nullptr)
148     #ifdef QT_QTDBUS_FOUND
149     , mpris(nullptr)
150     #endif
151     , statusTimer(nullptr)
152     , playQueueSearchTimer(nullptr)
153     #if !defined Q_OS_WIN && !defined Q_OS_MAC
154     , mpdAccessibilityTimer(nullptr)
155     , showMenubarAction(nullptr)
156     #endif
157     , contextTimer(nullptr)
158     , contextSwitchTime(0)
159     , connectedState(CS_Init)
160     , stopAfterCurrent(false)
161     , responsiveSidebar(false)
162     #if defined Q_OS_WIN
163     , thumbnailTooolbar(0)
164     #endif
165 {
166     stopTrackButton=nullptr;
167     coverWidget=nullptr;
168     tabWidget=nullptr;
169     savePlayQueueButton=nullptr;
170     centerPlayQueueButton=nullptr;
171     midSpacer=nullptr;
172 
173     QPoint p=pos();
174     ActionCollection::setMainWidget(this);
175     trayItem=new TrayItem(this);
176     #ifdef QT_QTDBUS_FOUND
177     new CantataAdaptor(this);
178     QDBusConnection::sessionBus().registerObject("/cantata", this);
179     #endif
180     setMinimumHeight(Utils::scaleForDpi(480));
181     setMinimumWidth(Utils::scaleForDpi(400));
182     QWidget *widget = new QWidget(this);
183     setupUi(widget);
184     setCentralWidget(widget);
185     messageWidget->hide();
186 
187     // Need to set these values here, as used in library/device loading...
188     Song::setComposerGenres(Settings::self()->composerGenres());
189     Song::setUseOriginalYear(Settings::self()->useOriginalYear());
190 
191     int hSpace=Utils::layoutSpacing(this);
192     int vSpace=Utils::scaleForDpi(2);
193     toolbarLayout->setContentsMargins(hSpace, vSpace, hSpace, vSpace);
194     toolbar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
195     setWindowTitle("Cantata");
196     QWidget *tb=toolbar;
197     #ifdef Q_OS_MAC
198     setUnifiedTitleAndToolBarOnMac(true);
199     QToolBar *topToolBar = addToolBar("ToolBar");
200     WindowManager *wm=new WindowManager(topToolBar);
201     wm->initialize(WindowManager::WM_DRAG_MENU_AND_TOOLBAR);
202     wm->registerWidgetAndChildren(topToolBar);
203     topToolBar->setObjectName("MainToolBar");
204     topToolBar->addWidget(toolbar);
205     topToolBar->setMovable(false);
206     topToolBar->setContextMenuPolicy(Qt::PreventContextMenu);
207 
208     topToolBar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
209     topToolBar->setContentsMargins(0, 0, 0, 0);
210     QLayout *l=topToolBar->layout();
211     if (l) {
212         l->setMargin(0);
213         l->setSpacing(0);
214     }
215     topToolBar->ensurePolished();
216     toolbar=topToolBar;
217     #elif !defined Q_OS_WIN
218     QProxyStyle *proxy=qobject_cast<QProxyStyle *>(style());
219     QStyle *check=proxy && proxy->baseStyle() ? proxy->baseStyle() : style();
220     if (check->inherits("Kvantum::Style")) {
221         QToolBar *topToolBar = addToolBar("ToolBar");
222         topToolBar->setObjectName("MainToolBar");
223         topToolBar->addWidget(toolbar);
224         topToolBar->setMovable(false);
225         topToolBar->setContextMenuPolicy(Qt::PreventContextMenu);
226         topToolBar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
227         topToolBar->setContentsMargins(0, 0, 0, 0);
228         QLayout *l=topToolBar->layout();
229         if (l) {
230             l->setMargin(0);
231             l->setSpacing(0);
232         }
233         topToolBar->ensurePolished();
234         toolbar=topToolBar;
235     } else {
236         toolbar->setFixedHeight(qMax(54, (int)(fontMetrics().height()*3.25)+(toolbarLayout->spacing()*3)+(vSpace*2)));
237     }
238     #endif
239 
240     toolbar->ensurePolished();
241     toolbar->adjustSize();
242     coverWidget->setSize(toolbar->height()-(vSpace*2));
243     tb->setMinimumHeight(toolbar->height());
244     nowPlaying->initColors();
245     nowPlaying->adjustSize();
246     nowPlaying->setFixedHeight(nowPlaying->height());
247     volumeSlider->setColor(nowPlaying->textColor());
248     Icons::self()->initToolbarIcons(nowPlaying->textColor());
249     Icons::self()->initSidebarIcons();
250     QColor iconCol=Utils::monoIconColor();
251 
252     setWindowIcon(Icons::self()->appIcon);
253 
254     prefAction=ActionCollection::get()->createAction("configure", Utils::KDE==Utils::currentDe() ? tr("Configure Cantata...") : tr("Preferences..."),
255                                                      Icons::self()->configureIcon);
256     connect(prefAction, SIGNAL(triggered()),this, SLOT(showPreferencesDialog()));
257     quitAction = ActionCollection::get()->createAction("quit", tr("Quit"), MonoIcon::icon(FontAwesome::poweroff, MonoIcon::constRed, MonoIcon::constRed));
258     connect(quitAction, SIGNAL(triggered()), this, SLOT(quit()));
259     quitAction->setShortcut(QKeySequence::Quit);
260     Action *aboutAction=ActionCollection::get()->createAction("about", tr("About Cantata..."), Icons::self()->appIcon);
261     connect(aboutAction, SIGNAL(triggered()),this, SLOT(showAboutDialog()));
262     #ifdef Q_OS_MAC
263     prefAction->setMenuRole(QAction::PreferencesRole);
264     quitAction->setMenuRole(QAction::QuitRole);
265     aboutAction->setMenuRole(QAction::AboutRole);
266     #endif
267     restoreAction = new Action(tr("Show Window"), this);
268     connect(restoreAction, SIGNAL(triggered()), this, SLOT(restoreWindow()));
269 
270     serverInfoAction=ActionCollection::get()->createAction("mpdinfo", tr("Server information..."), MonoIcon::icon(FontAwesome::server, iconCol));
271     connect(serverInfoAction, SIGNAL(triggered()),this, SLOT(showServerInfo()));
272     serverInfoAction->setEnabled(Settings::self()->firstRun());
273     refreshDbAction = ActionCollection::get()->createAction("refresh", tr("Refresh Database"), Icons::self()->refreshIcon);
274     doDbRefreshAction = new Action(refreshDbAction->icon(), tr("Refresh"), this);
275     refreshDbAction->setEnabled(false);
276     connectAction = new Action(Icons::self()->connectIcon, tr("Connect"), this);
277     connectionsAction = new Action(MonoIcon::icon(FontAwesome::server, iconCol), tr("Collection"), this);
278     outputsAction = new Action(MonoIcon::icon(FontAwesome::volumeup, iconCol), tr("Outputs"), this);
279     stopAfterTrackAction = ActionCollection::get()->createAction("stopaftertrack", tr("Stop After Track"), Icons::self()->toolbarStopIcon);
280 
281     QList<int> seeks=QList<int>() << 5 << 30 << 60;
282     QList<int> seekShortcuts=QList<int>() << (int)Qt::ControlModifier << (int)Qt::ShiftModifier << (int)(Qt::ControlModifier|Qt::ShiftModifier);
283     for (int i=0; i<seeks.length(); ++i) {
284         int seek=seeks.at(i);
285         Action *fwdAction = ActionCollection::get()->createAction("seekfwd"+QString::number(seek), tr("Seek forward (%1 seconds)").arg(seek));
286         Action *revAction = ActionCollection::get()->createAction("seekrev"+QString::number(seek), tr("Seek backward (%1 seconds)").arg(seek));
287         fwdAction->setProperty("offset", seek);
288         revAction->setProperty("offset", -1*seek);
289         connect(fwdAction, SIGNAL(triggered()), MPDConnection::self(), SLOT(seek()));
290         connect(revAction, SIGNAL(triggered()), MPDConnection::self(), SLOT(seek()));
291         addAction(fwdAction);
292         addAction(revAction);
293         if (i<seekShortcuts.length()) {
294             fwdAction->setShortcut((Qt::RightToLeft==layoutDirection() ? Qt::Key_Left : Qt::Key_Right)+seekShortcuts.at(i));
295             revAction->setShortcut((Qt::RightToLeft==layoutDirection() ? Qt::Key_Right : Qt::Key_Left)+seekShortcuts.at(i));
296         }
297     }
298 
299     addPlayQueueToStoredPlaylistAction = new Action(Icons::self()->playlistListIcon, tr("Add To Stored Playlist"), this);
300     #ifdef ENABLE_DEVICES_SUPPORT
301     copyToDeviceAction = new Action(StdActions::self()->copyToDeviceAction->icon(), Utils::strippedText(StdActions::self()->copyToDeviceAction->text()), this);
302     copyToDeviceAction->setMenu(DevicesModel::self()->menu()->duplicate(nullptr));
303     #endif
304     cropPlayQueueAction = ActionCollection::get()->createAction("cropplaylist", tr("Crop Others"));
305     addStreamToPlayQueueAction = ActionCollection::get()->createAction("addstreamtoplayqueue", tr("Add Stream URL"));
306     addLocalFilesToPlayQueueAction = ActionCollection::get()->createAction("addlocalfiles", tr("Add Local Files"));
307     QIcon clearIcon = MonoIcon::icon(FontAwesome::times, MonoIcon::constRed, MonoIcon::constRed);
308     clearPlayQueueAction = ActionCollection::get()->createAction("clearplaylist", tr("Clear"), clearIcon);
309     clearPlayQueueAction->setShortcut(Qt::ControlModifier+Qt::Key_K);
310     centerPlayQueueAction = ActionCollection::get()->createAction("centerplaylist", tr("Center On Current Track"), Icons::self()->centrePlayQueueOnTrackIcon);
311     expandInterfaceAction = ActionCollection::get()->createAction("expandinterface", tr("Expanded Interface"), MonoIcon::icon(FontAwesome::expand, iconCol));
312     expandInterfaceAction->setCheckable(true);
313     songInfoAction = ActionCollection::get()->createAction("showsonginfo", tr("Show Current Song Information"), Icons::self()->infoIcon);
314     songInfoAction->setShortcut(Qt::Key_F12);
315     songInfoAction->setCheckable(true);
316     fullScreenAction = ActionCollection::get()->createAction("fullScreen", tr("Full Screen"), MonoIcon::icon(FontAwesome::arrowsalt, iconCol));
317     #ifndef Q_OS_MAC
318     fullScreenAction->setShortcut(Qt::Key_F11);
319     #endif
320     randomPlayQueueAction = ActionCollection::get()->createAction("randomplaylist", tr("Random"), Icons::self()->shuffleIcon);
321     repeatPlayQueueAction = ActionCollection::get()->createAction("repeatplaylist", tr("Repeat"), Icons::self()->repeatIcon);
322     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."));
323     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."));
324     searchPlayQueueAction = ActionCollection::get()->createAction("searchplaylist", tr("Find in Play Queue"), Icons::self()->searchIcon);
325     addAction(searchPlayQueueAction);
326     searchPlayQueueAction->setShortcut(Qt::ControlModifier+Qt::ShiftModifier+Qt::Key_F);
327     #ifdef ENABLE_HTTP_STREAM_PLAYBACK
328     streamPlayAction = ActionCollection::get()->createAction("streamplay", tr("Play HTTP Output Stream"), Icons::self()->httpStreamIcon);
329     streamPlayAction->setCheckable(true);
330     streamPlayAction->setChecked(false);
331     streamPlayAction->setVisible(false);
332     #endif
333 
334     locateAction = new Action(Icons::self()->searchIcon, tr("Locate In Library"), this);
335     locateArtistAction = ActionCollection::get()->createAction("locateartist", tr("Artist"));
336     locateAlbumAction = ActionCollection::get()->createAction("locatealbum", tr("Album"));
337     locateTrackAction = ActionCollection::get()->createAction("locatetrack", tr("Track"));
338     locateArtistAction->setSettingsText(locateAction);
339     locateAlbumAction->setSettingsText(locateAction);
340     locateTrackAction->setSettingsText(locateAction);
341     addAction(locateAction);
342 
343     QMenu *locateMenu=new QMenu();
344     locateMenu->addAction(locateArtistAction);
345     locateMenu->addAction(locateAlbumAction);
346     locateMenu->addAction(locateTrackAction);
347     locateAction->setMenu(locateMenu);
348 
349     playNextAction = ActionCollection::get()->createAction("playnext", tr("Play Next"));
350     #ifdef TAGLIB_FOUND
351     editPlayQueueTagsAction = ActionCollection::get()->createAction("editpqtags", Utils::strippedText(StdActions::self()->editTagsAction->text()), StdActions::self()->editTagsAction->icon());
352     editPlayQueueTagsAction->setSettingsText(tr("Edit Track Information (Play Queue)"));
353     #endif
354     addAction(expandAllAction = ActionCollection::get()->createAction("expandall", tr("Expand All")));
355     expandAllAction->setShortcut(Qt::ControlModifier+Qt::Key_Down);
356     addAction(collapseAllAction = ActionCollection::get()->createAction("collapseall", tr("Collapse All")));
357     collapseAllAction->setShortcut(Qt::ControlModifier+Qt::Key_Up);
358     cancelAction = ActionCollection::get()->createAction("cancel", tr("Cancel"), Icons::self()->cancelIcon);
359     cancelAction->setShortcut(Qt::AltModifier+Qt::Key_Escape);
360     connect(cancelAction, SIGNAL(triggered()), messageWidget, SLOT(animatedHide()));
361 
362     StdActions::self()->playPauseTrackAction->setEnabled(false);
363     StdActions::self()->nextTrackAction->setEnabled(false);
364     updateNextTrack(-1);
365     StdActions::self()->prevTrackAction->setEnabled(false);
366     enableStopActions(false);
367     volumeSlider->initActions();
368 
369     connectionsAction->setMenu(new QMenu(this));
370     connectionsGroup=new QActionGroup(connectionsAction->menu());
371     outputsAction->setMenu(new QMenu(this));
372     outputsAction->setVisible(false);
373     addPlayQueueToStoredPlaylistAction->setMenu(PlaylistsModel::self()->menu()->duplicate(nullptr));
374 
375     playPauseTrackButton->setDefaultAction(StdActions::self()->playPauseTrackAction);
376     stopTrackButton->setDefaultAction(StdActions::self()->stopPlaybackAction);
377     nextTrackButton->setDefaultAction(StdActions::self()->nextTrackAction);
378     prevTrackButton->setDefaultAction(StdActions::self()->prevTrackAction);
379 
380     QMenu *stopMenu=new QMenu(this);
381     stopMenu->addAction(StdActions::self()->stopPlaybackAction);
382     stopMenu->addAction(StdActions::self()->stopAfterCurrentTrackAction);
383     stopTrackButton->setMenu(stopMenu);
384     stopTrackButton->setPopupMode(QToolButton::DelayedPopup);
385 
386     clearPlayQueueAction->setEnabled(false);
387     centerPlayQueueAction->setEnabled(false);
388     StdActions::self()->savePlayQueueAction->setEnabled(false);
389     addStreamToPlayQueueAction->setEnabled(false);
390     addLocalFilesToPlayQueueAction->setEnabled(false);
391     clearPlayQueueButton->setDefaultAction(clearPlayQueueAction);
392     savePlayQueueButton->setDefaultAction(StdActions::self()->savePlayQueueAction);
393     centerPlayQueueButton->setDefaultAction(centerPlayQueueAction);
394     randomButton->setDefaultAction(randomPlayQueueAction);
395     repeatButton->setDefaultAction(repeatPlayQueueAction);
396     singleButton->setDefaultAction(singlePlayQueueAction);
397     consumeButton->setDefaultAction(consumePlayQueueAction);
398 
399     QStringList hiddenPages=Settings::self()->hiddenPages();
400     playQueuePage=new PlayQueuePage(this);
401     contextPage=new ContextPage(this);
402     QBoxLayout *layout=new QBoxLayout(QBoxLayout::TopToBottom, playQueuePage);
403     layout->setContentsMargins(0, 0, 0, 0);
404     bool playQueueInSidebar=!hiddenPages.contains(playQueuePage->metaObject()->className());
405     bool contextInSidebar=!hiddenPages.contains(contextPage->metaObject()->className());
406     layout=new QBoxLayout(QBoxLayout::TopToBottom, contextPage);
407     layout->setContentsMargins(0, 0, 0, 0);
408 
409     // Build sidebar...
410     #define TAB_ACTION(A) A->icon(), A->text(), A->text()
411     int sidebarPageShortcutKey=Qt::Key_1;
412     addAction(showPlayQueueAction = ActionCollection::get()->createAction("showplayqueue", tr("Play Queue"), Icons::self()->playqueueIcon));
413     showPlayQueueAction->setShortcut(Qt::ControlModifier+Qt::ShiftModifier+Qt::Key_Q);
414     tabWidget->addTab(playQueuePage, TAB_ACTION(showPlayQueueAction), playQueueInSidebar);
415     connect(showPlayQueueAction, SIGNAL(triggered()), this, SLOT(showPlayQueue()));
416     libraryPage = new LibraryPage(this);
417     addAction(libraryTabAction = ActionCollection::get()->createAction("showlibrarytab", tr("Library"), Icons::self()->libraryIcon));
418     libraryTabAction->setShortcut(Qt::ControlModifier+Qt::ShiftModifier+nextKey(sidebarPageShortcutKey));
419     tabWidget->addTab(libraryPage, TAB_ACTION(libraryTabAction), !hiddenPages.contains(libraryPage->metaObject()->className()));
420     connect(libraryTabAction, SIGNAL(triggered()), this, SLOT(showLibraryTab()));
421     folderPage = new FolderPage(this);
422     addAction(foldersTabAction = ActionCollection::get()->createAction("showfolderstab", tr("Folders"), Icons::self()->foldersIcon));
423     foldersTabAction->setShortcut(Qt::ControlModifier+Qt::ShiftModifier+nextKey(sidebarPageShortcutKey));
424     tabWidget->addTab(folderPage, TAB_ACTION(foldersTabAction), !hiddenPages.contains(folderPage->metaObject()->className()));
425     connect(foldersTabAction, SIGNAL(triggered()), this, SLOT(showFoldersTab()));
426     folderPage->setEnabled(!hiddenPages.contains(folderPage->metaObject()->className()));
427     playlistsPage = new PlaylistsPage(this);
428     addAction(playlistsTabAction = ActionCollection::get()->createAction("showplayliststab", tr("Playlists"), Icons::self()->playlistsIcon));
429     playlistsTabAction->setShortcut(Qt::ControlModifier+Qt::ShiftModifier+nextKey(sidebarPageShortcutKey));
430     tabWidget->addTab(playlistsPage, TAB_ACTION(playlistsTabAction), !hiddenPages.contains(playlistsPage->metaObject()->className()));
431     connect(playlistsTabAction, SIGNAL(triggered()), this, SLOT(showPlaylistsTab()));
432     connect(playlistsPage, SIGNAL(error(const QString &)), SLOT(showError(const QString &)));
433     connect(DynamicPlaylists::self(), SIGNAL(error(const QString &)), SLOT(showError(const QString &)));
434     connect(DynamicPlaylists::self(), SIGNAL(running(bool)), dynamicLabel, SLOT(setVisible(bool)));
435     connect(DynamicPlaylists::self(), SIGNAL(running(bool)), this, SLOT(controlDynamicButton()));
436     stopDynamicButton->setDefaultAction(DynamicPlaylists::self()->stopAct());
437     onlinePage = new OnlineServicesPage(this);
438     addAction(onlineTabAction = ActionCollection::get()->createAction("showonlinetab", tr("Internet"), Icons::self()->onlineIcon));
439     onlineTabAction->setShortcut(Qt::ControlModifier+Qt::ShiftModifier+nextKey(sidebarPageShortcutKey));
440     tabWidget->addTab(onlinePage, TAB_ACTION(onlineTabAction), !hiddenPages.contains(onlinePage->metaObject()->className()));
441     onlinePage->setEnabled(!hiddenPages.contains(onlinePage->metaObject()->className()));
442     connect(onlineTabAction, SIGNAL(triggered()), this, SLOT(showOnlineTab()));
443 //    connect(onlinePage, SIGNAL(addToDevice(const QString &, const QString &, const QList<Song> &)), SLOT(copyToDevice(const QString &, const QString &, const QList<Song> &)));
444     connect(onlinePage, SIGNAL(error(const QString &)), this, SLOT(showError(const QString &)));
445     #ifdef ENABLE_DEVICES_SUPPORT
446     devicesPage = new DevicesPage(this);
447     addAction(devicesTabAction = ActionCollection::get()->createAction("showdevicestab", tr("Devices"), Icons::self()->devicesIcon));
448     devicesTabAction->setShortcut(Qt::ControlModifier+Qt::ShiftModifier+nextKey(sidebarPageShortcutKey));
449     tabWidget->addTab(devicesPage, TAB_ACTION(devicesTabAction), !hiddenPages.contains(devicesPage->metaObject()->className()));
450     DevicesModel::self()->setEnabled(!hiddenPages.contains(devicesPage->metaObject()->className()));
451     connect(devicesTabAction, SIGNAL(triggered()), this, SLOT(showDevicesTab()));
452     #endif
453     searchPage = new SearchPage(this);
454     addAction(searchTabAction = ActionCollection::get()->createAction("showsearchtab", tr("Search"), Icons::self()->searchTabIcon));
455     searchTabAction->setShortcut(Qt::ControlModifier+Qt::ShiftModifier+nextKey(sidebarPageShortcutKey));
456     connect(searchTabAction, SIGNAL(triggered()), this, SLOT(showSearchTab()));
457     connect(searchPage, SIGNAL(locate(QList<Song>)), this, SLOT(locateTracks(QList<Song>)));
458     tabWidget->addTab(searchPage, TAB_ACTION(searchTabAction), !hiddenPages.contains(searchPage->metaObject()->className()));
459     tabWidget->addTab(contextPage, Icons::self()->infoSidebarIcon, tr("Info"), songInfoAction->text(),
460                       !hiddenPages.contains(contextPage->metaObject()->className()));
461     tabWidget->setStyle(Settings::self()->sidebar());
462 
463     if (playQueueInSidebar) {
464         tabToggled(PAGE_PLAYQUEUE);
465     } else {
466         tabWidget->setCurrentIndex(PAGE_LIBRARY);
467     }
468 
469     if (contextInSidebar) {
470         tabToggled(PAGE_CONTEXT);
471     } else {
472         tabWidget->toggleTab(PAGE_CONTEXT, false);
473     }
474 
475     expandInterfaceAction->setChecked(Settings::self()->showPlaylist());
476     fullScreenAction->setEnabled(expandInterfaceAction->isChecked());
477     if (fullScreenAction->isEnabled()) {
478         fullScreenAction->setChecked(Settings::self()->showFullScreen());
479     }
480     randomPlayQueueAction->setCheckable(true);
481     repeatPlayQueueAction->setCheckable(true);
482     singlePlayQueueAction->setCheckable(true);
483     consumePlayQueueAction->setCheckable(true);
484 
485     songInfoButton->setDefaultAction(songInfoAction);
486     fullScreenLabel->setVisible(false);
487     connect(fullScreenLabel, SIGNAL(leftClickedUrl()), fullScreenAction, SIGNAL(triggered()));
488     connect(playQueueSearchWidget, SIGNAL(active(bool)), playQueue, SLOT(searchActive(bool)));
489     if (Configuration(playQueuePage->metaObject()->className()).get(ItemView::constSearchActiveKey, false)) {
490         playQueueSearchWidget->activate();
491     } else {
492         playQueueSearchWidget->setVisible(false);
493     }
494     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>"
495                               "<li><b><i>#2000</i></b> return tracks from 2000</li>"
496                               "<li><b><i>#1980-1989</i></b> return tracks from the 80's</li>"
497                               "<li><b><i>Blah #2000</i></b> to search for string <i>Blah</i> and only return tracks from 2000</li>"
498                               "</ul></p>"));
499     QList<QToolButton *> playbackBtns=QList<QToolButton *>() << prevTrackButton << stopTrackButton << playPauseTrackButton << nextTrackButton;
500     QList<QToolButton *> controlBtns=QList<QToolButton *>() << menuButton << songInfoButton;
501     int playbackIconSizeNonScaled=24==Icons::self()->toolbarPlayIcon.actualSize(QSize(24, 24)).width() ? 24 : 28;
502     int playbackIconSize=Utils::scaleForDpi(playbackIconSizeNonScaled);
503     int playPauseIconSize=Utils::scaleForDpi(32);
504     int controlIconSize=Utils::scaleForDpi(22);
505     int controlButtonSize=Utils::scaleForDpi(32);
506     int playbackButtonSize=28==playbackIconSizeNonScaled ? Utils::scaleForDpi(34) : controlButtonSize;
507 
508     for (QToolButton *b: controlBtns) {
509         b->setAutoRaise(true);
510         b->setToolButtonStyle(Qt::ToolButtonIconOnly);
511         b->setFixedSize(QSize(controlButtonSize, controlButtonSize));
512         b->setIconSize(QSize(controlIconSize, controlIconSize));
513     }
514     for (QToolButton *b: playbackBtns) {
515         b->setAutoRaise(true);
516         b->setToolButtonStyle(Qt::ToolButtonIconOnly);
517         b->setFixedSize(QSize(playbackButtonSize, playbackButtonSize));
518         b->setIconSize(QSize(playbackIconSize, playbackIconSize));
519     }
520 
521     playPauseTrackButton->setIconSize(QSize(playPauseIconSize, playPauseIconSize));
522     playPauseTrackButton->setFixedSize(QSize(playPauseIconSize+6, playPauseIconSize+6));
523 
524     QList<QWidget *> pqWidgets = QList<QWidget *>() << stopDynamicButton << dynamicLabel << playQueueStatsLabel << fullScreenLabel
525                                                     << repeatButton << singleButton << randomButton << consumeButton << midSpacer
526                                                     << centerPlayQueueButton << savePlayQueueButton << clearPlayQueueButton << sizeGrip;
527     for (const auto &item: pqWidgets) {
528         Application::fixSize(item);
529     }
530 
531     if (fullScreenAction->isEnabled()) {
532         fullScreenAction->setChecked(Settings::self()->showFullScreen());
533     }
534 
535     randomPlayQueueAction->setChecked(false);
536     repeatPlayQueueAction->setChecked(false);
537     singlePlayQueueAction->setChecked(false);
538     consumePlayQueueAction->setChecked(false);
539 
540     expandedSize=Settings::self()->mainWindowSize();
541     collapsedSize=Settings::self()->mainWindowCollapsedSize();
542 
543     int width=playPauseTrackButton->width()*25;
544     QSize defaultSize(playPauseTrackButton->width()*25, playPauseTrackButton->height()*18);
545 
546     if (Settings::self()->firstRun() || (expandInterfaceAction->isChecked() && expandedSize.isEmpty())) {
547         resize(defaultSize);
548         splitter->setSizes(QList<int>() << width*0.4 << width*0.6);
549     } else {
550         if (expandInterfaceAction->isChecked()) {
551             if (!expandedSize.isEmpty()) {
552                 if (!Settings::self()->maximized() && expandedSize.width()>1) {
553                     resize(expandedSize);
554                     expandOrCollapse(false);
555                 } else {
556                     resize(expandedSize.width()>1 ? expandedSize : defaultSize);
557                     // Issue #1137 Under Windows, Cantata does not restore maximized correctly if context is in sidebar
558                     // ...so, set maximized after shown
559                     QTimer::singleShot(0, this, SLOT(showMaximized()));
560                     if (expandedSize.width()<=1) {
561                         expandedSize=defaultSize;
562                     }
563                 }
564             }
565         } else {
566             if (!collapsedSize.isEmpty() && collapsedSize.width()>1) {
567                 resize(collapsedSize);
568                 expandOrCollapse(false);
569             }
570         }
571 
572         if (!playQueueInSidebar) {
573             QByteArray state=Settings::self()->splitterState();
574             if (state.isEmpty()) {
575                 int width=playPauseTrackButton->width()*25;
576                 splitter->setSizes(QList<int>() << width*0.4 << width*0.6);
577             } else {
578                 splitter->restoreState(Settings::self()->splitterState());
579             }
580         }
581         if (fullScreenAction->isChecked()) {
582             fullScreen();
583         }
584     }
585 
586     #ifndef Q_OS_WIN
587     #ifdef Q_OS_MAC
588     bool showMenubar = true;
589     #else
590     bool showMenubar = Utils::Gnome!=Utils::currentDe();
591     #endif
592     if (showMenubar) {
593         #ifdef Q_OS_MAC
594         menuButton->setVisible(false);
595         #else
596         showMenubarAction = ActionCollection::get()->createAction("showmenubar", tr("Show Menubar"));
597         showMenubarAction->setShortcut(Qt::ControlModifier+Qt::Key_M);
598         showMenubarAction->setCheckable(true);
599         connect(showMenubarAction, SIGNAL(toggled(bool)), this, SLOT(toggleMenubar()));
600         #endif
601 
602         QMenu *menu=new QMenu(tr("&Music"), this);
603         addMenuAction(menu, refreshDbAction);
604         menu->addSeparator();
605         addMenuAction(menu, connectionsAction);
606         addMenuAction(menu, outputsAction);
607         #ifdef ENABLE_HTTP_STREAM_PLAYBACK
608         addMenuAction(menu, streamPlayAction);
609         #endif
610         menu->addSeparator();
611         addMenuAction(menu, quitAction);
612         menuBar()->addMenu(menu);
613         menu=new QMenu(tr("&Edit"), this);
614         addMenuAction(menu, PlayQueueModel::self()->undoAct());
615         addMenuAction(menu, PlayQueueModel::self()->redoAct());
616         menu->addSeparator();
617         addMenuAction(menu, StdActions::self()->searchAction);
618         addMenuAction(menu, searchPlayQueueAction);
619         if (Utils::KDE!=Utils::currentDe()) {
620             menu->addSeparator();
621             addMenuAction(menu, prefAction);
622         }
623         menuBar()->addMenu(menu);
624         if (Utils::KDE!=Utils::currentDe()) {
625             menu=new QMenu(tr("&View"), this);
626             #ifndef Q_OS_MAC
627             if (showMenubarAction) {
628                 addMenuAction(menu, showMenubarAction);
629                 menu->addSeparator();
630             }
631             #endif
632             addMenuAction(menu, expandInterfaceAction);
633             addMenuAction(menu, fullScreenAction);
634             //addMenuAction(menu, songInfoAction);
635             menuBar()->addMenu(menu);
636         }
637         menu=new QMenu(tr("&Queue"), this);
638         addMenuAction(menu, clearPlayQueueAction);
639         addMenuAction(menu, StdActions::self()->savePlayQueueAction);
640         addMenuAction(menu, addStreamToPlayQueueAction);
641         addMenuAction(menu, addLocalFilesToPlayQueueAction);
642         menu->addSeparator();
643         addMenuAction(menu, PlayQueueModel::self()->shuffleAct());
644         addMenuAction(menu, PlayQueueModel::self()->sortAct());
645         menuBar()->addMenu(menu);
646         if (Utils::KDE==Utils::currentDe()) {
647             menu=new QMenu(tr("&Settings"), this);
648             #ifndef Q_OS_MAC
649             if (showMenubarAction) {
650                 addMenuAction(menu, showMenubarAction);
651             }
652             #endif
653             addMenuAction(menu, expandInterfaceAction);
654             addMenuAction(menu, fullScreenAction);
655             //addMenuAction(menu, songInfoAction);
656             menu->addSeparator();
657             addMenuAction(menu, prefAction);
658             menuBar()->addMenu(menu);
659         }
660         #ifdef Q_OS_MAC
661         OSXStyle::self()->initWindowMenu(this);
662         #endif
663         menu=new QMenu(tr("&Help"), this);
664         addMenuAction(menu, serverInfoAction);
665         addMenuAction(menu, aboutAction);
666         menuBar()->addMenu(menu);
667     }
668     #endif // infdef Q_OS_WIN
669 
670     #ifndef Q_OS_MAC
671     QMenu *mainMenu=new QMenu(this);
672     mainMenu->addAction(expandInterfaceAction);
673     #ifndef Q_OS_WIN
674     if (showMenubarAction) {
675         mainMenu->addAction(showMenubarAction);
676         showMenubarAction->setChecked(Settings::self()->showMenubar());
677         toggleMenubar();
678     }
679     #endif
680     mainMenu->addAction(fullScreenAction);
681     mainMenu->addAction(connectionsAction);
682     mainMenu->addAction(outputsAction);
683     #ifdef ENABLE_HTTP_STREAM_PLAYBACK
684     mainMenu->addAction(streamPlayAction);
685     #endif
686     mainMenu->addAction(prefAction);
687     mainMenu->addAction(refreshDbAction);
688     mainMenu->addSeparator();
689     mainMenu->addAction(StdActions::self()->searchAction);
690     mainMenu->addAction(searchPlayQueueAction);
691     mainMenu->addSeparator();
692     mainMenu->addAction(serverInfoAction);
693     mainMenu->addAction(aboutAction);
694     mainMenu->addSeparator();
695     mainMenu->addAction(quitAction);
696     menuButton->setIcon(Icons::self()->toolbarMenuIcon);
697     menuButton->setAlignedMenu(mainMenu);
698     #endif // ifndef Q_OS_MAC
699 
700     dynamicLabel->setVisible(false);
701     stopDynamicButton->setVisible(false);
702     StdActions::self()->addWithPriorityAction->setVisible(false);
703     StdActions::self()->setPriorityAction->setVisible(false);
704 
705     playQueueProxyModel.setSourceModel(PlayQueueModel::self());
706     playQueue->setModel(&playQueueProxyModel);
707     playQueue->addAction(playQueue->removeFromAct());
708     ratingAction=new Action(tr("Set Rating"), this);
709     ratingAction->setMenu(new QMenu(nullptr));
710     for (int i=0; i<((Song::Rating_Max/Song::Rating_Step)+1); ++i) {
711         QString text;
712         if (0==i) {
713             text=tr("No Rating");
714         } else {
715             for (int s=0; s<i; ++s) {
716                 text+=QChar(0x2605);
717             }
718         }
719         Action *action=ActionCollection::get()->createAction(QLatin1String("rating")+QString::number(i), text);
720         action->setProperty(constRatingKey, i*Song::Rating_Step);
721         action->setShortcut(Qt::AltModifier+Qt::Key_0+i);
722         action->setSettingsText(ratingAction);
723         ratingAction->menu()->addAction(action);
724         connect(action, SIGNAL(triggered()), SLOT(setRating()));
725     }
726     playQueue->addAction(ratingAction);
727     playQueue->addAction(StdActions::self()->setPriorityAction);
728     playQueue->addAction(stopAfterTrackAction);
729     playQueue->addAction(locateAction);
730     #ifdef TAGLIB_FOUND
731     playQueue->addAction(editPlayQueueTagsAction);
732     #endif
733     playQueue->addAction(playNextAction);
734     Action *sep=new Action(this);
735     sep->setSeparator(true);
736     playQueue->addAction(sep);
737     playQueue->addAction(PlayQueueModel::self()->removeDuplicatesAct());
738     playQueue->addAction(clearPlayQueueAction);
739     playQueue->addAction(cropPlayQueueAction);
740     playQueue->addAction(StdActions::self()->savePlayQueueAction);
741     playQueue->addAction(addStreamToPlayQueueAction);
742     playQueue->addAction(addLocalFilesToPlayQueueAction);
743     playQueue->addAction(addPlayQueueToStoredPlaylistAction);
744     #ifdef ENABLE_DEVICES_SUPPORT
745     playQueue->addAction(copyToDeviceAction);
746     #endif
747     playQueue->addAction(PlayQueueModel::self()->shuffleAct());
748     playQueue->addAction(PlayQueueModel::self()->sortAct());
749     playQueue->addAction(PlayQueueModel::self()->undoAct());
750     playQueue->addAction(PlayQueueModel::self()->redoAct());
751     playQueue->readConfig();
752 
753     #ifdef ENABLE_DEVICES_SUPPORT
754     connect(DevicesModel::self(), SIGNAL(addToDevice(const QString &)), this, SLOT(addToDevice(const QString &)));
755     connect(DevicesModel::self(), SIGNAL(error(const QString &)), this, SLOT(showError(const QString &)));
756     connect(libraryPage, SIGNAL(addToDevice(const QString &, const QString &, const QList<Song> &)), SLOT(copyToDevice(const QString &, const QString &, const QList<Song> &)));
757     connect(folderPage->mpd(), SIGNAL(addToDevice(const QString &, const QString &, const QList<Song> &)), SLOT(copyToDevice(const QString &, const QString &, const QList<Song> &)));
758     connect(playlistsPage, SIGNAL(addToDevice(const QString &, const QString &, const QList<Song> &)), SLOT(copyToDevice(const QString &, const QString &, const QList<Song> &)));
759     connect(devicesPage, SIGNAL(addToDevice(const QString &, const QString &, const QList<Song> &)), SLOT(copyToDevice(const QString &, const QString &, const QList<Song> &)));
760     connect(searchPage, SIGNAL(addToDevice(const QString &, const QString &, const QList<Song> &)), SLOT(copyToDevice(const QString &, const QString &, const QList<Song> &)));
761     connect(StdActions::self()->deleteSongsAction, SIGNAL(triggered()), SLOT(deleteSongs()));
762     connect(devicesPage, SIGNAL(deleteSongs(const QString &, const QList<Song> &)), SLOT(deleteSongs(const QString &, const QList<Song> &)));
763     connect(libraryPage, SIGNAL(deleteSongs(const QString &, const QList<Song> &)), SLOT(deleteSongs(const QString &, const QList<Song> &)));
764     connect(folderPage->mpd(), SIGNAL(deleteSongs(const QString &, const QList<Song> &)), SLOT(deleteSongs(const QString &, const QList<Song> &)));
765     connect(searchPage, SIGNAL(deleteSongs(const QString &, const QList<Song> &)), SLOT(deleteSongs(const QString &, const QList<Song> &)));
766     #endif
767     for (QAction *act: StdActions::self()->setPriorityAction->menu()->actions()) {
768         connect(act, SIGNAL(triggered()), this, SLOT(addWithPriority()));
769     }
770     for (QAction *act: StdActions::self()->addWithPriorityAction->menu()->actions()) {
771         connect(act, SIGNAL(triggered()), this, SLOT(addWithPriority()));
772     }
773     connect(StdActions::self()->appendToPlayQueueAndPlayAction, SIGNAL(triggered()), this, SLOT(appendToPlayQueueAndPlay()));
774     connect(StdActions::self()->addToPlayQueueAndPlayAction, SIGNAL(triggered()), this, SLOT(addToPlayQueueAndPlay()));
775     connect(StdActions::self()->insertAfterCurrentAction, SIGNAL(triggered()), this, SLOT(insertIntoPlayQueue()));
776     connect(MPDConnection::self(), SIGNAL(outputsUpdated(const QList<Output> &)), this, SLOT(outputsUpdated(const QList<Output> &)));
777     connect(this, SIGNAL(enableOutput(quint32, bool)), MPDConnection::self(), SLOT(enableOutput(quint32, bool)));
778     connect(this, SIGNAL(outputs()), MPDConnection::self(), SLOT(outputs()));
779     connect(this, SIGNAL(pause(bool)), MPDConnection::self(), SLOT(setPause(bool)));
780     connect(this, SIGNAL(play()), MPDConnection::self(), SLOT(play()));
781     connect(this, SIGNAL(stop(bool)), MPDConnection::self(), SLOT(stopPlaying(bool)));
782     connect(this, SIGNAL(terminating()), MPDConnection::self(), SLOT(stop()));
783     connect(this, SIGNAL(getStatus()), MPDConnection::self(), SLOT(getStatus()));
784     connect(this, SIGNAL(playListInfo()), MPDConnection::self(), SLOT(playListInfo()));
785     connect(this, SIGNAL(currentSong()), MPDConnection::self(), SLOT(currentSong()));
786     connect(this, SIGNAL(setSeekId(qint32, quint32)), MPDConnection::self(), SLOT(setSeekId(qint32, quint32)));
787     connect(this, SIGNAL(startPlayingSongId(qint32)), MPDConnection::self(), SLOT(startPlayingSongId(qint32)));
788     connect(this, SIGNAL(setDetails(const MPDConnectionDetails &)), MPDConnection::self(), SLOT(setDetails(const MPDConnectionDetails &)));
789     connect(this, SIGNAL(setPriority(const QList<qint32> &, quint8, bool)), MPDConnection::self(), SLOT(setPriority(const QList<qint32> &, quint8, bool)));
790     connect(this, SIGNAL(addSongsToPlaylist(const QString &, const QStringList &)), MPDConnection::self(), SLOT(addToPlaylist(const QString &, const QStringList &)));
791     connect(PlayQueueModel::self(), SIGNAL(statsUpdated(int, quint32)), this, SLOT(updatePlayQueueStats(int, quint32)));
792     connect(PlayQueueModel::self(), SIGNAL(fetchingStreams()), playQueue, SLOT(showSpinner()));
793     connect(PlayQueueModel::self(), SIGNAL(streamsFetched()), playQueue, SLOT(hideSpinner()));
794     connect(PlayQueueModel::self(), SIGNAL(updateCurrent(Song)), SLOT(updateCurrentSong(Song)));
795     connect(PlayQueueModel::self(), SIGNAL(streamFetchStatus(QString)), playQueue, SLOT(streamFetchStatus(QString)));
796     connect(PlayQueueModel::self(), SIGNAL(error(QString)), SLOT(showError(QString)));
797     connect(playQueue, SIGNAL(cancelStreamFetch()), PlayQueueModel::self(), SLOT(cancelStreamFetch()));
798     connect(playQueue, SIGNAL(itemsSelected(bool)), SLOT(playQueueItemsSelected(bool)));
799     connect(MPDStatus::self(), SIGNAL(updated()), this, SLOT(updateStatus()));
800     connect(MPDConnection::self(), SIGNAL(playlistUpdated(const QList<Song> &, bool)), this, SLOT(updatePlayQueue(const QList<Song> &, bool)));
801     connect(MPDConnection::self(), SIGNAL(currentSongUpdated(Song)), this, SLOT(updateCurrentSong(Song)));
802     connect(MPDConnection::self(), SIGNAL(stateChanged(bool)), SLOT(mpdConnectionStateChanged(bool)));
803     connect(MPDConnection::self(), SIGNAL(error(const QString &, bool)), SLOT(showError(const QString &, bool)));
804     connect(MPDConnection::self(), SIGNAL(info(const QString &)), SLOT(showInformation(const QString &)));
805     connect(MPDConnection::self(), SIGNAL(dirChanged()), SLOT(checkMpdDir()));
806     connect(MPDConnection::self(), SIGNAL(connectionNotChanged(QString)), SLOT(mpdConnectionName(QString)));
807     connect(MpdLibraryModel::self(), SIGNAL(error(QString)), SLOT(showError(QString)));
808     connect(ApiKeys::self(), SIGNAL(error(const QString &)), SLOT(showError(const QString &)));
809     connect(refreshDbAction, SIGNAL(triggered()), this, SLOT(refreshDbPromp()));
810     connect(doDbRefreshAction, SIGNAL(triggered()), MpdLibraryModel::self(), SLOT(clearDb()));
811     connect(doDbRefreshAction, SIGNAL(triggered()), MPDConnection::self(), SLOT(update()));
812     connect(doDbRefreshAction, SIGNAL(triggered()), messageWidget, SLOT(animatedHide()));
813     connect(connectAction, SIGNAL(triggered()), this, SLOT(connectToMpd()));
814     connect(StdActions::self()->prevTrackAction, SIGNAL(triggered()), MPDConnection::self(), SLOT(goToPrevious()));
815     connect(StdActions::self()->nextTrackAction, SIGNAL(triggered()), MPDConnection::self(), SLOT(goToNext()));
816     connect(StdActions::self()->playPauseTrackAction, SIGNAL(triggered()), this, SLOT(playPauseTrack()));
817     connect(StdActions::self()->stopPlaybackAction, SIGNAL(triggered()), this, SLOT(stopPlayback()));
818     connect(StdActions::self()->stopAfterCurrentTrackAction, SIGNAL(triggered()), this, SLOT(stopAfterCurrentTrack()));
819     connect(stopAfterTrackAction, SIGNAL(triggered()), this, SLOT(stopAfterTrack()));
820     connect(this, SIGNAL(setVolume(int)), MPDConnection::self(), SLOT(setVolume(int)));
821     connect(nowPlaying, SIGNAL(sliderReleased()), this, SLOT(setPosition()));
822     connect(PlayQueueModel::self(), SIGNAL(currentSongRating(QString,quint8)), nowPlaying, SLOT(rating(QString,quint8)));
823     connect(randomPlayQueueAction, SIGNAL(triggered(bool)), MPDConnection::self(), SLOT(setRandom(bool)));
824     connect(repeatPlayQueueAction, SIGNAL(triggered(bool)), MPDConnection::self(), SLOT(setRepeat(bool)));
825     connect(singlePlayQueueAction, SIGNAL(triggered(bool)), MPDConnection::self(), SLOT(setSingle(bool)));
826     connect(consumePlayQueueAction, SIGNAL(triggered(bool)), MPDConnection::self(), SLOT(setConsume(bool)));
827     #ifdef ENABLE_HTTP_STREAM_PLAYBACK
828     connect(streamPlayAction, SIGNAL(triggered(bool)), HttpStream::self(), SLOT(setEnabled(bool)));
829     connect(MPDConnection::self(), SIGNAL(streamUrl(QString)), SLOT(streamUrl(QString)));
830     #endif
831     connect(playQueueSearchWidget, SIGNAL(returnPressed()), this, SLOT(searchPlayQueue()));
832     connect(playQueueSearchWidget, SIGNAL(textChanged(const QString)), this, SLOT(searchPlayQueue()));
833     connect(playQueueSearchWidget, SIGNAL(active(bool)), this, SLOT(playQueueSearchActivated(bool)));
834     connect(playQueue, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(playQueueItemActivated(const QModelIndex &)));
835     connect(StdActions::self()->removeAction, SIGNAL(triggered()), this, SLOT(removeItems()));
836     connect(StdActions::self()->appendToPlayQueueAction, SIGNAL(triggered()), this, SLOT(appendToPlayQueue()));
837     connect(StdActions::self()->replacePlayQueueAction, SIGNAL(triggered()), this, SLOT(replacePlayQueue()));
838     connect(playQueue->removeFromAct(), SIGNAL(triggered()), this, SLOT(removeFromPlayQueue()));
839     connect(clearPlayQueueAction, SIGNAL(triggered()), playQueueSearchWidget, SLOT(clear()));
840     connect(clearPlayQueueAction, SIGNAL(triggered()), this, SLOT(clearPlayQueue()));
841     connect(centerPlayQueueAction, SIGNAL(triggered()), this, SLOT(centerPlayQueue()));
842     connect(cropPlayQueueAction, SIGNAL(triggered()), this, SLOT(cropPlayQueue()));
843     connect(songInfoAction, SIGNAL(triggered()), this, SLOT(showSongInfo()));
844     connect(expandInterfaceAction, SIGNAL(triggered()), this, SLOT(expandOrCollapse()));
845     connect(fullScreenAction, SIGNAL(triggered()), this, SLOT(fullScreen()));
846     #ifdef TAGLIB_FOUND
847     connect(StdActions::self()->editTagsAction, SIGNAL(triggered()), this, SLOT(editTags()));
848     connect(editPlayQueueTagsAction, SIGNAL(triggered()), this, SLOT(editTags()));
849     connect(StdActions::self()->organiseFilesAction, SIGNAL(triggered()), SLOT(organiseFiles()));
850     #endif
851     connect(context, SIGNAL(findArtist(QString)), this, SLOT(locateArtist(QString)));
852     connect(context, SIGNAL(findAlbum(QString,QString)), this, SLOT(locateAlbum(QString,QString)));
853     connect(context, SIGNAL(playSong(QString)), PlayQueueModel::self(), SLOT(playSong(QString)));
854     connect(locateTrackAction, SIGNAL(triggered()), this, SLOT(locateTrack()));
855     connect(locateAlbumAction, SIGNAL(triggered()), this, SLOT(locateTrack()));
856     connect(locateArtistAction, SIGNAL(triggered()), this, SLOT(locateTrack()));
857     connect(this, SIGNAL(playNext(QList<quint32>,quint32,quint32)), MPDConnection::self(), SLOT(move(QList<quint32>,quint32,quint32)));
858     connect(playNextAction, SIGNAL(triggered()), this, SLOT(moveSelectionAfterCurrentSong()));
859     connect(qApp, SIGNAL(paletteChanged(const QPalette &)), this, SLOT(paletteChanged()));
860 
861     connect(StdActions::self()->searchAction, SIGNAL(triggered()), SLOT(showSearch()));
862     connect(searchPlayQueueAction, SIGNAL(triggered()), this, SLOT(showPlayQueueSearch()));
863     connect(playQueue, SIGNAL(focusSearch(QString)), playQueueSearchWidget, SLOT(activate(QString)));
864     connect(expandAllAction, SIGNAL(triggered()), this, SLOT(expandAll()));
865     connect(collapseAllAction, SIGNAL(triggered()), this, SLOT(collapseAll()));
866     connect(addStreamToPlayQueueAction, SIGNAL(triggered()), this, SLOT(addStreamToPlayQueue()));
867     connect(addLocalFilesToPlayQueueAction, SIGNAL(triggered()), this, SLOT(addLocalFilesToPlayQueue()));
868     connect(splitter, SIGNAL(splitterMoved(int, int)), this, SLOT(controlPlayQueueButtons()));
869     connect(StdActions::self()->setCoverAction, SIGNAL(triggered()), SLOT(setCover()));
870     #ifdef ENABLE_REPLAYGAIN_SUPPORT
871     connect(StdActions::self()->replaygainAction, SIGNAL(triggered()), SLOT(replayGain()));
872     #endif
873     connect(PlaylistsModel::self(), SIGNAL(addToNew()), this, SLOT(addToNewStoredPlaylist()));
874     connect(PlaylistsModel::self(), SIGNAL(addToExisting(const QString &)), this, SLOT(addToExistingStoredPlaylist(const QString &)));
875 // TODO: Why is this here???
876 //    connect(playlistsPage, SIGNAL(add(const QStringList &, bool, quint8)), PlayQueueModel::self(), SLOT(addItems(const QStringList &, bool, quint8)));
877     connect(coverWidget, SIGNAL(clicked()), expandInterfaceAction, SLOT(trigger()));
878     #if !defined Q_OS_WIN && !defined Q_OS_MAC
879     connect(MountPoints::self(), SIGNAL(updated()), SLOT(checkMpdAccessibility()));
880     connect(qApp, SIGNAL(commitDataRequest(QSessionManager&)), SLOT(commitDataRequest(QSessionManager&)));
881     #endif
882     playQueueItemsSelected(false);
883     playQueue->setFocus();
884 
885     MPDConnection::self()->start();
886     connectToMpd();
887 
888     QString page=Settings::self()->page();
889     for (int i=0; i<tabWidget->count(); ++i) {
890         if (tabWidget->widget(i)->metaObject()->className()==page) {
891             tabWidget->setCurrentIndex(i);
892             break;
893         }
894     }
895 
896     connect(tabWidget, SIGNAL(currentChanged(int)), this, SLOT(currentTabChanged(int)));
897     connect(tabWidget, SIGNAL(tabToggled(int)), this, SLOT(tabToggled(int)));
898     connect(tabWidget, SIGNAL(styleChanged(int)), this, SLOT(sidebarModeChanged()));
899 
900     readSettings();
901     updateConnectionsMenu();
902     ActionCollection::get()->readSettings();
903 
904     if (testAttribute(Qt::WA_TranslucentBackground)) {
905         // BUG: 146 - Work-around non-showing main window on start-up with transparent QtCurve windows.
906         move(p.isNull() ? QPoint(96, 96) : p);
907     }
908 
909     currentTabChanged(tabWidget->currentIndex());
910 
911     if (Settings::self()->firstRun() && MPDConnection::self()->isConnected()) {
912         mpdConnectionStateChanged(true);
913     }
914     MediaKeys::self()->start();
915     #ifdef Q_OS_MAC
916     dockMenu=new DockMenu(this);
917     #endif
918     updateActionToolTips();
919     CustomActions::self()->setMainWindow(this);
920 
921     if (Utils::useSystemTray() && Settings::self()->startHidden()) {
922         hide();
923     } else {
924         show();
925     }
926     QTimer::singleShot(0, this, SLOT(controlPlayQueueButtons()));
927 }
928 
~MainWindow()929 MainWindow::~MainWindow()
930 {
931     #if !defined Q_OS_WIN && !defined Q_OS_MAC
932     if (showMenubarAction) {
933         Settings::self()->saveShowMenubar(showMenubarAction->isChecked());
934     }
935     #endif
936     #ifdef ENABLE_HTTP_STREAM_PLAYBACK
937     HttpStream::self()->save();
938     #endif
939     bool hadCantataStreams=PlayQueueModel::self()->removeCantataStreams();
940     Settings::self()->saveShowFullScreen(fullScreenAction->isChecked());
941     if (!fullScreenAction->isChecked()) {
942         if (expandInterfaceAction->isChecked()) {
943             Settings::self()->saveMaximized(isMaximized());
944             Settings::self()->saveMainWindowSize(isMaximized() ? previousSize : size());
945         } else {
946             Settings::self()->saveMaximized(false);
947             Settings::self()->saveMainWindowSize(expandedSize);
948         }
949         Settings::self()->saveMainWindowCollapsedSize(expandInterfaceAction->isChecked() ? collapsedSize : size());
950         Settings::self()->saveShowPlaylist(expandInterfaceAction->isChecked());
951     }
952     #ifdef ENABLE_HTTP_STREAM_PLAYBACK
953     Settings::self()->savePlayStream(streamPlayAction->isVisible() && streamPlayAction->isChecked());
954     #endif
955     if (!fullScreenAction->isChecked()) {
956         if (!tabWidget->isEnabled(PAGE_PLAYQUEUE)) {
957             Settings::self()->saveSplitterState(splitter->saveState());
958         }
959     }
960     Settings::self()->savePage(tabWidget->currentWidget()->metaObject()->className());
961     playQueue->saveConfig();
962 //    playlistsPage->saveConfig();
963     context->saveConfig();
964     StreamsModel::self()->save();
965     nowPlaying->saveConfig();
966     Settings::self()->saveForceSingleClick(TreeView::getForceSingleClick());
967     if (Utils::useSystemTray()) {
968         Settings::StartupState startupState=Settings::self()->startupState();
969         Settings::self()->saveStartHidden(trayItem->isActive() && Settings::self()->minimiseOnClose() &&
970                                           ((isHidden() && Settings::SS_ShowMainWindow!=startupState) || (Settings::SS_HideMainWindow==startupState)));
971     }
972     Settings::self()->save();
973     disconnect(MPDConnection::self(), nullptr, nullptr, nullptr);
974     if (Settings::self()->stopOnExit()) {
975         DynamicPlaylists::self()->stop();
976     }
977     if (Settings::self()->stopOnExit()) {
978         emit terminating();
979         Utils::sleep(); // Allow time for stop to be sent...
980     } else if (hadCantataStreams) {
981         Utils::sleep(); // Allow time for removal of cantata streams...
982     }
983     #ifdef ENABLE_DEVICES_SUPPORT
984     DevicesModel::self()->stop();
985     #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND
986     Covers::self()->cleanCdda();
987     #endif
988     #endif
989     MediaKeys::self()->stop();
990     #ifdef TAGLIB_FOUND
991     Tags::stop();
992     #endif
993     ThreadCleaner::self()->stopAll();
994     Configuration(playQueuePage->metaObject()->className()).set(ItemView::constSearchActiveKey, playQueueSearchWidget->isActive());
995 }
996 
selectedSongs() const997 QList<Song> MainWindow::selectedSongs() const
998 {
999     return currentPage ? currentPage->selectedSongs() : QList<Song>();
1000 }
1001 
listActions() const1002 QStringList MainWindow::listActions() const
1003 {
1004     QStringList allActions;
1005     for(int i = 0; i < ActionCollection::get()->actions().count(); i++) {
1006         Action *action = qobject_cast<Action *>(ActionCollection::get()->actions().at(i));
1007         if (!action) {
1008             continue;
1009         }
1010         allActions.append(action->objectName()+" - "+Action::settingsText(action));
1011     }
1012     return allActions;
1013 }
1014 
triggerAction(const QString & name)1015 void MainWindow::triggerAction(const QString &name)
1016 {
1017     QAction *act=ActionCollection::get()->action(name);
1018     if (act) {
1019         act->trigger();
1020     }
1021 }
1022 
1023 #if !defined Q_OS_WIN
addMenuAction(QMenu * menu,QAction * action)1024 void MainWindow::addMenuAction(QMenu *menu, QAction *action)
1025 {
1026     menu->addAction(action);
1027     addAction(action); // Bind action to window, so that it works when fullscreen!
1028 }
1029 #endif
1030 
showError(const QString & message,bool showActions)1031 void MainWindow::showError(const QString &message, bool showActions)
1032 {
1033     if (!message.isEmpty()) {
1034         if (!isVisible()) {
1035             show();
1036         }
1037         expand();
1038     }
1039 
1040     if (QLatin1String("NO_SONGS")==message) {
1041         messageWidget->setError(tr("Failed to locate any songs matching the dynamic playlist rules."));
1042     } else {
1043         messageWidget->setError(message);
1044     }
1045     if (showActions) {
1046         messageWidget->setActions(QList<QAction*>() << prefAction << connectAction);
1047     } else {
1048         messageWidget->removeAllActions();
1049     }
1050     QApplication::alert(this);
1051 }
1052 
showInformation(const QString & message)1053 void MainWindow::showInformation(const QString &message)
1054 {
1055     if (!messageWidget->showingError()) {
1056         messageWidget->setInformation(message);
1057         messageWidget->removeAllActions();
1058     }
1059 }
1060 
mpdConnectionStateChanged(bool connected)1061 void MainWindow::mpdConnectionStateChanged(bool connected)
1062 {
1063     serverInfoAction->setEnabled(connected && !MPDConnection::self()->isMopidy());
1064     refreshDbAction->setEnabled(connected);
1065     addStreamToPlayQueueAction->setEnabled(connected);
1066     addLocalFilesToPlayQueueAction->setEnabled(connected && (HttpServer::self()->isAlive() || MPDConnection::self()->localFilePlaybackSupported()));
1067     if (connected) {
1068         //if (!messageWidget->showingError()) {
1069             messageWidget->hide();
1070         //}
1071         if (CS_Connected!=connectedState) {
1072             emit playListInfo();
1073             emit outputs();
1074             MpdLibraryModel::self()->reload();
1075             if (CS_Init!=connectedState) {
1076                 currentTabChanged(tabWidget->currentIndex());
1077             }
1078             connectedState=CS_Connected;
1079             StdActions::self()->addWithPriorityAction->setVisible(MPDConnection::self()->canUsePriority());
1080             StdActions::self()->setPriorityAction->setVisible(MPDConnection::self()->canUsePriority());
1081         }
1082     } else {
1083         libraryPage->clear();
1084         folderPage->mpd()->clear();
1085         PlaylistsModel::self()->clear();
1086         PlayQueueModel::self()->clear();
1087         searchPage->clear();
1088         connectedState=CS_Disconnected;
1089         outputsAction->setVisible(false);
1090         MPDStatus dummyStatus;
1091         updateStatus(&dummyStatus);
1092     }
1093     updateWindowTitle();
1094 }
1095 
keyPressEvent(QKeyEvent * event)1096 void MainWindow::keyPressEvent(QKeyEvent *event)
1097 {
1098     if ((Qt::Key_Enter==event->key() || Qt::Key_Return==event->key()) && playQueue->hasFocus()) {
1099         QModelIndexList selection=playQueue->selectedIndexes();
1100         if (!selection.isEmpty()) {
1101             //play the first selected song
1102             playQueueItemActivated(selection.first());
1103         }
1104     }
1105 }
1106 
showEvent(QShowEvent * event)1107 void MainWindow::showEvent(QShowEvent *event)
1108 {
1109     #if defined Q_OS_WIN
1110     if (!thumbnailTooolbar) {
1111         thumbnailTooolbar=new ThumbnailToolBar(this);
1112     }
1113     #endif
1114     QMainWindow::showEvent(event);
1115     controlView();
1116     if (!shown) {
1117         shown=true;
1118         // Work-around for qt5ct palette issues...
1119         QTimer::singleShot(0, this, SLOT(paletteChanged()));
1120     }
1121 }
1122 
closeEvent(QCloseEvent * event)1123 void MainWindow::closeEvent(QCloseEvent *event)
1124 {
1125     if (trayItem->isActive() && Settings::self()->minimiseOnClose() && event->spontaneous()) {
1126         lastPos=pos();
1127         hide();
1128         event->ignore();
1129     } else if (canClose()) {
1130         QMainWindow::closeEvent(event);
1131     }
1132 }
1133 
resizeEvent(QResizeEvent * event)1134 void MainWindow::resizeEvent(QResizeEvent *event)
1135 {
1136     previousSize=event->oldSize();
1137     QMainWindow::resizeEvent(event);
1138     controlView();
1139 }
1140 
playQueueItemsSelected(bool s)1141 void MainWindow::playQueueItemsSelected(bool s)
1142 {
1143     int rc=playQueue->model() ? playQueue->model()->rowCount() : 0;
1144     bool haveItems=rc>0;
1145     bool singleSelection=1==playQueue->selectedIndexes(false).count(); // Dont need sorted selection here...
1146     playQueue->removeFromAct()->setEnabled(s && haveItems);
1147     StdActions::self()->setPriorityAction->setEnabled(s && haveItems);
1148     locateAction->setEnabled(singleSelection);
1149     cropPlayQueueAction->setEnabled(playQueue->haveUnSelectedItems() && haveItems);
1150     #ifdef TAGLIB_FOUND
1151     editPlayQueueTagsAction->setEnabled(s && haveItems && MPDConnection::self()->getDetails().dirReadable);
1152     #endif
1153     addPlayQueueToStoredPlaylistAction->setEnabled(haveItems);
1154     #ifdef ENABLE_DEVICES_SUPPORT
1155     copyToDeviceAction->setEnabled(editPlayQueueTagsAction->isEnabled());
1156     #endif
1157     stopAfterTrackAction->setEnabled(singleSelection);
1158     ratingAction->setEnabled(s && haveItems);
1159 }
1160 
connectToMpd(const MPDConnectionDetails & details)1161 void MainWindow::connectToMpd(const MPDConnectionDetails &details)
1162 {
1163     if (!MPDConnection::self()->isConnected() || details!=MPDConnection::self()->getDetails()) {
1164         libraryPage->clear();
1165         folderPage->mpd()->clear();
1166         PlaylistsModel::self()->clear();
1167         PlayQueueModel::self()->clear();
1168         searchPage->clear();
1169         if (!MPDConnection::self()->getDetails().isEmpty() && details!=MPDConnection::self()->getDetails()) {
1170             DynamicPlaylists::self()->stop();
1171         }
1172         showInformation(tr("Connecting to %1").arg(details.description()));
1173         outputsAction->setVisible(false);
1174         if (CS_Init!=connectedState) {
1175             connectedState=CS_Disconnected;
1176         }
1177     }
1178     emit setDetails(details);
1179 }
1180 
connectToMpd()1181 void MainWindow::connectToMpd()
1182 {
1183     messageWidget->hide();
1184     connectToMpd(Settings::self()->connectionDetails());
1185 }
1186 
streamUrl(const QString & u)1187 void MainWindow::streamUrl(const QString &u)
1188 {
1189     #ifdef ENABLE_HTTP_STREAM_PLAYBACK
1190     streamPlayAction->setVisible(!u.isEmpty());
1191     streamPlayAction->setChecked(streamPlayAction->isVisible() && Settings::self()->playStream());
1192     HttpStream::self()->setEnabled(streamPlayAction->isChecked());
1193     #else
1194     Q_UNUSED(u)
1195     #endif
1196 }
1197 
refreshDbPromp()1198 void MainWindow::refreshDbPromp()
1199 {
1200     int btnLayout=style()->styleHint(QStyle::SH_DialogButtonLayout);
1201     if (QDialogButtonBox::GnomeLayout==btnLayout || QDialogButtonBox::MacLayout==btnLayout) {
1202         messageWidget->setActions(QList<QAction*>() << cancelAction << doDbRefreshAction);
1203     } else {
1204         messageWidget->setActions(QList<QAction*>() << doDbRefreshAction << cancelAction);
1205     }
1206     messageWidget->setWarning(tr("Refresh MPD Database?"), false);
1207     expand();
1208 }
1209 
showAboutDialog()1210 void MainWindow::showAboutDialog()
1211 {
1212     QMessageBox::about(this, tr("About Cantata"),
1213                        tr("<b>Cantata %1</b><br/><br/>MPD client.<br/><br/>"
1214                            "&copy; 2011-2020 Craig Drummond<br/>Released under the <a href=\"http://www.gnu.org/licenses/gpl.html\">GPLv3</a>").arg(PACKAGE_VERSION_STRING)+
1215                        QLatin1String("<br/><br/>")+
1216                        tr("Please refer to <a href=\"https://github.com/CDrummond/cantata/issues\">Cantata's issue tracker</a> for a list of known issues, and to report new issues.")+
1217                        QLatin1String("<br/><br/><i><small>")+
1218                        tr("Based upon <a href=\"http://lowblog.nl\">QtMPC</a> - &copy; 2007-2010 The QtMPC Authors<br/>")+
1219                        tr("Context view backdrops courtesy of <a href=\"http://www.fanart.tv\">FanArt.tv</a>")+QLatin1String("<br/>")+
1220                        tr("Context view metadata courtesy of <a href=\"http://www.wikipedia.org\">Wikipedia</a> and <a href=\"http://www.last.fm\">Last.fm</a>")+
1221                        QLatin1String("<br/><br/>")+tr("Please consider uploading your own music fan-art to <a href=\"http://www.fanart.tv\">FanArt.tv</a>")+
1222                        QLatin1String("</small></i>"));
1223 }
1224 
canClose()1225 bool MainWindow::canClose()
1226 {
1227     if (onlinePage->isDownloading() &&
1228             MessageBox::No==MessageBox::warningYesNo(this, tr("A Podcast is currently being downloaded\n\nQuitting now will abort the download."),
1229                                                      QString(), GuiItem(tr("Abort download and quit")), GuiItem("Do not quit just yet"))) {
1230         return false;
1231     }
1232     onlinePage->cancelAll();
1233     return true;
1234 }
1235 
expand()1236 void MainWindow::expand()
1237 {
1238     if (!expandInterfaceAction->isChecked()) {
1239         expandInterfaceAction->setChecked(true);
1240         expandOrCollapse();
1241     }
1242 }
1243 
canShowDialog()1244 bool MainWindow::canShowDialog()
1245 {
1246     if (PreferencesDialog::instanceCount() || CoverDialog::instanceCount()
1247         #ifdef TAGLIB_FOUND
1248         || TagEditor::instanceCount() || TrackOrganiser::instanceCount()
1249         #endif
1250         #ifdef ENABLE_DEVICES_SUPPORT
1251         || ActionDialog::instanceCount() || SyncDialog::instanceCount()
1252         #endif
1253         #ifdef ENABLE_REPLAYGAIN_SUPPORT
1254         || RgDialog::instanceCount()
1255         #endif
1256         ) {
1257         MessageBox::error(this, tr("Please close other dialogs first."));
1258         return false;
1259     }
1260     return true;
1261 }
1262 
showPreferencesDialog(const QString & page)1263 void MainWindow::showPreferencesDialog(const QString &page)
1264 {
1265     if (PreferencesDialog::instanceCount()) {
1266         emit showPreferencesPage(page.isEmpty() ? "collection" : page);
1267     }
1268     if (PreferencesDialog::instanceCount() || !canShowDialog()) {
1269         return;
1270     }
1271     PreferencesDialog *pref=new PreferencesDialog(this);
1272     controlConnectionsMenu(false);
1273     connect(pref, SIGNAL(settingsSaved()), this, SLOT(updateSettings()));
1274     connect(pref, SIGNAL(destroyed()), SLOT(controlConnectionsMenu()));
1275     connect(this, SIGNAL(showPreferencesPage(QString)), pref, SLOT(showPage(QString)));
1276     if (!page.isEmpty()) {
1277         pref->showPage(page);
1278     }
1279     pref->show();
1280 }
1281 
quit()1282 void MainWindow::quit()
1283 {
1284     if (!canClose()) {
1285         return;
1286     }
1287     #ifdef ENABLE_REPLAYGAIN_SUPPORT
1288     if (RgDialog::instanceCount()) {
1289         return;
1290     }
1291     #endif
1292     #ifdef TAGLIB_FOUND
1293     if (TagEditor::instanceCount() || 0!=TrackOrganiser::instanceCount()) {
1294         return;
1295     }
1296     #endif
1297     #ifdef ENABLE_DEVICES_SUPPORT
1298     if (ActionDialog::instanceCount() || SyncDialog::instanceCount()) {
1299         return;
1300     }
1301     #endif
1302     qApp->quit();
1303 }
1304 
commitDataRequest(QSessionManager & mgr)1305 void MainWindow::commitDataRequest(QSessionManager &mgr)
1306 {
1307     Q_UNUSED(mgr)
1308     #if !defined Q_OS_WIN && !defined Q_OS_MAC
1309     if (isVisible() && trayItem->isActive() && Settings::self()->minimiseOnClose()) {
1310         // Issue 1183 If We are using a tray item, and have window open - then cantata intercepts close events
1311         // and hides the window. This seems to prevent the logout dialog appwaring under GNOME Shell.
1312         // To work-around this if qApp emits commitDataRequest and our window is open, them close.
1313         QMainWindow::close();
1314     }
1315     #endif
1316 }
1317 
checkMpdDir()1318 void MainWindow::checkMpdDir()
1319 {
1320     #ifdef Q_OS_LINUX
1321     if (mpdAccessibilityTimer) {
1322         mpdAccessibilityTimer->stop();
1323     }
1324     MPDConnection::self()->setDirReadable();
1325     #endif
1326 
1327     #ifdef TAGLIB_FOUND
1328     if (StdActions::self()->editTagsAction->isEnabled()) {
1329         StdActions::self()->editTagsAction->setEnabled(MPDConnection::self()->getDetails().dirReadable);
1330     }
1331     #endif
1332     if (currentPage) {
1333         currentPage->controlActions();
1334     }
1335 }
1336 
outputsUpdated(const QList<Output> & outputs)1337 void MainWindow::outputsUpdated(const QList<Output> &outputs)
1338 {
1339     const char *constMpdConName="mpd-name";
1340     const char *constMpdEnabledOuptuts="mpd-outputs";
1341     QString lastConn=property(constMpdConName).toString();
1342     QString newConn=MPDConnection::self()->getDetails().name;
1343     setProperty(constMpdConName, newConn);
1344     outputsAction->setVisible(true);
1345     QSet<QString> enabledMpd;
1346     QSet<QString> lastEnabledMpd=QSet<QString>::fromList(property(constMpdEnabledOuptuts).toStringList());
1347     QSet<QString> mpd;
1348     QSet<QString> menuItems;
1349     QMenu *menu=outputsAction->menu();
1350     for (const Output &o: outputs) {
1351         if (o.enabled) {
1352             enabledMpd.insert(o.name);
1353         }
1354         mpd.insert(o.name);
1355     }
1356 
1357     for (QAction *act: menu->actions()) {
1358         menuItems.insert(act->data().toString());
1359     }
1360 
1361     if (menuItems!=mpd) {
1362         menu->clear();
1363         QList<Output> out=outputs;
1364         std::sort(out.begin(), out.end());
1365         int i=Qt::Key_1;
1366         for (const Output &o: out) {
1367             QAction *act=menu->addAction(o.name, this, SLOT(toggleOutput()));
1368             act->setData(o.id);
1369             act->setCheckable(true);
1370             act->setChecked(o.enabled);
1371             act->setShortcut(Qt::ControlModifier+Qt::AltModifier+nextKey(i));
1372         }
1373     } else {
1374         for (const Output &o: outputs) {
1375             for (QAction *act: menu->actions()) {
1376                 if (Utils::strippedText(act->text())==o.name) {
1377                     act->setChecked(o.enabled);
1378                     break;
1379                 }
1380             }
1381         }
1382     }
1383 
1384     if (newConn==lastConn && enabledMpd!=lastEnabledMpd && !menuItems.isEmpty()) {
1385         QSet<QString> switchedOn=enabledMpd-lastEnabledMpd;
1386         QSet<QString> switchedOff=lastEnabledMpd-enabledMpd;
1387 
1388         if (!switchedOn.isEmpty() && switchedOff.isEmpty()) {
1389             QStringList names=switchedOn.toList();
1390             std::sort(names.begin(), names.end());
1391             trayItem->showMessage(tr("Outputs"), tr("Enabled: %1").arg(names.join(QLatin1String(", "))));
1392         } else if (!switchedOff.isEmpty() && switchedOn.isEmpty()) {
1393             QStringList names=switchedOff.toList();
1394             std::sort(names.begin(), names.end());
1395             trayItem->showMessage(tr("Outputs"), tr("Disabled: %1").arg(names.join(QLatin1String(", "))));
1396         } else if (!switchedOn.isEmpty() && !switchedOff.isEmpty()) {
1397             QStringList on=switchedOn.toList();
1398             std::sort(on.begin(), on.end());
1399             QStringList off=switchedOff.toList();
1400             std::sort(off.begin(), off.end());
1401             trayItem->showMessage(tr("Outputs"),
1402                                   tr("Enabled: %1").arg(on.join(QLatin1String(", ")))+QLatin1Char('\n')+
1403                                   tr("Disabled: %1").arg(off.join(QLatin1String(", "))));
1404         }
1405     }
1406     setProperty(constMpdEnabledOuptuts, QStringList() << enabledMpd.toList());
1407     outputsAction->setVisible(outputs.count()>1);
1408     trayItem->updateOutputs();
1409 }
1410 
updateConnectionsMenu()1411 void MainWindow::updateConnectionsMenu()
1412 {
1413     QList<MPDConnectionDetails> connections=Settings::self()->allConnections();
1414     if (connections.count()<2) {
1415         connectionsAction->setVisible(false);
1416     } else {
1417         connectionsAction->setVisible(true);
1418         QString current=Settings::self()->currentConnection();
1419         QSet<QString> cfg;
1420         QSet<QString> menuItems;
1421         QMenu *menu=connectionsAction->menu();
1422         for (const MPDConnectionDetails &d: connections) {
1423             cfg.insert(d.name);
1424         }
1425 
1426         for (QAction *act: menu->actions()) {
1427             menuItems.insert(act->data().toString());
1428             act->setChecked(act->data().toString()==current);
1429         }
1430 
1431         if (menuItems!=cfg) {
1432             menu->clear();
1433             std::sort(connections.begin(), connections.end());
1434             int i=Qt::Key_1;
1435             for (const MPDConnectionDetails &d: connections) {
1436                 QAction *act=menu->addAction(d.getName(), this, SLOT(changeConnection()));
1437                 act->setData(d.name);
1438                 act->setCheckable(true);
1439                 act->setChecked(d.name==current);
1440                 act->setActionGroup(connectionsGroup);
1441                 act->setShortcut(Qt::ControlModifier+nextKey(i));
1442             }
1443         }
1444     }
1445     trayItem->updateConnections();
1446 }
1447 
controlConnectionsMenu(bool enable)1448 void MainWindow::controlConnectionsMenu(bool enable)
1449 {
1450     if (enable) {
1451         updateConnectionsMenu();
1452     }
1453     for (QAction *act: connectionsAction->menu()->actions()) {
1454         act->setEnabled(enable);
1455     }
1456 }
1457 
controlDynamicButton()1458 void MainWindow::controlDynamicButton()
1459 {
1460     stopDynamicButton->setVisible(DynamicPlaylists::self()->isRunning());
1461     PlayQueueModel::self()->enableUndo(!DynamicPlaylists::self()->isRunning());
1462 }
1463 
setRating()1464 void MainWindow::setRating()
1465 {
1466     Action *act=qobject_cast<Action *>(sender());
1467     if (act) {
1468         PlayQueueModel::self()->setRating(playQueueProxyModel.mapToSourceRows(playQueue->selectedIndexes()), act->property(constRatingKey).toUInt());
1469     }
1470 }
1471 
initMpris()1472 void MainWindow::initMpris()
1473 {
1474     #ifdef QT_QTDBUS_FOUND
1475     if (Settings::self()->mpris()) {
1476         if (!mpris) {
1477             mpris=new Mpris(this);
1478             connect(mpris, SIGNAL(showMainWindow()), this, SLOT(restoreWindow()));
1479         }
1480     } else if (mpris) {
1481         disconnect(mpris, SIGNAL(showMainWindow()), this, SLOT(restoreWindow()));
1482         mpris->deleteLater();
1483         mpris=nullptr;
1484     }
1485     CurrentCover::self()->setEnabled(mpris || Settings::self()->showPopups() || 0!=Settings::self()->playQueueBackground() || Settings::self()->showCoverWidget());
1486     #endif
1487 }
1488 
toggleMenubar()1489 void MainWindow::toggleMenubar()
1490 {
1491     #if !defined Q_OS_WIN && !defined Q_OS_MAC
1492     if (showMenubarAction) {
1493         menuButton->setVisible(!showMenubarAction->isChecked());
1494         menuBar()->setVisible(showMenubarAction->isChecked());
1495         setCollapsedSize();
1496     }
1497     #endif
1498 }
1499 
paletteChanged()1500 void MainWindow::paletteChanged()
1501 {
1502     QColor before = nowPlaying->textColor();
1503     nowPlaying->initColors();
1504     if (before == nowPlaying->textColor()) {
1505         return;
1506     }
1507 
1508     volumeSlider->setColor(nowPlaying->textColor());
1509 
1510     Icons::self()->initToolbarIcons(nowPlaying->textColor());
1511     StdActions::self()->prevTrackAction->setIcon(Icons::self()->toolbarPrevIcon);
1512     StdActions::self()->nextTrackAction->setIcon(Icons::self()->toolbarNextIcon);
1513     StdActions::self()->playPauseTrackAction->setIcon(MPDState_Playing==MPDStatus::self()->state() ? Icons::self()->toolbarPauseIcon : Icons::self()->toolbarPlayIcon);
1514     StdActions::self()->stopPlaybackAction->setIcon(Icons::self()->toolbarStopIcon);
1515     StdActions::self()->stopAfterCurrentTrackAction->setIcon(Icons::self()->toolbarStopIcon);
1516     StdActions::self()->stopAfterTrackAction->setIcon(Icons::self()->toolbarStopIcon);
1517     songInfoAction->setIcon(Icons::self()->infoIcon);
1518     menuButton->setIcon(Icons::self()->toolbarMenuIcon);
1519 }
1520 
readSettings()1521 void MainWindow::readSettings()
1522 {
1523     #ifdef QT_QTDBUS_FOUND
1524     // It appears as if the KDE MPRIS code does not like the MPRIS interface to be setup before the window is visible.
1525     // to work-around this, initMpris in the next event loop iteration.
1526     // See #863
1527     QTimer::singleShot(0, this, SLOT(initMpris()));
1528     #else
1529     CurrentCover::self()->setEnabled(Settings::self()->showPopups() || 0!=Settings::self()->playQueueBackground() || Settings::self()->showCoverWidget());
1530     #endif
1531 
1532     checkMpdDir();
1533     Song::setIgnorePrefixes(Settings::self()->ignorePrefixes());
1534     Covers::self()->readConfig();
1535     HttpServer::self()->readConfig();
1536     #ifdef ENABLE_DEVICES_SUPPORT
1537     StdActions::self()->deleteSongsAction->setVisible(Settings::self()->showDeleteAction());
1538     #endif
1539     Song::setComposerGenres(Settings::self()->composerGenres());
1540     trayItem->setup();
1541     autoScrollPlayQueue=Settings::self()->playQueueScroll();
1542     TreeView::setForceSingleClick(Settings::self()->forceSingleClick());
1543     #if (defined Q_OS_LINUX && defined QT_QTDBUS_FOUND) || (defined Q_OS_MAC && defined IOKIT_FOUND)
1544     PowerManagement::self()->setInhibitSuspend(Settings::self()->inhibitSuspend());
1545     #endif
1546     context->readConfig();
1547     tabWidget->setProperty(constUserSettingProp, Settings::self()->hiddenPages());
1548     tabWidget->setProperty(constUserSetting2Prop, Settings::self()->sidebar());
1549     coverWidget->setProperty(constUserSettingProp, Settings::self()->showCoverWidget());
1550     stopTrackButton->setProperty(constUserSettingProp, Settings::self()->showStopButton());
1551     responsiveSidebar=Settings::self()->responsiveSidebar();
1552     controlView(true);
1553     #if defined Q_OS_WIN
1554     if (thumbnailTooolbar) {
1555         thumbnailTooolbar->readSettings();
1556     }
1557     #endif
1558     searchPlayQueueAction->setEnabled(Settings::self()->playQueueSearch());
1559     searchPlayQueueAction->setVisible(Settings::self()->playQueueSearch());
1560     nowPlaying->readConfig();
1561     setCollapsedSize();
1562     toggleSplitterAutoHide();
1563     if (contextSwitchTime!=Settings::self()->contextSwitchTime()) {
1564         contextSwitchTime=Settings::self()->contextSwitchTime();
1565         if (0==contextSwitchTime && contextTimer) {
1566             contextTimer->stop();
1567             contextTimer->deleteLater();
1568             contextTimer=nullptr;
1569         }
1570     }
1571     MPDConnection::self()->setVolumeFadeDuration(Settings::self()->stopFadeDuration());
1572     MPDParseUtils::setSingleTracksFolders(Settings::self()->singleTracksFolders());
1573     MPDParseUtils::setCueFileSupport(Settings::self()->cueSupport());
1574     volumeSlider->setPageStep(Settings::self()->volumeStep());
1575 }
1576 
updateSettings()1577 void MainWindow::updateSettings()
1578 {
1579     connectToMpd();
1580     Settings::self()->save();
1581     readSettings();
1582 
1583     bool wasAutoExpand=playQueue->isAutoExpand();
1584     bool wasStartClosed=playQueue->isStartClosed();
1585     bool wasGrouped=playQueue->isGrouped();
1586     playQueue->readConfig();
1587 
1588     if (wasGrouped!=playQueue->isGrouped() ||
1589         (playQueue->isGrouped() && (wasAutoExpand!=playQueue->isAutoExpand() || wasStartClosed!=playQueue->isStartClosed())) ) {
1590         QModelIndex idx=playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(PlayQueueModel::self()->currentSongRow(), 0));
1591         playQueue->updateRows(idx.row(), current.key, autoScrollPlayQueue && playQueueProxyModel.isEmpty() && MPDState_Playing==MPDStatus::self()->state());
1592     }
1593 
1594     updateActionToolTips();
1595 }
1596 
toggleOutput()1597 void MainWindow::toggleOutput()
1598 {
1599     QAction *act=qobject_cast<QAction *>(sender());
1600     if (act) {
1601         emit enableOutput(act->data().toUInt(), act->isChecked());
1602     }
1603 }
1604 
changeConnection()1605 void MainWindow::changeConnection()
1606 {
1607     QAction *act=qobject_cast<QAction *>(sender());
1608     if (act) {
1609         Settings::self()->saveCurrentConnection(act->data().toString());
1610         connectToMpd();
1611     }
1612 }
1613 
showServerInfo()1614 void MainWindow::showServerInfo()
1615 {
1616     QStringList handlers=MPDConnection::self()->urlHandlers().toList();
1617     QStringList tags=MPDConnection::self()->tags().toList();
1618     std::sort(handlers.begin(), handlers.end());
1619     std::sort(tags.begin(), tags.end());
1620     long version=MPDConnection::self()->version();
1621     QDateTime dbUpdate;
1622     dbUpdate.setTime_t(MPDStats::self()->dbUpdate());
1623     MessageBox::information(this,
1624                                   #ifdef Q_OS_MAC
1625                                   tr("Server Information")+QLatin1String("<br/><br/>")+
1626                                   #endif
1627                                   QLatin1String("<p><table>")+
1628                                   tr("<tr><td colspan=\"2\"><b>Server</b></td></tr>"
1629                                        "<tr><td align=\"right\">Protocol:&nbsp;</td><td>%1.%2.%3</td></tr>"
1630                                        "<tr><td align=\"right\">Uptime:&nbsp;</td><td>%4</td></tr>"
1631                                        "<tr><td align=\"right\">Playing:&nbsp;</td><td>%5</td></tr>"
1632                                        "<tr><td align=\"right\">Handlers:&nbsp;</td><td>%6</td></tr>"
1633                                        "<tr><td align=\"right\">Tags:&nbsp;</td><td>%7</td></tr>")
1634                                        .arg((version>>16)&0xFF).arg((version>>8)&0xFF).arg(version&0xFF)
1635                                        .arg(Utils::formatDuration(MPDStats::self()->uptime()))
1636                                        .arg(Utils::formatDuration(MPDStats::self()->playtime()))
1637                                        .arg(handlers.join(", ")).arg(tags.join(", "))+
1638                                   QLatin1String("<tr/>")+
1639                                   tr("<tr><td colspan=\"2\"><b>Database</b></td></tr>"
1640                                        "<tr><td align=\"right\">Artists:&nbsp;</td><td>%1</td></tr>"
1641                                        "<tr><td align=\"right\">Albums:&nbsp;</td><td>%2</td></tr>"
1642                                        "<tr><td align=\"right\">Songs:&nbsp;</td><td>%3</td></tr>"
1643                                        "<tr><td align=\"right\">Duration:&nbsp;</td><td>%4</td></tr>"
1644                                        "<tr><td align=\"right\">Updated:&nbsp;</td><td>%5</td></tr>")
1645                                        .arg(MPDStats::self()->artists()).arg(MPDStats::self()->albums()).arg(MPDStats::self()->songs())
1646                                        .arg(Utils::formatDuration(MPDStats::self()->dbPlaytime())).arg(dbUpdate.toString(Qt::SystemLocaleShortDate))+
1647                                   QLatin1String("</table></p>"),
1648                             tr("Server Information"));
1649 }
1650 
enableStopActions(bool enable)1651 void MainWindow::enableStopActions(bool enable)
1652 {
1653     StdActions::self()->stopAfterCurrentTrackAction->setEnabled(enable);
1654     StdActions::self()->stopPlaybackAction->setEnabled(enable);
1655 }
1656 
stopPlayback()1657 void MainWindow::stopPlayback()
1658 {
1659     emit stop();
1660     enableStopActions(false);
1661     StdActions::self()->nextTrackAction->setEnabled(false);
1662     StdActions::self()->prevTrackAction->setEnabled(false);
1663 }
1664 
stopAfterCurrentTrack()1665 void MainWindow::stopAfterCurrentTrack()
1666 {
1667     PlayQueueModel::self()->clearStopAfterTrack();
1668     emit stop(true);
1669 }
1670 
stopAfterTrack()1671 void MainWindow::stopAfterTrack()
1672 {
1673     QModelIndexList selected=playQueue->selectedIndexes(false); // Dont need sorted selection here...
1674     if (1==selected.count()) {
1675         QModelIndex idx=playQueueProxyModel.mapToSource(selected.first());
1676         PlayQueueModel::self()->setStopAfterTrack(PlayQueueModel::self()->getIdByRow(idx.row()));
1677     }
1678 }
1679 
playPauseTrack()1680 void MainWindow::playPauseTrack()
1681 {
1682     switch (MPDStatus::self()->state()) {
1683     case MPDState_Playing:
1684         emit pause(true);
1685         break;
1686     case MPDState_Paused:
1687         emit pause(false);
1688         break;
1689     default:
1690         if (PlayQueueModel::self()->rowCount()>0) {
1691             if (-1!=PlayQueueModel::self()->currentSong() && -1!=PlayQueueModel::self()->currentSongRow()) {
1692                 emit startPlayingSongId(PlayQueueModel::self()->currentSong());
1693             } else {
1694                 emit play();
1695             }
1696         }
1697     }
1698 }
1699 
setPosition()1700 void MainWindow::setPosition()
1701 {
1702     emit setSeekId(MPDStatus::self()->songId(), nowPlaying->value());
1703 }
1704 
searchPlayQueue()1705 void MainWindow::searchPlayQueue()
1706 {
1707     if (playQueueSearchWidget->text().isEmpty()) {
1708         if (playQueueSearchTimer) {
1709             playQueueSearchTimer->stop();
1710         }
1711         realSearchPlayQueue();
1712     } else {
1713         if (!playQueueSearchTimer) {
1714             playQueueSearchTimer=new QTimer(this);
1715             playQueueSearchTimer->setSingleShot(true);
1716             connect(playQueueSearchTimer, SIGNAL(timeout()), SLOT(realSearchPlayQueue()));
1717         }
1718         playQueueSearchTimer->start(250);
1719     }
1720 }
1721 
realSearchPlayQueue()1722 void MainWindow::realSearchPlayQueue()
1723 {
1724     if (playQueueSearchTimer) {
1725         playQueueSearchTimer->stop();
1726     }
1727     QString filter=playQueueSearchWidget->text().trimmed();
1728     if (filter.length()<2) {
1729         filter=QString();
1730     }
1731 
1732     if (filter!=playQueueProxyModel.filterText()) {
1733         playQueue->setFilterActive(!filter.isEmpty());
1734         playQueue->clearSelection();
1735         playQueueProxyModel.update(filter);
1736         QModelIndex idx=playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(PlayQueueModel::self()->currentSongRow(), 0));
1737         playQueue->updateRows(idx.row(), current.key, autoScrollPlayQueue && playQueueProxyModel.isEmpty() && MPDState_Playing==MPDStatus::self()->state());
1738         scrollPlayQueue();
1739     }
1740 }
1741 
playQueueSearchActivated(bool a)1742 void MainWindow::playQueueSearchActivated(bool a)
1743 {
1744     if (!a && playQueue->isVisible()) {
1745         playQueue->setFocus();
1746     }
1747 }
1748 
updatePlayQueue(const QList<Song> & songs,bool isComplete)1749 void MainWindow::updatePlayQueue(const QList<Song> &songs, bool isComplete)
1750 {
1751     StdActions::self()->playPauseTrackAction->setEnabled(!songs.isEmpty());
1752     StdActions::self()->nextTrackAction->setEnabled(StdActions::self()->stopPlaybackAction->isEnabled() && !songs.isEmpty() && MPDStatus::self()->nextSongId()!=-1);
1753     StdActions::self()->prevTrackAction->setEnabled(StdActions::self()->stopPlaybackAction->isEnabled() && !songs.isEmpty() && (songs.count()>1 || current.time>5));
1754     StdActions::self()->savePlayQueueAction->setEnabled(!songs.isEmpty());
1755     clearPlayQueueAction->setEnabled(!songs.isEmpty());
1756 
1757     int topRow=-1;
1758     QModelIndex topIndex=PlayQueueModel::self()->lastCommandWasUnodOrRedo() ? playQueue->indexAt(QPoint(0, 0)) : QModelIndex();
1759     if (topIndex.isValid()) {
1760         topRow=playQueueProxyModel.mapToSource(topIndex).row();
1761     }
1762     bool wasEmpty=0==PlayQueueModel::self()->rowCount();
1763     bool songChanged=false;
1764 
1765     PlayQueueModel::self()->update(songs, isComplete);
1766 
1767     if (songs.isEmpty()) {
1768         updateCurrentSong(Song(), wasEmpty);
1769     } else if (wasEmpty || current.isStandardStream()) {
1770         // Check to see if it has been updated...
1771         Song pqSong=PlayQueueModel::self()->getSongByRow(PlayQueueModel::self()->currentSongRow());
1772         if (wasEmpty || pqSong.isDifferent(current) ) {
1773             updateCurrentSong(pqSong, wasEmpty);
1774             songChanged=true;
1775         }
1776     }
1777 
1778     if (songChanged) {
1779         StdActions::self()->prevTrackAction->setEnabled(StdActions::self()->stopPlaybackAction->isEnabled() && !songs.isEmpty() && (songs.count()>1 || current.time>5));
1780     }
1781 
1782     QModelIndex idx=playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(PlayQueueModel::self()->currentSongRow(), 0));
1783     bool scroll=songChanged && autoScrollPlayQueue && playQueueProxyModel.isEmpty() && (wasEmpty || MPDState_Playing==MPDStatus::self()->state());
1784     playQueue->updateRows(idx.row(), current.key, scroll, wasEmpty);
1785     if (!scroll && topRow>0 && topRow<PlayQueueModel::self()->rowCount()) {
1786         playQueue->scrollTo(playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(topRow, PlayQueueModel::COL_TITLE)), QAbstractItemView::PositionAtTop);
1787     }
1788 
1789     playQueueItemsSelected(playQueue->haveSelectedItems());
1790 }
1791 
updateWindowTitle()1792 void MainWindow::updateWindowTitle()
1793 {
1794     bool multipleConnections=connectionsAction->isVisible();
1795     QString connection=MPDConnection::self()->getDetails().getName();
1796     setWindowTitle(multipleConnections ? tr("Cantata (%1)").arg(connection) : "Cantata");
1797 }
1798 
updateCurrentSong(Song song,bool wasEmpty)1799 void MainWindow::updateCurrentSong(Song song, bool wasEmpty)
1800 {
1801     if (song.isCdda()) {
1802         emit getStatus();
1803         if (song.isUnknownAlbum()) {
1804             Song pqSong=PlayQueueModel::self()->getSongById(song.id);
1805             if (!pqSong.isEmpty()) {
1806                 song=pqSong;
1807             }
1808         }
1809     }
1810 
1811 //    if (song.isCantataStream()) {
1812 //        Song mod=HttpServer::self()->decodeUrl(song.file);
1813 //        if (!mod.title.isEmpty()) {
1814 //            mod.id=song.id;
1815 //            song=mod;
1816 //        }
1817 //    }
1818 
1819     bool diffSong=song.isDifferent(current);
1820     current=song;
1821 
1822     CurrentCover::self()->update(current);
1823     if (current.time<5 && MPDStatus::self()->songId()==current.id && MPDStatus::self()->timeTotal()>5) {
1824         current.time=MPDStatus::self()->timeTotal();
1825     }
1826     nowPlaying->setEnabled(-1!=current.id && !current.isCdda() && (!currentIsStream() || current.time>5));
1827     nowPlaying->update(current);
1828     bool isPlaying=MPDState_Playing==MPDStatus::self()->state();
1829     PlayQueueModel::self()->updateCurrentSong(current.id);
1830     QModelIndex idx=playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(PlayQueueModel::self()->currentSongRow(), 0));
1831     playQueue->updateRows(idx.row(), current.key, autoScrollPlayQueue && playQueueProxyModel.isEmpty() && isPlaying, wasEmpty);
1832     scrollPlayQueue(wasEmpty);
1833     if (diffSong) {
1834         #ifdef QT_QTDBUS_FOUND
1835         if (mpris) {
1836             mpris->updateCurrentSong(current);
1837         }
1838         #endif
1839         #ifdef Q_OS_WIN
1840         if (thumbnailTooolbar) {
1841             thumbnailTooolbar->updateCurrentSong(current);
1842         }
1843         #endif
1844         context->update(current);
1845         trayItem->songChanged(song, isPlaying);
1846     }
1847     centerPlayQueueAction->setEnabled(!song.isEmpty());
1848 }
1849 
scrollPlayQueue(bool wasEmpty)1850 void MainWindow::scrollPlayQueue(bool wasEmpty)
1851 {
1852     if (autoScrollPlayQueue && (wasEmpty || MPDState_Playing==MPDStatus::self()->state()) && !playQueue->isGrouped()) {
1853         qint32 row=PlayQueueModel::self()->currentSongRow();
1854         if (row>=0) {
1855             playQueue->scrollTo(playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(row, 0)), QAbstractItemView::PositionAtCenter);
1856         }
1857     }
1858 }
1859 
updateStatus()1860 void MainWindow::updateStatus()
1861 {
1862     updateStatus(MPDStatus::self());
1863 }
1864 
updateStatus(MPDStatus * const status)1865 void MainWindow::updateStatus(MPDStatus * const status)
1866 {
1867     if (!status->error().isEmpty()) {
1868         showError(tr("MPD reported the following error: %1").arg(status->error()));
1869     }
1870 
1871     if (MPDState_Stopped==status->state() || MPDState_Inactive==status->state()) {
1872         nowPlaying->clearTimes();
1873         PlayQueueModel::self()->clearStopAfterTrack();
1874         if (statusTimer) {
1875             statusTimer->stop();
1876             statusTimer->setProperty("count", 0);
1877         }
1878     } else {
1879         nowPlaying->setRange(0, 0==status->timeTotal() && 0!=current.time && (current.isCdda() || current.isCantataStream())
1880                                     ? current.time : status->timeTotal());
1881         nowPlaying->setValue(status->timeElapsed());
1882         if (0==status->timeTotal() && 0==status->timeElapsed()) {
1883             if (!statusTimer) {
1884                 statusTimer=new QTimer(this);
1885                 statusTimer->setSingleShot(true);
1886                 connect(statusTimer, SIGNAL(timeout()), SIGNAL(getStatus()));
1887             }
1888             QVariant id=statusTimer->property("id");
1889             if (!id.isValid() || id.toInt()!=current.id) {
1890                 statusTimer->setProperty("id", current.id);
1891                 statusTimer->setProperty("count", 0);
1892                 statusTimer->start(250);
1893             } else if (statusTimer->property("count").toInt()<12) {
1894                 statusTimer->setProperty("count", statusTimer->property("count").toInt()+1);
1895                 statusTimer->start(250);
1896             }
1897         } else if (!nowPlaying->isEnabled()) {
1898             nowPlaying->setEnabled(-1!=current.id && !current.isCdda() && (!currentIsStream() || status->timeTotal()>5));
1899         }
1900     }
1901 
1902     randomPlayQueueAction->setChecked(status->random());
1903     repeatPlayQueueAction->setChecked(status->repeat());
1904     singlePlayQueueAction->setChecked(status->single());
1905     consumePlayQueueAction->setChecked(status->consume());
1906     updateNextTrack(status->nextSongId());
1907 
1908     if (status->timeElapsed()<64800 && (!currentIsStream() || (status->timeTotal()>0 && status->timeElapsed()<=status->timeTotal()))) {
1909         if (status->state() == MPDState_Stopped || status->state() == MPDState_Inactive) {
1910             nowPlaying->setRange(0, 0);
1911         } else {
1912             nowPlaying->setValue(status->timeElapsed());
1913         }
1914     }
1915 
1916     PlayQueueModel::self()->setState(status->state());
1917     StdActions::self()->playPauseTrackAction->setEnabled(status->playlistLength()>0);
1918     switch (status->state()) {
1919     case MPDState_Playing:
1920         StdActions::self()->playPauseTrackAction->setIcon(Icons::self()->toolbarPauseIcon);
1921         enableStopActions(true);
1922         StdActions::self()->nextTrackAction->setEnabled(status->nextSongId()!=-1);
1923         StdActions::self()->prevTrackAction->setEnabled(status->playlistLength()>1 || status->timeTotal()>5 || current.time>5);
1924         nowPlaying->startTimer();
1925         break;
1926     case MPDState_Inactive:
1927     case MPDState_Stopped:
1928         StdActions::self()->playPauseTrackAction->setIcon(Icons::self()->toolbarPlayIcon);
1929         enableStopActions(false);
1930         StdActions::self()->nextTrackAction->setEnabled(false);
1931         StdActions::self()->prevTrackAction->setEnabled(false);
1932         if (!StdActions::self()->playPauseTrackAction->isEnabled()) {
1933             current=Song();
1934             nowPlaying->update(current);
1935             CurrentCover::self()->update(current);
1936             context->update(current);
1937         }
1938         current.id=0;
1939         trayItem->setToolTip("cantata", tr("Cantata"), QLatin1String("<i>")+tr("Playback stopped")+QLatin1String("</i>"));
1940         nowPlaying->stopTimer();
1941         break;
1942     case MPDState_Paused:
1943         StdActions::self()->playPauseTrackAction->setIcon(Icons::self()->toolbarPlayIcon);
1944         enableStopActions(0!=status->playlistLength());
1945         StdActions::self()->nextTrackAction->setEnabled(status->nextSongId()!=-1);
1946         StdActions::self()->prevTrackAction->setEnabled(status->playlistLength()>1 || status->timeTotal()>5 || current.time>5);
1947         nowPlaying->stopTimer();
1948     default:
1949         break;
1950     }
1951 
1952     // Check if song has changed or we're playing again after being stopped, and update song info if needed
1953     if (MPDState_Inactive!=status->state() &&
1954         (MPDState_Inactive==lastState || (MPDState_Stopped==lastState && MPDState_Playing==status->state()) || lastSongId != status->songId())) {
1955         emit currentSong();
1956     }
1957     if (status->state()!=lastState && (MPDState_Playing==status->state() || MPDState_Stopped==status->state())) {
1958         startContextTimer();
1959     }
1960 
1961     // Update status info
1962     lastState = status->state();
1963     lastSongId = status->songId();
1964     #ifdef QT_QTDBUS_FOUND
1965     if (mpris) {
1966         mpris->updateStatus(status);
1967     }
1968     #endif
1969     #if defined Q_OS_WIN
1970     if (thumbnailTooolbar) {
1971         thumbnailTooolbar->updateStatus(status);
1972     }
1973     #endif
1974     #ifdef Q_OS_MAC
1975     dockMenu->update(status);
1976     #endif
1977 }
1978 
playQueueItemActivated(const QModelIndex & index)1979 void MainWindow::playQueueItemActivated(const QModelIndex &index)
1980 {
1981     if (index.isValid()) {
1982         emit startPlayingSongId(PlayQueueModel::self()->getIdByRow(playQueueProxyModel.mapToSource(index).row()));
1983     }
1984 }
1985 
clearPlayQueue()1986 void MainWindow::clearPlayQueue()
1987 {
1988     if (!Settings::self()->playQueueConfirmClear() ||
1989         MessageBox::Yes==MessageBox::questionYesNo(this, tr("Remove all songs from play queue?"))) {
1990         if (dynamicLabel->isVisible()) {
1991             DynamicPlaylists::self()->stop(true);
1992         } else {
1993             PlayQueueModel::self()->removeAll();
1994         }
1995     }
1996 }
1997 
centerPlayQueue()1998 void MainWindow::centerPlayQueue()
1999 {
2000     QModelIndex idx=playQueueProxyModel.mapFromSource(PlayQueueModel::self()->index(PlayQueueModel::self()->currentSongRow(),
2001                                                                            playQueue->isGrouped() ? 0 : PlayQueueModel::COL_TITLE));
2002     if (idx.isValid()) {
2003         if (playQueue->isGrouped()) {
2004             playQueue->updateRows(idx.row(), current.key, true, true);
2005         } else {
2006             playQueue->scrollTo(idx, QAbstractItemView::PositionAtCenter);
2007         }
2008     }
2009 }
2010 
appendToPlayQueue(int action,quint8 priority,bool decreasePriority)2011 void MainWindow::appendToPlayQueue(int action, quint8 priority, bool decreasePriority)
2012 {
2013     playQueueSearchWidget->clear();
2014     if (currentPage) {
2015         currentPage->addSelectionToPlaylist(QString(), action, priority, decreasePriority);
2016     }
2017 }
2018 
addWithPriority()2019 void MainWindow::addWithPriority()
2020 {
2021     QAction *act=qobject_cast<QAction *>(sender());
2022 
2023     if (!act || !MPDConnection::self()->canUsePriority()) {
2024         return;
2025     }
2026 
2027     int prio=act->data().toInt();
2028     bool isPlayQueue=playQueue->hasFocus();
2029     bool decreasePriority=false;
2030     QModelIndexList pqItems;
2031 
2032     if (isPlayQueue) {
2033         pqItems=playQueue->selectedIndexes();
2034         if (pqItems.isEmpty()) {
2035             return;
2036         }
2037     }
2038 
2039     if (-1==prio) {
2040         InputDialog dlg(tr("Priority"), tr("Enter priority (0..255):"), 150, 0, 255, 5, this);
2041         QCheckBox *dec = new QCheckBox(tr("Decrease priority for each subsequent track"), this);
2042         dec->setChecked(false);
2043         dlg.addExtraWidget(dec);
2044         if (QDialog::Accepted!=dlg.exec()) {
2045             return;
2046         }
2047         prio=dlg.spinBox()->value();
2048         decreasePriority=dec->isChecked();
2049     }
2050 
2051     if (prio>=0 && prio<=255) {
2052         if (isPlayQueue) {
2053             QList<qint32> ids;
2054             for (const QModelIndex &idx: pqItems) {
2055                 ids.append(PlayQueueModel::self()->getIdByRow(playQueueProxyModel.mapToSource(idx).row()));
2056             }
2057             emit setPriority(ids, prio, decreasePriority);
2058         } else {
2059             appendToPlayQueue(false, prio, decreasePriority);
2060         }
2061     }
2062 }
2063 
addToNewStoredPlaylist()2064 void MainWindow::addToNewStoredPlaylist()
2065 {
2066     bool pq=playQueue->hasFocus();
2067     for(;;) {
2068         QString name = InputDialog::getText(tr("Playlist Name"), tr("Enter a name for the playlist:"), QString(), nullptr, this);
2069 
2070         if (name==MPDConnection::constStreamsPlayListName) {
2071             MessageBox::error(this, tr("'%1' is used to store favorite streams, please choose another name.").arg(name));
2072             continue;
2073         }
2074         if (PlaylistsModel::self()->exists(name)) {
2075             switch(MessageBox::warningYesNoCancel(this, tr("A playlist named '%1' already exists!\n\nAdd to that playlist?").arg(name),
2076                                                   tr("Existing Playlist"))) {
2077             case MessageBox::Cancel:  return;
2078             case MessageBox::Yes:     break;
2079             case MessageBox::No:
2080             default:                  continue;
2081             }
2082         }
2083 
2084         if (!name.isEmpty()) {
2085             addToExistingStoredPlaylist(name, pq);
2086         }
2087         break;
2088     }
2089 }
2090 
addToExistingStoredPlaylist(const QString & name,bool pq)2091 void MainWindow::addToExistingStoredPlaylist(const QString &name, bool pq)
2092 {
2093     if (pq) {
2094         QModelIndexList items = playQueue->selectedIndexes();
2095         QStringList files;
2096         if (items.isEmpty()) {
2097             files = PlayQueueModel::self()->filenames();
2098         } else {
2099             for (const QModelIndex &idx: items) {
2100                 Song s = PlayQueueModel::self()->getSongByRow(playQueueProxyModel.mapToSource(idx).row());
2101                 if (!s.file.isEmpty()) {
2102                     files.append(s.file);
2103                 }
2104             }
2105         }
2106         if (!files.isEmpty()) {
2107             emit addSongsToPlaylist(name, files);
2108         }
2109     } else if (currentPage) {
2110         currentPage->addSelectionToPlaylist(name);
2111     }
2112 }
2113 
addStreamToPlayQueue()2114 void MainWindow::addStreamToPlayQueue()
2115 {
2116     StreamDialog dlg(this, true);
2117 
2118     if (QDialog::Accepted==dlg.exec()) {
2119         QString url=dlg.url();
2120 
2121         if (dlg.save()) {
2122             StreamsModel::self()->addToFavourites(url, dlg.name());
2123         }
2124         PlayQueueModel::self()->addItems(QStringList() << StreamsModel::modifyUrl(url, true, dlg.name()), MPDConnection::Append, 0, false);
2125     }
2126 }
2127 
addLocalFilesToPlayQueue()2128 void MainWindow::addLocalFilesToPlayQueue()
2129 {
2130     QString extensions;
2131     for (const auto &ext: PlayQueueModel::constFileExtensions) {
2132         if (!extensions.isEmpty()) {
2133             extensions+=" ";
2134         }
2135         extensions+="*"+ext;
2136     }
2137     QStringList files=QFileDialog::getOpenFileNames(this, tr("Select Music Files"), QString(), tr("Music Files ")+"("+extensions+")");
2138     if (!files.isEmpty()) {
2139         PlayQueueModel::self()->load(files);
2140     }
2141 }
2142 
removeItems()2143 void MainWindow::removeItems()
2144 {
2145     if (currentPage) {
2146         currentPage->removeItems();
2147     }
2148 }
2149 
checkMpdAccessibility()2150 void MainWindow::checkMpdAccessibility()
2151 {
2152     #ifdef Q_OS_LINUX
2153     if (!mpdAccessibilityTimer) {
2154         mpdAccessibilityTimer=new QTimer(this);
2155         connect(mpdAccessibilityTimer, SIGNAL(timeout()), SLOT(checkMpdDir()));
2156     }
2157     mpdAccessibilityTimer->start(500);
2158     #endif
2159 }
2160 
updatePlayQueueStats(int songs,quint32 time)2161 void MainWindow::updatePlayQueueStats(int songs, quint32 time)
2162 {
2163     if (0==songs) {
2164         playQueueStatsLabel->setText(QString());
2165     } else if (0==time) {
2166         playQueueStatsLabel->setText(tr("%n Track(s)", "", songs));
2167     } else {
2168         playQueueStatsLabel->setText(tr("%n Tracks (%1)", "", songs).arg(Utils::formatDuration(time)));
2169     }
2170 }
2171 
showSongInfo()2172 void MainWindow::showSongInfo()
2173 {
2174     if (songInfoAction->isCheckable()) {
2175         stack->setCurrentWidget(songInfoAction->isChecked() ? (QWidget *)context : (QWidget *)splitter);
2176     } else {
2177         showTab(PAGE_CONTEXT);
2178     }
2179 }
2180 
fullScreen()2181 void MainWindow::fullScreen()
2182 {
2183     if (expandInterfaceAction->isChecked()) {
2184         #ifndef Q_OS_MAC
2185         fullScreenLabel->setVisible(!isFullScreen());
2186         #endif
2187         expandInterfaceAction->setEnabled(isFullScreen());
2188         if (isFullScreen()) {
2189             showNormal();
2190         } else {
2191             showFullScreen();
2192         }
2193     } else {
2194         fullScreenAction->setChecked(false);
2195     }
2196 }
2197 
sidebarModeChanged()2198 void MainWindow::sidebarModeChanged()
2199 {
2200     if (expandInterfaceAction->isChecked()) {
2201         setMinimumHeight(calcMinHeight());
2202     }
2203 }
2204 
currentTabChanged(int index)2205 void MainWindow::currentTabChanged(int index)
2206 {
2207     prevPage=index;
2208     controlDynamicButton();
2209     switch(index) {
2210     case PAGE_LIBRARY:   currentPage=libraryPage;   break;
2211     case PAGE_FOLDERS:   currentPage=folderPage;    break;
2212     case PAGE_PLAYLISTS: currentPage=playlistsPage; break;
2213     case PAGE_ONLINE:    currentPage=onlinePage;    break;
2214     #ifdef ENABLE_DEVICES_SUPPORT
2215     case PAGE_DEVICES:   currentPage=devicesPage;   break;
2216     #endif
2217     case PAGE_SEARCH:    currentPage=searchPage;    break;
2218     default:             currentPage=nullptr;       break;
2219     }
2220     if (currentPage) {
2221         currentPage->controlActions();
2222     }
2223 }
2224 
tabToggled(int index)2225 void MainWindow::tabToggled(int index)
2226 {
2227     switch (index) {
2228     case PAGE_PLAYQUEUE:
2229         if (tabWidget->isEnabled(index)) {
2230             splitter->setAutohidable(0, Settings::self()->splitterAutoHide() && !tabWidget->isEnabled(PAGE_PLAYQUEUE));
2231             playQueueWidget->setParent(playQueuePage);
2232             playQueuePage->layout()->addWidget(playQueueWidget);
2233             playQueueWidget->setVisible(true);
2234         } else {
2235             playQueuePage->layout()->removeWidget(playQueueWidget);
2236             playQueueWidget->setParent(splitter);
2237             playQueueWidget->setVisible(true);
2238             splitter->setAutohidable(0, Settings::self()->splitterAutoHide() && !tabWidget->isEnabled(PAGE_PLAYQUEUE));
2239         }
2240         playQueue->updatePalette();
2241         break;
2242     case PAGE_CONTEXT:
2243         if (tabWidget->isEnabled(index) && songInfoAction->isCheckable()) {
2244             context->setParent(contextPage);
2245             contextPage->layout()->addWidget(context);
2246             context->setVisible(true);
2247             songInfoButton->setVisible(false);
2248             songInfoAction->setCheckable(false);
2249         } else if (!songInfoAction->isCheckable()) {
2250             contextPage->layout()->removeWidget(context);
2251             stack->addWidget(context);
2252             songInfoButton->setVisible(true);
2253             songInfoAction->setCheckable(true);
2254         }
2255         break;
2256     case PAGE_LIBRARY:
2257         locateAction->setVisible(tabWidget->isEnabled(index));
2258         break;
2259     case PAGE_FOLDERS:
2260         folderPage->setEnabled(!folderPage->isEnabled());
2261         break;
2262     case PAGE_PLAYLISTS:
2263         break;
2264     case PAGE_ONLINE:
2265         onlinePage->setEnabled(onlinePage->isEnabled());
2266         break;
2267     #ifdef ENABLE_DEVICES_SUPPORT
2268     case PAGE_DEVICES:
2269         DevicesModel::self()->setEnabled(!DevicesModel::self()->isEnabled());
2270         StdActions::self()->copyToDeviceAction->setVisible(DevicesModel::self()->isEnabled());
2271         break;
2272     #endif
2273     default:
2274         break;
2275     }
2276     sidebarModeChanged();
2277 }
2278 
toggleSplitterAutoHide()2279 void MainWindow::toggleSplitterAutoHide()
2280 {
2281     bool ah=Settings::self()->splitterAutoHide();
2282     if (splitter->isAutoHideEnabled()!=ah) {
2283         splitter->setAutoHideEnabled(ah);
2284         splitter->setAutohidable(0, ah);
2285     }
2286 }
2287 
locateTracks(const QList<Song> & songs)2288 void MainWindow::locateTracks(const QList<Song> &songs)
2289 {
2290     if (!songs.isEmpty() && tabWidget->isEnabled(PAGE_LIBRARY)) {
2291         showLibraryTab();
2292         libraryPage->showSongs(songs);
2293     }
2294 }
2295 
locateTrack()2296 void MainWindow::locateTrack()
2297 {
2298     if (!locateAction->isVisible() || !locateAction->isEnabled()) {
2299         return;
2300     }
2301     Action *act = qobject_cast<Action *>(sender());
2302     if (!act) {
2303         return;
2304     }
2305 
2306     QList<Song> songs = playQueue->selectedSongs();
2307     if (1!=songs.count() || !tabWidget->isEnabled(PAGE_LIBRARY)) {
2308         return;
2309     }
2310     showLibraryTab();
2311     if (locateTrackAction==act) {
2312         libraryPage->showSongs(songs);
2313     }
2314     Song s = songs.first();
2315     if (locateAlbumAction==act) {
2316         libraryPage->showAlbum(s.albumArtist(), s.albumId());
2317     }
2318     if (locateArtistAction==act) {
2319         libraryPage->showArtist(s.albumArtist());
2320     }
2321 }
2322 
moveSelectionAfterCurrentSong()2323 void MainWindow::moveSelectionAfterCurrentSong()
2324 {
2325     QList<int> selectedRows = playQueueProxyModel.mapToSourceRows(playQueue->selectedIndexes());
2326     int currentSongIdx = PlayQueueModel::self()->currentSongRow();
2327     QList<quint32> selectedSongIds;
2328 
2329     for (int row: selectedRows) {
2330         if (currentSongIdx!=row) {
2331             selectedSongIds.append( (quint32) row);
2332         }
2333     }
2334 
2335     if( !selectedSongIds.empty() ) {
2336         emit playNext(selectedSongIds, currentSongIdx+1, PlayQueueModel::self()->rowCount());
2337     }
2338 }
2339 
locateArtist(const QString & artist)2340 void MainWindow::locateArtist(const QString &artist)
2341 {
2342     if (songInfoAction->isCheckable()) {
2343         songInfoAction->setChecked(false);
2344         showSongInfo();
2345     }
2346     showLibraryTab();
2347     libraryPage->showArtist(artist);
2348 }
2349 
locateAlbum(const QString & artist,const QString & album)2350 void MainWindow::locateAlbum(const QString &artist, const QString &album)
2351 {
2352     if (songInfoAction->isCheckable()) {
2353         songInfoAction->setChecked(false);
2354         showSongInfo();
2355     }
2356     showLibraryTab();
2357     libraryPage->showAlbum(artist, album);
2358 }
2359 
dynamicStatus(const QString & message)2360 void MainWindow::dynamicStatus(const QString &message)
2361 {
2362     DynamicPlaylists::self()->helperMessage(message);
2363 }
2364 
setCollection(const QString & collection)2365 void MainWindow::setCollection(const QString &collection)
2366 {
2367     if (!connectionsAction->isVisible()) {
2368         return;
2369     }
2370     for (QAction *act: connectionsAction->menu()->actions()) {
2371         if (Utils::strippedText(act->text())==collection) {
2372             if (!act->isChecked()) {
2373                 act->trigger();
2374             }
2375             break;
2376         }
2377     }
2378 }
2379 
mpdConnectionName(const QString & name)2380 void MainWindow::mpdConnectionName(const QString &name)
2381 {
2382     for (QAction *act: connectionsAction->menu()->actions()) {
2383         if (Utils::strippedText(act->text())==name) {
2384             if (!act->isChecked()) {
2385                 act->setChecked(true);
2386             }
2387             break;
2388         }
2389     }
2390 }
2391 
showPlayQueueSearch()2392 void MainWindow::showPlayQueueSearch()
2393 {
2394     playQueueSearchWidget->activate();
2395 }
2396 
showSearch()2397 void MainWindow::showSearch()
2398 {
2399     if (!searchPlayQueueAction->isEnabled() && playQueue->hasFocus()) {
2400         playQueueSearchWidget->activate();
2401     } else if (context->isVisible()) {
2402         context->search();
2403     } else if (currentPage && splitter->sizes().at(0)>0) {
2404         if (currentPage==folderPage && folderPage->mpd()->isVisible()) {
2405             showTab(PAGE_SEARCH);
2406             if (PAGE_SEARCH==tabWidget->currentIndex()) {
2407                 searchPage->setSearchCategory("file");
2408             }
2409         } else {
2410             currentPage->focusSearch();
2411         }
2412     } else if (!searchPlayQueueAction->isEnabled() && (playQueuePage->isVisible() || playQueue->isVisible())) {
2413         playQueueSearchWidget->activate();
2414     }
2415 }
2416 
expandAll()2417 void MainWindow::expandAll()
2418 {
2419     QWidget *f=QApplication::focusWidget();
2420     if (f && qobject_cast<TreeView *>(f) && !qobject_cast<GroupedView *>(f)) {
2421         static_cast<TreeView *>(f)->expandAll(QModelIndex(), true);
2422     }
2423 }
2424 
collapseAll()2425 void MainWindow::collapseAll()
2426 {
2427     QWidget *f=QApplication::focusWidget();
2428     if (f && qobject_cast<QTreeView *>(f) && !qobject_cast<GroupedView *>(f)) {
2429         static_cast<QTreeView *>(f)->collapseAll();
2430     }
2431 }
2432 
editTags()2433 void MainWindow::editTags()
2434 {
2435     #ifdef TAGLIB_FOUND
2436     if (TagEditor::instanceCount() || !canShowDialog()) {
2437         return;
2438     }
2439     QList<Song> songs;
2440     bool isPlayQueue=playQueue->hasFocus();
2441     if (isPlayQueue) {
2442         songs=playQueue->selectedSongs();
2443     } else if (currentPage) {
2444         songs=currentPage->selectedSongs();
2445     }
2446     if (songs.isEmpty()) {
2447         return;
2448     }
2449     QSet<QString> artists, albumArtists, composers, albums, genres;
2450     QString udi;
2451     #ifdef ENABLE_DEVICES_SUPPORT
2452     if (!isPlayQueue && currentPage==devicesPage) {
2453         DevicesModel::self()->getDetails(artists, albumArtists, composers, albums, genres);
2454         udi=devicesPage->activeFsDeviceUdi();
2455         if (udi.isEmpty()) {
2456             return;
2457         }
2458     }
2459     #endif
2460     MpdLibraryModel::self()->getDetails(artists, albumArtists, composers, albums, genres);
2461     TagEditor *dlg=new TagEditor(this, songs, artists, albumArtists, composers, albums, genres, udi);
2462     dlg->show();
2463     #endif
2464 }
2465 
organiseFiles()2466 void MainWindow::organiseFiles()
2467 {
2468     #ifdef TAGLIB_FOUND
2469     if (TrackOrganiser::instanceCount() || !canShowDialog()) {
2470         return;
2471     }
2472 
2473     QList<Song> songs;
2474     if (currentPage) {
2475         songs=currentPage->selectedSongs();
2476     }
2477 
2478     if (!songs.isEmpty()) {
2479         QString udi;
2480         #ifdef ENABLE_DEVICES_SUPPORT
2481         if (currentPage==devicesPage) {
2482             udi=devicesPage->activeFsDeviceUdi();
2483             if (udi.isEmpty()) {
2484                 return;
2485             }
2486         }
2487         #endif
2488 
2489         TrackOrganiser *dlg=new TrackOrganiser(this);
2490         dlg->show(songs, udi);
2491     }
2492     #endif
2493 }
2494 
addToDevice(const QString & udi)2495 void MainWindow::addToDevice(const QString &udi)
2496 {
2497     #ifdef ENABLE_DEVICES_SUPPORT
2498     if (playQueue->hasFocus()) {
2499         copyToDevice(QString(), udi, playQueue->selectedSongs());
2500     } else if (currentPage) {
2501         currentPage->addSelectionToDevice(udi);
2502     }
2503     #else
2504     Q_UNUSED(udi)
2505     #endif
2506 }
2507 
deleteSongs()2508 void MainWindow::deleteSongs()
2509 {
2510     #ifdef ENABLE_DEVICES_SUPPORT
2511     if (!StdActions::self()->deleteSongsAction->isVisible()) {
2512         return;
2513     }
2514     if (currentPage) {
2515         currentPage->deleteSongs();
2516     }
2517     #endif
2518 }
2519 
copyToDevice(const QString & from,const QString & to,const QList<Song> & songs)2520 void MainWindow::copyToDevice(const QString &from, const QString &to, const QList<Song> &songs)
2521 {
2522     #ifdef ENABLE_DEVICES_SUPPORT
2523     if (songs.isEmpty() || ActionDialog::instanceCount() || !canShowDialog()) {
2524         return;
2525     }
2526     ActionDialog *dlg=new ActionDialog(this);
2527     dlg->copy(from, to, songs);
2528     #else
2529     Q_UNUSED(from) Q_UNUSED(to) Q_UNUSED(songs)
2530     #endif
2531 }
2532 
deleteSongs(const QString & from,const QList<Song> & songs)2533 void MainWindow::deleteSongs(const QString &from, const QList<Song> &songs)
2534 {
2535     #ifdef ENABLE_DEVICES_SUPPORT
2536     if (songs.isEmpty() || ActionDialog::instanceCount() || !canShowDialog()) {
2537         return;
2538     }
2539     ActionDialog *dlg=new ActionDialog(this);
2540     dlg->remove(from, songs);
2541     #else
2542     Q_UNUSED(from) Q_UNUSED(songs)
2543     #endif
2544 }
2545 
replayGain()2546 void MainWindow::replayGain()
2547 {
2548     #ifdef ENABLE_REPLAYGAIN_SUPPORT
2549     if (RgDialog::instanceCount() || !canShowDialog()) {
2550         return;
2551     }
2552 
2553     QList<Song> songs;
2554     if (currentPage) {
2555         songs=currentPage->selectedSongs();
2556     }
2557 
2558     if (!songs.isEmpty()) {
2559         QString udi;
2560         #ifdef ENABLE_DEVICES_SUPPORT
2561         if (currentPage==devicesPage) {
2562             udi=devicesPage->activeFsDeviceUdi();
2563             if (udi.isEmpty()) {
2564                 return;
2565             }
2566         }
2567         #endif
2568         RgDialog *dlg=new RgDialog(this);
2569         dlg->show(songs, udi);
2570     }
2571     #endif
2572 }
2573 
setCover()2574 void MainWindow::setCover()
2575 {
2576     if (CoverDialog::instanceCount() || !canShowDialog()) {
2577         return;
2578     }
2579 
2580     Song song;
2581     if (currentPage) {
2582         song=currentPage->coverRequest();
2583     }
2584 
2585     if (!song.isEmpty()) {
2586         CoverDialog *dlg=new CoverDialog(this);
2587         dlg->show(song);
2588     }
2589 }
2590 
updateNextTrack(int nextTrackId)2591 void MainWindow::updateNextTrack(int nextTrackId)
2592 {
2593     if (-1!=nextTrackId && MPDState_Stopped==MPDStatus::self()->state()) {
2594         nextTrackId=-1; // nextSongId is not accurate if we are stopped.
2595     }
2596     QString tt=StdActions::self()->nextTrackAction->property("tooltip").toString();
2597     if (-1==nextTrackId && tt.isEmpty()) {
2598         StdActions::self()->nextTrackAction->setProperty("tooltip", StdActions::self()->nextTrackAction->toolTip());
2599     } else if (-1==nextTrackId) {
2600         StdActions::self()->nextTrackAction->setToolTip(tt);
2601         StdActions::self()->nextTrackAction->setProperty("trackid", nextTrackId);
2602     } else if (nextTrackId!=StdActions::self()->nextTrackAction->property("trackid").toInt()) {
2603         Song s=PlayQueueModel::self()->getSongByRow(PlayQueueModel::self()->getRowById(nextTrackId));
2604         if (!s.artist.isEmpty() && !s.title.isEmpty()) {
2605             tt+=QLatin1String("<br/><i><small>")+s.artistSong()+QLatin1String("</small></i>");
2606         } else {
2607             nextTrackId=-1;
2608         }
2609         StdActions::self()->nextTrackAction->setToolTip(tt);
2610         StdActions::self()->nextTrackAction->setProperty("trackid", nextTrackId);
2611     }
2612 }
2613 
updateActionToolTips()2614 void MainWindow::updateActionToolTips()
2615 {
2616     ActionCollection::get()->updateToolTips();
2617     tabWidget->setToolTip(PAGE_PLAYQUEUE, showPlayQueueAction->toolTip());
2618     tabWidget->setToolTip(PAGE_LIBRARY, libraryTabAction->toolTip());
2619     tabWidget->setToolTip(PAGE_FOLDERS, foldersTabAction->toolTip());
2620     tabWidget->setToolTip(PAGE_PLAYLISTS, playlistsTabAction->toolTip());
2621     tabWidget->setToolTip(PAGE_ONLINE, onlineTabAction->toolTip());
2622     #ifdef ENABLE_DEVICES_SUPPORT
2623     tabWidget->setToolTip(PAGE_DEVICES, devicesTabAction->toolTip());
2624     #endif
2625     tabWidget->setToolTip(PAGE_SEARCH, searchTabAction->toolTip());
2626     tabWidget->setToolTip(PAGE_CONTEXT, songInfoAction->toolTip());
2627 }
2628 
startContextTimer()2629 void MainWindow::startContextTimer()
2630 {
2631     if (!contextSwitchTime || current.isStandardStream()) {
2632         return;
2633     }
2634     if (!contextTimer) {
2635         contextTimer=new QTimer(this);
2636         contextTimer->setSingleShot(true);
2637         connect(contextTimer, SIGNAL(timeout()), this, SLOT(toggleContext()));
2638     }
2639     contextTimer->start(contextSwitchTime);
2640 }
2641 
calcMinHeight()2642 int MainWindow::calcMinHeight()
2643 {
2644     return tabWidget->style()&FancyTabWidget::Side && tabWidget->style()&FancyTabWidget::Large
2645             ? calcCollapsedSize()+(tabWidget->visibleCount()*tabWidget->tabSize().height())
2646             : Utils::scaleForDpi(256);
2647 }
2648 
calcCollapsedSize()2649 int MainWindow::calcCollapsedSize()
2650 {
2651     #if !defined Q_OS_MAC && !defined Q_OS_WIN
2652     return toolbar->height()+(showMenubarAction && menuBar() && menuBar()->isVisible() ? menuBar()->height() : 0);
2653     #else
2654     return toolbar->height();
2655     #endif
2656 }
2657 
setCollapsedSize()2658 void MainWindow::setCollapsedSize()
2659 {
2660     if (!expandInterfaceAction->isChecked()) {
2661         int w=width();
2662         adjustSize();
2663         collapsedSize=QSize(w, calcCollapsedSize());
2664         resize(collapsedSize);
2665         setFixedHeight(collapsedSize.height());
2666     }
2667 }
2668 
controlView(bool forceUpdate)2669 void MainWindow::controlView(bool forceUpdate)
2670 {
2671     if (!stopTrackButton || !coverWidget || !tabWidget) {
2672         controlPlayQueueButtons();
2673         return;
2674     }
2675 
2676     static int lastWidth=-1;
2677 
2678     if (forceUpdate || -1==lastWidth || qAbs(lastWidth-width())>20) {
2679         bool stopEnabled = stopTrackButton->property(constUserSettingProp).toBool();
2680         bool coverWidgetEnabled = coverWidget->property(constUserSettingProp).toBool();
2681         int tabWidgetStyle = tabWidget->property(constUserSetting2Prop).toInt();
2682         QStringList tabWidgetPages = tabWidget->property(constUserSettingProp).toStringList();
2683 
2684         if ((!stopEnabled && !coverWidgetEnabled) || width()<Utils::scaleForDpi(450)) {
2685             stopTrackButton->setVisible(false);
2686             coverWidget->setEnabled(false);
2687         } else if (stopEnabled && coverWidgetEnabled) {
2688             if (width()>Utils::scaleForDpi(550)) {
2689                 stopTrackButton->setVisible(true);
2690                 coverWidget->setEnabled(true);
2691             } else if (width()>Utils::scaleForDpi(450)) {
2692                 stopTrackButton->setVisible(false);
2693                 coverWidget->setEnabled(true);
2694             }
2695         } else {
2696             coverWidget->setEnabled(coverWidgetEnabled);
2697             stopTrackButton->setVisible(stopEnabled);
2698         }
2699 
2700         if (expandInterfaceAction->isChecked() && (responsiveSidebar || forceUpdate)) {
2701             if (!responsiveSidebar || width()>Utils::scaleForDpi(450)) {
2702                 if (forceUpdate || singlePane) {
2703                     int index=tabWidget->currentIndex();
2704                     if (forceUpdate || tabWidget->style()!=tabWidgetStyle) {
2705                         tabWidget->setStyle(tabWidgetStyle);
2706                     }
2707                     tabWidget->setHiddenPages(tabWidgetPages);
2708                     tabWidget->setCurrentIndex(index);
2709                 }
2710                 singlePane=false;
2711             } else if (responsiveSidebar) {
2712                 if (forceUpdate || !singlePane) {
2713                     int index=tabWidget->currentIndex();
2714                     int smallStyle=FancyTabWidget::Small|FancyTabWidget::Top|FancyTabWidget::IconOnly;
2715                     if (forceUpdate || tabWidget->style()!=smallStyle) {
2716                         tabWidget->setStyle(smallStyle);
2717                     }
2718                     tabWidgetPages.removeAll(QLatin1String("PlayQueuePage"));
2719                     tabWidgetPages.removeAll(QLatin1String("ContextPage"));
2720                     tabWidget->setHiddenPages(tabWidgetPages);
2721 
2722                     if (forceUpdate && !isVisible() && PAGE_PLAYQUEUE!=index && PAGE_CONTEXT!=index) {
2723                         QString page=Settings::self()->page();
2724                         for (int i=0; i<tabWidget->count(); ++i) {
2725                             if (tabWidget->widget(i)->metaObject()->className()==page) {
2726                                 index=i;
2727                                 break;
2728                             }
2729                         }
2730                     }
2731 
2732                     tabWidget->setCurrentIndex(index);
2733                 }
2734                 singlePane=true;
2735             }
2736         }
2737     }
2738     controlPlayQueueButtons();
2739 }
2740 
controlPlayQueueButtons()2741 void MainWindow::controlPlayQueueButtons()
2742 {
2743     if (!savePlayQueueButton || !centerPlayQueueButton || !midSpacer) {
2744         return;
2745     }
2746 
2747     savePlayQueueButton->setVisible(singlePane || playQueueWidget->width()>Utils::scaleForDpi(320));
2748     centerPlayQueueButton->setVisible(singlePane || playQueueWidget->width()>(Utils::scaleForDpi(320)+centerPlayQueueButton->width()));
2749     midSpacer->setVisible(singlePane || centerPlayQueueButton->isVisible());
2750 }
2751 
expandOrCollapse(bool saveCurrentSize)2752 void MainWindow::expandOrCollapse(bool saveCurrentSize)
2753 {
2754     if (!expandInterfaceAction->isChecked() && (isFullScreen() || isMaximized() || messageWidget->isVisible())) {
2755         expandInterfaceAction->setChecked(true);
2756         return;
2757     }
2758 
2759     static bool lastMax=false;
2760 
2761     bool showing=expandInterfaceAction->isChecked();
2762     QPoint p(isVisible() ? pos() : QPoint());
2763 
2764     if (!showing) {
2765         setMinimumHeight(0);
2766         lastMax=isMaximized();
2767         if (saveCurrentSize) {
2768             expandedSize=size();
2769         }
2770     } else {
2771         if (saveCurrentSize) {
2772             collapsedSize=size();
2773         }
2774         setMinimumHeight(calcMinHeight());
2775         setMaximumHeight(QWIDGETSIZE_MAX);
2776     }
2777     int prevWidth=size().width();
2778     stack->setVisible(showing);
2779     if (!showing) {
2780         setWindowState(windowState()&~Qt::WindowMaximized);
2781     }
2782     adjustSize();
2783 
2784     if (showing) {
2785         resize(qMax(prevWidth, expandedSize.width()), expandedSize.height());
2786         if (lastMax) {
2787             showMaximized();
2788         }
2789         controlView();
2790     } else {
2791         // Width also sometimes expands, so make sure this is no larger than it was before...
2792         collapsedSize=QSize(collapsedSize.isValid() ? qMin(collapsedSize.width(), prevWidth) : prevWidth, calcCollapsedSize());
2793         resize(collapsedSize);
2794         setFixedHeight(size().height());
2795     }
2796     if (!p.isNull()) {
2797         move(p);
2798     }
2799 
2800     fullScreenAction->setEnabled(showing);
2801     songInfoButton->setVisible(songInfoAction->isCheckable() && showing);
2802     songInfoAction->setEnabled(showing);
2803 }
2804 
toggleContext()2805 void MainWindow::toggleContext()
2806 {
2807     if ( songInfoButton->isVisible()) {
2808          if ( (MPDState_Playing==MPDStatus::self()->state() && !songInfoAction->isChecked()) ||
2809                (MPDState_Stopped==MPDStatus::self()->state() && songInfoAction->isChecked()) ) {
2810             songInfoAction->trigger();
2811          }
2812     } else if (MPDState_Playing==MPDStatus::self()->state() && PAGE_CONTEXT!=tabWidget->currentIndex()) {
2813         int pp=prevPage;
2814         showTab(PAGE_CONTEXT);
2815         prevPage=pp;
2816     } else if (MPDState_Stopped==MPDStatus::self()->state() && PAGE_CONTEXT==tabWidget->currentIndex() && -1!=prevPage) {
2817         showTab(prevPage);
2818     }
2819 }
2820 
hideWindow()2821 void MainWindow::hideWindow()
2822 {
2823     lastPos=pos();
2824     hide();
2825 }
2826 
restoreWindow()2827 void MainWindow::restoreWindow()
2828 {
2829     bool wasHidden=isHidden();
2830     Utils::raiseWindow(this);
2831     if (wasHidden && !lastPos.isNull()) {
2832         move(lastPos);
2833     }
2834 }
2835 
2836 #include "moc_mainwindow.cpp"
2837