diff options
author | Jan Holthuis <jholthuis@mixxx.org> | 2021-07-07 23:26:03 +0200 |
---|---|---|
committer | Jan Holthuis <jholthuis@mixxx.org> | 2021-07-07 23:32:10 +0200 |
commit | 85e22c7bf1c6f48e21f4df982f66ddd8d71c8c5e (patch) | |
tree | 1995b81b80a42902c68174c3f00c99a8a4f6c74f /src/mixxxmainwindow.cpp | |
parent | a88e298df6239cbeedf9e688fbaddaf63833c808 (diff) |
Rename mixxx.{cpp,h} to mixxxmainwindow.{cpp,h}
Diffstat (limited to 'src/mixxxmainwindow.cpp')
-rw-r--r-- | src/mixxxmainwindow.cpp | 1251 |
1 files changed, 1251 insertions, 0 deletions
diff --git a/src/mixxxmainwindow.cpp b/src/mixxxmainwindow.cpp new file mode 100644 index 0000000000..dd9647ecf1 --- /dev/null +++ b/src/mixxxmainwindow.cpp @@ -0,0 +1,1251 @@ +#include "mixxxmainwindow.h" + +#include <QDesktopServices> +#include <QFileDialog> +#include <QGLFormat> +#include <QGuiApplication> +#include <QInputMethod> +#include <QLocale> +#include <QScreen> +#include <QStandardPaths> +#include <QUrl> +#include <QtDebug> + +#include "defs_urls.h" +#include "dialog/dlgabout.h" +#include "dialog/dlgdevelopertools.h" +#include "dialog/dlgkeywheel.h" +#include "effects/builtin/builtinbackend.h" +#include "effects/effectsmanager.h" +#include "engine/enginemaster.h" +#include "moc_mixxxmainwindow.cpp" +#include "preferences/constants.h" +#include "preferences/dialog/dlgprefeq.h" +#include "preferences/dialog/dlgpreferences.h" +#ifdef __LILV__ +#include "effects/lv2/lv2backend.h" +#endif +#ifdef __BROADCAST__ +#include "broadcast/broadcastmanager.h" +#endif +#include "control/controlpushbutton.h" +#include "controllers/controllermanager.h" +#include "controllers/keyboard/keyboardeventfilter.h" +#include "database/mixxxdb.h" +#include "library/coverartcache.h" +#include "library/library.h" +#include "library/library_preferences.h" +#ifdef __ENGINEPRIME__ +#include "library/export/libraryexporter.h" +#endif +#include "library/trackcollection.h" +#include "library/trackcollectionmanager.h" +#include "mixer/playerinfo.h" +#include "mixer/playermanager.h" +#include "preferences/settingsmanager.h" +#include "recording/recordingmanager.h" +#include "skin/legacy/launchimage.h" +#include "skin/legacy/legacyskinparser.h" +#include "skin/skinloader.h" +#include "soundio/soundmanager.h" +#include "sources/soundsourceproxy.h" +#include "track/track.h" +#include "util/db/dbconnectionpooled.h" +#include "util/debug.h" +#include "util/experiment.h" +#include "util/font.h" +#include "util/logger.h" +#include "util/math.h" +#include "util/sandbox.h" +#include "util/screensaver.h" +#include "util/statsmanager.h" +#include "util/time.h" +#include "util/timer.h" +#include "util/translations.h" +#include "util/versionstore.h" +#include "util/widgethelper.h" +#include "waveform/guitick.h" +#include "waveform/sharedglcontext.h" +#include "waveform/visualsmanager.h" +#include "waveform/waveformwidgetfactory.h" +#include "widget/wmainmenubar.h" + +#ifdef __VINYLCONTROL__ +#include "vinylcontrol/vinylcontrolmanager.h" +#endif + +#ifdef __MODPLUG__ +#include "preferences/dialog/dlgprefmodplug.h" +#endif + +#if defined(Q_OS_LINUX) +#include <X11/Xlib.h> +#include <X11/Xlibint.h> + +#include <QtX11Extras/QX11Info> +// Xlibint.h predates C++ and defines macros which conflict +// with references to std::max and std::min +#undef max +#undef min +#endif + +MixxxMainWindow::MixxxMainWindow( + QApplication* pApp, std::shared_ptr<mixxx::CoreServices> pCoreServices) + : m_pCoreServices(pCoreServices), + m_pCentralWidget(nullptr), + m_pLaunchImage(nullptr), + m_pGuiTick(nullptr), + m_pDeveloperToolsDlg(nullptr), + m_pPrefDlg(nullptr), + m_pKeywheel(nullptr), +#ifdef __ENGINEPRIME__ + m_pLibraryExporter(nullptr), +#endif + m_toolTipsCfg(mixxx::TooltipsPreference::TOOLTIPS_ON) { + DEBUG_ASSERT(pApp); + DEBUG_ASSERT(pCoreServices); + m_pCoreServices->initializeSettings(); + m_pCoreServices->initializeKeyboard(); + // These depend on the settings + createMenuBar(); + m_pMenuBar->hide(); + + initializeWindow(); + + // Show launch image immediately so the user knows Mixxx is starting + m_pSkinLoader = std::make_unique<mixxx::skin::SkinLoader>(m_pCoreServices->getSettings()); + m_pLaunchImage = m_pSkinLoader->loadLaunchImage(this); + m_pCentralWidget = (QWidget*)m_pLaunchImage; + setCentralWidget(m_pCentralWidget); + + show(); + pApp->processEvents(); + + m_pGuiTick = new GuiTick(); + m_pVisualsManager = new VisualsManager(); + + connect( + m_pCoreServices.get(), + &mixxx::CoreServices::initializationProgressUpdate, + this, + &MixxxMainWindow::initializationProgressUpdate); + + // Inhibit the screensaver if the option is set. (Do it before creating the preferences dialog) + UserSettingsPointer pConfig = m_pCoreServices->getSettings(); + int inhibit = pConfig->getValue<int>(ConfigKey("[Config]", "InhibitScreensaver"), -1); + if (inhibit == -1) { + inhibit = static_cast<int>(mixxx::ScreenSaverPreference::PREVENT_ON); + pConfig->setValue<int>(ConfigKey("[Config]", "InhibitScreensaver"), inhibit); + } + m_inhibitScreensaver = static_cast<mixxx::ScreenSaverPreference>(inhibit); + if (m_inhibitScreensaver == mixxx::ScreenSaverPreference::PREVENT_ON) { + mixxx::ScreenSaverHelper::inhibit(); + } + + m_pCoreServices->initialize(pApp); + + // Set the visibility of tooltips, default "1" = ON + m_toolTipsCfg = static_cast<mixxx::TooltipsPreference>( + pConfig->getValue(ConfigKey("[Controls]", "Tooltips"), + static_cast<int>(mixxx::TooltipsPreference::TOOLTIPS_ON))); + +#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); +#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. + // Remember to refresh the Fullscreen menu item after connectMenuBar() + bool fullscreenPref = m_pCoreServices->getSettings()->getValue<bool>( + ConfigKey("[Config]", "StartInFullscreen")); + if (CmdlineArgs::Instance().getStartInFullscreen() || fullscreenPref) { + slotViewFullScreen(true); + } + + initializationProgressUpdate(65, tr("skin")); + + installEventFilter(m_pCoreServices->getKeyboardEventFilter().get()); + + DEBUG_ASSERT(m_pCoreServices->getPlayerManager()); + const QStringList visualGroups = m_pCoreServices->getPlayerManager()->getVisualPlayerGroups(); + for (const QString& group : visualGroups) { + m_pVisualsManager->addDeck(group); + } + + // 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); + + QGLWidget* pContextWidget = new QGLWidget(this); + pContextWidget->setGeometry(QRect(0, 0, 3, 3)); + pContextWidget->hide(); + SharedGLContext::setWidget(pContextWidget); + } + + WaveformWidgetFactory::createInstance(); // takes a long time + WaveformWidgetFactory::instance()->setConfig(m_pCoreServices->getSettings()); + WaveformWidgetFactory::instance()->startVSync(m_pGuiTick, m_pVisualsManager); + + 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( + this, + m_pSkinLoader, + m_pCoreServices->getSoundManager(), + m_pCoreServices->getPlayerManager(), + m_pCoreServices->getControllerManager(), + m_pCoreServices->getVinylControlManager(), + m_pCoreServices->getLV2Backend(), + m_pCoreServices->getEffectsManager(), + m_pCoreServices->getSettingsManager(), + m_pCoreServices->getLibrary()); + m_pPrefDlg->setWindowIcon(QIcon(":/images/icons/mixxx.svg")); + m_pPrefDlg->setHidden(true); + + // Connect signals to the menubar. Should be done before emit newSkinLoaded. + connectMenuBar(); + + QWidget* oldWidget = m_pCentralWidget; + + // Load default styles that can be overridden by skins + QFile file(":/skins/default.qss"); + if (file.open(QIODevice::ReadOnly)) { + QByteArray fileBytes = file.readAll(); + QString style = QString::fromLocal8Bit(fileBytes.constData(), + fileBytes.length()); + setStyleSheet(style); + } else { + qWarning() << "Failed to load default skin styles!"; + } + + if (!loadConfiguredSkin()) { + reportCriticalErrorAndQuit( + "default skin cannot be loaded - see <b>mixxx</b> 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(); + } + + // Install an event filter to catch certain QT events, such as tooltips. + // This allows us to turn off tooltips. + pApp->installEventFilter(this); // The eventfilter is located in this + // Mixxx class as a callback. + + // Try open player device If that fails, the preference panel is opened. + bool retryClicked; + do { + retryClicked = false; + SoundDeviceError result = m_pCoreServices->getSoundManager()->setupDevices(); + if (result == SOUNDDEVICE_ERROR_DEVICE_COUNT || + result == SOUNDDEVICE_ERROR_EXCESSIVE_OUTPUT_CHANNEL) { + if (soundDeviceBusyDlg(&retryClicked) != QDialog::Accepted) { + exit(0); + } + } else if (result != SOUNDDEVICE_ERROR_OK) { + if (soundDeviceErrorMsgDlg(result, &retryClicked) != + QDialog::Accepted) { + exit(0); + } + } + } while (retryClicked); + + // test for at least one out device, if none, display another dlg that + // says "mixxx will barely work with no outs" + // In case persisting errors, the user has already received a message + // box from the preferences dialog above. So we can watch here just the + // output count. + while (m_pCoreServices->getSoundManager()->getConfig().getOutputs().count() == 0) { + // 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; + } + } + + // 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); + + // 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(m_pCoreServices->getPlayerManager().get(), + &PlayerManager::noMicrophoneInputConfigured, + this, + &MixxxMainWindow::slotNoMicrophoneInputConfigured); + connect(m_pCoreServices->getPlayerManager().get(), + &PlayerManager::noAuxiliaryInputConfigured, + this, + &MixxxMainWindow::slotNoAuxiliaryInputConfigured); + connect(m_pCoreServices->getPlayerManager().get(), + &PlayerManager::noDeckPassthroughInputConfigured, + this, + &MixxxMainWindow::slotNoDeckPassthroughInputConfigured); + connect(m_pCoreServices->getPlayerManager().get(), + &PlayerManager::noVinylControlInputConfigured, + this, + &MixxxMainWindow::slotNoVinylControlInputConfigured); + + connect(&PlayerInfo::instance(), + &PlayerInfo::currentPlayingTrackChanged, + this, + &MixxxMainWindow::slotUpdateWindowTitle); + connect(&PlayerInfo::instance(), + &PlayerInfo::currentPlayingDeckChanged, + this, + &MixxxMainWindow::slotChangedPlayingDeck); +} + +MixxxMainWindow::~MixxxMainWindow() { + Timer t("~MixxxMainWindow"); + t.start(); + + if (m_inhibitScreensaver != mixxx::ScreenSaverPreference::PREVENT_OFF) { + mixxx::ScreenSaverHelper::uninhibit(); + } + + // 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://bugs.launchpad.net/mixxx/+bug/1882474 + // https://bugs.launchpad.net/mixxx/+bug/1909485 + // So let's quit fullscreen if StartInFullscreen is not checked in Preferences. + bool fullscreenPref = m_pCoreServices->getSettings()->getValue<bool>( + ConfigKey("[Config]", "StartInFullscreen")); + if (isFullScreen() && !fullscreenPref) { + slotViewFullScreen(false); + // After returning from fullscreen the main window incl. window decoration + // may be too large for the screen. + // Maximize the window so we can store a geometry that fits the screen. + 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"; + m_pCentralWidget = nullptr; + QPointer<QWidget> 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"; + + QPointer<WMainMenuBar> 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 pinter 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"; + if (m_pDeveloperToolsDlg) { + 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; + + WaveformWidgetFactory::destroy(); + + delete m_pGuiTick; + delete m_pVisualsManager; + + if (m_inhibitScreensaver != mixxx::ScreenSaverPreference::PREVENT_OFF) { + mixxx::ScreenSaverHelper::uninhibit(); + } + + m_pCoreServices->shutdown(); +} + +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) + restoreGeometry(QByteArray::fromBase64( + m_pCoreServices->getSettings() + ->getValueString(ConfigKey("[MainWindow]", "geometry")) + .toUtf8())); + restoreState(QByteArray::fromBase64( + m_pCoreServices->getSettings() + ->getValueString(ConfigKey("[MainWindow]", "state")) + .toUtf8())); + + setWindowIcon(QIcon(":/images/icons/mixxx.svg")); + slotUpdateWindowTitle(TrackPointer()); +} + +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) { + QDesktopServices::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); + 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( + "<html> <p>" % + tr("Mixxx was unable to open all the configured sound devices.") + + "</p> <p>" % + m_pCoreServices->getSoundManager()->getErrorDeviceName() % + " is used by another application or not plugged in." + "</p><ul>" + "<li>" % + tr("<b>Retry</b> after closing the other application " + "or reconnecting a sound device") % + "</li>" + "<li>" % + tr("<b>Reconfigure</b> Mixxx's sound device settings.") % + "</li>" + "<li>" % + tr("Get <b>Help</b> from the Mixxx Wiki.") % + "</li>" + "<li>" % + tr("<b>Exit</b> Mixxx.") % + "</li>" + "</ul></html>"); + return soundDeviceErrorDlg(title, text, retryClicked); +} + + +QDialog::DialogCode MixxxMainWindow::soundDeviceErrorMsgDlg( + SoundDeviceError err, bool* retryClicked) { + QString title(tr("Sound Device Error")); + QString text("<html> <p>" % + tr("Mixxx was unable to open all the configured sound " + "devices.") + + "</p> <p>" % + m_pCoreServices->getSoundManager() + ->getLastErrorMessage(err) + .replace("\n", "<br/>") % + "</p><ul>" + "<li>" % + tr("<b>Retry</b> after fixing an issue") % + "</li>" + "<li>" % + tr("<b>Reconfigure</b> Mixxx's sound device settings.") % + "</li>" + "<li>" % + tr("Get <b>Help</b> from the Mixxx Wiki.") % + "</li>" + "<li>" % + tr("<b>Exit</b> Mixxx.") % + "</li>" + "</ul></html>"); + 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( + "<html>" + tr("Mixxx was configured without any output sound devices. " + "Audio processing will be disabled without a configured output device.") + + "<ul>" + "<li>" + + tr("<b>Continue</b> without any outputs.") + + "</li>" + "<li>" + + tr("<b>Reconfigure</b> Mixxx's sound device settings.") + + "</li>" + "<li>" + + tr("<b>Exit</b> Mixxx.") + + "</li>" + "</ul></html>" + ); + + 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->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(); + + // 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); + } + } + this->setWindowTitle(appTitle); +} + +void MixxxMainWindow::createMenuBar() { + ScopedTimer t("MixxxMainWindow::createMenuBar"); + DEBUG_ASSERT(m_pCoreServices->getKeyboardConfig()); + m_pMenuBar = make_parented<WMainMenuBar>( + this, m_pCoreServices->getSettings(), m_pCoreServices->getKeyboardConfig().get()); + 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("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); + + // 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()); + + // Keyboard shortcuts + connect(m_pMenuBar, + &WMainMenuBar::toggleKeyboardShortcuts, + m_pCoreServices.get(), + &mixxx::CoreServices::slotOptionsKeyboard, + Qt::UniqueConnection); + + // 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 + + if (m_pCoreServices->getPlayerManager()) { + connect(m_pCoreServices->getPlayerManager().get(), + &PlayerManager::numberOfDecksChanged, + m_pMenuBar, + &WMainMenuBar::onNumberOfDecksChanged, + Qt::UniqueConnection); + m_pMenuBar->onNumberOfDecksChanged(m_pCoreServices->getPlayerManager()->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::createCrate, + m_pCoreServices->getLibrary().get(), + &Library::slotCreateCrate, + Qt::UniqueConnection); + connect(m_pMenuBar, + &WMainMenuBar::createPlaylist, + m_pCoreServices->getLibrary().get(), + &Library::slotCreatePlaylist, + Qt::UniqueConnection); + } + +#ifdef __ENGINEPRIME__ + DEBUG_ASSERT(m_pLibraryExporter); + connect(m_pMenuBar, + &WMainMenuBar::exportLibrary, + m_pLibraryExporter.get(), + &mixxx::LibraryExporter::slotRequestExport, + Qt::UniqueConnection); +#endif +} + +void MixxxMainWindow::slotFileLoadSongPlayer(int deck) { + QString group = m_pCoreServices->getPlayerManager()->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(PREF_LEGACY_LIBRARY_DIR), + 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; + } + + if (toggle) { + showFullScreen(); +#ifdef __LINUX__ + // Fix for "No menu bar with ubuntu unity in full screen mode" Bug + // #885890 and Bug #1076789. Before touching anything here, please read + // those bugs. + createMenuBar(); + connectMenuBar(); + if (m_pMenuBar->isNativeMenuBar()) { + m_pMenuBar->setNativeMenuBar(false); + } +#endif + } else { +#ifdef __LINUX__ + createMenuBar(); + connectMenuBar(); +#endif + showNormal(); + } + emit fullScreenChanged(toggle); +} + +void MixxxMainWindow::slotOptionsPreferences() { + m_pPrefDlg->show(); + m_pPrefDlg->raise(); + m_pPrefDlg->activate |