From 5f99ab2c22fe9d9e9afa00edae9b2dff2ef70762 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Thu, 19 Oct 2023 20:29:12 +0100 Subject: [PATCH] chore: keep the option not to have QML and Qt 5 --- .github/workflows/build.yml | 22 +++ CMakeLists.txt | 194 ++++++++++++---------- src/control/controlmodel.cpp | 2 + src/control/controlsortfiltermodel.h | 4 + src/coreservices.cpp | 35 ---- src/coreservices.h | 5 +- src/engine/enginebuffer.cpp | 2 + src/library/librarycontrol.cpp | 64 +++++-- src/main.cpp | 9 +- src/mixer/basetrackplayer.cpp | 2 + src/preferences/dialog/dlgpreferences.cpp | 4 + src/qml/asyncimageprovider.cpp | 2 +- src/qml/qmlapplication.cpp | 39 ++++- src/qml/qmlapplication.h | 2 +- src/qml/qmldlgpreferencesproxy.cpp | 21 ++- src/qml/qmldlgpreferencesproxy.h | 15 +- src/skin/legacy/legacyskinparser.cpp | 30 ++++ src/test/controlobjectaliastest.cpp | 2 + src/util/cmdlineargs.cpp | 5 +- src/util/cmdlineargs.h | 4 + src/waveform/visualplayposition.h | 4 - 21 files changed, 303 insertions(+), 164 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0df7db1e251a..e96b5e406953 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,6 +36,28 @@ jobs: artifacts_path: build/*.deb artifacts_slug: ubuntu-jammy qt_qpa_platform: offscreen + - name: Ubuntu 22.04 (QML) + os: ubuntu-22.04 + cmake_args: >- + -DQT6=ON + -DQML=ON + -DBULK=ON + -DFFMPEG=ON + -DLOCALECOMPARE=ON + -DMAD=ON + -DMODPLUG=ON + -DWAVPACK=ON + -DINSTALL_USER_UDEV_RULES=OFF + ctest_args: [] + compiler_cache: ccache + compiler_cache_path: ~/.ccache + cpack_generator: DEB + buildenv_basepath: /home/runner/buildenv + buildenv_script: tools/debian_buildenv.sh + artifacts_name: Ubuntu 22.04 Qt6 DEB + artifacts_path: build/*.deb + artifacts_slug: ubuntu-jammy + qt_qpa_platform: offscreen - name: macOS 11 x64 os: macos-11 cmake_args: >- diff --git a/CMakeLists.txt b/CMakeLists.txt index 28973526bb01..7c7bc6b9fca2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,17 +127,21 @@ if(NOT CMAKE_CONFIGURATION_TYPES) endif() endif() -set(QT6 ON) +option(QT6 "Build with Qt6" ON) +option(QML "Build with QML" ON) option(QOPENGL "Use QOpenGLWindow based widget instead of QGLWidget" ON) -if(NOT QT6) - message(FATAL_ERROR "Building requires QT6=ON") +if(QML AND NOT QT6) + message(FATAL_ERROR "Building with option QML=ON requires QT6=ON") endif() if(QOPENGL) add_compile_definitions(MIXXX_USE_QOPENGL) endif() +if(QML) + add_compile_definitions(MIXXX_USE_QML) +endif() if(APPLE) # Check if xcode-select is installed @@ -1192,7 +1196,10 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/widget/wwidgetgroup.cpp src/widget/wwidgetstack.cpp ) -set(MIXXX_PRECOMPILED_HEADER +set(MIXXX_COMMON_PRECOMPILED_HEADER + src/util/assert.h +) +target_precompile_headers(mixxx-lib PUBLIC src/audio/frame.h src/audio/signalinfo.h src/audio/streaminfo.h @@ -1234,7 +1241,6 @@ set(MIXXX_PRECOMPILED_HEADER src/track/trackrecord.h src/track/trackref.h src/util/alphabetafilter.h - src/util/assert.h src/util/battery/battery.h src/util/cache.h src/util/circularbuffer.h @@ -1349,6 +1355,7 @@ set(MIXXX_PRECOMPILED_HEADER src/util/workerthread.h src/util/workerthreadscheduler.h src/util/xml.h + ${MIXXX_COMMON_PRECOMPILED_HEADER} ) target_sources(mixxx-lib PRIVATE src/mixxxmainwindow.cpp @@ -1402,6 +1409,12 @@ target_sources(mixxx-lib PRIVATE src/widget/wvumeterlegacy.cpp src/widget/wwaveformviewer.cpp ) +if (NOT QML) + target_sources(mixxx-lib PRIVATE + src/control/controlmodel.cpp + src/control/controlsortfiltermodel.cpp + ) +endif() if(QOPENGL) target_sources(mixxx-lib PRIVATE src/shaders/endoftrackshader.cpp @@ -2532,8 +2545,10 @@ if(QT6) else() find_package(QT 5.12 NAMES Qt5 COMPONENTS Core REQUIRED) endif() -list(APPEND QT_EXTRA_COMPONENTS "Quick") -list(APPEND QT_EXTRA_COMPONENTS "QuickControls2") +if(QML) + list(APPEND QT_EXTRA_COMPONENTS "Quick") + list(APPEND QT_EXTRA_COMPONENTS "QuickControls2") +endif() find_package(Qt${QT_VERSION_MAJOR} COMPONENTS ${QT_COMPONENTS} @@ -2542,89 +2557,94 @@ find_package(Qt${QT_VERSION_MAJOR} ) # PUBLIC is required below to find included headers foreach(COMPONENT ${QT_COMPONENTS}) - target_link_libraries(mixxx-lib PUBLIC Qt6::${COMPONENT}) + target_link_libraries(mixxx-lib PUBLIC Qt${QT_VERSION_MAJOR}::${COMPONENT}) endforeach() if(QT_EXTRA_COMPONENTS) foreach(COMPONENT ${QT_EXTRA_COMPONENTS}) - target_link_libraries(mixxx-lib PUBLIC Qt6::${COMPONENT}) + target_link_libraries(mixxx-lib PUBLIC Qt${QT_VERSION_MAJOR}::${COMPONENT}) endforeach() endif() -set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml) -qt_add_library(mixxx-qml-lib STATIC) -foreach(COMPONENT ${QT_COMPONENTS}) - target_link_libraries(mixxx-qml-lib PUBLIC Qt6::${COMPONENT}) -endforeach() -if(QT_EXTRA_COMPONENTS) - foreach(COMPONENT ${QT_EXTRA_COMPONENTS}) - target_link_libraries(mixxx-qml-lib PUBLIC Qt6::${COMPONENT}) +if(QML) + set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml) + qt_add_library(mixxx-qml-lib STATIC) + foreach(COMPONENT ${QT_COMPONENTS}) + target_link_libraries(mixxx-qml-lib PUBLIC Qt${QT_VERSION_MAJOR}::${COMPONENT}) endforeach() -endif() -set_target_properties(mixxx-qml-lib PROPERTIES AUTOMOC ON) -qt_add_qml_module(mixxx-qml-lib - URI Mixxx - VERSION 1.0 - RESOURCE_PREFIX /mixxx.org/imports - IMPORTS QtQuick - QML_FILES - res/qml/Mixxx/MathUtils.mjs - res/qml/Mixxx/PlayerDropArea.qml -) -target_link_libraries(mixxx-lib PRIVATE mixxx-qml-lib) - -# FIXME: Currently we need to add these include directories due to -# QTBUG-87221. We should figure out a better way to fix this. -# See: https://bugreports.qt.io/browse/QTBUG-87221 -target_include_directories(mixxx-qml-lib PRIVATE src/control src/qml) -target_include_directories(mixxx-qml-lib PUBLIC src/ ${CMAKE_BINARY_DIR}/src) -target_include_directories(mixxx-qml-lib SYSTEM PUBLIC lib/rigtorp/SPSCQueue/include lib/portaudio) - -target_link_libraries(mixxx-qml-lib PUBLIC mixxx-proto) -target_link_libraries(mixxx-qml-libplugin PUBLIC mixxx-proto) - -target_precompile_headers(mixxx-qml-lib PUBLIC ${MIXXX_PRECOMPILED_HEADER}) - -target_link_libraries(mixxx-qml-lib PRIVATE mixxx-qml-libplugin) - -qt_add_library(mixxx-qml-mixxxcontrols STATIC) -set_target_properties(mixxx-qml-mixxxcontrols PROPERTIES AUTOMOC ON) -qt_add_qml_module(mixxx-qml-mixxxcontrols - URI Mixxx.Controls - VERSION 1.0 - RESOURCE_PREFIX /mixxx.org/imports - OPTIONAL_IMPORTS Mixxx - QML_FILES - res/qml/Mixxx/Controls/Knob.qml - res/qml/Mixxx/Controls/Slider.qml - res/qml/Mixxx/Controls/Spinny.qml - res/qml/Mixxx/Controls/WaveformOverviewHotcueMarker.qml - res/qml/Mixxx/Controls/WaveformOverviewMarker.qml - res/qml/Mixxx/Controls/WaveformOverview.qml -) -target_link_libraries(mixxx-qml-lib PRIVATE mixxx-qml-mixxxcontrolsplugin) - -target_sources(mixxx-qml-lib PRIVATE - src/qml/asyncimageprovider.cpp - src/qml/qmlapplication.cpp - src/qml/qmlcontrolproxy.cpp - src/qml/qmlconfigproxy.cpp - src/qml/qmldlgpreferencesproxy.cpp - src/qml/qmleffectmanifestparametersmodel.cpp - src/qml/qmleffectsmanagerproxy.cpp - src/qml/qmleffectslotproxy.cpp - src/qml/qmllibraryproxy.cpp - src/qml/qmllibrarytracklistmodel.cpp - src/qml/qmlplayermanagerproxy.cpp - src/qml/qmlplayerproxy.cpp - src/qml/qmlvisibleeffectsmodel.cpp - src/qml/qmlwaveformoverview.cpp - src/control/controlmodel.cpp - src/control/controlsortfiltermodel.cpp -) + if(QT_EXTRA_COMPONENTS) + foreach(COMPONENT ${QT_EXTRA_COMPONENTS}) + target_link_libraries(mixxx-qml-lib PUBLIC Qt${QT_VERSION_MAJOR}::${COMPONENT}) + endforeach() + endif() + set_target_properties(mixxx-qml-lib PROPERTIES AUTOMOC ON) + qt_add_qml_module(mixxx-qml-lib + URI Mixxx + VERSION 1.0 + RESOURCE_PREFIX /mixxx.org/imports + IMPORTS QtQuick + QML_FILES + res/qml/Mixxx/MathUtils.mjs + res/qml/Mixxx/PlayerDropArea.qml + ) + target_link_libraries(mixxx-lib PRIVATE mixxx-qml-lib) + + # FIXME: Currently we need to add these include directories due to + # QTBUG-87221. We should figure out a better way to fix this. + # See: https://bugreports.qt.io/browse/QTBUG-87221 + target_include_directories(mixxx-qml-lib PRIVATE src/control src/qml) + target_include_directories(mixxx-qml-lib PUBLIC src/ ${CMAKE_BINARY_DIR}/src) + target_include_directories(mixxx-qml-lib SYSTEM PUBLIC lib/rigtorp/SPSCQueue/include lib/portaudio) -# qt_finalize_target takes care that the resources :/mixxx.org/imports/Mixxx/ -# and :/mixxx.org/imports/Mixxx/Controls are placed into beginning of the binary -qt_finalize_target(mixxx) + target_link_libraries(mixxx-qml-lib PUBLIC mixxx-proto) + target_link_libraries(mixxx-qml-libplugin PUBLIC mixxx-proto) + + target_precompile_headers(mixxx-qml-lib PUBLIC + ${MIXXX_COMMON_PRECOMPILED_HEADER} + ) + + target_link_libraries(mixxx-qml-lib PRIVATE mixxx-qml-libplugin) + + qt_add_library(mixxx-qml-mixxxcontrols STATIC) + set_target_properties(mixxx-qml-mixxxcontrols PROPERTIES AUTOMOC ON) + qt_add_qml_module(mixxx-qml-mixxxcontrols + URI Mixxx.Controls + VERSION 1.0 + RESOURCE_PREFIX /mixxx.org/imports + OPTIONAL_IMPORTS Mixxx + QML_FILES + res/qml/Mixxx/Controls/Knob.qml + res/qml/Mixxx/Controls/Slider.qml + res/qml/Mixxx/Controls/Spinny.qml + res/qml/Mixxx/Controls/WaveformOverviewHotcueMarker.qml + res/qml/Mixxx/Controls/WaveformOverviewMarker.qml + res/qml/Mixxx/Controls/WaveformOverview.qml + ) + target_link_libraries(mixxx-qml-lib PRIVATE mixxx-qml-mixxxcontrolsplugin) + + target_sources(mixxx-qml-lib PRIVATE + src/qml/asyncimageprovider.cpp + src/qml/qmlapplication.cpp + src/qml/qmlcontrolproxy.cpp + src/qml/qmlconfigproxy.cpp + src/qml/qmldlgpreferencesproxy.cpp + src/qml/qmleffectmanifestparametersmodel.cpp + src/qml/qmleffectsmanagerproxy.cpp + src/qml/qmleffectslotproxy.cpp + src/qml/qmllibraryproxy.cpp + src/qml/qmllibrarytracklistmodel.cpp + src/qml/qmlplayermanagerproxy.cpp + src/qml/qmlplayerproxy.cpp + src/qml/qmlvisibleeffectsmodel.cpp + src/qml/qmlwaveformoverview.cpp + # The following source needs to be in this target to get QML_ELEMENT properly interpreted + src/control/controlmodel.cpp + src/control/controlsortfiltermodel.cpp + ) + + # qt_finalize_target takes care that the resources :/mixxx.org/imports/Mixxx/ + # and :/mixxx.org/imports/Mixxx/Controls are placed into beginning of the binary + qt_finalize_target(mixxx) +endif() target_compile_definitions(mixxx-lib PUBLIC QT_TABLET_SUPPORT QT_USE_QSTRINGBUILDER) is_static_library(Qt_IS_STATIC Qt${QT_VERSION_MAJOR}::Core) @@ -3207,7 +3227,9 @@ if(BROADCAST) src/encoder/encoderbroadcastsettings.cpp ) target_compile_definitions(mixxx-lib PUBLIC __BROADCAST__) - target_compile_definitions(mixxx-qml-lib PUBLIC __BROADCAST__) + if (QML) + target_compile_definitions(mixxx-qml-lib PUBLIC __BROADCAST__) + endif() endif() # Opus (RFC 6716) @@ -3283,8 +3305,10 @@ endif() find_package(Microsoft.GSL CONFIG) if(Microsoft.GSL_FOUND) target_link_libraries(mixxx-lib PRIVATE Microsoft.GSL::GSL) - target_link_libraries(mixxx-qml-lib PRIVATE Microsoft.GSL::GSL) - target_link_libraries(mixxx-qml-libplugin PRIVATE Microsoft.GSL::GSL) + if (QML) + target_link_libraries(mixxx-qml-lib PRIVATE Microsoft.GSL::GSL) + target_link_libraries(mixxx-qml-libplugin PRIVATE Microsoft.GSL::GSL) + endif() else() # check if the headers have been installed without cmake config (< 3.1.0) check_include_file_cxx(gsl/gsl HAVE_GSL_GSL) diff --git a/src/control/controlmodel.cpp b/src/control/controlmodel.cpp index 23dd7c8d5845..65b69fa87355 100644 --- a/src/control/controlmodel.cpp +++ b/src/control/controlmodel.cpp @@ -1,5 +1,7 @@ #include "control/controlmodel.h" +#include + #include "moc_controlmodel.cpp" ControlModel::ControlModel(QObject* pParent) diff --git a/src/control/controlsortfiltermodel.h b/src/control/controlsortfiltermodel.h index b17f8e6027b1..62db5a012b38 100644 --- a/src/control/controlsortfiltermodel.h +++ b/src/control/controlsortfiltermodel.h @@ -2,7 +2,11 @@ #include #include +#ifdef MIXXX_USE_QML #include +#else +#define QML_ELEMENT +#endif #include "control/controlmodel.h" diff --git a/src/coreservices.cpp b/src/coreservices.cpp index f9fd3a476fa4..a63599fe6d82 100644 --- a/src/coreservices.cpp +++ b/src/coreservices.cpp @@ -27,14 +27,6 @@ #ifdef __MODPLUG__ #include "preferences/dialog/dlgprefmodplug.h" #endif -#include "qml/qmlconfigproxy.h" -#include "qml/qmlcontrolproxy.h" -#include "qml/qmldlgpreferencesproxy.h" -#include "qml/qmleffectslotproxy.h" -#include "qml/qmleffectsmanagerproxy.h" -#include "qml/qmllibraryproxy.h" -#include "qml/qmlplayermanagerproxy.h" -#include "qml/qmlplayerproxy.h" #include "soundio/soundmanager.h" #include "sources/soundsourceproxy.h" #include "util/db/dbconnectionpooled.h" @@ -453,26 +445,6 @@ void CoreServices::initialize(QApplication* pApp) { } m_isInitialized = true; - initializeQMLSignletons(); -} - -void CoreServices::initializeQMLSignletons() { - // Any uncreateable non-singleton types registered here require - // arguments that we don't want to expose to QML directly. Instead, they - // can be retrieved by member properties or methods from the singleton - // types. - // - // The alternative would be to register their *arguments* in the QML - // system, which would improve nothing, or we had to expose them as - // singletons to that they can be accessed by components instantiated by - // QML, which would also be suboptimal. - mixxx::qml::QmlEffectsManagerProxy::registerEffectsManager(getEffectsManager()); - mixxx::qml::QmlPlayerManagerProxy::registerPlayerManager(getPlayerManager()); - mixxx::qml::QmlConfigProxy::registerUserSettings(getSettings()); - mixxx::qml::QmlLibraryProxy::registerLibrary(getLibrary()); - // FIXME: DlgPreferences has some initialization logic that must be executed - // before the GUI is shown, at least for the effects system. - mixxx::qml::QmlDlgPreferencesProxy::registerDlgPreferences(makeDlgPreferences()); } void CoreServices::initializeKeyboard() { @@ -578,13 +550,6 @@ void CoreServices::finalize() { Timer t("CoreServices::~CoreServices"); t.start(); - // Delete all the QML singletons in order to prevent leak detection - mixxx::qml::QmlEffectsManagerProxy::registerEffectsManager(nullptr); - mixxx::qml::QmlPlayerManagerProxy::registerPlayerManager(nullptr); - mixxx::qml::QmlConfigProxy::registerUserSettings(nullptr); - mixxx::qml::QmlLibraryProxy::registerLibrary(nullptr); - mixxx::qml::QmlDlgPreferencesProxy::registerDlgPreferences(nullptr); - // Stop all pending library operations qDebug() << t.elapsed(false).debugMillisWithUnit() << "stopping pending Library tasks"; m_pTrackCollectionManager->stopLibraryScan(); diff --git a/src/coreservices.h b/src/coreservices.h index aaf4c4f56f69..71f84cfce701 100644 --- a/src/coreservices.h +++ b/src/coreservices.h @@ -105,6 +105,8 @@ class CoreServices : public QObject { return m_pScreensaverManager; } + std::shared_ptr makeDlgPreferences() const; + signals: void initializationProgressUpdate(int progress, const QString& serviceName); @@ -117,9 +119,6 @@ class CoreServices : public QObject { void initializeSettings(); void initializeScreensaverManager(); void initializeLogging(); - void initializeQMLSignletons(); - - std::shared_ptr makeDlgPreferences() const; /// Tear down CoreServices that were previously initialized by `initialize()`. void finalize(); diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index c1337e26a59e..b6a9be0b289f 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -36,7 +36,9 @@ #include "util/sample.h" #include "util/timer.h" #include "waveform/visualplayposition.h" +#ifndef MIXXX_USE_QML #include "waveform/waveformwidgetfactory.h" +#endif #ifdef __VINYLCONTROL__ #include "engine/controls/vinylcontrolcontrol.h" diff --git a/src/library/librarycontrol.cpp b/src/library/librarycontrol.cpp index 781c4faca1ee..60c2ef634553 100644 --- a/src/library/librarycontrol.cpp +++ b/src/library/librarycontrol.cpp @@ -82,7 +82,10 @@ LibraryControl::LibraryControl(Library* pLibrary) m_pMoveUp = std::make_unique(ConfigKey("[Library]", "MoveUp")); m_pMoveDown = std::make_unique(ConfigKey("[Library]", "MoveDown")); m_pMoveVertical = std::make_unique(ConfigKey("[Library]", "MoveVertical"), false); - if (!CmdlineArgs::Instance().getQml()) { +#ifdef MIXXX_USE_QML + if (!CmdlineArgs::Instance().getQml()) +#endif + { connect(m_pMoveUp.get(), &ControlPushButton::valueChanged, this, @@ -101,7 +104,10 @@ LibraryControl::LibraryControl(Library* pLibrary) m_pScrollUp = std::make_unique(ConfigKey("[Library]", "ScrollUp")); m_pScrollDown = std::make_unique(ConfigKey("[Library]", "ScrollDown")); m_pScrollVertical = std::make_unique(ConfigKey("[Library]", "ScrollVertical"), false); - if (!CmdlineArgs::Instance().getQml()) { +#ifdef MIXXX_USE_QML + if (!CmdlineArgs::Instance().getQml()) +#endif + { connect(m_pScrollUp.get(), &ControlPushButton::valueChanged, this, @@ -120,7 +126,10 @@ LibraryControl::LibraryControl(Library* pLibrary) m_pMoveLeft = std::make_unique(ConfigKey("[Library]", "MoveLeft")); m_pMoveRight = std::make_unique(ConfigKey("[Library]", "MoveRight")); m_pMoveHorizontal = std::make_unique(ConfigKey("[Library]", "MoveHorizontal"), false); - if (!CmdlineArgs::Instance().getQml()) { +#ifdef MIXXX_USE_QML + if (!CmdlineArgs::Instance().getQml()) +#endif + { connect(m_pMoveLeft.get(), &ControlPushButton::valueChanged, this, @@ -140,7 +149,10 @@ LibraryControl::LibraryControl(Library* pLibrary) m_pMoveFocusForward = std::make_unique(ConfigKey("[Library]", "MoveFocusForward")); m_pMoveFocusBackward = std::make_unique(ConfigKey("[Library]", "MoveFocusBackward")); m_pMoveFocus = std::make_unique(ConfigKey("[Library]", "MoveFocus"), false); - if (!CmdlineArgs::Instance().getQml()) { +#ifdef MIXXX_USE_QML + if (!CmdlineArgs::Instance().getQml()) +#endif + { connect(m_pMoveFocusForward.get(), &ControlPushButton::valueChanged, this, @@ -159,7 +171,10 @@ LibraryControl::LibraryControl(Library* pLibrary) m_pFocusedWidgetCO = std::make_unique( ConfigKey("[Library]", "focused_widget")); m_pFocusedWidgetCO->setStates(static_cast(FocusWidget::Count)); - if (!CmdlineArgs::Instance().getQml()) { +#ifdef MIXXX_USE_QML + if (!CmdlineArgs::Instance().getQml()) +#endif + { m_pFocusedWidgetCO->connectValueChangeRequest( this, [this](double value) { @@ -183,14 +198,20 @@ LibraryControl::LibraryControl(Library* pLibrary) m_pRefocusPrevWidgetCO = std::make_unique( ConfigKey("[Library]", "refocus_prev_widget")); m_pRefocusPrevWidgetCO->setButtonMode(ControlPushButton::TRIGGER); - if (!CmdlineArgs::Instance().getQml()) { +#ifdef MIXXX_USE_QML + if (!CmdlineArgs::Instance().getQml()) +#endif + { m_pRefocusPrevWidgetCO->connectValueChangeRequest(this, &LibraryControl::refocusPrevLibraryWidget); } // Control to "goto" the currently selected item in focused widget (context dependent) m_pGoToItem = std::make_unique(ConfigKey("[Library]", "GoToItem")); - if (!CmdlineArgs::Instance().getQml()) { +#ifdef MIXXX_USE_QML + if (!CmdlineArgs::Instance().getQml()) +#endif + { connect(m_pGoToItem.get(), &ControlPushButton::valueChanged, this, @@ -199,7 +220,12 @@ LibraryControl::LibraryControl(Library* pLibrary) // Auto DJ controls m_pAutoDjAddTop = std::make_unique(ConfigKey("[Library]","AutoDjAddTop")); - if (!CmdlineArgs::Instance().getQml()) { + m_pAutoDjAddTop->addAlias(ConfigKey( + QStringLiteral("[Playlist]"), QStringLiteral("AutoDjAddTop"))); +#ifdef MIXXX_USE_QML + if (!CmdlineArgs::Instance().getQml()) +#endif + { connect(m_pAutoDjAddTop.get(), &ControlPushButton::valueChanged, this, @@ -207,7 +233,12 @@ LibraryControl::LibraryControl(Library* pLibrary) } m_pAutoDjAddBottom = std::make_unique(ConfigKey("[Library]","AutoDjAddBottom")); - if (!CmdlineArgs::Instance().getQml()) { + m_pAutoDjAddBottom->addAlias(ConfigKey( + QStringLiteral("[Playlist]"), QStringLiteral("AutoDjAddBottom"))); +#ifdef MIXXX_USE_QML + if (!CmdlineArgs::Instance().getQml()) +#endif + { connect(m_pAutoDjAddBottom.get(), &ControlPushButton::valueChanged, this, @@ -216,7 +247,10 @@ LibraryControl::LibraryControl(Library* pLibrary) m_pAutoDjAddReplace = std::make_unique( ConfigKey("[Library]", "AutoDjAddReplace")); - if (!CmdlineArgs::Instance().getQml()) { +#ifdef MIXXX_USE_QML + if (!CmdlineArgs::Instance().getQml()) +#endif + { connect(m_pAutoDjAddReplace.get(), &ControlPushButton::valueChanged, this, @@ -230,7 +264,10 @@ LibraryControl::LibraryControl(Library* pLibrary) m_pSortColumnToggle = std::make_unique(ConfigKey("[Library]", "sort_column_toggle"), false); m_pSortFocusedColumn = std::make_unique( ConfigKey("[Library]", "sort_focused_column")); - if (!CmdlineArgs::Instance().getQml()) { +#ifdef MIXXX_USE_QML + if (!CmdlineArgs::Instance().getQml()) +#endif + { connect(m_pSortColumn.get(), &ControlEncoder::valueChanged, this, @@ -419,7 +456,10 @@ LibraryControl::LibraryControl(Library* pLibrary) this, &LibraryControl::slotLoadSelectedIntoFirstStopped); - if (!CmdlineArgs::Instance().getQml()) { +#ifdef MIXXX_USE_QML + if (!CmdlineArgs::Instance().getQml()) +#endif + { QApplication* app = qApp; // Update controls if any widget in any Mixxx window gets or loses focus connect(app, diff --git a/src/main.cpp b/src/main.cpp index e4ce2ac6c0d1..061106b7b0ed 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,8 +13,10 @@ #include "coreservices.h" #include "errordialoghandler.h" #include "mixxxapplication.h" -#include "mixxxmainwindow.h" +#ifdef MIXXX_USE_QML #include "qml/qmlapplication.h" +#endif +#include "mixxxmainwindow.h" #include "sources/soundsourceproxy.h" #include "util/cmdlineargs.h" #include "util/console.h" @@ -37,10 +39,13 @@ int runMixxx(MixxxApplication* pApp, const CmdlineArgs& args) { CmdlineArgs::Instance().parseForUserFeedback(); int exitCode; +#ifdef MIXXX_USE_QML if (args.getQml()) { mixxx::qml::QmlApplication qmlApplication(pApp, pCoreServices); exitCode = pApp->exec(); - } else { + } else +#endif + { // This scope ensures that `MixxxMainWindow` is destroyed *before* // CoreServices is shut down. Otherwise a debug assertion complaining about // leaked COs may be triggered. diff --git a/src/mixer/basetrackplayer.cpp b/src/mixer/basetrackplayer.cpp index 7f37b54bc783..1a11670ad715 100644 --- a/src/mixer/basetrackplayer.cpp +++ b/src/mixer/basetrackplayer.cpp @@ -17,8 +17,10 @@ #include "track/track.h" #include "util/sandbox.h" #include "vinylcontrol/defs_vinylcontrol.h" +#ifndef MIXXX_USE_QML #include "waveform/renderers/waveformwidgetrenderer.h" #include "waveform/visualsmanager.h" +#endif namespace { diff --git a/src/preferences/dialog/dlgpreferences.cpp b/src/preferences/dialog/dlgpreferences.cpp index b35f3a138c67..243b3e0fa2c4 100644 --- a/src/preferences/dialog/dlgpreferences.cpp +++ b/src/preferences/dialog/dlgpreferences.cpp @@ -25,7 +25,9 @@ #include "preferences/dialog/dlgprefeffects.h" #include "preferences/dialog/dlgprefinterface.h" #include "preferences/dialog/dlgprefmixer.h" +#ifndef MIXXX_USE_QML #include "preferences/dialog/dlgprefwaveform.h" +#endif #ifdef __BROADCAST__ #include "preferences/dialog/dlgprefbroadcast.h" @@ -156,6 +158,7 @@ DlgPreferences::DlgPreferences( tr("Interface"), "ic_preferences_interface.svg"); +#ifndef MIXXX_USE_QML // ugly proxy for determining whether this is being instantiated for QML or legacy QWidgets GUI if (pSkinLoader) { DlgPrefWaveform* pWaveformPage = new DlgPrefWaveform(this, m_pConfig, pLibrary); @@ -170,6 +173,7 @@ DlgPreferences::DlgPreferences( &DlgPreferences::reloadUserInterface, Qt::DirectConnection); } +#endif addPageWidget(PreferencesPage( new DlgPrefColors(this, m_pConfig, pLibrary), diff --git a/src/qml/asyncimageprovider.cpp b/src/qml/asyncimageprovider.cpp index d6cddab73264..9162fdcca774 100644 --- a/src/qml/asyncimageprovider.cpp +++ b/src/qml/asyncimageprovider.cpp @@ -82,7 +82,7 @@ void AsyncImageResponse::run() { AsyncImageProvider::AsyncImageProvider( std::shared_ptr pTrackCollectionManager) : QQuickAsyncImageProvider(), - m_pTrackCollectionManager(std::move(pTrackCollectionManager)) { + m_pTrackCollectionManager(pTrackCollectionManager) { } QQuickImageResponse* AsyncImageProvider::requestImageResponse( diff --git a/src/qml/qmlapplication.cpp b/src/qml/qmlapplication.cpp index e83ae22e1d2b..be6d9cb14a35 100644 --- a/src/qml/qmlapplication.cpp +++ b/src/qml/qmlapplication.cpp @@ -42,8 +42,8 @@ namespace qml { QmlApplication::QmlApplication( QApplication* app, std::shared_ptr pCoreServices) - : m_pCoreServices(std::move(pCoreServices)), - m_mainFilePath(m_pCoreServices->getSettings()->getResourcePath() + kMainQmlFileName), + : m_pCoreServices(pCoreServices), + m_mainFilePath(pCoreServices->getSettings()->getResourcePath() + kMainQmlFileName), m_pAppEngine(nullptr), m_fileWatcher({m_mainFilePath}) { m_pCoreServices->initialize(app); @@ -54,9 +54,33 @@ QmlApplication::QmlApplication( exit(reInt); } + // FIXME: DlgPreferences has some initialization logic that must be executed + // before the GUI is shown, at least for the effects system. + std::shared_ptr pDlgPreferences = m_pCoreServices->makeDlgPreferences(); + // Without this, QApplication will quit when the last QWidget QWindow is + // closed because it does not take into account the window created by + // the QQmlApplicationEngine. + pDlgPreferences->setAttribute(Qt::WA_QuitOnClose, false); + // Since DlgPreferences is only meant to be used in the main QML engine, it + // follows a strict singleton pattern design + QmlDlgPreferencesProxy::s_pInstance = new QmlDlgPreferencesProxy(pDlgPreferences, this); + + // Any uncreateable non-singleton types registered here require arguments + // that we don't want to expose to QML directly. Instead, they can be + // retrieved by member properties or methods from the singleton types. + // + // The alternative would be to register their *arguments* in the QML + // system, which would improve nothing, or we had to expose them as + // singletons to that they can be accessed by components instantiated by + // QML, which would also be suboptimal. + QmlEffectsManagerProxy::registerEffectsManager(pCoreServices->getEffectsManager()); + QmlPlayerManagerProxy::registerPlayerManager(pCoreServices->getPlayerManager()); + QmlConfigProxy::registerUserSettings(pCoreServices->getSettings()); + QmlLibraryProxy::registerLibrary(pCoreServices->getLibrary()); + loadQml(m_mainFilePath); - m_pCoreServices->getControllerManager()->setUpDevices(); + pCoreServices->getControllerManager()->setUpDevices(); connect(&m_fileWatcher, &QFileSystemWatcher::fileChanged, @@ -64,6 +88,15 @@ QmlApplication::QmlApplication( &QmlApplication::loadQml); } +QmlApplication::~QmlApplication() { + // Delete all the QML singletons in order to prevent leak detection in CoreService + QmlEffectsManagerProxy::registerEffectsManager(nullptr); + QmlPlayerManagerProxy::registerPlayerManager(nullptr); + QmlConfigProxy::registerUserSettings(nullptr); + QmlLibraryProxy::registerLibrary(nullptr); + QmlDlgPreferencesProxy::s_pInstance->deleteLater(); +} + void QmlApplication::loadQml(const QString& path) { // QQmlApplicationEngine::load creates a new window but also leaves the old one, // so it is necessary to destroy the old QQmlApplicationEngine and create a new one. diff --git a/src/qml/qmlapplication.h b/src/qml/qmlapplication.h index 8b19841435ff..abdc431e45f4 100644 --- a/src/qml/qmlapplication.h +++ b/src/qml/qmlapplication.h @@ -15,7 +15,7 @@ class QmlApplication : public QObject { QmlApplication( QApplication* app, std::shared_ptr pCoreServices); - ~QmlApplication() override = default; + ~QmlApplication() override; public slots: void loadQml(const QString& path); diff --git a/src/qml/qmldlgpreferencesproxy.cpp b/src/qml/qmldlgpreferencesproxy.cpp index 10465d7c267c..78521cfbcf05 100644 --- a/src/qml/qmldlgpreferencesproxy.cpp +++ b/src/qml/qmldlgpreferencesproxy.cpp @@ -20,17 +20,30 @@ void QmlDlgPreferencesProxy::show() { QmlDlgPreferencesProxy* QmlDlgPreferencesProxy::create( QQmlEngine* pQmlEngine, QJSEngine* pJsEngine) { + Q_UNUSED(pQmlEngine); + // The implementation of this method is mostly taken from the code example // that shows the replacement for `qmlRegisterSingletonInstance()` when // using `QML_SINGLETON`. // https://doc.qt.io/qt-6/qqmlengine.html#QML_SINGLETON // The instance has to exist before it is used. We cannot replace it. - VERIFY_OR_DEBUG_ASSERT(s_pDlgPreferences) { - qWarning() << "DlgPreferences hasn't been registered yet"; - return nullptr; + DEBUG_ASSERT(s_pInstance); + + // The engine has to have the same thread affinity as the singleton. + DEBUG_ASSERT(pJsEngine->thread() == s_pInstance->thread()); + + // There can only be one engine accessing the singleton. + if (s_pJsEngine) { + DEBUG_ASSERT(pJsEngine == s_pJsEngine); + } else { + s_pJsEngine = pJsEngine; } - return new QmlDlgPreferencesProxy(s_pDlgPreferences, pQmlEngine); + + // Explicitly specify C++ ownership so that the engine doesn't delete + // the instance. + QJSEngine::setObjectOwnership(s_pInstance, QJSEngine::CppOwnership); + return s_pInstance; } } // namespace qml diff --git a/src/qml/qmldlgpreferencesproxy.h b/src/qml/qmldlgpreferencesproxy.h index 0d3b26f558fb..724c7efc5be3 100644 --- a/src/qml/qmldlgpreferencesproxy.h +++ b/src/qml/qmldlgpreferencesproxy.h @@ -20,21 +20,10 @@ class QmlDlgPreferencesProxy : public QObject { Q_INVOKABLE void show(); static QmlDlgPreferencesProxy* create(QQmlEngine* pQmlEngine, QJSEngine* pJsEngine); - static void registerDlgPreferences(std::shared_ptr pDlgPreferences) { - s_pDlgPreferences = std::move(pDlgPreferences); - - if (!s_pDlgPreferences) - return; - - // Without this, QApplication will quit when the last QWidget QWindow is - // closed because it does not take into account the window created by - // the QQmlApplicationEngine. - s_pDlgPreferences->setAttribute(Qt::WA_QuitOnClose, false); - } + static inline QmlDlgPreferencesProxy* s_pInstance = nullptr; private: - static inline std::shared_ptr s_pDlgPreferences; - + static inline QJSEngine* s_pJsEngine = nullptr; std::shared_ptr m_pDlgPreferences; }; diff --git a/src/skin/legacy/legacyskinparser.cpp b/src/skin/legacy/legacyskinparser.cpp index b185ba3e449a..c095fd505ad7 100644 --- a/src/skin/legacy/legacyskinparser.cpp +++ b/src/skin/legacy/legacyskinparser.cpp @@ -30,7 +30,9 @@ #include "util/timer.h" #include "util/valuetransformer.h" #include "util/xml.h" +#ifndef MIXXX_USE_QML #include "waveform/vsyncthread.h" +#endif #include "waveform/waveformwidgetfactory.h" #include "widget/controlwidgetconnection.h" #include "widget/wbasewidget.h" @@ -73,8 +75,10 @@ #include "widget/wsizeawarestack.h" #include "widget/wskincolor.h" #include "widget/wslidercomposed.h" +#ifndef MIXXX_USE_QML #include "widget/wspinny.h" #include "widget/wspinnyglsl.h" +#endif #include "widget/wsplitter.h" #include "widget/wstarrating.h" #include "widget/wstatuslight.h" @@ -82,9 +86,11 @@ #include "widget/wtrackproperty.h" #include "widget/wtracktext.h" #include "widget/wtrackwidgetgroup.h" +#ifndef MIXXX_USE_QML #include "widget/wvumeter.h" #include "widget/wvumeterglsl.h" #include "widget/wvumeterlegacy.h" +#endif #include "widget/wwaveformviewer.h" #include "widget/wwidget.h" #include "widget/wwidgetgroup.h" @@ -944,6 +950,11 @@ void LegacySkinParser::setupLabelWidget(const QDomElement& element, WLabel* pLab } QWidget* LegacySkinParser::parseOverview(const QDomElement& node) { +#ifdef MIXXX_USE_QML + Q_UNUSED(node); + + return nullptr; +#else QString group = lookupNodeGroup(node); BaseTrackPlayer* pPlayer = m_pPlayerManager->getPlayer(group); if (!pPlayer) { @@ -981,9 +992,15 @@ QWidget* LegacySkinParser::parseOverview(const QDomElement& node) { connect(pPlayer, &BaseTrackPlayer::loadingTrack, overviewWidget, &WOverview::slotLoadingTrack); return overviewWidget; +#endif } QWidget* LegacySkinParser::parseVisual(const QDomElement& node) { +#ifdef MIXXX_USE_QML + Q_UNUSED(node); + + return nullptr; +#else QString group = lookupNodeGroup(node); BaseTrackPlayer* pPlayer = m_pPlayerManager->getPlayer(group); if (!pPlayer) { @@ -1025,6 +1042,7 @@ QWidget* LegacySkinParser::parseVisual(const QDomElement& node) { viewer->slotTrackLoaded(pPlayer->getLoadedTrack()); return viewer; +#endif } QWidget* LegacySkinParser::parseText(const QDomElement& node) { @@ -1255,6 +1273,11 @@ QWidget* LegacySkinParser::parseRecordingDuration(const QDomElement& node) { } QWidget* LegacySkinParser::parseSpinny(const QDomElement& node) { +#ifdef MIXXX_USE_QML + Q_UNUSED(node); + + return nullptr; +#else if (CmdlineArgs::Instance().getSafeMode()) { WLabel* dummy = new WLabel(m_pParent); //: Shown when Mixxx is running in safe mode. @@ -1325,9 +1348,15 @@ QWidget* LegacySkinParser::parseSpinny(const QDomElement& node) { pSpinny->installEventFilter(m_pControllerManager->getControllerLearningEventFilter()); pSpinny->Init(); return pSpinny; +#endif } QWidget* LegacySkinParser::parseVuMeter(const QDomElement& node) { +#ifdef MIXXX_USE_QML + Q_UNUSED(node); + + return nullptr; +#else auto* pWaveformWidgetFactory = WaveformWidgetFactory::instance(); if (CmdlineArgs::Instance().getUseLegacyVuMeter() || (!pWaveformWidgetFactory->isOpenGlAvailable() && @@ -1385,6 +1414,7 @@ QWidget* LegacySkinParser::parseVuMeter(const QDomElement& node) { pVuMeterWidget->Init(); return pVuMeterWidget; +#endif } QWidget* LegacySkinParser::parseSearchBox(const QDomElement& node) { diff --git a/src/test/controlobjectaliastest.cpp b/src/test/controlobjectaliastest.cpp index 3f26eaf158c0..28c2765be663 100644 --- a/src/test/controlobjectaliastest.cpp +++ b/src/test/controlobjectaliastest.cpp @@ -24,6 +24,7 @@ const QString kSkinGroup = QStringLiteral("[Skin]"); class ControlObjectAliasTest : public MixxxTest { }; +#ifndef MIXXX_USE_QML TEST_F(ControlObjectAliasTest, GuiTick) { auto guiTick = GuiTick(); @@ -35,6 +36,7 @@ TEST_F(ControlObjectAliasTest, GuiTick) { auto period50msLegacy = ControlProxy(ConfigKey(kLegacyGroup, QStringLiteral("guiTick50ms"))); EXPECT_DOUBLE_EQ(period50ms.get(), period50msLegacy.get()); } +#endif TEST_F(ControlObjectAliasTest, ControlIndicatorTimer) { auto controlIndicatorTimer = mixxx::ControlIndicatorTimer(); diff --git a/src/util/cmdlineargs.cpp b/src/util/cmdlineargs.cpp index a677b9bbfdae..c99b65518840 100644 --- a/src/util/cmdlineargs.cpp +++ b/src/util/cmdlineargs.cpp @@ -215,12 +215,13 @@ bool CmdlineArgs::parse(const QStringList& arguments, CmdlineArgs::ParseMode mod : QString()); parser.addOption(developer); +#ifdef MIXXX_USE_QML const QCommandLineOption qml(QStringLiteral("qml"), forUserFeedback ? QCoreApplication::translate("CmdlineArgs", "Loads experimental QML GUI instead of legacy QWidget skin") : QString()); parser.addOption(qml); - +#endif const QCommandLineOption safeMode(QStringLiteral("safe-mode"), forUserFeedback ? QCoreApplication::translate("CmdlineArgs", "Enables safe-mode. Disables OpenGL waveforms, and " @@ -356,7 +357,9 @@ bool CmdlineArgs::parse(const QStringList& arguments, CmdlineArgs::ParseMode mod m_controllerDebug = parser.isSet(controllerDebug) || parser.isSet(controllerDebugDeprecated); m_controllerAbortOnWarning = parser.isSet(controllerAbortOnWarning); m_developer = parser.isSet(developer); +#ifdef MIXXX_USE_QML m_qml = parser.isSet(qml); +#endif m_safeMode = parser.isSet(safeMode) || parser.isSet(safeModeDeprecated); m_debugAssertBreak = parser.isSet(debugAssertBreak) || parser.isSet(debugAssertBreakDeprecated); diff --git a/src/util/cmdlineargs.h b/src/util/cmdlineargs.h index 062b977ec42b..d7f1602f86b4 100644 --- a/src/util/cmdlineargs.h +++ b/src/util/cmdlineargs.h @@ -38,9 +38,11 @@ class CmdlineArgs final { return m_controllerAbortOnWarning; } bool getDeveloper() const { return m_developer; } +#ifdef MIXXX_USE_QML bool getQml() const { return m_qml; } +#endif bool getSafeMode() const { return m_safeMode; } bool useColors() const { return m_useColors; @@ -84,7 +86,9 @@ class CmdlineArgs final { bool m_controllerDebug; bool m_controllerAbortOnWarning; // Controller Engine will be stricter bool m_developer; // Developer Mode +#ifdef MIXXX_USE_QML bool m_qml; +#endif bool m_safeMode; bool m_useLegacyVuMeter; bool m_useLegacySpinny; diff --git a/src/waveform/visualplayposition.h b/src/waveform/visualplayposition.h index f275a640534a..884dbdc65f4b 100644 --- a/src/waveform/visualplayposition.h +++ b/src/waveform/visualplayposition.h @@ -9,11 +9,7 @@ #include "util/performancetimer.h" class ControlProxy; -#ifdef MIXXX_USE_QML -typedef void VSyncThread; -#else class VSyncThread; -#endif // This class is for synchronizing the sound device DAC time with the waveforms, displayed on the // graphic device, using the CPU time