#include "mixxxmainwindow.h" #include #include #include #include #include #include #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include #endif #if defined(__LINUX__) && !defined(__ANDROID__) #include #include #endif #ifdef MIXXX_USE_QOPENGL #include #include "widget/tooltipqopengl.h" #include "widget/winitialglwidget.h" #endif #include "controllers/keyboard/keyboardeventfilter.h" #include "coreservices.h" #include "defs_urls.h" #include "dialog/dlgabout.h" #include "dialog/dlgdevelopertools.h" #include "dialog/dlgkeywheel.h" #include "moc_mixxxmainwindow.cpp" #include "preferences/dialog/dlgpreferences.h" #ifdef __BROADCAST__ #include "broadcast/broadcastmanager.h" #endif #include "control/controlindicatortimer.h" #include "library/library.h" #include "library/library_decl.h" #include "library/library_prefs.h" #ifdef __ENGINEPRIME__ #include "library/export/libraryexporter.h" #endif #include "library/library_prefs.h" #include "library/trackcollectionmanager.h" #include "mixer/playerinfo.h" #include "mixer/playermanager.h" #include "recording/recordingmanager.h" #include "skin/legacy/launchimage.h" #include "skin/skinloader.h" #include "soundio/soundmanager.h" #include "sources/soundsourceproxy.h" #include "track/track.h" #include "util/debug.h" #include "util/desktophelper.h" #include "util/menubarhelper.h" #include "util/sandbox.h" #include "util/scopedoverridecursor.h" #include "util/timer.h" #include "util/versionstore.h" #include "waveform/guitick.h" #include "waveform/sharedglcontext.h" #include "waveform/visualsmanager.h" #include "waveform/waveformwidgetfactory.h" #include "widget/wglwidget.h" #include "widget/wmainmenubar.h" #ifdef __VINYLCONTROL__ #include "vinylcontrol/vinylcontrolmanager.h" #endif namespace { const ConfigKey kHideMenuBarConfigKey = ConfigKey("[Config]", "hide_menubar"); const ConfigKey kMenuBarHintConfigKey = ConfigKey("[Config]", "show_menubar_hint"); } // namespace MixxxMainWindow::MixxxMainWindow(std::shared_ptr pCoreServices) : m_pCoreServices(pCoreServices), m_pCentralWidget(nullptr), m_pLaunchImage(nullptr), #ifndef __APPLE__ m_prevState(Qt::WindowNoState), #endif m_noVinylInputDialog(nullptr), m_noPassthroughInputDialog(nullptr), m_noMicInputDialog(nullptr), m_noAuxInputDialog(nullptr), m_pGuiTick(nullptr), #ifdef __LINUX__ m_supportsGlobalMenuBar(mixxx::desktopSupportsGlobalMenuBar()), #endif m_inRebootMixxxView(false), m_pDeveloperToolsDlg(nullptr), m_pPrefDlg(nullptr), m_toolTipsCfg(mixxx::preferences::Tooltips::On) { DEBUG_ASSERT(pCoreServices); // These depend on the settings #ifdef __LINUX__ // If the desktop features a global menubar and we'll go fullscreen during // startup, set Qt::AA_DontUseNativeMenuBar so the menubar is placed in the // window like it's done in slotViewFullScreen(). On other desktops this // attribute has no effect. This is a safe alternative to setNativeMenuBar() // which can cause a crash when using menu shortcuts like Alt+F after resetting // the menubar. See https://github.com/mixxxdj/mixxx/issues/11320 if (m_supportsGlobalMenuBar) { bool fullscreenPref = m_pCoreServices->getSettings()->getValue( ConfigKey("[Config]", "StartInFullscreen")); QApplication::setAttribute( Qt::AA_DontUseNativeMenuBar, CmdlineArgs::Instance().getStartInFullscreen() || fullscreenPref); } #endif // __LINUX__ connect(m_pCoreServices.get(), &mixxx::CoreServices::libraryScanSummary, this, &MixxxMainWindow::slotLibraryScanSummaryDlg); createMenuBar(); m_pMenuBar->hide(); initializeWindow(); // Show launch image immediately so the user knows Mixxx is starting m_pSkinLoader = std::make_unique(m_pCoreServices->getSettings()); m_pLaunchImage = m_pSkinLoader->loadLaunchImage(this); m_pCentralWidget = (QWidget*)m_pLaunchImage; setCentralWidget(m_pCentralWidget); show(); m_pGuiTick = new GuiTick(); m_pVisualsManager = new VisualsManager(); } #ifdef MIXXX_USE_QOPENGL void MixxxMainWindow::initializeQOpenGL() { #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // Qt 6 will nno longer crash if no GL is available and // QGLFormat::hasOpenGL() has been removed. if (!CmdlineArgs::Instance().getSafeMode() && QGLFormat::hasOpenGL()) { #else // With EGLFS there is always exactly one native window and one EGL window surface // OpenGL windows cannot be embedded into our QWidgets main window we already have. // https://doc.qt.io/qt-6/embedded-linux.html bool isEglfs = QGuiApplication::platformName() == "eglfs"; if (!CmdlineArgs::Instance().getSafeMode() && !isEglfs) { #endif QOpenGLContext context; context.setFormat(WaveformWidgetFactory::getSurfaceFormat(m_pCoreServices->getSettings())); if (context.create()) { std::pair version = context.format().version(); qDebug().noquote() << "QOpenGLContext created:" << QGuiApplication::platformName() << context.format().renderableType() << QString("V%1.%2").arg(QString::number(version.first), QString::number(version.second)) << context.format().profile(); // This widget and its QOpenGLWindow will be used to query QOpenGL // information (version, driver, etc) in WaveformWidgetFactory. // The "SharedGLContext" terminology here doesn't really apply, // but allows us to take advantage of the existing classes. auto pWidget = make_parented(this); pWidget->setGeometry(QRect(0, 0, 3, 3)); SharedGLContext::setWidget(pWidget); // When the widget's QOpenGLWindow has been initialized, we continue // with the actual initialization connect(pWidget, &WInitialGLWidget::onInitialized, this, &MixxxMainWindow::initialize); pWidget->show(); return; } qDebug() << "QOpenGLContext::create() failed"; } qInfo() << "Initializing without OpenGL"; initialize(); } #endif void MixxxMainWindow::initialize() { m_pCoreServices->getControlIndicatorTimer()->setLegacyVsyncEnabled(true); UserSettingsPointer pConfig = m_pCoreServices->getSettings(); // Set the visibility of tooltips, default "1" = ON m_toolTipsCfg = pConfig->getValue( ConfigKey("[Controls]", "Tooltips"), mixxx::preferences::Tooltips::On); #ifdef MIXXX_USE_QOPENGL ToolTipQOpenGL::singleton().setActive( m_toolTipsCfg == mixxx::preferences::Tooltips::On); #endif #ifdef __ENGINEPRIME__ // Initialise library exporter // This has to be done before switching to fullscreen m_pLibraryExporter = m_pCoreServices->getLibrary()->makeLibraryExporter(this); connect(m_pCoreServices->getLibrary().get(), &Library::exportLibrary, m_pLibraryExporter.get(), &mixxx::LibraryExporter::slotRequestExport); connect(m_pCoreServices->getLibrary().get(), &Library::exportCrate, m_pLibraryExporter.get(), &mixxx::LibraryExporter::slotRequestExportWithInitialCrate); connect(m_pCoreServices->getLibrary().get(), &Library::exportPlaylist, m_pLibraryExporter.get(), &mixxx::LibraryExporter::slotRequestExportWithInitialPlaylist); #endif // Turn on fullscreen mode // if we were told to start in fullscreen mode on the command-line // or if the user chose to always start in fullscreen mode. // The Fullscreen menu item is refreshed in connectMenuBar() bool fullscreenPref = m_pCoreServices->getSettings()->getValue( ConfigKey("[Config]", "StartInFullscreen")); if ((CmdlineArgs::Instance().getStartInFullscreen() || fullscreenPref) && // could be we're fullscreen already after setGeomtery(previousGeometry) !isFullScreen()) { showFullScreen(); } initializationProgressUpdate(65, tr("skin")); // Install an event filter to catch certain QT events, such as tooltips. // This allows us to turn off tooltips. installEventFilter(m_pCoreServices->getKeyboardEventFilter().get()); auto pPlayerManager = m_pCoreServices->getPlayerManager(); DEBUG_ASSERT(pPlayerManager); const QStringList visualGroups = pPlayerManager->getVisualPlayerGroups(); for (const QString& group : visualGroups) { m_pVisualsManager->addDeck(group); } connect(pPlayerManager.get(), &PlayerManagerInterface::numberOfDecksChanged, this, [this](int decks) { for (int i = 0; i < decks; ++i) { QString group = PlayerManager::groupForDeck(i); m_pVisualsManager->addDeckIfNotExist(group); } }); connect(pPlayerManager.get(), &PlayerManagerInterface::numberOfSamplersChanged, this, [this](int decks) { for (int i = 0; i < decks; ++i) { QString group = PlayerManager::groupForSampler(i); m_pVisualsManager->addDeckIfNotExist(group); } }); #ifndef MIXXX_USE_QOPENGL // Before creating the first skin we need to create a QGLWidget so that all // the QGLWidget's we create can use it as a shared QGLContext. if (!CmdlineArgs::Instance().getSafeMode() && QGLFormat::hasOpenGL()) { QGLFormat glFormat; glFormat.setDirectRendering(true); glFormat.setDoubleBuffer(true); glFormat.setDepth(false); // Disable waiting for vertical Sync // This can be enabled when using a single Threads for each QGLContext // Setting 1 causes QGLContext::swapBuffer to sleep until the next VSync #if defined(__APPLE__) // On OS X, syncing to vsync has good performance FPS-wise and // eliminates tearing. glFormat.setSwapInterval(1); #else // Otherwise, turn VSync off because it could cause horrible FPS on // Linux. // TODO(XXX): Make this configurable. // TODO(XXX): What should we do on Windows? glFormat.setSwapInterval(0); #endif glFormat.setRgba(true); QGLFormat::setDefaultFormat(glFormat); WGLWidget* pContextWidget = new WGLWidget(this); pContextWidget->setGeometry(QRect(0, 0, 3, 3)); pContextWidget->hide(); SharedGLContext::setWidget(pContextWidget); } #endif WaveformWidgetFactory::createInstance(); // takes a long time WaveformWidgetFactory::instance()->setConfig(m_pCoreServices->getSettings()); WaveformWidgetFactory::instance()->startVSync(m_pGuiTick, m_pVisualsManager, false); connect(this, &MixxxMainWindow::skinLoaded, m_pCoreServices->getLibrary().get(), &Library::onSkinLoadFinished); connect(this, &MixxxMainWindow::skinLoaded, WaveformWidgetFactory::instance(), &WaveformWidgetFactory::slotSkinLoaded); // Initialize preference dialog m_pPrefDlg = new DlgPreferences( m_pCoreServices->getScreensaverManager(), m_pSkinLoader, m_pCoreServices->getSoundManager(), m_pCoreServices->getControllerManager(), m_pCoreServices->getVinylControlManager(), m_pCoreServices->getEffectsManager(), m_pCoreServices->getSettingsManager(), m_pCoreServices->getLibrary()); m_pPrefDlg->setWindowIcon(QIcon(MIXXX_ICON_PATH)); m_pPrefDlg->setHidden(true); connect(m_pPrefDlg, &DlgPreferences::tooltipModeChanged, this, &MixxxMainWindow::slotTooltipModeChanged); connect(m_pPrefDlg, &DlgPreferences::reloadUserInterface, this, &MixxxMainWindow::rebootMixxxView, Qt::DirectConnection); #ifndef __APPLE__ connect(m_pPrefDlg, &DlgPreferences::menuBarAutoHideChanged, this, &MixxxMainWindow::slotUpdateMenuBarAltKeyConnection, Qt::DirectConnection); #endif // Connect signals to the menubar. Should be done before emit skinLoaded. connectMenuBar(); QWidget* oldWidget = m_pCentralWidget; tryParseAndSetDefaultStyleSheet(); if (!loadConfiguredSkin()) { reportCriticalErrorAndQuit( "default skin cannot be loaded - see mixxx trace for more information"); m_pCentralWidget = oldWidget; //TODO (XXX) add dialog to warn user and launch skin choice page } else { m_pMenuBar->setStyleSheet(m_pCentralWidget->styleSheet()); } // Check direct rendering and warn user if they don't have it if (!CmdlineArgs::Instance().getSafeMode()) { checkDirectRendering(); } // Sound hardware setup // Try to open configured devices. If that fails, display dialogs // that allow to either retry, reconfigure devices or exit. bool retryClicked; do { retryClicked = false; SoundDeviceStatus result = m_pCoreServices->getSoundManager()->setupDevices(); if (result == SoundDeviceStatus::ErrorDeviceCount || result == SoundDeviceStatus::ErrorExcessiveOutputChannel) { if (soundDeviceBusyDlg(&retryClicked) != QDialog::Accepted) { exit(0); } } else if (result != SoundDeviceStatus::Ok) { if (soundDeviceErrorMsgDlg(result, &retryClicked) != QDialog::Accepted) { exit(0); } } } while (retryClicked); // Test for at least one output device. If none, display another dialog // that says "mixxx will barely work with no outs". // In case of persisting errors, the user has already received a message // above. So we can just check the output count here. while (m_pCoreServices->getSoundManager() ->getConfig() .getOutputs() .isEmpty() && !m_pCoreServices->getSoundManager()->pipewireSkipConfig()) { // Exit when we press the Exit button in the noSoundDlg dialog // only call it if result != OK bool continueClicked = false; if (noOutputDlg(&continueClicked) != QDialog::Accepted) { exit(0); } if (continueClicked) { break; } } // The user has either reconfigured devices or accepted no outputs, // so it's now safe to write the new config to disk. m_pCoreServices->getSoundManager()->getConfig().writeToDisk(); // this has to be after the OpenGL widgets are created or depending on a // million different variables the first waveform may be horribly // corrupted. See bug 521509 -- bkgood ?? -- vrince setCentralWidget(m_pCentralWidget); #ifndef __APPLE__ // Ask for permission to auto-hide the menu bar if applicable. #ifdef __LINUX__ // This makes no sense when starting in windowed mode with a global menu, // we'll ask when going fullscreen. if (!m_supportsGlobalMenuBar || isFullScreen()) { alwaysHideMenuBarDlg(); slotUpdateMenuBarAltKeyConnection(); } #else alwaysHideMenuBarDlg(); slotUpdateMenuBarAltKeyConnection(); #endif #endif // Show the menubar after the launch image is replaced by the skin widget, // otherwise it would shift the launch image shortly before the skin is visible. m_pMenuBar->show(); // The launch image widget is automatically disposed, but we still have a // pointer to it. m_pLaunchImage = nullptr; connect(pPlayerManager.get(), &PlayerManager::noMicrophoneInputConfigured, this, &MixxxMainWindow::slotNoMicrophoneInputConfigured); connect(pPlayerManager.get(), &PlayerManager::noAuxiliaryInputConfigured, this, &MixxxMainWindow::slotNoAuxiliaryInputConfigured); connect(pPlayerManager.get(), &PlayerManager::noDeckPassthroughInputConfigured, this, &MixxxMainWindow::slotNoDeckPassthroughInputConfigured); connect(pPlayerManager.get(), &PlayerManager::noVinylControlInputConfigured, this, &MixxxMainWindow::slotNoVinylControlInputConfigured); connect(&PlayerInfo::instance(), &PlayerInfo::currentPlayingTrackChanged, this, &MixxxMainWindow::slotUpdateWindowTitle); // Start Auto DJ if the cmdline arg is passed. if (CmdlineArgs::Instance().getStartAutoDJ()) { qDebug("Enabling Auto DJ from CLI flag."); ControlObject::set(ConfigKey("[AutoDJ]", "enabled"), 1.0); // Switch to Auto DJ feature auto* pLibrary = m_pCoreServices->getLibrary().get(); // Note: auto-scroll is disabled but that doesn't really matter here // because the sidebar is still in its initial state (top feature visible, // AutoDj is second from the top by default, all features collapsed). pLibrary->showAutoDJ(); } } MixxxMainWindow::~MixxxMainWindow() { Timer t("~MixxxMainWindow"); t.start(); // Save the current window state (position, maximized, etc) // Note(ronso0): Unfortunately saveGeometry() also stores the fullscreen state. // On next start restoreGeometry would enable fullscreen mode even though that // might not be requested (no '--fullscreen' command line arg and // [Config],StartInFullscreen is '0'. // https://github.com/mixxxdj/mixxx/issues/10005 // So let's quit fullscreen if StartInFullscreen is not checked in Preferences. bool fullscreenPref = m_pCoreServices->getSettings()->getValue( ConfigKey("[Config]", "StartInFullscreen")); if (isFullScreen() && !fullscreenPref) { // Simply maximize the window so we can store a geometry that fits the screen. // Don't call slotViewFullScreen(false) (calls showNormal()) because that // can make the main window incl. window decoration too large for the screen. #ifndef __APPLE__ // Before, store the expected window state so eventFilter() will ignore // the following QWindowChangeEvent and not recreate & re-sync the menu bar. m_prevState = Qt::WindowMaximized; #endif showMaximized(); } m_pCoreServices->getSettings()->set(ConfigKey("[MainWindow]", "geometry"), QString(saveGeometry().toBase64())); m_pCoreServices->getSettings()->set(ConfigKey("[MainWindow]", "state"), QString(saveState().toBase64())); // GUI depends on KeyboardEventFilter, PlayerManager, Library qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting skin"; // Clear widget pointer list and destroy all update connections before we // delete the main widget (ie. all WBaseWidgets) to prevent KeyboardEventFilter // accessing dangling pointers. m_pCoreServices->getKeyboardEventFilter()->clearWidgets(); m_pCentralWidget = nullptr; QPointer pSkin(centralWidget()); setCentralWidget(nullptr); if (!pSkin.isNull()) { QCoreApplication::sendPostedEvents(pSkin, QEvent::DeferredDelete); } // Our central widget is now deleted. VERIFY_OR_DEBUG_ASSERT(pSkin.isNull()) { qWarning() << "Central widget was not deleted by our sendPostedEvents trick."; } // Delete Controls created by skins qDeleteAll(m_skinCreatedControls); m_skinCreatedControls.clear(); // TODO() Verify if this comment still applies: // WMainMenuBar holds references to controls so we need to delete it // before MixxxMainWindow is destroyed. QMainWindow calls deleteLater() in // setMenuBar() but we need to delete it now so we can ask for // DeferredDelete events to be processed for it. Once Mixxx shutdown lives // outside of MixxxMainWindow the parent relationship will directly destroy // the WMainMenuBar and this will no longer be a problem. qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting menubar"; // Clear action pointer list before we delete the menubar // to prevent KeyboardEventFilter accessing dangling pointers. m_pCoreServices->getKeyboardEventFilter()->clearMenuBarActions(); QPointer pMenuBar = m_pMenuBar.toWeakRef(); DEBUG_ASSERT(menuBar() == m_pMenuBar.get()); // We need to reset the parented pointer here that it does not become a // dangling pointer after the object has been deleted. m_pMenuBar = nullptr; setMenuBar(nullptr); if (!pMenuBar.isNull()) { QCoreApplication::sendPostedEvents(pMenuBar, QEvent::DeferredDelete); } // Our main menu is now deleted. VERIFY_OR_DEBUG_ASSERT(pMenuBar.isNull()) { qWarning() << "WMainMenuBar was not deleted by our sendPostedEvents trick."; } qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting DeveloperToolsDlg"; delete m_pDeveloperToolsDlg; #ifdef __ENGINEPRIME__ qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting LibraryExporter"; m_pLibraryExporter.reset(); #endif qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting DlgPreferences"; delete m_pPrefDlg; m_pCoreServices->getControlIndicatorTimer()->setLegacyVsyncEnabled(false); qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting ControllerManager"; WaveformWidgetFactory::destroy(); delete m_pGuiTick; delete m_pVisualsManager; } void MixxxMainWindow::initializeWindow() { // be sure createMenuBar() is called first DEBUG_ASSERT(m_pMenuBar); QPalette Pal(palette()); // safe default QMenuBar background QColor MenuBarBackground(m_pMenuBar->palette().color(QPalette::Window)); Pal.setColor(QPalette::Window, QColor(0x202020)); setAutoFillBackground(true); setPalette(Pal); // restore default QMenuBar background Pal.setColor(QPalette::Window, MenuBarBackground); m_pMenuBar->setPalette(Pal); // Restore the current window state (position, maximized, etc). // This will also restore fullscreen and thereby create a seamless // start if we did shut down while in fullscreen mode and with // [Config],StartInFullscreen = 1 // (slotViewFullScreen(true) in initialize() is a no-op then) restoreGeometry(QByteArray::fromBase64( m_pCoreServices->getSettings() ->getValueString(ConfigKey("[MainWindow]", "geometry")) .toUtf8())); restoreState(QByteArray::fromBase64( m_pCoreServices->getSettings() ->getValueString(ConfigKey("[MainWindow]", "state")) .toUtf8())); setWindowIcon(QIcon(MIXXX_ICON_PATH)); slotUpdateWindowTitle(TrackPointer()); } #ifndef __APPLE__ void MixxxMainWindow::alwaysHideMenuBarDlg() { // Don't show the dialog if the user unchecked "Ask me again" if (!m_pCoreServices->getSettings()->getValue( kMenuBarHintConfigKey, true)) { return; } QString title = tr("Allow Mixxx to hide the menu bar?"); //: Always show the menu bar? QString hideBtnLabel = tr("Hide"); QString showBtnLabel = tr("Always show"); //: Keep formatting tags (bold text) and
(linebreak). //: %1 is the placeholder for the 'Always show' button label QString desc = tr( "The Mixxx menu bar is hidden and can be toggled with a single press " "of the Alt key.

