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