Skip to content

Commit

Permalink
chore: add a runtime arg to allow switch between legacy and QML UI
Browse files Browse the repository at this point in the history
  • Loading branch information
acolombier committed Oct 20, 2023
1 parent bac60bb commit ac3c551
Show file tree
Hide file tree
Showing 24 changed files with 474 additions and 461 deletions.
353 changes: 182 additions & 171 deletions CMakeLists.txt

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/control/controlmodel.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "control/controlmodel.h"

#include <QStringBuilder>

#include "moc_controlmodel.cpp"

ControlModel::ControlModel(QObject* pParent)
Expand Down
2 changes: 0 additions & 2 deletions src/engine/enginebuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@
#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"
Expand Down
331 changes: 184 additions & 147 deletions src/library/librarycontrol.cpp

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@
#include "mixxxapplication.h"
#ifdef MIXXX_USE_QML
#include "qml/qmlapplication.h"
#else
#include "mixxxmainwindow.h"
#endif
#include "mixxxmainwindow.h"
#include "sources/soundsourceproxy.h"
#include "util/cmdlineargs.h"
#include "util/console.h"
Expand All @@ -27,9 +26,7 @@
namespace {

// Exit codes
#ifndef MIXXX_USE_QML
constexpr int kFatalErrorOnStartupExitCode = 1;
#endif
constexpr int kParseCmdlineArgsErrorExitCode = 2;

constexpr char kScaleFactorEnvVar[] = "QT_SCALE_FACTOR";
Expand All @@ -43,9 +40,11 @@ int runMixxx(MixxxApplication* pApp, const CmdlineArgs& args) {

int exitCode;
#ifdef MIXXX_USE_QML
mixxx::qml::QmlApplication qmlApplication(pApp, pCoreServices);
exitCode = pApp->exec();
#else
if (args.isQml()) {
mixxx::qml::QmlApplication qmlApplication(pApp, pCoreServices);
exitCode = pApp->exec();
} else
#endif
{
// This scope ensures that `MixxxMainWindow` is destroyed *before*
// CoreServices is shut down. Otherwise a debug assertion complaining about
Expand Down Expand Up @@ -82,7 +81,6 @@ int runMixxx(MixxxApplication* pApp, const CmdlineArgs& args) {
exitCode = pApp->exec();
}
}
#endif
return exitCode;
}

Expand Down
2 changes: 0 additions & 2 deletions src/mixer/basetrackplayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@
#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 {

Expand Down
4 changes: 0 additions & 4 deletions src/preferences/dialog/dlgpreferences.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@
#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"
Expand Down Expand Up @@ -158,7 +156,6 @@ 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);
Expand All @@ -173,7 +170,6 @@ DlgPreferences::DlgPreferences(
&DlgPreferences::reloadUserInterface,
Qt::DirectConnection);
}
#endif