" "Click %1 to agree.

" "Click %2 to disable that, for example if you don't use Mixxx " "with a keyboard.

" "You can change this setting any time in Preferences -> Interface." "
") // line break for some extra margin to the checkbox .arg(hideBtnLabel, showBtnLabel); QMessageBox msg; msg.setIcon(QMessageBox::Question); msg.setWindowTitle(title); msg.setText(desc); QCheckBox askAgainCheckBox; askAgainCheckBox.setText(tr("Ask me again")); askAgainCheckBox.setCheckState(Qt::Checked); msg.setCheckBox(&askAgainCheckBox); QPushButton* pHideBtn = msg.addButton(hideBtnLabel, QMessageBox::AcceptRole); QPushButton* pShowBtn = msg.addButton(showBtnLabel, QMessageBox::RejectRole); msg.setDefaultButton(pShowBtn); msg.exec(); m_pCoreServices->getSettings()->setValue( kMenuBarHintConfigKey, askAgainCheckBox.checkState() == Qt::Checked ? 1 : 0); m_pCoreServices->getSettings()->setValue( kHideMenuBarConfigKey, msg.clickedButton() == pHideBtn ? 1 : 0); } #endif QDialog::DialogCode MixxxMainWindow::soundDeviceErrorDlg( const QString &title, const QString &text, bool* retryClicked) { QMessageBox msgBox; msgBox.setIcon(QMessageBox::Warning); msgBox.setWindowTitle(title); msgBox.setText(text); QPushButton* retryButton = msgBox.addButton(tr("Retry"), QMessageBox::ActionRole); QPushButton* reconfigureButton = msgBox.addButton(tr("Reconfigure"), QMessageBox::ActionRole); QPushButton* wikiButton = msgBox.addButton(tr("Help"), QMessageBox::ActionRole); QPushButton* exitButton = msgBox.addButton(tr("Exit"), QMessageBox::ActionRole); while (true) { msgBox.exec(); if (msgBox.clickedButton() == retryButton) { m_pCoreServices->getSoundManager()->clearAndQueryDevices(); *retryClicked = true; return QDialog::Accepted; } else if (msgBox.clickedButton() == wikiButton) { mixxx::DesktopHelper::openUrl(QUrl(MIXXX_WIKI_TROUBLESHOOTING_SOUND_URL)); wikiButton->setEnabled(false); } else if (msgBox.clickedButton() == reconfigureButton) { msgBox.hide(); m_pCoreServices->getSoundManager()->clearAndQueryDevices(); // This way of opening the dialog allows us to use it synchronously m_pPrefDlg->setWindowModality(Qt::ApplicationModal); // Open preferences, sound hardware page is selected (default on first call) m_pPrefDlg->exec(); if (m_pPrefDlg->result() == QDialog::Accepted) { return QDialog::Accepted; } msgBox.show(); } else if (msgBox.clickedButton() == exitButton) { // Will finally quit Mixxx return QDialog::Rejected; } } } QDialog::DialogCode MixxxMainWindow::soundDeviceBusyDlg(bool* retryClicked) { QString title(tr("Sound Device Busy")); QString text( "

