1 /***************************************************************************
2                           mixxx.cpp  -  description
3                              -------------------
4     begin                : Mon Feb 18 09:48:17 CET 2002
5     copyright            : (C) 2002 by Tue and Ken Haste Andersen
6     email                :
7 ***************************************************************************/
8 
9 /***************************************************************************
10 *                                                                         *
11 *   This program is free software; you can redistribute it and/or modify  *
12 *   it under the terms of the GNU General Public License as published by  *
13 *   the Free Software Foundation; either version 2 of the License, or     *
14 *   (at your option) any later version.                                   *
15 *                                                                         *
16 ***************************************************************************/
17 
18 #include "mixxx.h"
19 
20 #include <QDesktopServices>
21 #include <QFileDialog>
22 #include <QGLFormat>
23 #include <QGLWidget>
24 #include <QGuiApplication>
25 #include <QInputMethod>
26 #include <QLocale>
27 #include <QScreen>
28 #include <QStandardPaths>
29 #include <QUrl>
30 #include <QtDebug>
31 
32 #include "defs_urls.h"
33 #include "dialog/dlgabout.h"
34 #include "dialog/dlgdevelopertools.h"
35 #include "effects/builtin/builtinbackend.h"
36 #include "effects/effectsmanager.h"
37 #include "engine/enginemaster.h"
38 #include "moc_mixxx.cpp"
39 #include "preferences/constants.h"
40 #include "preferences/dialog/dlgprefeq.h"
41 #include "preferences/dialog/dlgpreferences.h"
42 #ifdef __LILV__
43 #include "effects/lv2/lv2backend.h"
44 #endif
45 
46 #include "broadcast/broadcastmanager.h"
47 #include "control/controlpushbutton.h"
48 #include "controllers/controllermanager.h"
49 #include "controllers/keyboard/keyboardeventfilter.h"
50 #include "database/mixxxdb.h"
51 #include "library/coverartcache.h"
52 #include "library/library.h"
53 #include "library/library_preferences.h"
54 #include "library/trackcollection.h"
55 #include "library/trackcollectionmanager.h"
56 #include "mixer/playerinfo.h"
57 #include "mixer/playermanager.h"
58 #include "preferences/settingsmanager.h"
59 #include "recording/recordingmanager.h"
60 #include "skin/legacy/launchimage.h"
61 #include "skin/legacy/legacyskinparser.h"
62 #include "skin/skinloader.h"
63 #include "soundio/soundmanager.h"
64 #include "sources/soundsourceproxy.h"
65 #include "track/track.h"
66 #include "util/db/dbconnectionpooled.h"
67 #include "util/debug.h"
68 #include "util/experiment.h"
69 #include "util/font.h"
70 #include "util/logger.h"
71 #include "util/math.h"
72 #include "util/sandbox.h"
73 #include "util/screensaver.h"
74 #include "util/statsmanager.h"
75 #include "util/time.h"
76 #include "util/timer.h"
77 #include "util/translations.h"
78 #include "util/versionstore.h"
79 #include "util/widgethelper.h"
80 #include "waveform/guitick.h"
81 #include "waveform/sharedglcontext.h"
82 #include "waveform/visualsmanager.h"
83 #include "waveform/waveformwidgetfactory.h"
84 #include "widget/wmainmenubar.h"
85 
86 #ifdef __VINYLCONTROL__
87 #include "vinylcontrol/vinylcontrolmanager.h"
88 #endif
89 
90 #ifdef __MODPLUG__
91 #include "preferences/dialog/dlgprefmodplug.h"
92 #endif
93 
94 #if defined(Q_OS_LINUX)
95 #include <QtX11Extras/QX11Info>
96 #include <X11/Xlib.h>
97 #include <X11/Xlibint.h>
98 // Xlibint.h predates C++ and defines macros which conflict
99 // with references to std::max and std::min
100 #undef max
101 #undef min
102 #endif
103 
104 namespace {
105 
106 const mixxx::Logger kLogger("MixxxMainWindow");
107 
108 // hack around https://gitlab.freedesktop.org/xorg/lib/libx11/issues/25
109 // https://bugs.launchpad.net/mixxx/+bug/1805559
110 #if defined(Q_OS_LINUX)
111 typedef Bool (*WireToErrorType)(Display*, XErrorEvent*, xError*);
112 
113 const int NUM_HANDLERS = 256;
114 WireToErrorType __oldHandlers[NUM_HANDLERS] = {nullptr};
115 
__xErrorHandler(Display * display,XErrorEvent * event,xError * error)116 Bool __xErrorHandler(Display* display, XErrorEvent* event, xError* error) {
117     // Call any previous handler first in case it needs to do real work.
118     auto code = static_cast<int>(event->error_code);
119     if (__oldHandlers[code] != nullptr) {
120         __oldHandlers[code](display, event, error);
121     }
122 
123     // Always return false so the error does not get passed to the normal
124     // application defined handler.
125     return False;
126 }
127 
128 #endif
129 
inputLocale()130 inline QLocale inputLocale() {
131     // Use the default config for local keyboard
132     QInputMethod* pInputMethod = QGuiApplication::inputMethod();
133     return pInputMethod ? pInputMethod->locale() :
134             QLocale(QLocale::English);
135 }
136 
137 } // anonymous namespace
138 
139 // static
140 const int MixxxMainWindow::kMicrophoneCount = 4;
141 // static
142 const int MixxxMainWindow::kAuxiliaryCount = 4;
143 
MixxxMainWindow(QApplication * pApp,const CmdlineArgs & args)144 MixxxMainWindow::MixxxMainWindow(QApplication* pApp, const CmdlineArgs& args)
145         : m_pWidgetParent(nullptr),
146           m_pLaunchImage(nullptr),
147           m_pEffectsManager(nullptr),
148           m_pEngine(nullptr),
149           m_pSkinLoader(nullptr),
150           m_pSoundManager(nullptr),
151           m_pPlayerManager(nullptr),
152           m_pRecordingManager(nullptr),
153 #ifdef __BROADCAST__
154           m_pBroadcastManager(nullptr),
155 #endif
156           m_pControllerManager(nullptr),
157           m_pGuiTick(nullptr),
158 #ifdef __VINYLCONTROL__
159           m_pVCManager(nullptr),
160 #endif
161           m_pKeyboard(nullptr),
162           m_pLibrary(nullptr),
163           m_pDeveloperToolsDlg(nullptr),
164           m_pPrefDlg(nullptr),
165           m_pKbdConfig(nullptr),
166           m_pKbdConfigEmpty(nullptr),
167           m_toolTipsCfg(mixxx::TooltipsPreference::TOOLTIPS_ON),
168           m_runtime_timer("MixxxMainWindow::runtime"),
169           m_cmdLineArgs(args),
170           m_pTouchShift(nullptr) {
171     m_runtime_timer.start();
172     mixxx::Time::start();
173 
174 #ifdef __APPLE__
175     // TODO: At this point it is too late to provide the same settings path to all components
176     // and too early to log errors and give users advises in their system language.
177     // Calling this from main.cpp before the QApplication is initialized may cause a crash
178     // due to potential QMessageBox invocations within migrateOldSettings().
179     // Solution: Start Mixxx with default settings, migrate the preferences, and then restart
180     // immediately.
181     if (!args.getSettingsPathSet()) {
182         CmdlineArgs::Instance().setSettingsPath(Sandbox::migrateOldSettings());
183     }
184 #endif
185 
186     QString settingsPath = args.getSettingsPath();
187 
188     mixxx::LogFlags logFlags = mixxx::LogFlag::LogToFile;
189     if (args.getDebugAssertBreak()) {
190         logFlags.setFlag(mixxx::LogFlag::DebugAssertBreak);
191     }
192     mixxx::Logging::initialize(
193             settingsPath,
194             args.getLogLevel(),
195             args.getLogFlushLevel(),
196             logFlags);
197 
198     VERIFY_OR_DEBUG_ASSERT(SoundSourceProxy::registerProviders()) {
199         qCritical() << "Failed to register any SoundSource providers";
200         return;
201     }
202 
203     VersionStore::logBuildDetails();
204 
205     // Only record stats in developer mode.
206     if (m_cmdLineArgs.getDeveloper()) {
207         StatsManager::createInstance();
208     }
209 
210     m_pSettingsManager = std::make_unique<SettingsManager>(settingsPath);
211 
212     initializeKeyboard();
213     installEventFilter(m_pKeyboard);
214 
215     // Menubar depends on translations.
216     mixxx::Translations::initializeTranslations(
217         m_pSettingsManager->settings(), pApp, args.getLocale());
218 
219     createMenuBar();
220     m_pMenuBar->hide();
221 
222     initializeWindow();
223 
224     // First load launch image to show a the user a quick responds
225     m_pSkinLoader = new SkinLoader(m_pSettingsManager->settings());
226     m_pLaunchImage = m_pSkinLoader->loadLaunchImage(this);
227     m_pWidgetParent = (QWidget*)m_pLaunchImage;
228     setCentralWidget(m_pWidgetParent);
229 
230     show();
231     pApp->processEvents();
232 
233     initialize(pApp, args);
234 }
235 
~MixxxMainWindow()236 MixxxMainWindow::~MixxxMainWindow() {
237     if (m_pDeveloperToolsDlg) {
238         delete m_pDeveloperToolsDlg;
239     }
240     finalize();
241     // SkinLoader depends on Config;
242     delete m_pSkinLoader;
243 }
244 
initialize(QApplication * pApp,const CmdlineArgs & args)245 void MixxxMainWindow::initialize(QApplication* pApp, const CmdlineArgs& args) {
246     ScopedTimer t("MixxxMainWindow::initialize");
247 
248 #if defined(Q_OS_LINUX)
249     // XESetWireToError will segfault if running as a Wayland client
250     if (pApp->platformName() == QLatin1String("xcb")) {
251         for (auto i = 0; i < NUM_HANDLERS; ++i) {
252             XESetWireToError(QX11Info::display(), i, &__xErrorHandler);
253         }
254     }
255 #endif
256 
257     UserSettingsPointer pConfig = m_pSettingsManager->settings();
258 
259     Sandbox::setPermissionsFilePath(QDir(pConfig->getSettingsPath()).filePath("sandbox.cfg"));
260 
261     // Turn on fullscreen mode
262     // if we were told to start in fullscreen mode on the command-line
263     // or if the user chose to always start in fullscreen mode.
264     // Remember to refresh the Fullscreen menu item after connectMenuBar()
265     bool fullscreenPref = pConfig->getValue<bool>(
266             ConfigKey("[Config]", "StartInFullscreen"));
267     if (args.getStartInFullscreen() || fullscreenPref) {
268         showFullScreen();
269     }
270 
271     QString resourcePath = pConfig->getResourcePath();
272 
273     FontUtils::initializeFonts(resourcePath); // takes a long time
274 
275     launchProgress(2);
276 
277     // Set the visibility of tooltips, default "1" = ON
278     m_toolTipsCfg = static_cast<mixxx::TooltipsPreference>(
279         pConfig->getValue(ConfigKey("[Controls]", "Tooltips"),
280                 static_cast<int>(mixxx::TooltipsPreference::TOOLTIPS_ON)));
281 
282     m_pTouchShift = new ControlPushButton(ConfigKey("[Controls]", "touch_shift"));
283 
284     m_pDbConnectionPool = MixxxDb(pConfig).connectionPool();
285     if (!m_pDbConnectionPool) {
286         // TODO(XXX) something a little more elegant
287         exit(-1);
288     }
289     // Create a connection for the main thread
290     m_pDbConnectionPool->createThreadLocalConnection();
291     if (!initializeDatabase()) {
292         // TODO(XXX) something a little more elegant
293         exit(-1);
294     }
295 
296     auto pChannelHandleFactory = std::make_shared<ChannelHandleFactory>();
297 
298     // Create the Effects subsystem.
299     m_pEffectsManager = new EffectsManager(this, pConfig, pChannelHandleFactory);
300 
301     // Starting the master (mixing of the channels and effects):
302     m_pEngine = new EngineMaster(
303             pConfig,
304             "[Master]",
305             m_pEffectsManager,
306             pChannelHandleFactory,
307             true);
308 
309     // Create effect backends. We do this after creating EngineMaster to allow
310     // effect backends to refer to controls that are produced by the engine.
311     BuiltInBackend* pBuiltInBackend = new BuiltInBackend(m_pEffectsManager);
312     m_pEffectsManager->addEffectsBackend(pBuiltInBackend);
313 #ifdef __LILV__
314     LV2Backend* pLV2Backend = new LV2Backend(m_pEffectsManager);
315     m_pEffectsManager->addEffectsBackend(pLV2Backend);
316 #else
317     LV2Backend* pLV2Backend = nullptr;
318 #endif
319 
320     // Sets up the EffectChains and EffectRacks (long)
321     m_pEffectsManager->setup();
322 
323     launchProgress(8);
324 
325     // Although m_pSoundManager is created here, m_pSoundManager->setupDevices()
326     // needs to be called after m_pPlayerManager registers sound IO for each EngineChannel.
327     m_pSoundManager = new SoundManager(pConfig, m_pEngine);
328     m_pEngine->registerNonEngineChannelSoundIO(m_pSoundManager);
329 
330     m_pRecordingManager = new RecordingManager(pConfig, m_pEngine);
331 
332 #ifdef __BROADCAST__
333     m_pBroadcastManager = new BroadcastManager(
334             m_pSettingsManager.get(),
335             m_pSoundManager);
336 #endif
337 
338     launchProgress(11);
339 
340     // Needs to be created before CueControl (decks) and WTrackTableView.
341     m_pGuiTick = new GuiTick();
342     m_pVisualsManager = new VisualsManager();
343 
344 #ifdef __VINYLCONTROL__
345     m_pVCManager = new VinylControlManager(this, pConfig, m_pSoundManager);
346 #else
347     m_pVCManager = NULL;
348 #endif
349 
350     // Create the player manager. (long)
351     m_pPlayerManager = new PlayerManager(pConfig, m_pSoundManager,
352             m_pEffectsManager, m_pVisualsManager, m_pEngine);
353     connect(m_pPlayerManager,
354             &PlayerManager::noMicrophoneInputConfigured,
355             this,
356             &MixxxMainWindow::slotNoMicrophoneInputConfigured);
357     connect(m_pPlayerManager,
358             &PlayerManager::noAuxiliaryInputConfigured,
359             this,
360             &MixxxMainWindow::slotNoAuxiliaryInputConfigured);
361     connect(m_pPlayerManager,
362             &PlayerManager::noDeckPassthroughInputConfigured,
363             this,
364             &MixxxMainWindow::slotNoDeckPassthroughInputConfigured);
365     connect(m_pPlayerManager,
366             &PlayerManager::noVinylControlInputConfigured,
367             this,
368             &MixxxMainWindow::slotNoVinylControlInputConfigured);
369     PlayerInfo::create();
370 
371     for (int i = 0; i < kMicrophoneCount; ++i) {
372         m_pPlayerManager->addMicrophone();
373     }
374 
375     for (int i = 0; i < kAuxiliaryCount; ++i) {
376         m_pPlayerManager->addAuxiliary();
377     }
378 
379     m_pPlayerManager->addConfiguredDecks();
380     m_pPlayerManager->addSampler();
381     m_pPlayerManager->addSampler();
382     m_pPlayerManager->addSampler();
383     m_pPlayerManager->addSampler();
384     m_pPlayerManager->addPreviewDeck();
385 
386     launchProgress(30);
387 
388     m_pEffectsManager->loadEffectChains();
389 
390 #ifdef __VINYLCONTROL__
391     m_pVCManager->init();
392 #endif
393 
394 #ifdef __MODPLUG__
395     // restore the configuration for the modplug library before trying to load a module
396     DlgPrefModplug* pModplugPrefs = new DlgPrefModplug(nullptr, pConfig);
397     pModplugPrefs->loadSettings();
398     pModplugPrefs->applySettings();
399     delete pModplugPrefs; // not needed anymore
400 #endif
401 
402     CoverArtCache::createInstance();
403 
404     launchProgress(30);
405 
406     m_pTrackCollectionManager = new TrackCollectionManager(
407             this,
408             pConfig,
409             m_pDbConnectionPool);
410 
411     launchProgress(35);
412 
413     m_pLibrary = new Library(
414             this,
415             pConfig,
416             m_pDbConnectionPool,
417             m_pTrackCollectionManager,
418             m_pPlayerManager,
419             m_pRecordingManager);
420 
421     // Binding the PlayManager to the Library may already trigger
422     // loading of tracks which requires that the GlobalTrackCache has
423     // been created. Otherwise Mixxx might hang when accessing
424     // the uninitialized singleton instance!
425     m_pPlayerManager->bindToLibrary(m_pLibrary);
426 
427     launchProgress(40);
428 
429     // Get Music dir
430     bool hasChanged_MusicDir = false;
431 
432     QStringList dirs = m_pLibrary->getDirs();
433     if (dirs.size() < 1) {
434         // TODO(XXX) this needs to be smarter, we can't distinguish between an empty
435         // path return value (not sure if this is normally possible, but it is
436         // possible with the Windows 7 "Music" library, which is what
437         // QStandardPaths::writableLocation(QStandardPaths::MusicLocation)
438         // resolves to) and a user hitting 'cancel'. If we get a blank return
439         // but the user didn't hit cancel, we need to know this and let the
440         // user take some course of action -- bkgood
441         QString fd = QFileDialog::getExistingDirectory(
442             this, tr("Choose music library directory"),
443             QStandardPaths::writableLocation(QStandardPaths::MusicLocation));
444         if (!fd.isEmpty()) {
445             // adds Folder to database.
446             m_pLibrary->slotRequestAddDir(fd);
447             hasChanged_MusicDir = true;
448         }
449     }
450 
451     // Call inits to invoke all other construction parts
452 
453     // Initialize controller sub-system,
454     // but do not set up controllers until the end of the application startup
455     // (long)
456     qDebug() << "Creating ControllerManager";
457     m_pControllerManager = new ControllerManager(pConfig);
458 
459     launchProgress(47);
460 
461     // Before creating the first skin we need to create a QGLWidget so that all
462     // the QGLWidget's we create can use it as a shared QGLContext.
463     if (!CmdlineArgs::Instance().getSafeMode() && QGLFormat::hasOpenGL()) {
464         QGLFormat glFormat;
465         glFormat.setDirectRendering(true);
466         glFormat.setDoubleBuffer(true);
467         glFormat.setDepth(false);
468         // Disable waiting for vertical Sync
469         // This can be enabled when using a single Threads for each QGLContext
470         // Setting 1 causes QGLContext::swapBuffer to sleep until the next VSync
471 #if defined(__APPLE__)
472         // On OS X, syncing to vsync has good performance FPS-wise and
473         // eliminates tearing.
474         glFormat.setSwapInterval(1);
475 #else
476         // Otherwise, turn VSync off because it could cause horrible FPS on
477         // Linux.
478         // TODO(XXX): Make this configurable.
479         // TODO(XXX): What should we do on Windows?
480         glFormat.setSwapInterval(0);
481 #endif
482         glFormat.setRgba(true);
483         QGLFormat::setDefaultFormat(glFormat);
484 
485         QGLWidget* pContextWidget = new QGLWidget(this);
486         pContextWidget->setGeometry(QRect(0, 0, 3, 3));
487         pContextWidget->hide();
488         SharedGLContext::setWidget(pContextWidget);
489     }
490 
491     WaveformWidgetFactory::createInstance(); // takes a long time
492     WaveformWidgetFactory::instance()->setConfig(pConfig);
493     WaveformWidgetFactory::instance()->startVSync(m_pGuiTick, m_pVisualsManager);
494 
495     launchProgress(52);
496 
497     connect(this,
498             &MixxxMainWindow::skinLoaded,
499             m_pLibrary,
500             &Library::onSkinLoadFinished);
501 
502     connect(this,
503             &MixxxMainWindow::skinLoaded,
504             WaveformWidgetFactory::instance(),
505             &WaveformWidgetFactory::slotSkinLoaded);
506 
507     // Inhibit the screensaver if the option is set. (Do it before creating the preferences dialog)
508     int inhibit = pConfig->getValue<int>(ConfigKey("[Config]","InhibitScreensaver"),-1);
509     if (inhibit == -1) {
510         inhibit = static_cast<int>(mixxx::ScreenSaverPreference::PREVENT_ON);
511         pConfig->setValue<int>(ConfigKey("[Config]","InhibitScreensaver"), inhibit);
512     }
513     m_inhibitScreensaver = static_cast<mixxx::ScreenSaverPreference>(inhibit);
514     if (m_inhibitScreensaver == mixxx::ScreenSaverPreference::PREVENT_ON) {
515         mixxx::ScreenSaverHelper::inhibit();
516     }
517 
518     // Initialize preference dialog
519     m_pPrefDlg = new DlgPreferences(
520             this,
521             m_pSkinLoader,
522             m_pSoundManager,
523             m_pPlayerManager,
524             m_pControllerManager,
525             m_pVCManager,
526             pLV2Backend,
527             m_pEffectsManager,
528             m_pSettingsManager.get(),
529             m_pLibrary);
530     m_pPrefDlg->setWindowIcon(QIcon(":/images/icons/mixxx.svg"));
531     m_pPrefDlg->setHidden(true);
532 
533     launchProgress(60);
534 
535     // Connect signals to the menubar. Should be done before emit newSkinLoaded.
536     connectMenuBar();
537     // Refresh the Fullscreen checkbox for the case we went fullscreen earlier
538     emit fullScreenChanged(isFullScreen());
539 
540     launchProgress(63);
541 
542     QWidget* oldWidget = m_pWidgetParent;
543 
544     // Load default styles that can be overridden by skins
545     QFile file(":/skins/default.qss");
546     if (file.open(QIODevice::ReadOnly)) {
547         QByteArray fileBytes = file.readAll();
548         QString style = QString::fromLocal8Bit(fileBytes.constData(),
549                                                fileBytes.length());
550         setStyleSheet(style);
551     } else {
552         qWarning() << "Failed to load default skin styles!";
553     }
554 
555     // Load skin to a QWidget that we set as the central widget. Assignment
556     // intentional in next line.
557     m_pWidgetParent = m_pSkinLoader->loadConfiguredSkin(this,
558             &m_skinCreatedControls,
559             m_pKeyboard,
560             m_pPlayerManager,
561             m_pControllerManager,
562             m_pLibrary,
563             m_pVCManager,
564             m_pEffectsManager,
565             m_pRecordingManager);
566     if (!m_pWidgetParent) {
567         reportCriticalErrorAndQuit(
568                 "default skin cannot be loaded see <b>mixxx</b> trace for more information.");
569 
570         m_pWidgetParent = oldWidget;
571         //TODO (XXX) add dialog to warn user and launch skin choice page
572     } else {
573         m_pMenuBar->setStyleSheet(m_pWidgetParent->styleSheet());
574     }
575 
576     // Fake a 100 % progress here.
577     // At a later place it will newer shown up, since it is
578     // immediately replaced by the real widget.
579     launchProgress(100);
580 
581     // Check direct rendering and warn user if they don't have it
582     if (!CmdlineArgs::Instance().getSafeMode()) {
583         checkDirectRendering();
584     }
585 
586     // Install an event filter to catch certain QT events, such as tooltips.
587     // This allows us to turn off tooltips.
588     pApp->installEventFilter(this); // The eventfilter is located in this
589                                     // Mixxx class as a callback.
590     emit skinLoaded();
591 
592     // Wait until all other ControlObjects are set up before initializing
593     // controllers
594     m_pControllerManager->setUpDevices();
595 
596     // Scan the library for new files and directories
597     bool rescan = pConfig->getValue<bool>(
598             ConfigKey("[Library]","RescanOnStartup"));
599     // rescan the library if we get a new plugin
600     QList<QString> prev_plugins_list =
601             pConfig->getValueString(
602                            ConfigKey("[Library]", "SupportedFileExtensions"))
603                     .split(',',
604 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
605                             Qt::SkipEmptyParts);
606 #else
607                             QString::SkipEmptyParts);
608 #endif
609 
610     // TODO: QSet<T>::fromList(const QList<T>&) is deprecated and should be
611     // replaced with QSet<T>(list.begin(), list.end()).
612     // However, the proposed alternative has just been introduced in Qt
613     // 5.14. Until the minimum required Qt version of Mixxx is increased,
614     // we need a version check here
615     QSet<QString> prev_plugins =
616 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
617         QSet<QString>(prev_plugins_list.begin(), prev_plugins_list.end());
618 #else
619         QSet<QString>::fromList(prev_plugins_list);
620 #endif
621 
622     const QList<QString> curr_plugins_list = SoundSourceProxy::getSupportedFileExtensions();
623     QSet<QString> curr_plugins =
624 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
625         QSet<QString>(curr_plugins_list.begin(), curr_plugins_list.end());
626 #else
627         QSet<QString>::fromList(curr_plugins_list);
628 #endif
629 
630     rescan = rescan || (prev_plugins != curr_plugins);
631     pConfig->set(ConfigKey("[Library]", "SupportedFileExtensions"), curr_plugins_list.join(","));
632 
633     // Scan the library directory. Do this after the skinloader has
634     // loaded a skin, see Bug #1047435
635     if (rescan || hasChanged_MusicDir || m_pSettingsManager->shouldRescanLibrary()) {
636         m_pTrackCollectionManager->startLibraryScan();
637     }
638 
639     // This has to be done before m_pSoundManager->setupDevices()
640     // https://bugs.launchpad.net/mixxx/+bug/1758189
641     m_pPlayerManager->loadSamplers();
642 
643     // Try open player device If that fails, the preference panel is opened.
644     bool retryClicked;
645     do {
646         retryClicked = false;
647         SoundDeviceError result = m_pSoundManager->setupDevices();
648         if (result == SOUNDDEVICE_ERROR_DEVICE_COUNT ||
649                 result == SOUNDDEVICE_ERROR_EXCESSIVE_OUTPUT_CHANNEL) {
650             if (soundDeviceBusyDlg(&retryClicked) != QDialog::Accepted) {
651                 exit(0);
652             }
653         } else if (result != SOUNDDEVICE_ERROR_OK) {
654             if (soundDeviceErrorMsgDlg(result, &retryClicked) !=
655                     QDialog::Accepted) {
656                 exit(0);
657             }
658         }
659     } while (retryClicked);
660 
661     // test for at least one out device, if none, display another dlg that
662     // says "mixxx will barely work with no outs"
663     // In case persisting errors, the user has already received a message
664     // box from the preferences dialog above. So we can watch here just the
665     // output count.
666     while (m_pSoundManager->getConfig().getOutputs().count() == 0) {
667         // Exit when we press the Exit button in the noSoundDlg dialog
668         // only call it if result != OK
669         bool continueClicked = false;
670         if (noOutputDlg(&continueClicked) != QDialog::Accepted) {
671             exit(0);
672         }
673         if (continueClicked) {
674             break;
675         }
676    }
677 
678     // Load tracks in args.qlMusicFiles (command line arguments) into player
679     // 1 and 2:
680     const QList<QString>& musicFiles = args.getMusicFiles();
681     for (int i = 0; i < (int)m_pPlayerManager->numDecks()
682             && i < musicFiles.count(); ++i) {
683         if (SoundSourceProxy::isFileNameSupported(musicFiles.at(i))) {
684             m_pPlayerManager->slotLoadToDeck(musicFiles.at(i), i+1);
685         }
686     }
687 
688     connect(&PlayerInfo::instance(),
689             &PlayerInfo::currentPlayingTrackChanged,
690             this,
691             &MixxxMainWindow::slotUpdateWindowTitle);
692 
693     connect(&PlayerInfo::instance(),
694             &PlayerInfo::currentPlayingDeckChanged,
695             this,
696             &MixxxMainWindow::slotChangedPlayingDeck);
697 
698     // this has to be after the OpenGL widgets are created or depending on a
699     // million different variables the first waveform may be horribly
700     // corrupted. See bug 521509 -- bkgood ?? -- vrince
701     setCentralWidget(m_pWidgetParent);
702 
703     // Show the menubar after the launch image is replaced by the skin widget,
704     // otherwise it would shift the launch image shortly before the skin is visible.
705     m_pMenuBar->show();
706 
707     // The launch image widget is automatically disposed, but we still have a
708     // pointer to it.
709     m_pLaunchImage = nullptr;
710 }
711 
finalize()712 void MixxxMainWindow::finalize() {
713     Timer t("MixxxMainWindow::~finalize");
714     t.start();
715 
716     if (m_inhibitScreensaver != mixxx::ScreenSaverPreference::PREVENT_OFF) {
717         mixxx::ScreenSaverHelper::uninhibit();
718     }
719 
720     // Stop all pending library operations
721     qDebug() << t.elapsed(false).debugMillisWithUnit() << "stopping pending Library tasks";
722     m_pTrackCollectionManager->stopLibraryScan();
723     m_pLibrary->stopPendingTasks();
724 
725     // Save the current window state (position, maximized, etc)
726     // Note(ronso0): Unfortunately saveGeometry() also stores the fullscreen state.
727     // On next start restoreGeometry would enable fullscreen mode even though that
728     // might not be requested (no '--fullscreen' command line arg and
729     // [Config],StartInFullscreen is '0'.
730     // https://bugs.launchpad.net/mixxx/+bug/1882474
731     // https://bugs.launchpad.net/mixxx/+bug/1909485
732     // So let's quit fullscreen if StartInFullscreen is not checked in Preferences.
733     bool fullscreenPref = m_pSettingsManager->settings()->getValue<bool>(
734             ConfigKey("[Config]", "StartInFullscreen"));
735     if (isFullScreen() && !fullscreenPref) {
736         slotViewFullScreen(false);
737         // After returning from fullscreen the main window incl. window decoration
738         // may be too large for the screen.
739         // Maximize the window so we can store a geometry that fits the screen.
740         showMaximized();
741     }
742     m_pSettingsManager->settings()->set(ConfigKey("[MainWindow]", "geometry"),
743         QString(saveGeometry().toBase64()));
744     m_pSettingsManager->settings()->set(ConfigKey("[MainWindow]", "state"),
745         QString(saveState().toBase64()));
746 
747     qDebug() << "Destroying MixxxMainWindow";
748 
749     qDebug() << t.elapsed(false).debugMillisWithUnit() << "saving configuration";
750     m_pSettingsManager->save();
751 
752     // GUI depends on KeyboardEventFilter, PlayerManager, Library
753     qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting skin";
754     m_pWidgetParent = nullptr;
755     QPointer<QWidget> pSkin(centralWidget());
756     setCentralWidget(nullptr);
757     if (!pSkin.isNull()) {
758         QCoreApplication::sendPostedEvents(pSkin, QEvent::DeferredDelete);
759     }
760     // Our central widget is now deleted.
761     VERIFY_OR_DEBUG_ASSERT(pSkin.isNull()) {
762         qWarning() << "Central widget was not deleted by our sendPostedEvents trick.";
763     }
764 
765     // Delete Controls created by skins
766     qDeleteAll(m_skinCreatedControls);
767     m_skinCreatedControls.clear();
768 
769     // TODO() Verify if this comment still applies:
770     // WMainMenuBar holds references to controls so we need to delete it
771     // before MixxxMainWindow is destroyed. QMainWindow calls deleteLater() in
772     // setMenuBar() but we need to delete it now so we can ask for
773     // DeferredDelete events to be processed for it. Once Mixxx shutdown lives
774     // outside of MixxxMainWindow the parent relationship will directly destroy
775     // the WMainMenuBar and this will no longer be a problem.
776     qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting menubar";
777 
778     QPointer<WMainMenuBar> pMenuBar = m_pMenuBar.toWeakRef();
779     DEBUG_ASSERT(menuBar() == m_pMenuBar.get());
780     // We need to reset the parented pointer here that it does not become a
781     // dangling pinter after the object has been deleted.
782     m_pMenuBar = nullptr;
783     setMenuBar(nullptr);
784     if (!pMenuBar.isNull()) {
785         QCoreApplication::sendPostedEvents(pMenuBar, QEvent::DeferredDelete);
786     }
787     // Our main menu is now deleted.
788     VERIFY_OR_DEBUG_ASSERT(pMenuBar.isNull()) {
789         qWarning() << "WMainMenuBar was not deleted by our sendPostedEvents trick.";
790     }
791 
792     // SoundManager depend on Engine and Config
793     qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting SoundManager";
794     delete m_pSoundManager;
795 
796     // ControllerManager depends on Config
797     qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting ControllerManager";
798     delete m_pControllerManager;
799 
800 #ifdef __VINYLCONTROL__
801     // VinylControlManager depends on a CO the engine owns
802     // (vinylcontrol_enabled in VinylControlControl)
803     qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting VinylControlManager";
804     delete m_pVCManager;
805 #endif
806 
807     // CoverArtCache is fairly independent of everything else.
808     CoverArtCache::destroy();
809 
810     // PlayerManager depends on Engine, SoundManager, VinylControlManager, and Config
811     // The player manager has to be deleted before the library to ensure
812     // that all modified track metadata of loaded tracks is saved.
813     qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting PlayerManager";
814     delete m_pPlayerManager;
815 
816     // Destroy PlayerInfo explicitly to release the track
817     // pointers of tracks that were still loaded in decks
818     // or samplers when PlayerManager was destroyed!
819     PlayerInfo::destroy();
820 
821     // Delete the library after the view so there are no dangling pointers to
822     // the data models.
823     // Depends on RecordingManager and PlayerManager
824     qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting Library";
825     delete m_pLibrary;
826 
827     // RecordingManager depends on config, engine
828     qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting RecordingManager";
829     delete m_pRecordingManager;
830 
831 #ifdef __BROADCAST__
832     // BroadcastManager depends on config, engine
833     qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting BroadcastManager";
834     delete m_pBroadcastManager;
835 #endif
836 
837     // EngineMaster depends on Config and m_pEffectsManager.
838     qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting EngineMaster";
839     delete m_pEngine;
840 
841     qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting DlgPreferences";
842     delete m_pPrefDlg;
843 
844     // Must delete after EngineMaster and DlgPrefEq.
845     qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting EffectsManager";
846     delete m_pEffectsManager;
847 
848     delete m_pTouchShift;
849 
850     WaveformWidgetFactory::destroy();
851 
852     delete m_pGuiTick;
853     delete m_pVisualsManager;
854 
855     // Delete the track collections after all internal track pointers
856     // in other components have been released by deleting those components
857     // beforehand!
858     qDebug() << t.elapsed(false).debugMillisWithUnit() << "detaching all track collections";
859     delete m_pTrackCollectionManager;
860 
861     qDebug() << t.elapsed(false).debugMillisWithUnit() << "closing database connection(s)";
862     m_pDbConnectionPool->destroyThreadLocalConnection();
863     m_pDbConnectionPool.reset(); // should drop the last reference
864 
865     // HACK: Save config again. We saved it once before doing some dangerous
866     // stuff. We only really want to save it here, but the first one was just
867     // a precaution. The earlier one can be removed when stuff is more stable
868     // at exit.
869     m_pSettingsManager->save();
870 
871     // Check for leaked ControlObjects and give warnings.
872     {
873         const QList<QSharedPointer<ControlDoublePrivate>> leakedControls =
874                 ControlDoublePrivate::takeAllInstances();
875         if (!leakedControls.isEmpty()) {
876             qWarning()
877                     << "The following"
878                     << leakedControls.size()
879                     << "controls were leaked:";
880             for (auto pCDP : leakedControls) {
881                 ConfigKey key = pCDP->getKey();
882                 qWarning() << key.group << key.item << pCDP->getCreatorCO();
883                 // Deleting leaked objects helps to satisfy valgrind.
884                 // These delete calls could cause crashes if a destructor for a control
885                 // we thought was leaked is triggered after this one exits.
886                 // So, only delete so if developer mode is on.
887                 if (CmdlineArgs::Instance().getDeveloper()) {
888                     pCDP->deleteCreatorCO();
889                 }
890             }
891             DEBUG_ASSERT(!"Controls were leaked!");
892         }
893         // Finally drop all shared pointers by exiting this scope
894     }
895 
896     Sandbox::shutdown();
897 
898     qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting SettingsManager";
899     m_pSettingsManager.reset();
900 
901     delete m_pKeyboard;
902     delete m_pKbdConfig;
903     delete m_pKbdConfigEmpty;
904 
905     t.elapsed(true);
906     // Report the total time we have been running.
907     m_runtime_timer.elapsed(true);
908 
909     if (m_cmdLineArgs.getDeveloper()) {
910         StatsManager::destroy();
911     }
912 }
913 
initializeDatabase()914 bool MixxxMainWindow::initializeDatabase() {
915     kLogger.info() << "Connecting to database";
916     QSqlDatabase dbConnection = mixxx::DbConnectionPooled(m_pDbConnectionPool);
917     if (!dbConnection.isOpen()) {
918         QMessageBox::critical(nullptr,
919                 tr("Cannot open database"),
920                 tr("Unable to establish a database connection.\n"
921                    "Mixxx requires QT with SQLite support. Please read "
922                    "the Qt SQL driver documentation for information on how "
923                    "to build it.\n\n"
924                    "Click OK to exit."),
925                 QMessageBox::Ok);
926         return false;
927     }
928 
929     kLogger.info() << "Initializing or upgrading database schema";
930     return MixxxDb::initDatabaseSchema(dbConnection);
931 }
932 
initializeWindow()933 void MixxxMainWindow::initializeWindow() {
934     // be sure createMenuBar() is called first
935     DEBUG_ASSERT(m_pMenuBar);
936 
937     QPalette Pal(palette());
938     // safe default QMenuBar background
939     QColor MenuBarBackground(m_pMenuBar->palette().color(QPalette::Window));
940     Pal.setColor(QPalette::Window, QColor(0x202020));
941     setAutoFillBackground(true);
942     setPalette(Pal);
943     // restore default QMenuBar background
944     Pal.setColor(QPalette::Window, MenuBarBackground);
945     m_pMenuBar->setPalette(Pal);
946 
947     // Restore the current window state (position, maximized, etc)
948     restoreGeometry(QByteArray::fromBase64(m_pSettingsManager->settings()->getValueString(
949         ConfigKey("[MainWindow]", "geometry")).toUtf8()));
950     restoreState(QByteArray::fromBase64(m_pSettingsManager->settings()->getValueString(
951         ConfigKey("[MainWindow]", "state")).toUtf8()));
952 
953     setWindowIcon(QIcon(":/images/icons/mixxx.svg"));
954     slotUpdateWindowTitle(TrackPointer());
955 }
956 
initializeKeyboard()957 void MixxxMainWindow::initializeKeyboard() {
958     UserSettingsPointer pConfig = m_pSettingsManager->settings();
959     QString resourcePath = pConfig->getResourcePath();
960 
961     // Set the default value in settings file
962     if (pConfig->getValueString(ConfigKey("[Keyboard]", "Enabled")).length() == 0) {
963         pConfig->set(ConfigKey("[Keyboard]","Enabled"), ConfigValue(1));
964     }
965 
966     // Read keyboard configuration and set kdbConfig object in WWidget
967     // Check first in user's Mixxx directory
968     QString userKeyboard = QDir(pConfig->getSettingsPath()).filePath("Custom.kbd.cfg");
969 
970     // Empty keyboard configuration
971     m_pKbdConfigEmpty = new ConfigObject<ConfigValueKbd>(QString());
972 
973     if (QFile::exists(userKeyboard)) {
974         qDebug() << "Found and will use custom keyboard preset" << userKeyboard;
975         m_pKbdConfig = new ConfigObject<ConfigValueKbd>(userKeyboard);
976     } else {
977         // Default to the locale for the main input method (e.g. keyboard).
978         QLocale locale = inputLocale();
979 
980         // check if a default keyboard exists
981         QString defaultKeyboard = QString(resourcePath).append("keyboard/");
982         defaultKeyboard += locale.name();
983         defaultKeyboard += ".kbd.cfg";
984         qDebug() << "Found and will use default keyboard preset" << defaultKeyboard;
985 
986         if (!QFile::exists(defaultKeyboard)) {
987             qDebug() << defaultKeyboard << " not found, using en_US.kbd.cfg";
988             defaultKeyboard = QString(resourcePath).append("keyboard/").append("en_US.kbd.cfg");
989             if (!QFile::exists(defaultKeyboard)) {
990                 qDebug() << defaultKeyboard << " not found, starting without shortcuts";
991                 defaultKeyboard = "";
992             }
993         }
994         m_pKbdConfig = new ConfigObject<ConfigValueKbd>(defaultKeyboard);
995     }
996 
997     // TODO(XXX) leak pKbdConfig, KeyboardEventFilter owns it? Maybe roll all keyboard
998     // initialization into KeyboardEventFilter
999     // Workaround for today: KeyboardEventFilter calls delete
1000     bool keyboardShortcutsEnabled = pConfig->getValue<bool>(
1001             ConfigKey("[Keyboard]", "Enabled"));
1002     m_pKeyboard = new KeyboardEventFilter(keyboardShortcutsEnabled ? m_pKbdConfig : m_pKbdConfigEmpty);
1003 }
1004 
soundDeviceErrorDlg(const QString & title,const QString & text,bool * retryClicked)1005 QDialog::DialogCode MixxxMainWindow::soundDeviceErrorDlg(
1006         const QString &title, const QString &text, bool* retryClicked) {
1007     QMessageBox msgBox;
1008     msgBox.setIcon(QMessageBox::Warning);
1009     msgBox.setWindowTitle(title);
1010     msgBox.setText(text);
1011 
1012     QPushButton* retryButton =
1013             msgBox.addButton(tr("Retry"), QMessageBox::ActionRole);
1014     QPushButton* reconfigureButton =
1015             msgBox.addButton(tr("Reconfigure"), QMessageBox::ActionRole);
1016     QPushButton* wikiButton =
1017             msgBox.addButton(tr("Help"), QMessageBox::ActionRole);
1018     QPushButton* exitButton =
1019             msgBox.addButton(tr("Exit"), QMessageBox::ActionRole);
1020 
1021     while (true)
1022     {
1023         msgBox.exec();
1024 
1025         if (msgBox.clickedButton() == retryButton) {
1026             m_pSoundManager->clearAndQueryDevices();
1027             *retryClicked = true;
1028             return QDialog::Accepted;
1029         } else if (msgBox.clickedButton() == wikiButton) {
1030             QDesktopServices::openUrl(QUrl(MIXXX_WIKI_TROUBLESHOOTING_SOUND_URL));
1031             wikiButton->setEnabled(false);
1032         } else if (msgBox.clickedButton() == reconfigureButton) {
1033             msgBox.hide();
1034 
1035             m_pSoundManager->clearAndQueryDevices();
1036             // This way of opening the dialog allows us to use it synchronously
1037             m_pPrefDlg->setWindowModality(Qt::ApplicationModal);
1038             m_pPrefDlg->exec();
1039             if (m_pPrefDlg->result() == QDialog::Accepted) {
1040                 return QDialog::Accepted;
1041             }
1042 
1043             msgBox.show();
1044         } else if (msgBox.clickedButton() == exitButton) {
1045             // Will finally quit Mixxx
1046             return QDialog::Rejected;
1047         }
1048     }
1049 }
1050 
soundDeviceBusyDlg(bool * retryClicked)1051 QDialog::DialogCode MixxxMainWindow::soundDeviceBusyDlg(bool* retryClicked) {
1052     QString title(tr("Sound Device Busy"));
1053     QString text(
1054             "<html> <p>" %
1055             tr("Mixxx was unable to open all the configured sound devices.") +
1056             "</p> <p>" %
1057             m_pSoundManager->getErrorDeviceName() %
1058             " is used by another application or not plugged in."
1059             "</p><ul>"
1060                 "<li>" %
1061                     tr("<b>Retry</b> after closing the other application "
1062                     "or reconnecting a sound device") %
1063                 "</li>"
1064                 "<li>" %
1065                     tr("<b>Reconfigure</b> Mixxx's sound device settings.") %
1066                 "</li>"
1067                 "<li>" %
1068                     tr("Get <b>Help</b> from the Mixxx Wiki.") %
1069                 "</li>"
1070                 "<li>" %
1071                     tr("<b>Exit</b> Mixxx.") %
1072                 "</li>"
1073             "</ul></html>"
1074     );
1075     return soundDeviceErrorDlg(title, text, retryClicked);
1076 }
1077 
1078 
soundDeviceErrorMsgDlg(SoundDeviceError err,bool * retryClicked)1079 QDialog::DialogCode MixxxMainWindow::soundDeviceErrorMsgDlg(
1080         SoundDeviceError err, bool* retryClicked) {
1081     QString title(tr("Sound Device Error"));
1082     QString text(
1083             "<html> <p>" %
1084             tr("Mixxx was unable to open all the configured sound devices.") +
1085             "</p> <p>" %
1086             m_pSoundManager->getLastErrorMessage(err).replace("\n", "<br/>") %
1087             "</p><ul>"
1088                 "<li>" %
1089                     tr("<b>Retry</b> after fixing an issue") %
1090                 "</li>"
1091                 "<li>" %
1092                     tr("<b>Reconfigure</b> Mixxx's sound device settings.") %
1093                 "</li>"
1094                 "<li>" %
1095                     tr("Get <b>Help</b> from the Mixxx Wiki.") %
1096                 "</li>"
1097                 "<li>" %
1098                     tr("<b>Exit</b> Mixxx.") %
1099                 "</li>"
1100             "</ul></html>"
1101     );
1102     return soundDeviceErrorDlg(title, text, retryClicked);
1103 }
1104 
noOutputDlg(bool * continueClicked)1105 QDialog::DialogCode MixxxMainWindow::noOutputDlg(bool* continueClicked) {
1106     QMessageBox msgBox;
1107     msgBox.setIcon(QMessageBox::Warning);
1108     msgBox.setWindowTitle(tr("No Output Devices"));
1109     msgBox.setText(
1110             "<html>" + tr("Mixxx was configured without any output sound devices. "
1111             "Audio processing will be disabled without a configured output device.") +
1112             "<ul>"
1113                 "<li>" +
1114                     tr("<b>Continue</b> without any outputs.") +
1115                 "</li>"
1116                 "<li>" +
1117                     tr("<b>Reconfigure</b> Mixxx's sound device settings.") +
1118                 "</li>"
1119                 "<li>" +
1120                     tr("<b>Exit</b> Mixxx.") +
1121                 "</li>"
1122             "</ul></html>"
1123     );
1124 
1125     QPushButton* continueButton =
1126             msgBox.addButton(tr("Continue"), QMessageBox::ActionRole);
1127     QPushButton* reconfigureButton =
1128             msgBox.addButton(tr("Reconfigure"), QMessageBox::ActionRole);
1129     QPushButton* exitButton =
1130             msgBox.addButton(tr("Exit"), QMessageBox::ActionRole);
1131 
1132     while (true)
1133     {
1134         msgBox.exec();
1135 
1136         if (msgBox.clickedButton() == continueButton) {
1137             *continueClicked = true;
1138             return QDialog::Accepted;
1139         } else if (msgBox.clickedButton() == reconfigureButton) {
1140             msgBox.hide();
1141 
1142             // This way of opening the dialog allows us to use it synchronously
1143             m_pPrefDlg->setWindowModality(Qt::ApplicationModal);
1144             m_pPrefDlg->exec();
1145             if (m_pPrefDlg->result() == QDialog::Accepted) {
1146                 return QDialog::Accepted;
1147             }
1148 
1149             msgBox.show();
1150 
1151         } else if (msgBox.clickedButton() == exitButton) {
1152             // Will finally quit Mixxx
1153             return QDialog::Rejected;
1154         }
1155     }
1156 }
1157 
slotUpdateWindowTitle(TrackPointer pTrack)1158 void MixxxMainWindow::slotUpdateWindowTitle(TrackPointer pTrack) {
1159     QString appTitle = VersionStore::applicationName();
1160 
1161     // If we have a track, use getInfo() to format a summary string and prepend
1162     // it to the title.
1163     // TODO(rryan): Does this violate Mac App Store policies?
1164     if (pTrack) {
1165         QString trackInfo = pTrack->getInfo();
1166         if (!trackInfo.isEmpty()) {
1167             appTitle = QString("%1 | %2").arg(trackInfo, appTitle);
1168         }
1169     }
1170     this->setWindowTitle(appTitle);
1171 }
1172 
createMenuBar()1173 void MixxxMainWindow::createMenuBar() {
1174     ScopedTimer t("MixxxMainWindow::createMenuBar");
1175     DEBUG_ASSERT(m_pKbdConfig != nullptr);
1176     m_pMenuBar = make_parented<WMainMenuBar>(this, m_pSettingsManager->settings(), m_pKbdConfig);
1177     if (m_pWidgetParent) {
1178         m_pMenuBar->setStyleSheet(m_pWidgetParent->styleSheet());
1179     }
1180     setMenuBar(m_pMenuBar);
1181 }
1182 
connectMenuBar()1183 void MixxxMainWindow::connectMenuBar() {
1184     // This function might be invoked multiple times on startup
1185     // so all connections must be unique!
1186 
1187     ScopedTimer t("MixxxMainWindow::connectMenuBar");
1188     connect(this,
1189             &MixxxMainWindow::skinLoaded,
1190             m_pMenuBar,
1191             &WMainMenuBar::onNewSkinLoaded,
1192             Qt::UniqueConnection);
1193 
1194     // Misc
1195     connect(m_pMenuBar,
1196             &WMainMenuBar::quit,
1197             this,
1198             &MixxxMainWindow::close,
1199             Qt::UniqueConnection);
1200     connect(m_pMenuBar,
1201             &WMainMenuBar::showPreferences,
1202             this,
1203             &MixxxMainWindow::slotOptionsPreferences,
1204             Qt::UniqueConnection);
1205     connect(m_pMenuBar,
1206             &WMainMenuBar::loadTrackToDeck,
1207             this,
1208             &MixxxMainWindow::slotFileLoadSongPlayer,
1209             Qt::UniqueConnection);
1210 
1211     // Fullscreen
1212     connect(m_pMenuBar,
1213             &WMainMenuBar::toggleFullScreen,
1214             this,
1215             &MixxxMainWindow::slotViewFullScreen,
1216             Qt::UniqueConnection);
1217     connect(this,
1218             &MixxxMainWindow::fullScreenChanged,
1219             m_pMenuBar,
1220             &WMainMenuBar::onFullScreenStateChange,
1221             Qt::UniqueConnection);
1222 
1223     // Keyboard shortcuts
1224     connect(m_pMenuBar,
1225             &WMainMenuBar::toggleKeyboardShortcuts,
1226             this,
1227             &MixxxMainWindow::slotOptionsKeyboard,
1228             Qt::UniqueConnection);
1229 
1230     // Help
1231     connect(m_pMenuBar,
1232             &WMainMenuBar::showAbout,
1233             this,
1234             &MixxxMainWindow::slotHelpAbout,
1235             Qt::UniqueConnection);
1236 
1237     // Developer
1238     connect(m_pMenuBar,
1239             &WMainMenuBar::reloadSkin,
1240             this,
1241             &MixxxMainWindow::rebootMixxxView,
1242             Qt::UniqueConnection);
1243     connect(m_pMenuBar,
1244             &WMainMenuBar::toggleDeveloperTools,
1245             this,
1246             &MixxxMainWindow::slotDeveloperTools,
1247             Qt::UniqueConnection);
1248 
1249     if (m_pRecordingManager) {
1250         connect(m_pRecordingManager,
1251                 &RecordingManager::isRecording,
1252                 m_pMenuBar,
1253                 &WMainMenuBar::onRecordingStateChange,
1254                 Qt::UniqueConnection);
1255         connect(m_pMenuBar,
1256                 &WMainMenuBar::toggleRecording,
1257                 m_pRecordingManager,
1258                 &RecordingManager::slotSetRecording,
1259                 Qt::UniqueConnection);
1260         m_pMenuBar->onRecordingStateChange(m_pRecordingManager->isRecordingActive());
1261     }
1262 
1263 #ifdef __BROADCAST__
1264     if (m_pBroadcastManager) {
1265         connect(m_pBroadcastManager,
1266                 &BroadcastManager::broadcastEnabled,
1267                 m_pMenuBar,
1268                 &WMainMenuBar::onBroadcastingStateChange,
1269                 Qt::UniqueConnection);
1270         connect(m_pMenuBar,
1271                 &WMainMenuBar::toggleBroadcasting,
1272                 m_pBroadcastManager,
1273                 &BroadcastManager::setEnabled,
1274                 Qt::UniqueConnection);
1275         m_pMenuBar->onBroadcastingStateChange(m_pBroadcastManager->isEnabled());
1276     }
1277 #endif
1278 
1279 #ifdef __VINYLCONTROL__
1280     if (m_pVCManager) {
1281         connect(m_pMenuBar,
1282                 &WMainMenuBar::toggleVinylControl,
1283                 m_pVCManager,
1284                 &VinylControlManager::toggleVinylControl,
1285                 Qt::UniqueConnection);
1286         connect(m_pVCManager,
1287                 &VinylControlManager::vinylControlDeckEnabled,
1288                 m_pMenuBar,
1289                 &WMainMenuBar::onVinylControlDeckEnabledStateChange,
1290                 Qt::UniqueConnection);
1291     }
1292 #endif
1293 
1294     if (m_pPlayerManager) {
1295         connect(m_pPlayerManager,
1296                 &PlayerManager::numberOfDecksChanged,
1297                 m_pMenuBar,
1298                 &WMainMenuBar::onNumberOfDecksChanged,
1299                 Qt::UniqueConnection);
1300         m_pMenuBar->onNumberOfDecksChanged(m_pPlayerManager->numberOfDecks());
1301     }
1302 
1303     if (m_pTrackCollectionManager) {
1304         connect(m_pMenuBar,
1305                 &WMainMenuBar::rescanLibrary,
1306                 m_pTrackCollectionManager,
1307                 &TrackCollectionManager::startLibraryScan,
1308                 Qt::UniqueConnection);
1309         connect(m_pTrackCollectionManager,
1310                 &TrackCollectionManager::libraryScanStarted,
1311                 m_pMenuBar,
1312                 &WMainMenuBar::onLibraryScanStarted,
1313                 Qt::UniqueConnection);
1314         connect(m_pTrackCollectionManager,
1315                 &TrackCollectionManager::libraryScanFinished,
1316                 m_pMenuBar,
1317                 &WMainMenuBar::onLibraryScanFinished,
1318                 Qt::UniqueConnection);
1319     }
1320 
1321     if (m_pLibrary) {
1322         connect(m_pMenuBar,
1323                 &WMainMenuBar::createCrate,
1324                 m_pLibrary,
1325                 &Library::slotCreateCrate,
1326                 Qt::UniqueConnection);
1327         connect(m_pMenuBar,
1328                 &WMainMenuBar::createPlaylist,
1329                 m_pLibrary,
1330                 &Library::slotCreatePlaylist,
1331                 Qt::UniqueConnection);
1332     }
1333 }
1334 
slotFileLoadSongPlayer(int deck)1335 void MixxxMainWindow::slotFileLoadSongPlayer(int deck) {
1336     QString group = m_pPlayerManager->groupForDeck(deck-1);
1337 
1338     QString loadTrackText = tr("Load track to Deck %1").arg(QString::number(deck));
1339     QString deckWarningMessage = tr("Deck %1 is currently playing a track.")
1340             .arg(QString::number(deck));
1341     QString areYouSure = tr("Are you sure you want to load a new track?");
1342 
1343     if (ControlObject::get(ConfigKey(group, "play")) > 0.0) {
1344         int ret = QMessageBox::warning(this,
1345                 VersionStore::applicationName(),
1346                 deckWarningMessage + "\n" + areYouSure,
1347                 QMessageBox::Yes | QMessageBox::No,
1348                 QMessageBox::No);
1349 
1350         if (ret != QMessageBox::Yes) {
1351             return;
1352         }
1353     }
1354 
1355     UserSettingsPointer pConfig = m_pSettingsManager->settings();
1356     QString trackPath =
1357         QFileDialog::getOpenFileName(
1358             this,
1359             loadTrackText,
1360             pConfig->getValueString(PREF_LEGACY_LIBRARY_DIR),
1361             QString("Audio (%1)")
1362                 .arg(SoundSourceProxy::getSupportedFileNamePatterns().join(" ")));
1363 
1364 
1365     if (!trackPath.isNull()) {
1366         // The user has picked a file via a file dialog. This means the system
1367         // sandboxer (if we are sandboxed) has granted us permission to this
1368         // folder. Create a security bookmark while we have permission so that
1369         // we can access the folder on future runs. We need to canonicalize the
1370         // path so we first wrap the directory string with a QDir.
1371         QFileInfo trackInfo(trackPath);
1372         Sandbox::createSecurityToken(trackInfo);
1373 
1374         m_pPlayerManager->slotLoadToDeck(trackPath, deck);
1375     }
1376 }
1377 
1378 
slotOptionsKeyboard(bool toggle)1379 void MixxxMainWindow::slotOptionsKeyboard(bool toggle) {
1380     UserSettingsPointer pConfig = m_pSettingsManager->settings();
1381     if (toggle) {
1382         //qDebug() << "Enable keyboard shortcuts/mappings";
1383         m_pKeyboard->setKeyboardConfig(m_pKbdConfig);
1384         pConfig->set(ConfigKey("[Keyboard]","Enabled"), ConfigValue(1));
1385     } else {
1386         //qDebug() << "Disable keyboard shortcuts/mappings";
1387         m_pKeyboard->setKeyboardConfig(m_pKbdConfigEmpty);
1388         pConfig->set(ConfigKey("[Keyboard]","Enabled"), ConfigValue(0));
1389     }
1390 }
1391 
slotDeveloperTools(bool visible)1392 void MixxxMainWindow::slotDeveloperTools(bool visible) {
1393     if (visible) {
1394         if (m_pDeveloperToolsDlg == nullptr) {
1395             UserSettingsPointer pConfig = m_pSettingsManager->settings();
1396             m_pDeveloperToolsDlg = new DlgDeveloperTools(this, pConfig);
1397             connect(m_pDeveloperToolsDlg,
1398                     &DlgDeveloperTools::destroyed,
1399                     this,
1400                     &MixxxMainWindow::slotDeveloperToolsClosed);
1401             connect(this,
1402                     &MixxxMainWindow::closeDeveloperToolsDlgChecked,
1403                     m_pDeveloperToolsDlg,
1404                     &DlgDeveloperTools::done);
1405             connect(m_pDeveloperToolsDlg,
1406                     &DlgDeveloperTools::destroyed,
1407                     m_pMenuBar,
1408                     &WMainMenuBar::onDeveloperToolsHidden);
1409         }
1410         m_pMenuBar->onDeveloperToolsShown();
1411         m_pDeveloperToolsDlg->show();
1412         m_pDeveloperToolsDlg->activateWindow();
1413     } else {
1414         emit closeDeveloperToolsDlgChecked(0);
1415     }
1416 }
1417 
slotDeveloperToolsClosed()1418 void MixxxMainWindow::slotDeveloperToolsClosed() {
1419     m_pDeveloperToolsDlg = nullptr;
1420 }
1421 
slotViewFullScreen(bool toggle)1422 void MixxxMainWindow::slotViewFullScreen(bool toggle) {
1423     if (isFullScreen() == toggle) {
1424         return;
1425     }
1426 
1427     if (toggle) {
1428         showFullScreen();
1429 #ifdef __LINUX__
1430         // Fix for "No menu bar with ubuntu unity in full screen mode" Bug
1431         // #885890 and Bug #1076789. Before touching anything here, please read
1432         // those bugs.
1433         createMenuBar();
1434         connectMenuBar();
1435         if (m_pMenuBar->isNativeMenuBar()) {
1436             m_pMenuBar->setNativeMenuBar(false);
1437         }
1438 #endif
1439     } else {
1440 #ifdef __LINUX__
1441         createMenuBar();
1442         connectMenuBar();
1443 #endif
1444         showNormal();
1445     }
1446     emit fullScreenChanged(toggle);
1447 }
1448 
slotOptionsPreferences()1449 void MixxxMainWindow::slotOptionsPreferences() {
1450     m_pPrefDlg->show();
1451     m_pPrefDlg->raise();
1452     m_pPrefDlg->activateWindow();
1453 }
1454 
slotNoVinylControlInputConfigured()1455 void MixxxMainWindow::slotNoVinylControlInputConfigured() {
1456     QMessageBox::StandardButton btn = QMessageBox::warning(
1457             this,
1458             VersionStore::applicationName(),
1459             tr("There is no input device selected for this vinyl control.\n"
1460                "Please select an input device in the sound hardware preferences first."),
1461             QMessageBox::Ok | QMessageBox::Cancel,
1462             QMessageBox::Cancel);
1463     if (btn == QMessageBox::Ok) {
1464         m_pPrefDlg->show();
1465         m_pPrefDlg->showSoundHardwarePage();
1466     }
1467 }
1468 
slotNoDeckPassthroughInputConfigured()1469 void MixxxMainWindow::slotNoDeckPassthroughInputConfigured() {
1470     QMessageBox::StandardButton btn = QMessageBox::warning(
1471             this,
1472             VersionStore::applicationName(),
1473             tr("There is no input device selected for this passthrough control.\n"
1474                "Please select an input device in the sound hardware preferences first."),
1475             QMessageBox::Ok | QMessageBox::Cancel,
1476             QMessageBox::Cancel);
1477     if (btn == QMessageBox::Ok) {
1478         m_pPrefDlg->show();
1479         m_pPrefDlg->showSoundHardwarePage();
1480     }
1481 }
1482 
slotNoMicrophoneInputConfigured()1483 void MixxxMainWindow::slotNoMicrophoneInputConfigured() {
1484     QMessageBox::StandardButton btn = QMessageBox::question(
1485             this,
1486             VersionStore::applicationName(),
1487             tr("There is no input device selected for this microphone.\n"
1488                "Do you want to select an input device?"),
1489             QMessageBox::Ok | QMessageBox::Cancel,
1490             QMessageBox::Cancel);
1491     if (btn == QMessageBox::Ok) {
1492         m_pPrefDlg->show();
1493         m_pPrefDlg->showSoundHardwarePage();
1494     }
1495 }
1496 
slotNoAuxiliaryInputConfigured()1497 void MixxxMainWindow::slotNoAuxiliaryInputConfigured() {
1498     QMessageBox::StandardButton btn = QMessageBox::question(
1499             this,
1500             VersionStore::applicationName(),
1501             tr("There is no input device selected for this auxiliary.\n"
1502                "Do you want to select an input device?"),
1503             QMessageBox::Ok | QMessageBox::Cancel,
1504             QMessageBox::Cancel);
1505     if (btn == QMessageBox::Ok) {
1506         m_pPrefDlg->show();
1507         m_pPrefDlg->showSoundHardwarePage();
1508     }
1509 }
1510 
slotChangedPlayingDeck(int deck)1511 void MixxxMainWindow::slotChangedPlayingDeck(int deck) {
1512     if (m_inhibitScreensaver == mixxx::ScreenSaverPreference::PREVENT_ON_PLAY) {
1513         if (deck==-1) {
1514             // If no deck is playing, allow the screensaver to run.
1515             mixxx::ScreenSaverHelper::uninhibit();
1516         } else {
1517             mixxx::ScreenSaverHelper::inhibit();
1518         }
1519     }
1520 }
1521 
slotHelpAbout()1522 void MixxxMainWindow::slotHelpAbout() {
1523     DlgAbout* about = new DlgAbout(this);
1524     about->show();
1525 }
1526 
setToolTipsCfg(mixxx::TooltipsPreference tt)1527 void MixxxMainWindow::setToolTipsCfg(mixxx::TooltipsPreference tt) {
1528     UserSettingsPointer pConfig = m_pSettingsManager->settings();
1529     pConfig->set(ConfigKey("[Controls]","Tooltips"),
1530                  ConfigValue(static_cast<int>(tt)));
1531     m_toolTipsCfg = tt;
1532 }
1533 
rebootMixxxView()1534 void MixxxMainWindow::rebootMixxxView() {
1535     qDebug() << "Now in rebootMixxxView...";
1536 
1537     // safe geometry for later restoration
1538     const QRect initGeometry = geometry();
1539 
1540     // We need to tell the menu bar that we are about to delete the old skin and
1541     // create a new one. It holds "visibility" controls (e.g. "Show Samplers")
1542     // that need to be deleted -- otherwise we can't tell what features the skin
1543     // supports since the controls from the previous skin will be left over.
1544     m_pMenuBar->onNewSkinAboutToLoad();
1545 
1546     if (m_pWidgetParent) {
1547         m_pWidgetParent->hide();
1548         WaveformWidgetFactory::instance()->destroyWidgets();
1549         delete m_pWidgetParent;
1550         m_pWidgetParent = nullptr;
1551     }
1552 
1553     // Workaround for changing skins while fullscreen, just go out of fullscreen
1554     // mode. If you change skins while in fullscreen (on Linux, at least) the
1555     // window returns to 0,0 but and the backdrop disappears so it looks as if
1556     // it is not fullscreen, but acts as if it is.
1557     bool wasFullScreen = isFullScreen();
1558     slotViewFullScreen(false);
1559 
1560     // Load skin to a QWidget that we set as the central widget. Assignment
1561     // intentional in next line.
1562     m_pWidgetParent = m_pSkinLoader->loadConfiguredSkin(this,
1563             &m_skinCreatedControls,
1564             m_pKeyboard,
1565             m_pPlayerManager,
1566             m_pControllerManager,
1567             m_pLibrary,
1568             m_pVCManager,
1569             m_pEffectsManager,
1570             m_pRecordingManager);
1571     if (!m_pWidgetParent) {
1572         QMessageBox::critical(this,
1573                               tr("Error in skin file"),
1574                               tr("The selected skin cannot be loaded."));
1575         // m_pWidgetParent is NULL, we can't continue.
1576         return;
1577     }
1578     m_pMenuBar->setStyleSheet(m_pWidgetParent->styleSheet());
1579 
1580     setCentralWidget(m_pWidgetParent);
1581 #ifdef __LINUX__
1582     // don't adjustSize() on Linux as this wouldn't use the entire available area
1583     // to paint the new skin with X11
1584     // https://bugs.launchpad.net/mixxx/+bug/1773587
1585 #else
1586     adjustSize();
1587 #endif
1588 
1589     if (wasFullScreen) {
1590         slotViewFullScreen(true);
1591     } else {
1592         // Programatic placement at this point is very problematic.
1593         // The screen() method returns stale data (primary screen)
1594         // until the user interacts with mixxx again. Keyboard shortcuts
1595         // do not count, moving window, opening menu etc does
1596         // Therefore the placement logic was removed by a simple geometry restore.
1597         // If the minimum size of the new skin is larger then the restored
1598         // geometry, the window will be enlarged right & bottom which is
1599         // safe as the menu is still reachable.
1600         setGeometry(initGeometry);
1601     }
1602 
1603     qDebug() << "rebootMixxxView DONE";
1604     emit skinLoaded();
1605 }
1606 
eventFilter(QObject * obj,QEvent * event)1607 bool MixxxMainWindow::eventFilter(QObject* obj, QEvent* event) {
1608     if (event->type() == QEvent::ToolTip) {
1609         // return true for no tool tips
1610         switch (m_toolTipsCfg) {
1611             case mixxx::TooltipsPreference::TOOLTIPS_ONLY_IN_LIBRARY:
1612                 if (dynamic_cast<WBaseWidget*>(obj) != nullptr) {
1613                     return true;
1614                 }
1615                 break;
1616             case mixxx::TooltipsPreference::TOOLTIPS_ON:
1617                 break;
1618             case mixxx::TooltipsPreference::TOOLTIPS_OFF:
1619                 return true;
1620             default:
1621                 DEBUG_ASSERT(!"m_toolTipsCfg value unknown");
1622                 return true;
1623         }
1624     }
1625     // standard event processing
1626     return QMainWindow::eventFilter(obj, event);
1627 }
1628 
closeEvent(QCloseEvent * event)1629 void MixxxMainWindow::closeEvent(QCloseEvent *event) {
1630     // WARNING: We can receive a CloseEvent while only partially
1631     // initialized. This is because we call QApplication::processEvents to
1632     // render LaunchImage progress in the constructor.
1633     if (!confirmExit()) {
1634         event->ignore();
1635         return;
1636     }
1637     QMainWindow::closeEvent(event);
1638 }
1639 
1640 
checkDirectRendering()1641 void MixxxMainWindow::checkDirectRendering() {
1642     // IF
1643     //  * A waveform viewer exists
1644     // AND
1645     //  * The waveform viewer is an OpenGL waveform viewer
1646     // AND
1647     //  * The waveform viewer does not have direct rendering enabled.
1648     // THEN
1649     //  * Warn user
1650 
1651     WaveformWidgetFactory* factory = WaveformWidgetFactory::instance();
1652     if (!factory) {
1653         return;
1654     }
1655 
1656     UserSettingsPointer pConfig = m_pSettingsManager->settings();
1657 
1658     if (!factory->isOpenGlAvailable() && !factory->isOpenGlesAvailable() &&
1659         pConfig->getValueString(ConfigKey("[Direct Rendering]", "Warned")) != QString("yes")) {
1660         QMessageBox::warning(nullptr,
1661                 tr("OpenGL Direct Rendering"),
1662                 tr("Direct rendering is not enabled on your machine.<br><br>"
1663                    "This means that the waveform displays will be very<br>"
1664                    "<b>slow and may tax your CPU heavily</b>. Either update "
1665                    "your<br>"
1666                    "configuration to enable direct rendering, or disable<br>"
1667                    "the waveform displays in the Mixxx preferences by "
1668                    "selecting<br>"
1669                    "\"Empty\" as the waveform display in the 'Interface' "
1670                    "section."));
1671         pConfig->set(ConfigKey("[Direct Rendering]", "Warned"), QString("yes"));
1672     }
1673 }
1674 
confirmExit()1675 bool MixxxMainWindow::confirmExit() {
1676     bool playing(false);
1677     bool playingSampler(false);
1678     unsigned int deckCount = m_pPlayerManager->numDecks();
1679     unsigned int samplerCount = m_pPlayerManager->numSamplers();
1680     for (unsigned int i = 0; i < deckCount; ++i) {
1681         if (ControlObject::toBool(
1682                     ConfigKey(PlayerManager::groupForDeck(i), "play"))) {
1683             playing = true;
1684             break;
1685         }
1686     }
1687     for (unsigned int i = 0; i < samplerCount; ++i) {
1688         if (ControlObject::toBool(
1689                     ConfigKey(PlayerManager::groupForSampler(i), "play"))) {
1690             playingSampler = true;
1691             break;
1692         }
1693     }
1694     if (playing) {
1695         QMessageBox::StandardButton btn = QMessageBox::question(this,
1696             tr("Confirm Exit"),
1697             tr("A deck is currently playing. Exit Mixxx?"),
1698             QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
1699         if (btn == QMessageBox::No) {
1700             return false;
1701         }
1702     } else if (playingSampler) {
1703         QMessageBox::StandardButton btn = QMessageBox::question(this,
1704             tr("Confirm Exit"),
1705             tr("A sampler is currently playing. Exit Mixxx?"),
1706             QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
1707         if (btn == QMessageBox::No) {
1708             return false;
1709         }
1710     }
1711     if (m_pPrefDlg && m_pPrefDlg->isVisible()) {
1712         QMessageBox::StandardButton btn = QMessageBox::question(
1713             this, tr("Confirm Exit"),
1714             tr("The preferences window is still open.") + "<br>" +
1715             tr("Discard any changes and exit Mixxx?"),
1716             QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
1717         if (btn == QMessageBox::No) {
1718             return false;
1719         }
1720         else {
1721             m_pPrefDlg->close();
1722         }
1723     }
1724 
1725     return true;
1726 }
1727 
setInhibitScreensaver(mixxx::ScreenSaverPreference newInhibit)1728 void MixxxMainWindow::setInhibitScreensaver(mixxx::ScreenSaverPreference newInhibit)
1729 {
1730     UserSettingsPointer pConfig = m_pSettingsManager->settings();
1731 
1732     if (m_inhibitScreensaver != mixxx::ScreenSaverPreference::PREVENT_OFF) {
1733         mixxx::ScreenSaverHelper::uninhibit();
1734     }
1735 
1736     if (newInhibit == mixxx::ScreenSaverPreference::PREVENT_ON) {
1737         mixxx::ScreenSaverHelper::inhibit();
1738     } else if (newInhibit == mixxx::ScreenSaverPreference::PREVENT_ON_PLAY
1739             && PlayerInfo::instance().getCurrentPlayingDeck()!=-1) {
1740         mixxx::ScreenSaverHelper::inhibit();
1741     }
1742     int inhibit_int = static_cast<int>(newInhibit);
1743     pConfig->setValue<int>(ConfigKey("[Config]","InhibitScreensaver"), inhibit_int);
1744     m_inhibitScreensaver = newInhibit;
1745 }
1746 
getInhibitScreensaver()1747 mixxx::ScreenSaverPreference MixxxMainWindow::getInhibitScreensaver()
1748 {
1749     return m_inhibitScreensaver;
1750 }
1751 
launchProgress(int progress)1752 void MixxxMainWindow::launchProgress(int progress) {
1753     if (m_pLaunchImage) {
1754         m_pLaunchImage->progress(progress);
1755     }
1756     qApp->processEvents();
1757 }
1758