1 /* GUI_Player.cpp */
2
3 /* Copyright (C) 2011-2020 Michael Lugmair (Lucio Carreras)
4 *
5 * This file is part of sayonara player
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "GUI_Player.h"
22 #include "GUI_Logger.h"
23 #include "GUI_TrayIcon.h"
24 #include "GUI_PlayerMenubar.h"
25 #include "GUI_Controls.h"
26 #include "GUI_ControlsNew.h"
27 #include "VersionChecker.h"
28 #include "Translator.h"
29
30 #include "Interfaces/CoverDataProvider.h"
31 #include "Interfaces/DynamicPlayback.h"
32 #include "Interfaces/PlaylistInterface.h"
33 #include "Interfaces/PlayManager.h"
34
35 #include "Components/Playlist/PlaylistHandler.h"
36 #include "Components/LibraryManagement/LibraryPluginHandler.h"
37 #include "Components/LibraryManagement/AbstractLibraryContainer.h"
38
39 #include "Gui/Covers/CoverButton.h"
40 #include "Gui/Player/ui_GUI_Player.h"
41 #include "Gui/Plugins/PlayerPluginBase.h"
42 #include "Gui/Plugins/PlayerPluginHandler.h"
43
44 #include "Utils/Utils.h"
45 #include "Utils/Message/Message.h"
46 #include "Utils/Logger/Logger.h"
47 #include "Utils/Language/Language.h"
48 #include "Utils/Settings/Settings.h"
49 #include "Utils/MetaData/MetaData.h"
50
51 #include "Gui/Utils/GuiUtils.h"
52 #include "Gui/Utils/EventFilter.h"
53
54 #include <QApplication>
55 #include <QAction>
56 #include <QDataStream>
57 #include <QKeySequence>
58 #include <QTimer>
59
60 struct GUI_Player::Private
61 {
62 Menubar* menubar = nullptr;
63 std::shared_ptr<GUI_Logger> logger = nullptr;
64 GUI_TrayIcon* trayIcon = nullptr;
65 GUI_ControlsBase* controls = nullptr;
66 CoverDataProvider* coverProvider;
67 PlayManager* playManager;
68 bool shutdownRequested;
69
PrivateGUI_Player::Private70 Private(PlayManager* playManager, PlaylistCreator* playlistCreator, CoverDataProvider* coverProvider,
71 GUI_Player* parent) :
72 coverProvider(coverProvider),
73 playManager(playManager),
74 shutdownRequested(false)
75 {
76 logger = std::make_shared<GUI_Logger>(parent);
77 menubar = new Menubar(playlistCreator, parent);
78 }
79 };
80
GUI_Player(PlayManager * playManager,Playlist::Handler * playlistHandler,CoverDataProvider * coverProvider,DynamicPlaybackChecker * dynamicPlaybackChecker,QWidget * parent)81 GUI_Player::GUI_Player(PlayManager* playManager, Playlist::Handler* playlistHandler, CoverDataProvider* coverProvider,
82 DynamicPlaybackChecker* dynamicPlaybackChecker, QWidget* parent) :
83 Gui::MainWindow(parent),
84 MessageReceiverInterface("Player Main Window")
85 {
86 m = Pimpl::make<Private>(playManager, playlistHandler, coverProvider, this);
87
88 languageChanged();
89
90 ui = new Ui::GUI_Player();
91 ui->setupUi(this);
92 ui->retranslateUi(this);
93 ui->playlistWidget->init(playlistHandler, playManager, dynamicPlaybackChecker);
94
95 ui->pluginWidget->setVisible(false);
96
97 Message::registerReceiver(this);
98
99 this->setMenuBar(m->menubar);
100 this->setWindowIcon(Gui::Util::icon("logo.png", Gui::Util::NoTheme));
101 this->setAttribute(Qt::WA_DeleteOnClose, false);
102
103 initControls();
104 initLibrary();
105 initFontChangeFix();
106 initGeometry(); // init geometry before initConnections
107 initConnections();
108 initTrayActions();
109
110 currentTrackChanged(m->playManager->currentTrack());
111
112 if(GetSetting(Set::Player_NotifyNewVersion))
113 {
114 auto* vc = new VersionChecker(this);
115 connect(vc, &VersionChecker::sigFinished, vc, &QObject::deleteLater);
116 }
117
118 ListenSettingNoCall(Set::Player_Fullscreen, GUI_Player::fullscreenChanged);
119 ListenSettingNoCall(Set::Lib_Show, GUI_Player::showLibraryChanged);
120 ListenSettingNoCall(SetNoDB::Player_Quit, GUI_Player::reallyClose);
121 ListenSettingNoCall(Set::Player_ControlStyle, GUI_Player::controlstyleChanged);
122 }
123
~GUI_Player()124 GUI_Player::~GUI_Player()
125 {
126 spLog(Log::Debug, this) << "Player closed.";
127 delete ui;
128 ui = nullptr;
129 }
130
getGeometryVersion(const QByteArray & geometry)131 static int16_t getGeometryVersion(const QByteArray& geometry)
132 {
133 QDataStream str(geometry);
134 int32_t ignoreThis;
135 int16_t ret;
136
137 str >> ignoreThis >> ret;
138 return ret;
139 }
140
initGeometry()141 void GUI_Player::initGeometry()
142 {
143 QByteArray geometry = GetSetting(Set::Player_Geometry);
144 if(!geometry.isEmpty())
145 {
146 // newer version of qt store more values than older versions
147 // older version have trouble using the new representation,
148 // so we have to trim it
149 int16_t ourGeometryVersion = getGeometryVersion(this->saveGeometry());
150 int16_t dbGeometryVersion = getGeometryVersion(geometry);
151
152 if(ourGeometryVersion < dbGeometryVersion)
153 {
154 Gui::Util::placeInScreenCenter(this, 0.8f, 0.8f);
155 }
156
157 else
158 {
159 this->restoreGeometry(geometry);
160 }
161 }
162
163 else
164 {
165 Gui::Util::placeInScreenCenter(this, 0.8f, 0.8f);
166 }
167
168 if(GetSetting(Set::Player_StartInTray))
169 {
170 this->setHidden(true);
171 }
172
173 else if(GetSetting(Set::Player_Fullscreen))
174 {
175 this->showFullScreen();
176 }
177
178 else if(GetSetting(Set::Player_Maximized))
179 {
180 this->showMaximized();
181 }
182
183 else
184 {
185 this->showNormal();
186 }
187
188 this->initMainSplitter();
189 this->initControlSplitter();
190 }
191
initMainSplitter()192 void GUI_Player::initMainSplitter()
193 {
194 ui->libraryWidget->setVisible(GetSetting(Set::Lib_Show));
195
196 const QByteArray splitterState = GetSetting(Set::Player_SplitterState);
197 if(!splitterState.isEmpty())
198 {
199 ui->splitter->restoreState(splitterState);
200 }
201
202 else
203 {
204 int w1 = width() / 3;
205 int w2 = width() - w1;
206 ui->splitter->setSizes({w1, w2});
207 }
208 }
209
initControlSplitter()210 void GUI_Player::initControlSplitter()
211 {
212 const QByteArray splitterState = GetSetting(Set::Player_SplitterControls);
213 if(!splitterState.isEmpty())
214 {
215 ui->splitterControls->restoreState(splitterState);
216 }
217
218 QApplication::processEvents();
219 this->checkControlSplitter();
220 }
221
initFontChangeFix()222 void GUI_Player::initFontChangeFix()
223 {
224 auto* filter = new Gui::GenericFilter(QEvent::Paint, this);
225 connect(filter, &Gui::GenericFilter::sigEvent, this, [=](QEvent::Type t) {
226 if(t == QEvent::Type::Paint)
227 {
228 this->removeEventFilter(filter);
229 this->skinChanged();
230 this->update();
231 }
232 });
233
234 installEventFilter(filter);
235 }
236
initConnections()237 void GUI_Player::initConnections()
238 {
239 auto* lph = Library::PluginHandler::instance();
240 connect(lph, &Library::PluginHandler::sigCurrentLibraryChanged, this, &GUI_Player::currentLibraryChanged);
241
242 connect(m->playManager, &PlayManager::sigCurrentTrackChanged, this, &GUI_Player::currentTrackChanged);
243 connect(m->playManager, &PlayManager::sigPlaystateChanged, this, &GUI_Player::playstateChanged);
244 connect(m->playManager, &PlayManager::sigError, this, &GUI_Player::playError);
245
246 connect(ui->splitter, &QSplitter::splitterMoved, this, &GUI_Player::splitterMainMoved);
247 connect(ui->splitterControls, &QSplitter::splitterMoved, this, &GUI_Player::splitterControlsMoved);
248
249 connect(m->menubar, &Menubar::sigCloseClicked, this, &GUI_Player::reallyClose);
250 connect(m->menubar, &Menubar::sigLoggerClicked, m->logger.get(), &GUI_Logger::show);
251 connect(m->menubar, &Menubar::sigMinimizeClicked, this, &GUI_Player::minimize);
252
253 auto* pph = PlayerPlugin::Handler::instance();
254 connect(pph, &PlayerPlugin::Handler::sigPluginAdded, this, &GUI_Player::pluginAdded);
255 connect(pph, &PlayerPlugin::Handler::sigPluginActionTriggered, this, &GUI_Player::pluginActionTriggered);
256
257 auto* dblClickFilter = new Gui::GenericFilter(QEvent::MouseButtonDblClick, ui->splitterControls);
258 connect(dblClickFilter, &Gui::GenericFilter::sigEvent, this, [=](QEvent::Type) {
259 this->checkControlSplitter();
260 });
261
262 ui->splitterControls->handle(1)->installEventFilter(dblClickFilter);
263 }
264
registerPreferenceDialog(QAction * dialog_action)265 void GUI_Player::registerPreferenceDialog(QAction* dialog_action)
266 {
267 m->menubar->insertPreferenceAction(dialog_action);
268 }
269
initTrayActions()270 void GUI_Player::initTrayActions()
271 {
272 auto* trayIcon = new GUI_TrayIcon(m->playManager, this);
273
274 connect(trayIcon, &GUI_TrayIcon::sigCloseClicked, this, &GUI_Player::reallyClose);
275 connect(trayIcon, &GUI_TrayIcon::sigShowClicked, this, &GUI_Player::raise);
276 connect(trayIcon, &GUI_TrayIcon::sigWheelChanged, m->controls, &GUI_ControlsBase::changeVolumeByDelta);
277 connect(trayIcon, &GUI_TrayIcon::activated, this, &GUI_Player::trayIconActivated);
278
279 if(GetSetting(Set::Player_ShowTrayIcon))
280 {
281 trayIcon->show();
282 }
283
284 m->trayIcon = trayIcon;
285 }
286
trayIconActivated(QSystemTrayIcon::ActivationReason reason)287 void GUI_Player::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
288 {
289 if(reason != QSystemTrayIcon::Trigger)
290 {
291 return;
292 }
293
294 if(this->isMinimized() || !isVisible() || !isActiveWindow())
295 {
296 raise();
297 }
298
299 else
300 {
301 minimize();
302 }
303 }
304
currentTrackChanged(const MetaData & md)305 void GUI_Player::currentTrackChanged(const MetaData& md)
306 {
307 if(md.title().trimmed().isEmpty())
308 {
309 this->setWindowTitle("Sayonara " + GetSetting(Set::Player_Version));
310 }
311
312 else if(md.artist().trimmed().isEmpty())
313 {
314 this->setWindowTitle(md.title());
315 }
316
317 else
318 {
319 this->setWindowTitle(md.artist() + " - " + md.title());
320 }
321 }
322
playstateChanged(PlayState state)323 void GUI_Player::playstateChanged(PlayState state)
324 {
325 if(state == PlayState::Stopped)
326 {
327 setWindowTitle("Sayonara Player");
328 }
329 }
330
playError(const QString & message)331 void GUI_Player::playError(const QString& message)
332 {
333 const auto& md = m->playManager->currentTrack();
334 Message::warning
335 (
336 message + "\n\n" + md.filepath(),
337 Lang::get(Lang::Play)
338 );
339 }
340
pluginAdded(PlayerPlugin::Base * plugin)341 void GUI_Player::pluginAdded(PlayerPlugin::Base* plugin)
342 {
343 auto* pph = PlayerPlugin::Handler::instance();
344 if(plugin == pph->currentPlugin())
345 {
346 ui->pluginWidget->showCurrentPlugin();
347 }
348 }
349
pluginActionTriggered(bool b)350 void GUI_Player::pluginActionTriggered(bool b)
351 {
352 if(b)
353 {
354 ui->pluginWidget->showCurrentPlugin();
355 }
356
357 else
358 {
359 ui->pluginWidget->close();
360 }
361
362 checkControlSplitter();
363 }
364
initControls()365 void GUI_Player::initControls()
366 {
367 m->controls = (GetSetting(Set::Player_ControlStyle) == 0)
368 ? static_cast<GUI_ControlsBase*>(new GUI_Controls(m->playManager, m->coverProvider, this))
369 : static_cast<GUI_ControlsBase*>(new GUI_ControlsNew(m->playManager, m->coverProvider, this));
370
371 m->controls->init();
372 ui->controls->layout()->addWidget(m->controls);
373 ui->splitterControls->setHandleEnabled(m->controls->isExternResizeAllowed());
374 }
375
controlstyleChanged()376 void GUI_Player::controlstyleChanged()
377 {
378 ui->controls->layout()->removeWidget(m->controls);
379 m->controls->deleteLater();
380
381 initControls();
382
383 if(!m->controls->isExternResizeAllowed())
384 {
385 ui->splitterControls->setSizes({0, this->height()});
386 }
387
388 else
389 {
390 ui->splitterControls->setSizes({350, this->height() - 350});
391 }
392 }
393
currentLibraryChanged()394 void GUI_Player::currentLibraryChanged()
395 {
396 showLibraryChanged();
397 }
398
initLibrary()399 void GUI_Player::initLibrary()
400 {
401 bool isVisible = GetSetting(Set::Lib_Show);
402 ui->libraryWidget->setVisible(isVisible);
403
404 m->menubar->showLibraryMenu(isVisible);
405
406 if(isVisible)
407 {
408 addCurrentLibrary();
409 QWidget* w = Library::PluginHandler::instance()->currentLibraryWidget();
410 if(w)
411 {
412 w->show();
413 w->setFocus();
414 }
415 }
416
417 else
418 {
419 ui->libraryWidget->setVisible(false);
420 removeCurrentLibrary();
421 }
422 }
423
showLibraryChanged()424 void GUI_Player::showLibraryChanged()
425 {
426 // we have to do this here because init_library will show/hide ui->library_widget
427 bool wasVisible = ui->libraryWidget->isVisible();
428 int oldLibraryWidth = ui->libraryWidget->width();
429
430 initLibrary();
431
432 QSize playerSize = this->size();
433 QList<int> sizes = ui->splitter->sizes();
434
435 if(GetSetting(Set::Lib_Show))
436 {
437 if(!wasVisible)
438 {
439 // only change sizes if library wasn't visible until now,
440 // otherwise player becomes wider
441 sizes[1] = GetSetting(Set::Lib_OldWidth);
442 playerSize.setWidth(playerSize.width() + GetSetting(Set::Lib_OldWidth));
443 }
444 }
445
446 else
447 {
448 sizes[1] = 0;
449 playerSize.setWidth(playerSize.width() - ui->libraryWidget->width());
450
451 if(wasVisible)
452 {
453 SetSetting(Set::Lib_OldWidth, oldLibraryWidth);
454 }
455 }
456
457 if((playerSize != this->size()))
458 {
459 QApplication::processEvents();
460
461 if(this->minimumWidth() > playerSize.width())
462 {
463 this->setMinimumWidth(playerSize.width());
464 }
465
466 this->resize(playerSize);
467 ui->splitter->setSizes(sizes);
468 }
469 }
470
addCurrentLibrary()471 void GUI_Player::addCurrentLibrary()
472 {
473 QLayout* layout = ui->libraryWidget->layout();
474 if(!layout)
475 {
476 layout = new QVBoxLayout();
477 ui->libraryWidget->setLayout(layout);
478 }
479
480 removeCurrentLibrary();
481
482 QWidget* w = Library::PluginHandler::instance()->currentLibraryWidget();
483 if(w)
484 {
485 layout->addWidget(w);
486 }
487 }
488
removeCurrentLibrary()489 void GUI_Player::removeCurrentLibrary()
490 {
491 QLayout* layout = ui->libraryWidget->layout();
492 while(layout->count() > 0)
493 {
494 QLayoutItem* item = layout->takeAt(0);
495 if(item && item->widget())
496 {
497 item->widget()->hide();
498 }
499 }
500 }
501
splitterMainMoved(int,int)502 void GUI_Player::splitterMainMoved(int /*pos*/, int /*idx*/)
503 {
504 checkControlSplitter();
505 }
506
splitterControlsMoved(int,int)507 void GUI_Player::splitterControlsMoved(int /*pos*/, int /*idx*/)
508 {
509 checkControlSplitter();
510 }
511
checkControlSplitter()512 void GUI_Player::checkControlSplitter()
513 {
514 if(m->controls && m->controls->isExternResizeAllowed())
515 {
516 auto sizes = ui->splitterControls->sizes();
517
518 // remove empty space on top/bottom of cover
519 const auto difference = m->controls->btnCover()->verticalPadding();
520 sizes[0] -= difference;
521
522 if(sizes[0] > 0)
523 {
524 ui->splitterControls->widget(0)->setMaximumHeight(sizes[0]);
525 }
526
527 const auto minimumHeight = ui->pluginWidget->isVisible()
528 ? 350
529 : 200;
530
531 ui->splitterControls->widget(1)->setMinimumHeight(minimumHeight);
532 }
533 }
534
languageChanged()535 void GUI_Player::languageChanged()
536 {
537 const auto language = GetSetting(Set::Player_Language);
538 Translator::instance()->changeLanguage(this, language);
539
540 if(ui)
541 {
542 ui->retranslateUi(this);
543 }
544 }
545
minimize()546 void GUI_Player::minimize()
547 {
548 if(GetSetting(Set::Player_Min2Tray))
549 {
550 hide();
551 }
552
553 else
554 {
555 showMinimized();
556 }
557 }
558
fullscreenChanged()559 void GUI_Player::fullscreenChanged()
560 {
561 if(GetSetting(Set::Player_Fullscreen))
562 {
563 showFullScreen();
564 }
565
566 else
567 {
568 showNormal();
569 }
570 }
571
reallyClose()572 void GUI_Player::reallyClose()
573 {
574 spLog(Log::Info, this) << "closing player...";
575 Gui::MainWindow::close();
576 emit sigClosed();
577 }
578
requestShutdown()579 void GUI_Player::requestShutdown()
580 {
581 m->shutdownRequested = true;
582 }
583
resizeEvent(QResizeEvent * e)584 void GUI_Player::resizeEvent(QResizeEvent* e)
585 {
586 Gui::MainWindow::resizeEvent(e);
587 checkControlSplitter();
588 }
589
closeEvent(QCloseEvent * e)590 void GUI_Player::closeEvent(QCloseEvent* e)
591 {
592 SetSetting(Set::Player_Geometry, this->saveGeometry());
593 SetSetting(Set::Player_Maximized, this->isMaximized());
594 SetSetting(Set::Player_Fullscreen, this->isFullScreen());
595 SetSetting(Set::Player_SplitterState, ui->splitter->saveState());
596 SetSetting(Set::Player_SplitterControls, ui->splitterControls->saveState());
597
598 if(!m->shutdownRequested && GetSetting(Set::Player_Min2Tray) && !GetSetting(SetNoDB::Player_Quit))
599 {
600 if(GetSetting(Set::Player_514Fix))
601 {
602 e->ignore();
603 QApplication::processEvents();
604 }
605
606 this->hide();
607 }
608
609 else
610 {
611 m->trayIcon->hide();
612 Gui::MainWindow::closeEvent(e);
613 emit sigClosed();
614 }
615 }
616
event(QEvent * e)617 bool GUI_Player::event(QEvent* e)
618 {
619 bool b = Gui::MainWindow::event(e);
620
621 if(e->type() == QEvent::WindowStateChange)
622 {
623 m->menubar->setShowLibraryActionEnabled
624 (
625 !(isMaximized() || isFullScreen())
626 );
627 }
628
629 return b;
630 }
631