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 "© 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> - © 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: </td><td>%1.%2.%3</td></tr>"
1630 "<tr><td align=\"right\">Uptime: </td><td>%4</td></tr>"
1631 "<tr><td align=\"right\">Playing: </td><td>%5</td></tr>"
1632 "<tr><td align=\"right\">Handlers: </td><td>%6</td></tr>"
1633 "<tr><td align=\"right\">Tags: </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: </td><td>%1</td></tr>"
1641 "<tr><td align=\"right\">Albums: </td><td>%2</td></tr>"
1642 "<tr><td align=\"right\">Songs: </td><td>%3</td></tr>"
1643 "<tr><td align=\"right\">Duration: </td><td>%4</td></tr>"
1644 "<tr><td align=\"right\">Updated: </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