" % tr("Mixxx was unable to open all the configured sound devices.") + "

" % m_pCoreServices->getSoundManager()->getErrorDeviceName() % " is used by another application or not plugged in." "

    " "
  • " % tr("Retry after closing the other application " "or reconnecting a sound device") % "
  • " "
  • " % tr("Reconfigure Mixxx's sound device settings.") % "
  • " "
  • " % tr("Get Help from the Mixxx Wiki.") % "
  • " "
  • " % tr("Exit Mixxx.") % "
  • " "
"); return soundDeviceErrorDlg(title, text, retryClicked); } QDialog::DialogCode MixxxMainWindow::soundDeviceErrorMsgDlg( SoundDeviceStatus status, bool* retryClicked) { QString title(tr("Sound Device Error")); QString text("

" % tr("Mixxx was unable to open all the configured sound " "devices.") + "

" % m_pCoreServices->getSoundManager() ->getLastErrorMessage(status) .replace("\n", "
") % "

    " "
  • " % tr("Retry after fixing an issue") % "
  • " "
  • " % tr("Reconfigure Mixxx's sound device settings.") % "
  • " "
  • " % tr("Get Help from the Mixxx Wiki.") % "
  • " "
  • " % tr("Exit Mixxx.") % "
  • " "
"); return soundDeviceErrorDlg(title, text, retryClicked); } QDialog::DialogCode MixxxMainWindow::noOutputDlg(bool* continueClicked) { QMessageBox msgBox; msgBox.setIcon(QMessageBox::Warning); msgBox.setWindowTitle(tr("No Output Devices")); msgBox.setText( "" + tr("Mixxx was configured without any output sound devices. " "Audio processing will be disabled without a configured output device.") + "
    " "
  • " + tr("Continue without any outputs.") + "
  • " "
  • " + tr("Reconfigure Mixxx's sound device settings.") + "
  • " "
  • " + tr("Exit Mixxx.") + "
  • " "
