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