addPageWidget(PreferencesPage(
new DlgPrefColors(this, m_pConfig, pLibrary),
Expand Down
26 changes: 17 additions & 9 deletions src/qml/qmlapplication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
#include "control/controlsortfiltermodel.h"
#include "controllers/controllermanager.h"
#include "moc_qmlapplication.cpp"
#include "preferences/dialog/dlgpreferences.h"
#include "qml/asyncimageprovider.h"
#include "qml/qmlconfigproxy.h"
#include "qml/qmlcontrolproxy.h"
Expand Down Expand Up @@ -63,6 +62,10 @@ QmlApplication::QmlApplication(
// 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.
Expand All @@ -71,13 +74,10 @@ QmlApplication::QmlApplication(
// 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.
QmlDlgPreferencesProxy::s_pInstance = new QmlDlgPreferencesProxy(pDlgPreferences, this);
QmlEffectsManagerProxy::s_pInstance = new QmlEffectsManagerProxy(
pCoreServices->getEffectsManager(), this);
QmlPlayerManagerProxy::s_pInstance =
new QmlPlayerManagerProxy(pCoreServices->getPlayerManager(), this);
QmlConfigProxy::s_pInstance = new QmlConfigProxy(pCoreServices->getSettings(), this);
QmlLibraryProxy::s_pInstance = new QmlLibraryProxy(pCoreServices->getLibrary(), this);
QmlEffectsManagerProxy::registerEffectsManager(pCoreServices->getEffectsManager());
QmlPlayerManagerProxy::registerPlayerManager(pCoreServices->getPlayerManager());
QmlConfigProxy::registerUserSettings(pCoreServices->getSettings());
QmlLibraryProxy::registerLibrary(pCoreServices->getLibrary());

loadQml(m_mainFilePath);

Expand All @@ -89,12 +89,20 @@ 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.
m_pAppEngine = std::make_unique<QQmlApplicationEngine>();

const QFileInfo fileInfo(path);
m_pAppEngine->addImportPath(QStringLiteral(":/mixxx.org/imports"));

// No memory leak here, the QQmlEngine takes ownership of the provider
Expand Down
2 changes: 1 addition & 1 deletion src/qml/qmlapplication.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class QmlApplication : public QObject {
QmlApplication(
QApplication* app,
std::shared_ptr<CoreServices> pCoreServices);
~QmlApplication() override = default;
~QmlApplication() override;

public slots:
void loadQml(const QString& path);
Expand Down
21 changes: 4 additions & 17 deletions src/qml/qmlconfigproxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,30 +33,17 @@ QVariantList QmlConfigProxy::getTrackColorPalette() {

// static
QmlConfigProxy* QmlConfigProxy::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.
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;
VERIFY_OR_DEBUG_ASSERT(s_pUserSettings) {
qWarning() << "UserSettings hasn't been registered yet";
return nullptr;
}

// Explicitly specify C++ ownership so that the engine doesn't delete
// the instance.
QJSEngine::setObjectOwnership(s_pInstance, QJSEngine::CppOwnership);
return s_pInstance;
return new QmlConfigProxy(s_pUserSettings, pQmlEngine);
}

} // namespace qml
Expand Down
7 changes: 5 additions & 2 deletions src/qml/qmlconfigproxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ class QmlConfigProxy : public QObject {
Q_INVOKABLE QVariantList getTrackColorPalette();

static QmlConfigProxy* create(QQmlEngine* pQmlEngine, QJSEngine* pJsEngine);
static inline QmlConfigProxy* s_pInstance = nullptr;
static inline void registerUserSettings(UserSettingsPointer pConfig) {
s_pUserSettings = std::move(pConfig);
}

private:
static inline QJSEngine* s_pJsEngine = nullptr;
static inline UserSettingsPointer s_pUserSettings = nullptr;

const UserSettingsPointer m_pConfig;
};

Expand Down
21 changes: 4 additions & 17 deletions src/qml/qmleffectsmanagerproxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,30 +47,17 @@ QmlEffectSlotProxy* QmlEffectsManagerProxy::getEffectSlot(int unitNumber, int ef
// static
QmlEffectsManagerProxy* QmlEffectsManagerProxy::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.
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;
VERIFY_OR_DEBUG_ASSERT(s_pEffectManager) {
qWarning() << "EffectManager hasn't been registered yet";
return nullptr;
}

// Explicitly specify C++ ownership so that the engine doesn't delete
// the instance.
QJSEngine::setObjectOwnership(s_pInstance, QJSEngine::CppOwnership);
return s_pInstance;
return new QmlEffectsManagerProxy(s_pEffectManager, pQmlEngine);
}

} // namespace qml
Expand Down
7 changes: 5 additions & 2 deletions src/qml/qmleffectsmanagerproxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@ class QmlEffectsManagerProxy : public QObject {
int unitNumber, int effectNumber) const;

static QmlEffectsManagerProxy* create(QQmlEngine* pQmlEngine, QJSEngine* pJsEngine);
static inline QmlEffectsManagerProxy* s_pInstance = nullptr;
static void registerEffectsManager(std::shared_ptr<EffectsManager> pEffectsManager) {
s_pEffectManager = std::move(pEffectsManager);
}

private:
static inline QJSEngine* s_pJsEngine = nullptr;
static inline std::shared_ptr<EffectsManager> s_pEffectManager;

const std::shared_ptr<EffectsManager> m_pEffectsManager;
QmlVisibleEffectsModel* m_pVisibleEffectsModel;
};
Expand Down
21 changes: 4 additions & 17 deletions src/qml/qmllibraryproxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,17 @@ QmlLibraryProxy::QmlLibraryProxy(std::shared_ptr<Library> pLibrary, QObject* par

// static
QmlLibraryProxy* QmlLibraryProxy::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.
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;
VERIFY_OR_DEBUG_ASSERT(s_pLibrary) {
qWarning() << "Library hasn't been registered yet";
return nullptr;
}

// Explicitly specify C++ ownership so that the engine doesn't delete
// the instance.
QJSEngine::setObjectOwnership(s_pInstance, QJSEngine::CppOwnership);
return s_pInstance;
return new QmlLibraryProxy(s_pLibrary, pQmlEngine);
}

} // namespace qml
Expand Down
7 changes: 5 additions & 2 deletions src/qml/qmllibraryproxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ class QmlLibraryProxy : public QObject {
explicit QmlLibraryProxy(std::shared_ptr<Library> pLibrary, QObject* parent = nullptr);

static QmlLibraryProxy* create(QQmlEngine* pQmlEngine, QJSEngine* pJsEngine);
static inline QmlLibraryProxy* s_pInstance = nullptr;
static void registerLibrary(std::shared_ptr<Library> pLibrary) {
s_pLibrary = std::move(pLibrary);
}

private:
static inline QJSEngine* s_pJsEngine = nullptr;
static inline std::shared_ptr<Library> s_pLibrary;

std::shared_ptr<Library> m_pLibrary;

/// This needs to be a plain pointer because it's used as a `Q_PROPERTY` member variable.
Expand Down
21 changes: 4 additions & 17 deletions src/qml/qmlplayermanagerproxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,30 +61,17 @@ void QmlPlayerManagerProxy::loadLocationToPlayer(

// static
QmlPlayerManagerProxy* QmlPlayerManagerProxy::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.
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;
VERIFY_OR_DEBUG_ASSERT(s_pPlayerManager) {
qWarning() << "PlayerManager hasn't been registered yet";
return nullptr;
}

// Explicitly specify C++ ownership so that the engine doesn't delete
// the instance.
QJSEngine::setObjectOwnership(s_pInstance, QJSEngine::CppOwnership);
return s_pInstance;
return new QmlPlayerManagerProxy(s_pPlayerManager, pQmlEngine);
}

} // namespace qml
Expand Down
8 changes: 6 additions & 2 deletions src/qml/qmlplayermanagerproxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <QtQml>

#include "mixer/playermanager.h"
#include "qml/qmlplayerproxy.h"

namespace mixxx {
namespace qml {
Expand All @@ -25,10 +26,13 @@ class QmlPlayerManagerProxy : public QObject {
const QString& location, const QString& group, bool play = false);

static QmlPlayerManagerProxy* create(QQmlEngine* pQmlEngine, QJSEngine* pJsEngine);
static inline QmlPlayerManagerProxy* s_pInstance = nullptr;
static void registerPlayerManager(std::shared_ptr<PlayerManager> pPlayerManager) {
s_pPlayerManager = std::move(pPlayerManager);
}

private:
static inline QJSEngine* s_pJsEngine = nullptr;
static inline std::shared_ptr<PlayerManager> s_pPlayerManager;

const std::shared_ptr<PlayerManager> m_pPlayerManager;
};

Expand Down
2 changes: 1 addition & 1 deletion src/qml/qmlwaveformoverview.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class QmlWaveformOverview : public QQuickPaintedItem {
QmlWaveformOverview(QQuickItem* parent = nullptr);
~QmlWaveformOverview() override = default;

void paint(QPainter* painter);
void paint(QPainter* painter) override;

void setPlayer(QmlPlayerProxy* player);
QmlPlayerProxy* getPlayer() const;
Expand Down
Loading

0 comments on commit ac3c551

Please sign in to comment.