" ); QPushButton* continueButton = msgBox.addButton(tr("Continue"), QMessageBox::ActionRole); QPushButton* reconfigureButton = msgBox.addButton(tr("Reconfigure"), QMessageBox::ActionRole); QPushButton* exitButton = msgBox.addButton(tr("Exit"), QMessageBox::ActionRole); while (true) { msgBox.exec(); if (msgBox.clickedButton() == continueButton) { *continueClicked = true; return QDialog::Accepted; } else if (msgBox.clickedButton() == reconfigureButton) { msgBox.hide(); // This way of opening the dialog allows us to use it synchronously m_pPrefDlg->setWindowModality(Qt::ApplicationModal); m_pPrefDlg->showSoundHardwarePage(mixxx::preferences::SoundHardwareTab::Output); m_pPrefDlg->exec(); if (m_pPrefDlg->result() == QDialog::Accepted) { return QDialog::Accepted; } msgBox.show(); } else if (msgBox.clickedButton() == exitButton) { // Will finally quit Mixxx return QDialog::Rejected; } } } void MixxxMainWindow::slotUpdateWindowTitle(TrackPointer pTrack) { QString appTitle = VersionStore::applicationName(); QString filePath; // If we have a track, use getInfo() to format a summary string and prepend // it to the title. // TODO(rryan): Does this violate Mac App Store policies? if (pTrack) { QString trackInfo = pTrack->getInfo(); if (!trackInfo.isEmpty()) { appTitle = QString("%1 | %2").arg(trackInfo, appTitle); } filePath = pTrack->getLocation(); } setWindowTitle(appTitle); // Display a draggable proxy icon for the track in the title bar on // platforms that support it, e.g. macOS setWindowFilePath(filePath); } void MixxxMainWindow::createMenuBar() { ScopedTimer t(QStringLiteral("MixxxMainWindow::createMenuBar")); DEBUG_ASSERT(m_pCoreServices->getKeyboardEventFilter()); m_pMenuBar = make_parented( this, m_pCoreServices->getSettings(), m_pCoreServices->getKeyboardEventFilter()); if (m_pCentralWidget) { m_pMenuBar->setStyleSheet(m_pCentralWidget->styleSheet()); } setMenuBar(m_pMenuBar); } void MixxxMainWindow::connectMenuBar() { // This function might be invoked multiple times on startup // so all connections must be unique! ScopedTimer t(QStringLiteral("MixxxMainWindow::connectMenuBar")); connect(this, &MixxxMainWindow::skinLoaded, m_pMenuBar, &WMainMenuBar::onNewSkinLoaded, Qt::UniqueConnection); // Misc connect(m_pMenuBar, &WMainMenuBar::quit, this, &MixxxMainWindow::close, Qt::UniqueConnection); connect(m_pMenuBar, &WMainMenuBar::showPreferences, this, &MixxxMainWindow::slotOptionsPreferences, Qt::UniqueConnection); connect(m_pMenuBar, &WMainMenuBar::loadTrackToDeck, this, &MixxxMainWindow::slotFileLoadSongPlayer, Qt::UniqueConnection); connect(m_pMenuBar, &WMainMenuBar::showKeywheel, this, &MixxxMainWindow::slotShowKeywheel, Qt::UniqueConnection); #ifndef __APPLE__ // Menubar auto-hide connect(m_pMenuBar, &WMainMenuBar::menubarAutoHideChanged, this, &MixxxMainWindow::slotUpdateMenuBarAltKeyConnection, Qt::UniqueConnection); #endif // Fullscreen connect(m_pMenuBar, &WMainMenuBar::toggleFullScreen, this, &MixxxMainWindow::slotViewFullScreen, Qt::UniqueConnection); connect(this, &MixxxMainWindow::fullScreenChanged, m_pMenuBar, &WMainMenuBar::onFullScreenStateChange, Qt::UniqueConnection); // Refresh the Fullscreen checkbox for the case we went fullscreen earlier m_pMenuBar->onFullScreenStateChange(isFullScreen()); // Help connect(m_pMenuBar, &WMainMenuBar::showAbout, this, &MixxxMainWindow::slotHelpAbout, Qt::UniqueConnection); // Developer connect(m_pMenuBar, &WMainMenuBar::reloadSkin, this, &MixxxMainWindow::rebootMixxxView, Qt::UniqueConnection); connect(m_pMenuBar, &WMainMenuBar::toggleDeveloperTools, this, &MixxxMainWindow::slotDeveloperTools, Qt::UniqueConnection); if (m_pCoreServices->getRecordingManager()) { connect(m_pCoreServices->getRecordingManager().get(), &RecordingManager::isRecording, m_pMenuBar, &WMainMenuBar::onRecordingStateChange, Qt::UniqueConnection); connect(m_pMenuBar, &WMainMenuBar::toggleRecording, m_pCoreServices->getRecordingManager().get(), &RecordingManager::slotSetRecording, Qt::UniqueConnection); m_pMenuBar->onRecordingStateChange( m_pCoreServices->getRecordingManager()->isRecordingActive()); } #ifdef __BROADCAST__ if (m_pCoreServices->getBroadcastManager()) { connect(m_pCoreServices->getBroadcastManager().get(), &BroadcastManager::broadcastEnabled, m_pMenuBar, &WMainMenuBar::onBroadcastingStateChange, Qt::UniqueConnection); connect(m_pMenuBar, &WMainMenuBar::toggleBroadcasting, m_pCoreServices->getBroadcastManager().get(), &BroadcastManager::setEnabled, Qt::UniqueConnection); m_pMenuBar->onBroadcastingStateChange(m_pCoreServices->getBroadcastManager()->isEnabled()); } #endif #ifdef __VINYLCONTROL__ if (m_pCoreServices->getVinylControlManager()) { connect(m_pMenuBar, &WMainMenuBar::toggleVinylControl, m_pCoreServices->getVinylControlManager().get(), &VinylControlManager::toggleVinylControl, Qt::UniqueConnection); connect(m_pCoreServices->getVinylControlManager().get(), &VinylControlManager::vinylControlDeckEnabled, m_pMenuBar, &WMainMenuBar::onVinylControlDeckEnabledStateChange, Qt::UniqueConnection); } #endif auto pPlayerManager = m_pCoreServices->getPlayerManager(); if (pPlayerManager) { connect(pPlayerManager.get(), &PlayerManager::numberOfDecksChanged, m_pMenuBar, &WMainMenuBar::onNumberOfDecksChanged, Qt::UniqueConnection); m_pMenuBar->onNumberOfDecksChanged(pPlayerManager->numberOfDecks()); } if (m_pCoreServices->getTrackCollectionManager()) { connect(m_pMenuBar, &WMainMenuBar::rescanLibrary, m_pCoreServices->getTrackCollectionManager().get(), &TrackCollectionManager::startLibraryScan, Qt::UniqueConnection); connect(m_pCoreServices->getTrackCollectionManager().get(), &TrackCollectionManager::libraryScanStarted, m_pMenuBar, &WMainMenuBar::onLibraryScanStarted, Qt::UniqueConnection); connect(m_pCoreServices->getTrackCollectionManager().get(), &TrackCollectionManager::libraryScanFinished, m_pMenuBar, &WMainMenuBar::onLibraryScanFinished, Qt::UniqueConnection); } if (m_pCoreServices->getLibrary()) { connect(m_pMenuBar, &WMainMenuBar::searchInCurrentView, m_pCoreServices->getLibrary().get(), &Library::slotSearchInCurrentView, Qt::UniqueConnection); connect(m_pMenuBar, &WMainMenuBar::searchInAllTracks, m_pCoreServices->getLibrary().get(), &Library::slotSearchInAllTracks, Qt::UniqueConnection); connect(m_pMenuBar, &WMainMenuBar::createCrate, m_pCoreServices->getLibrary().get(), &Library::slotCreateCrate, Qt::UniqueConnection); connect(m_pMenuBar, &WMainMenuBar::createPlaylist, m_pCoreServices->getLibrary().get(), &Library::slotCreatePlaylist, Qt::UniqueConnection); connect(m_pMenuBar, &WMainMenuBar::showAutoDJ, m_pCoreServices->getLibrary().get(), &Library::showAutoDJ, Qt::UniqueConnection); } #ifdef __ENGINEPRIME__ DEBUG_ASSERT(m_pLibraryExporter); connect(m_pMenuBar, &WMainMenuBar::exportLibrary, m_pLibraryExporter.get(), &mixxx::LibraryExporter::slotRequestExport, Qt::UniqueConnection); #endif } /// Enable/disable listening to Alt key press for toggling the menubar. #ifndef __APPLE__ void MixxxMainWindow::slotUpdateMenuBarAltKeyConnection() { if (!m_pCoreServices->getKeyboardEventFilter() || !m_pMenuBar) { return; } if (m_pCoreServices->getSettings()->getValue(kHideMenuBarConfigKey, false)) { // with Qt::UniqueConnection we don't need to check whether we're already connected connect(m_pCoreServices->getKeyboardEventFilter().get(), &KeyboardEventFilter::altPressedWithoutKeys, m_pMenuBar, &WMainMenuBar::slotToggleMenuBar, Qt::UniqueConnection); m_pMenuBar->hideMenuBar(); } else { disconnect(m_pCoreServices->getKeyboardEventFilter().get(), &KeyboardEventFilter::altPressedWithoutKeys, m_pMenuBar, &WMainMenuBar::slotToggleMenuBar); m_pMenuBar->showMenuBar(); } } #endif void MixxxMainWindow::slotFileLoadSongPlayer(int deck) { QString group = PlayerManager::groupForDeck(deck - 1); QString loadTrackText = tr("Load track to Deck %1").arg(QString::number(deck)); QString deckWarningMessage = tr("Deck %1 is currently playing a track.") .arg(QString::number(deck)); QString areYouSure = tr("Are you sure you want to load a new track?"); if (ControlObject::get(ConfigKey(group, "play")) > 0.0) { int ret = QMessageBox::warning(this, VersionStore::applicationName(), deckWarningMessage + "\n" + areYouSure, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (ret != QMessageBox::Yes) { return; } } UserSettingsPointer pConfig = m_pCoreServices->getSettings(); QString trackPath = QFileDialog::getOpenFileName( this, loadTrackText, pConfig->getValueString(mixxx::library::prefs::kLegacyDirectoryConfigKey), QString("Audio (%1)") .arg(SoundSourceProxy::getSupportedFileNamePatterns().join(" "))); if (!trackPath.isNull()) { // The user has picked a file via a file dialog. This means the system // sandboxer (if we are sandboxed) has granted us permission to this // folder. Create a security bookmark while we have permission so that // we can access the folder on future runs. We need to canonicalize the // path so we first wrap the directory string with a QDir. mixxx::FileInfo fileInfo(trackPath); Sandbox::createSecurityToken(&fileInfo); m_pCoreServices->getPlayerManager()->slotLoadToDeck(trackPath, deck); } } void MixxxMainWindow::slotDeveloperTools(bool visible) { if (visible) { if (m_pDeveloperToolsDlg == nullptr) { UserSettingsPointer pConfig = m_pCoreServices->getSettings(); m_pDeveloperToolsDlg = new DlgDeveloperTools(this, pConfig); connect(m_pDeveloperToolsDlg, &DlgDeveloperTools::destroyed, this, &MixxxMainWindow::slotDeveloperToolsClosed); connect(this, &MixxxMainWindow::closeDeveloperToolsDlgChecked, m_pDeveloperToolsDlg, &DlgDeveloperTools::done); connect(m_pDeveloperToolsDlg, &DlgDeveloperTools::destroyed, m_pMenuBar, &WMainMenuBar::onDeveloperToolsHidden); } m_pMenuBar->onDeveloperToolsShown(); m_pDeveloperToolsDlg->show(); m_pDeveloperToolsDlg->activateWindow(); } else { emit closeDeveloperToolsDlgChecked(0); } } void MixxxMainWindow::slotDeveloperToolsClosed() { m_pDeveloperToolsDlg = nullptr; } void MixxxMainWindow::slotViewFullScreen(bool toggle) { if (isFullScreen() == toggle) { return; } // Just switch the window state here. eventFilter() will catch the // QWindowStateChangeEvent and inform the menu bar that fullscreen changed. if (toggle) { showFullScreen(); } else { showNormal(); } } void MixxxMainWindow::slotOptionsPreferences() { m_pPrefDlg->show(); m_pPrefDlg->raise(); m_pPrefDlg->activateWindow(); } void MixxxMainWindow::slotNoVinylControlInputConfigured() { if (m_noVinylInputDialog && m_noVinylInputDialog->isVisible()) { // Don't show redundant dialogs. // They might be triggered be repeated controller or keyboard and // can lockup the GUI. return; } if (!m_noVinylInputDialog) { m_noVinylInputDialog = make_parented( QMessageBox::Warning, VersionStore::applicationName(), tr("There is no input device selected for this vinyl control.\n" "Please select an input device in the sound hardware preferences first."), QMessageBox::Ok | QMessageBox::Cancel, this); m_noVinylInputDialog->setWindowModality(Qt::ApplicationModal); m_noVinylInputDialog->setDefaultButton(QMessageBox::Cancel); } m_noVinylInputDialog->exec(); if (m_noVinylInputDialog->clickedButton() == m_noVinylInputDialog->button(QMessageBox::Ok)) { m_pPrefDlg->show(); m_pPrefDlg->showSoundHardwarePage(mixxx::preferences::SoundHardwareTab::Input); } } void MixxxMainWindow::slotNoDeckPassthroughInputConfigured() { if (m_noPassthroughInputDialog && m_noPassthroughInputDialog->isVisible()) { // Don't show redundant dialogs. // They might be triggered be repeated controller or keyboard and // can lockup the GUI. return; } if (!m_noPassthroughInputDialog) { m_noPassthroughInputDialog = make_parented( QMessageBox::Warning, VersionStore::applicationName(), tr("There is no input device selected for this passthrough control.\n" "Please select an input device in the sound hardware preferences first."), QMessageBox::Ok | QMessageBox::Cancel, this); m_noPassthroughInputDialog->setWindowModality(Qt::ApplicationModal); m_noPassthroughInputDialog->setDefaultButton(QMessageBox::Cancel); } m_noPassthroughInputDialog->exec(); if (m_noPassthroughInputDialog->clickedButton() == m_noPassthroughInputDialog->button(QMessageBox::Ok)) { m_pPrefDlg->show(); m_pPrefDlg->showSoundHardwarePage(mixxx::preferences::SoundHardwareTab::Input); } } void MixxxMainWindow::slotNoMicrophoneInputConfigured() { if (m_noMicInputDialog && m_noMicInputDialog->isVisible()) { // Don't show redundant dialogs. // They might be triggered be repeated controller or keyboard and // can lockup the GUI. return; } if (!m_noMicInputDialog) { m_noMicInputDialog = make_parented( QMessageBox::Warning, VersionStore::applicationName(), tr("There is no input device selected for this microphone.\n" "Do you want to select an input device?"), QMessageBox::Ok | QMessageBox::Cancel, this); m_noMicInputDialog->setWindowModality(Qt::ApplicationModal); m_noMicInputDialog->setDefaultButton(QMessageBox::Cancel); } m_noMicInputDialog->exec(); if (m_noMicInputDialog->clickedButton() == m_noMicInputDialog->button(QMessageBox::Ok)) { m_pPrefDlg->show(); m_pPrefDlg->showSoundHardwarePage(mixxx::preferences::SoundHardwareTab::Input); } } void MixxxMainWindow::slotNoAuxiliaryInputConfigured() { if (m_noAuxInputDialog && m_noAuxInputDialog->isVisible()) { // Don't show redundant dialogs. // They might be triggered be repeated controller or keyboard and // can lockup the GUI. return; } if (!m_noAuxInputDialog) { m_noAuxInputDialog = make_parented( QMessageBox::Warning, VersionStore::applicationName(), tr("There is no input device selected for this auxiliary.\n" "Do you want to select an input device?"), QMessageBox::Ok | QMessageBox::Cancel, this); m_noAuxInputDialog->setWindowModality(Qt::ApplicationModal); m_noAuxInputDialog->setDefaultButton(QMessageBox::Cancel); } m_noAuxInputDialog->exec(); if (m_noAuxInputDialog->clickedButton() == m_noAuxInputDialog->button(QMessageBox::Ok)) { m_pPrefDlg->show(); m_pPrefDlg->showSoundHardwarePage(mixxx::preferences::SoundHardwareTab::Input); } } void MixxxMainWindow::slotHelpAbout() { DlgAbout* about = new DlgAbout; about->show(); } void MixxxMainWindow::slotLibraryScanSummaryDlg(const LibraryScanResultSummary& result) { if (!m_pCoreServices->getSettings()->getValue( mixxx::library::prefs::kShowScanSummaryConfigKey, true)) { return; } // Don't show the report dialog when the scan is run during startup and no // noteworthy changes have been detected. if (result.autoscan && result.numNewTracks == 0 && result.numNewMissingTracks == 0 && result.numRediscoveredTracks == 0) { return; } QMessageBox* pMsg = new QMessageBox(); pMsg->setAttribute(Qt::WA_DeleteOnClose); pMsg->setTextFormat(Qt::RichText); // required to get bold text with tags pMsg->setWindowTitle(tr("Library scan finished")); if (result.noDirectoriesConfigured) { pMsg->setText(tr("No music directories configured for scanning.") + QStringLiteral("
") + tr("Add directories in the library preferences.")); pMsg->show(); return; } QString summary = tr("Scan took %1").arg(result.durationString) + QStringLiteral("

"); if (result.numNewTracks == 0 && result.numMovedTracks == 0 && result.numNewMissingTracks == 0 && result.numRediscoveredTracks == 0) { summary += tr("No changes detected.") + QStringLiteral("
") + tr("%n track(s) in total", nullptr, result.tracksTotal) + QStringLiteral(""); } else { if (result.numNewTracks != 0) { summary += tr("%n new track(s) found", nullptr, result.numNewTracks) + QStringLiteral("
"); } if (result.numMovedTracks != 0) { summary += tr("%n moved track(s) detected", nullptr, result.numMovedTracks) + QStringLiteral("
"); } if (result.numNewMissingTracks != 0) { summary += tr("%n track(s) missing (%1 total)", nullptr, result.numNewMissingTracks) .arg(result.numMissingTracks); } if (result.numRediscoveredTracks != 0) { summary += QStringLiteral("
") + tr("%n track(s) rediscovered", nullptr, result.numRediscoveredTracks); } summary += QStringLiteral("

") + tr("%n track(s) in total", nullptr, result.tracksTotal) + QStringLiteral(""); } pMsg->setText(summary); pMsg->show(); } void MixxxMainWindow::slotShowKeywheel(bool toggle) { if (!m_pKeywheel) { m_pKeywheel = make_parented(this, m_pCoreServices->getSettings()); // uncheck the menu item on window close connect(m_pKeywheel.get(), &DlgKeywheel::finished, m_pMenuBar, &WMainMenuBar::onKeywheelChange); } if (toggle) { m_pKeywheel->show(); m_pKeywheel->raise(); } else { m_pKeywheel->hide(); } } void MixxxMainWindow::slotTooltipModeChanged(mixxx::preferences::Tooltips tt) { m_toolTipsCfg = tt; m_pCoreServices->getKeyboardEventFilter()->setShowOnlyKbdShortcuts( tt == mixxx::preferences::Tooltips::OnlyKbdShortcuts); #ifdef MIXXX_USE_QOPENGL ToolTipQOpenGL::singleton().setActive( m_toolTipsCfg == mixxx::preferences::Tooltips::On); #endif } void MixxxMainWindow::rebootMixxxView() { qDebug() << "Now in rebootMixxxView..."; m_inRebootMixxxView = true; ScopedWaitCursor cursor; // safe geometry for later restoration const QRect initGeometry = geometry(); // We need to tell the menu bar that we are about to delete the old skin and // create a new one. It holds "visibility" controls (e.g. "Show Samplers") // that need to be deleted -- otherwise we can't tell what features the skin // supports since the controls from the previous skin will be left over. m_pMenuBar->onNewSkinAboutToLoad(); if (m_pCentralWidget) { // Clear widget pointer list and destroy all update connections before // we delete the main widget (ie. all WBaseWidgets) to prevent // KeyboardEventFilter accessing dangling pointers, just in case a // shortcuts/tooltip update is triggered while we re/load a skin. m_pCoreServices->getKeyboardEventFilter()->clearWidgets(); m_pCentralWidget->hide(); WaveformWidgetFactory::instance()->destroyWidgets(); delete m_pCentralWidget; m_pCentralWidget = nullptr; } // Workaround for changing skins while fullscreen, just go out of fullscreen // mode. If you change skins while in fullscreen (on Linux, at least) the // window returns to 0,0 but and the backdrop disappears so it looks as if // it is not fullscreen, but acts as if it is. bool wasFullScreen = isFullScreen(); if (wasFullScreen) { showMaximized(); } tryParseAndSetDefaultStyleSheet(); if (!loadConfiguredSkin()) { QMessageBox::critical(this, tr("Error in skin file"), tr("The selected skin cannot be loaded.")); m_inRebootMixxxView = false; // m_pWidgetParent is NULL, we can't continue. return; } m_pMenuBar->setStyleSheet(m_pCentralWidget->styleSheet()); setCentralWidget(m_pCentralWidget); #ifdef __LINUX__ // don't adjustSize() on Linux as this wouldn't use the entire available area // to paint the new skin with X11 // https://github.com/mixxxdj/mixxx/issues/9309 #else adjustSize(); #endif if (wasFullScreen) { showFullScreen(); } else { // Programmatic placement at this point is very problematic. // The screen() method returns stale data (primary screen) // until the user interacts with mixxx again. Keyboard shortcuts // do not count, moving window, opening menu etc does // Therefore the placement logic was removed by a simple geometry restore. // If the minimum size of the new skin is larger then the restored // geometry, the window will be enlarged right & bottom which is // safe as the menu is still reachable. setGeometry(initGeometry); } m_inRebootMixxxView = false; qDebug() << "rebootMixxxView DONE"; } bool MixxxMainWindow::loadConfiguredSkin() { // TODO: use std::shared_ptr throughout skin widgets instead of these hacky get() calls m_pCentralWidget = m_pSkinLoader->loadConfiguredSkin(this, &m_skinCreatedControls, m_pCoreServices.get()); if (centralWidget() == m_pLaunchImage) { initializationProgressUpdate(100, ""); } emit skinLoaded(); return m_pCentralWidget != nullptr; } /// Try to load default styles that can be overridden by skins void MixxxMainWindow::tryParseAndSetDefaultStyleSheet() { const QString resPath = m_pCoreServices->getSettings()->getResourcePath(); QFile file(resPath + "/skins/default.qss"); if (file.open(QIODevice::ReadOnly)) { QByteArray fileBytes = file.readAll(); QString style = QString::fromUtf8(fileBytes); setStyleSheet(style); } else { qWarning() << "Failed to load default skin styles /skins/default.qss!"; } } /// Catch ToolTip and WindowStateChange events bool MixxxMainWindow::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::ToolTip) { // Always show tooltips if Ctrl is held down if (QApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) { return QMainWindow::eventFilter(obj, event); } // Always show tooltips for cue type buttons in the Cue menu if (QLatin1String(obj->metaObject()->className()) == "CueMenuPushButton") { return QMainWindow::eventFilter(obj, event); } // Always show tooltips in Preferences QWidget* activeWindow = QApplication::activeWindow(); if (activeWindow && QLatin1String(activeWindow->metaObject()->className()) == "DlgPreferences") { return QMainWindow::eventFilter(obj, event); } // For all other we follow the tooltip sett8ing. // Return true for no tool tips switch (m_toolTipsCfg) { case mixxx::preferences::Tooltips::OnlyInLibrary: // WLibrary's stacked widgets are not derived from WBaseWidget if (dynamic_cast(obj) != nullptr) { return true; } break; case mixxx::preferences::Tooltips::OnlyKbdShortcuts: if (dynamic_cast(obj) == nullptr) { return true; } break; case mixxx::preferences::Tooltips::On: break; case mixxx::preferences::Tooltips::Off: return true; default: DEBUG_ASSERT(!"m_toolTipsCfg value unknown"); return true; } } else if (event->type() == QEvent::WindowStateChange) { #ifndef __APPLE__ if (windowState() == m_prevState) { // Ignore no-op. This happens if another window is raised above // MixxxMianWindow, e.g. DlgPeferences. In such a case event->oldState() // will be Qt::WindowNoState which is wrong anyway, so there is nothing // to do internally. return QMainWindow::eventFilter(obj, event); } m_prevState = windowState(); #endif // Detect if we entered or quit fullscreen mode. QWindowStateChangeEvent* changeEvent = static_cast(event); const bool wasFullScreen = changeEvent->oldState() & Qt::WindowFullScreen; const bool isFullScreenNow = windowState() & Qt::WindowFullScreen; if ((isFullScreenNow && !wasFullScreen) || (!isFullScreenNow && wasFullScreen)) { #ifdef __LINUX__ // Fix for "No menu bar with ubuntu unity in full screen mode" // (issues #6072 and #6689). Before touching anything here, please // read those bugs. // Set this attribute instead of calling setNativeMenuBar(false), // see https://github.com/mixxxdj/mixxx/issues/11320 if (m_supportsGlobalMenuBar) { QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, isFullScreenNow); createMenuBar(); connectMenuBar(); } #endif #ifndef __APPLE__ #ifdef __LINUX__ // Only show the dialog if we are able to have the menubar in the // main window, only then we're able to hide it. if (!m_supportsGlobalMenuBar || isFullScreenNow) #endif { if (!m_inRebootMixxxView) { alwaysHideMenuBarDlg(); } slotUpdateMenuBarAltKeyConnection(); } #endif // This will toggle the Fullscreen checkbox and hide the menubar if // we go fullscreen. // Skip this during startup or the launchimage will be shifted // up & down when the menu is shown menu and 'hidden'. The menu // will be updated when the skin finished loading. if (centralWidget() != m_pLaunchImage) { emit fullScreenChanged(isFullScreen()); } } } // standard event processing return QMainWindow::eventFilter(obj, event); } void MixxxMainWindow::closeEvent(QCloseEvent *event) { // WARNING: We can receive a CloseEvent while only partially // initialized. This is because we call QApplication::processEvents to // render LaunchImage progress in the constructor. if (!confirmExit()) { event->ignore(); return; } QMainWindow::closeEvent(event); } void MixxxMainWindow::checkDirectRendering() { // IF // * A waveform viewer exists // AND // * The waveform viewer is an OpenGL waveform viewer // AND // * The waveform viewer does not have direct rendering enabled. // THEN // * Warn user WaveformWidgetFactory* factory = WaveformWidgetFactory::instance(); if (!factory) { return; } UserSettingsPointer pConfig = m_pCoreServices->getSettings(); if (!factory->isOpenGlAvailable() && !factory->isOpenGlesAvailable() && pConfig->getValueString(ConfigKey("[Direct Rendering]", "Warned")) != QString("yes")) { QMessageBox::warning(nullptr, tr("OpenGL Direct Rendering"), tr("Direct rendering is not enabled on your machine.

" "This means that the waveform displays will be very
" "slow and may tax your CPU heavily. Either update " "your
" "configuration to enable direct rendering, or disable
" "the waveform displays in the Mixxx preferences by " "selecting
" "\"Empty\" as the waveform display in the 'Interface' " "section.")); pConfig->set(ConfigKey("[Direct Rendering]", "Warned"), QString("yes")); } } bool MixxxMainWindow::confirmExit() { bool playing(false); bool playingSampler(false); auto pPlayerManager = m_pCoreServices->getPlayerManager(); int deckCount = pPlayerManager->numberOfDecks(); int samplerCount = pPlayerManager->numberOfSamplers(); for (int i = 0; i < deckCount; ++i) { if (ControlObject::toBool( ConfigKey(PlayerManager::groupForDeck(i), "play"))) { playing = true; break; } } for (int i = 0; i < samplerCount; ++i) { if (ControlObject::toBool( ConfigKey(PlayerManager::groupForSampler(i), "play"))) { playingSampler = true; break; } } if (playing) { QMessageBox::StandardButton btn = QMessageBox::question(this, tr("Confirm Exit"), tr("A deck is currently playing. Exit Mixxx?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (btn == QMessageBox::No) { return false; } } else if (playingSampler) { QMessageBox::StandardButton btn = QMessageBox::question(this, tr("Confirm Exit"), tr("A sampler is currently playing. Exit Mixxx?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (btn == QMessageBox::No) { return false; } } if (m_pPrefDlg && m_pPrefDlg->isVisible()) { QMessageBox::StandardButton btn = QMessageBox::question( this, tr("Confirm Exit"), tr("The preferences window is still open.") + "
" + tr("Discard any changes and exit Mixxx?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (btn == QMessageBox::No) { return false; } else { m_pPrefDlg->close(); } } return true; } void MixxxMainWindow::initializationProgressUpdate(int progress, const QString& serviceName) { if (m_pLaunchImage) { m_pLaunchImage->progress(progress, serviceName); } qApp->processEvents(); }