From b79fcbd91f03ef0c96fa5e1ab8044e378f0e9f70 Mon Sep 17 00:00:00 2001 From: Christophe Henry Date: Wed, 10 Jul 2024 13:48:20 +0200 Subject: [PATCH 01/10] QML-based components API for controllers --- res/controllers/Denon-DN-S3700.midi.xml | 15 +++++++++++++++ res/controllers/Denon-DN-S3700.qml | 17 +++++++++++++++++ .../legacy/controllerscriptenginelegacy.cpp | 1 + 3 files changed, 33 insertions(+) create mode 100644 res/controllers/Denon-DN-S3700.midi.xml create mode 100644 res/controllers/Denon-DN-S3700.qml diff --git a/res/controllers/Denon-DN-S3700.midi.xml b/res/controllers/Denon-DN-S3700.midi.xml new file mode 100644 index 000000000000..2949e1bc9a67 --- /dev/null +++ b/res/controllers/Denon-DN-S3700.midi.xml @@ -0,0 +1,15 @@ + + + + Denon DN-S3700 (QML) + christophehenry + Controller preset for Denon DN-S3700 turntable + + + + + + + + + diff --git a/res/controllers/Denon-DN-S3700.qml b/res/controllers/Denon-DN-S3700.qml new file mode 100644 index 000000000000..f0b0d9182e26 --- /dev/null +++ b/res/controllers/Denon-DN-S3700.qml @@ -0,0 +1,17 @@ +import QtQml + +QtObject { + id: controller + + property string controllerId: "" + property bool debugMode: false + + function init(controllerId, debugMode) { + controller.controllerId = controllerId; + controller.debugMode = debugMode; + console.log(controllerId, debugMode); + } + function shutdown() { + console.log(`Shutting down ${controller.controllerId} with debug mode ${controller.controllerId}`); + } +} diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 9cede503565b..3f437a2549e3 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -95,6 +95,7 @@ bool ControllerScriptEngineLegacy::callFunctionOnObjects( const QJSValue global = m_pJSEngine->globalObject(); + // TODO: ICI bool success = true; for (const QString& prefixName : scriptFunctionPrefixes) { QJSValue prefix = global.property(prefixName); From 39d20206afbae3c4f3fc00504e623611a81640b6 Mon Sep 17 00:00:00 2001 From: Christophe Henry Date: Thu, 11 Jul 2024 23:44:13 +0200 Subject: [PATCH 02/10] Dirty but working --- res/controllers/Denon-DN-S3700.midi.xml | 2 +- res/controllers/Denon-DN-S3700.qml | 9 ++- .../legacy/controllerscriptenginelegacy.cpp | 73 +++++++++++++++++-- 3 files changed, 72 insertions(+), 12 deletions(-) diff --git a/res/controllers/Denon-DN-S3700.midi.xml b/res/controllers/Denon-DN-S3700.midi.xml index 2949e1bc9a67..80353f62e4c7 100644 --- a/res/controllers/Denon-DN-S3700.midi.xml +++ b/res/controllers/Denon-DN-S3700.midi.xml @@ -1,5 +1,5 @@ - + Denon DN-S3700 (QML) christophehenry diff --git a/res/controllers/Denon-DN-S3700.qml b/res/controllers/Denon-DN-S3700.qml index f0b0d9182e26..16d65e901ad2 100644 --- a/res/controllers/Denon-DN-S3700.qml +++ b/res/controllers/Denon-DN-S3700.qml @@ -1,6 +1,6 @@ -import QtQml +import QtQuick -QtObject { +Item { id: controller property string controllerId: "" @@ -9,9 +9,10 @@ QtObject { function init(controllerId, debugMode) { controller.controllerId = controllerId; controller.debugMode = debugMode; - console.log(controllerId, debugMode); + console.error(controllerId, debugMode); + console.error(controller.controllerId, controller.debugMode); } function shutdown() { - console.log(`Shutting down ${controller.controllerId} with debug mode ${controller.controllerId}`); + console.error(`Shutting down ${controller.controllerId} with debug mode ${controller.controllerId}`); } } diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 3f437a2549e3..22d4a6344490 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -33,12 +33,12 @@ const QByteArray kScreenTransformFunctionUntypedSignature = "transformFrame(QVariant,QVariant)"); const QByteArray kScreenTransformFunctionTypedSignature = QMetaObject::normalizedSignature("transformFrame(QVariant,QDateTime)"); -const QByteArray kScreenInitFunctionUntypedSignature = +const QByteArray kQmlComponentInitFunctionUntypedSignature = QMetaObject::normalizedSignature( "init(QVariant,QVariant)"); -const QByteArray kScreenInitFunctionTypedSignature = +const QByteArray kQmlComponentFunctionTypedSignature = QMetaObject::normalizedSignature("init(QString,bool)"); -const QByteArray kScreenShutdownFunctionSignature = +const QByteArray kQmlComponentShutdownFunctionSignature = QMetaObject::normalizedSignature("shutdown()"); } // anonymous namespace #endif @@ -95,7 +95,6 @@ bool ControllerScriptEngineLegacy::callFunctionOnObjects( const QJSValue global = m_pJSEngine->globalObject(); - // TODO: ICI bool success = true; for (const QString& prefixName : scriptFunctionPrefixes) { QJSValue prefix = global.property(prefixName); @@ -155,7 +154,7 @@ bool ControllerScriptEngineLegacy::callShutdownFunction() { } QMetaMethod shutdownFunction; - int methodIdx = metaObject->indexOfMethod(kScreenShutdownFunctionSignature); + int methodIdx = metaObject->indexOfMethod(kQmlComponentShutdownFunctionSignature); if (methodIdx == -1 || !metaObject->method(methodIdx).isValid()) { qCDebug(m_logger) << "QML Scene for screen" << screenIdentifier @@ -219,12 +218,12 @@ bool ControllerScriptEngineLegacy::callInitFunction() { QMetaMethod initFunction; bool typed = false; - int methodIdx = metaObject->indexOfMethod(kScreenInitFunctionUntypedSignature); + int methodIdx = metaObject->indexOfMethod(kQmlComponentInitFunctionUntypedSignature); if (methodIdx == -1 || !metaObject->method(methodIdx).isValid()) { qCDebug(m_logger) << "QML Scene for screen" << screenIdentifier << "has no valid untyped init method."; - methodIdx = metaObject->indexOfMethod(kScreenInitFunctionTypedSignature); + methodIdx = metaObject->indexOfMethod(kQmlComponentFunctionTypedSignature); typed = true; } @@ -459,6 +458,66 @@ bool ControllerScriptEngineLegacy::initialize() { #ifdef MIXXX_USE_QML } else { if (script.identifier.isEmpty()) { + if (availableScreens.isEmpty()) { + QQmlComponent qmlComponent = QQmlComponent( + std::dynamic_pointer_cast(m_pJSEngine).get()); + + QFile scene = QFile(script.file.absoluteFilePath()); + + QDir dir(m_resourcePath + "/qml/"); + + scene.open(QIODevice::ReadOnly); + qmlComponent.setData(scene.readAll(), + // Obfuscate the scene filename to make it appear in the QML folder. + // This allows a smooth integration with QML components. + QUrl::fromLocalFile( + dir.absoluteFilePath(script.file.fileName()))); + scene.close(); + + while (qmlComponent.isLoading()) { + qCDebug(m_logger) << "Waiting for component " + << script.file.absoluteFilePath() + << " to be ready: " << qmlComponent.progress(); + QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents, 500); + } + + if (qmlComponent.isError()) { + const QList errorList = qmlComponent.errors(); + for (const QQmlError& error : errorList) { + qCWarning(m_logger) + << "Unable to load the QML scene:" + << error.url() << "at line" << error.line() + << ", error: " << error; + showQMLExceptionDialog(error, true); + } + } + + VERIFY_OR_DEBUG_ASSERT(qmlComponent.isReady()) { + qCWarning(m_logger) + << "QMLComponent isn't ready although " + "synchronous load was requested."; + } + + QObject* pRootObject = qmlComponent.createWithInitialProperties( + QVariantMap{}); + if (qmlComponent.isError()) { + const QList errorList = qmlComponent.errors(); + for (const QQmlError& error : errorList) { + qCWarning(m_logger) << error.url() << error.line() << error; + } + } + + std::shared_ptr rootItem = + std::shared_ptr(qobject_cast(pRootObject)); + if (!rootItem) { + qWarning("Oopsie run: Not a QQuickItem"); + delete pRootObject; + } + + watchFilePath(script.file.absoluteFilePath()); + + m_rootItems.insert("screenIdentifier", rootItem); + } while (!availableScreens.isEmpty()) { QString screenIdentifier(availableScreens.firstKey()); if (!bindSceneToScreen(script, From c4eba9343532fda9530cbeb29f800f8979d5ef81 Mon Sep 17 00:00:00 2001 From: Christophe Henry Date: Fri, 12 Jul 2024 15:59:35 +0200 Subject: [PATCH 03/10] MixxxController QML component --- CMakeLists.txt | 2 ++ src/qml/mixxxcontroller.cpp | 7 +++++++ src/qml/mixxxcontroller.h | 30 ++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 src/qml/mixxxcontroller.cpp create mode 100644 src/qml/mixxxcontroller.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b836179f73d..c0904dc81551 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2776,6 +2776,8 @@ if(QML) src/qml/qmlplayerproxy.cpp src/qml/qmlvisibleeffectsmodel.cpp src/qml/qmlwaveformoverview.cpp + src/qml/mixxxcontroller.cpp + src/qml/mixxxcontroller.h # The following sources need to be in this target to get QML_ELEMENT properly interpreted src/control/controlmodel.cpp src/control/controlsortfiltermodel.cpp diff --git a/src/qml/mixxxcontroller.cpp b/src/qml/mixxxcontroller.cpp new file mode 100644 index 000000000000..367bf9d0fad8 --- /dev/null +++ b/src/qml/mixxxcontroller.cpp @@ -0,0 +1,7 @@ +#include "mixxxcontroller.h" + +namespace mixxx { +namespace qml { + +} // namespace qml +} // namespace mixxx diff --git a/src/qml/mixxxcontroller.h b/src/qml/mixxxcontroller.h new file mode 100644 index 000000000000..b4313dc51d26 --- /dev/null +++ b/src/qml/mixxxcontroller.h @@ -0,0 +1,30 @@ +#ifndef MIXXX_MIXXXCONTROLLER_H +#define MIXXX_MIXXXCONTROLLER_H + +#include + +#include + +namespace mixxx { +namespace qml { + +class MixxxController : public QObject { + struct ControllerInfo { + Q_GADGET + public: + QString name; + QString author; + QString forums; + QString manual; + // Other? + }; + + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(ControllerInfo info READ info WRITE setInfo) +}; + +} // namespace qml +} // namespace mixxx + +#endif // MIXXX_MIXXXCONTROLLER_H From 2a03ebf2fd7363c2d345a41ce50d67ca4ee356fc Mon Sep 17 00:00:00 2001 From: Christophe Henry Date: Mon, 15 Jul 2024 13:09:01 +0200 Subject: [PATCH 04/10] Refactor QML instanciation --- res/controllers/Denon-DN-S3700.qml | 16 +- src/controllers/legacycontrollermapping.h | 2 + .../legacycontrollermappingfilehandler.cpp | 3 +- .../legacy/controllerscriptenginelegacy.cpp | 297 +++++++++--------- .../legacy/controllerscriptenginelegacy.h | 9 +- src/qml/mixxxcontroller.cpp | 7 +- src/qml/mixxxcontroller.h | 21 +- 7 files changed, 186 insertions(+), 169 deletions(-) diff --git a/res/controllers/Denon-DN-S3700.qml b/res/controllers/Denon-DN-S3700.qml index 16d65e901ad2..f947454805cb 100644 --- a/res/controllers/Denon-DN-S3700.qml +++ b/res/controllers/Denon-DN-S3700.qml @@ -1,18 +1,14 @@ -import QtQuick +import QtQml -Item { - id: controller +import "Mixxx" - property string controllerId: "" - property bool debugMode: false +MixxxController { + id: controller - function init(controllerId, debugMode) { - controller.controllerId = controllerId; - controller.debugMode = debugMode; - console.error(controllerId, debugMode); + function init() { console.error(controller.controllerId, controller.debugMode); } function shutdown() { - console.error(`Shutting down ${controller.controllerId} with debug mode ${controller.controllerId}`); + console.error(`Shutting down ${controller.controllerId} with debug mode ${controller.debugMode}`); } } diff --git a/src/controllers/legacycontrollermapping.h b/src/controllers/legacycontrollermapping.h index 3c74c7c85889..e3fbfa227af1 100644 --- a/src/controllers/legacycontrollermapping.h +++ b/src/controllers/legacycontrollermapping.h @@ -19,6 +19,7 @@ #include "controllers/legacycontrollersettingslayout.h" #include "defs_urls.h" #include "preferences/usersettings.h" +#include "qml/mixxxcontroller.h" #include "util/assert.h" /// This class represents a controller mapping, containing the data elements that @@ -374,6 +375,7 @@ class LegacyControllerMapping { #ifdef MIXXX_USE_QML QList m_modules; QList m_screens; + QList m_mixxxControllers; #endif QList m_scripts; DeviceDirections m_deviceDirection; diff --git a/src/controllers/legacycontrollermappingfilehandler.cpp b/src/controllers/legacycontrollermappingfilehandler.cpp index 7782ec8186ca..f9354d1f8325 100644 --- a/src/controllers/legacycontrollermappingfilehandler.cpp +++ b/src/controllers/legacycontrollermappingfilehandler.cpp @@ -390,7 +390,8 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping( QFileInfo file = findScriptFile(mapping, filename, systemMappingsPath); if (file.suffix() == "qml") { #ifdef MIXXX_USE_QML - QString identifier = scriptFile.attribute("identifier", ""); + QString identifier = scriptFile.attribute( + "identifier", scriptFile.attribute("functionprefix", "")); mapping->addScriptFile(LegacyControllerMapping::ScriptFileInfo{ filename, identifier, diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 22d4a6344490..e2ea75e17d39 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -128,6 +128,10 @@ bool ControllerScriptEngineLegacy::callShutdownFunction() { } #ifdef MIXXX_USE_QML + for (const auto& controller : m_mixxxController) { + controller->shutdown(); + } + if (!m_bQmlMode) { #endif return callFunctionOnObjects(m_scriptFunctionPrefixes, "shutdown"); @@ -201,6 +205,11 @@ bool ControllerScriptEngineLegacy::callInitFunction() { qCDebug(m_logger) << "Unhandled controller JS error is:" << m_pJSEngine->catchError().toString(); } + + for (const auto& controller : m_mixxxController) { + controller->init(); + } + QHashIterator> i(m_rootItems); bool success = true; while (i.hasNext()) { @@ -251,7 +260,49 @@ bool ControllerScriptEngineLegacy::callInitFunction() { // connection QQmlEngine::warnings -> // ControllerScriptEngineBase::handleQMLErrors } - return success; + + QListIterator> controllers(m_mixxxController); + bool controllersSuccess = true; + while (controllers.hasNext()) { + const QMetaObject* metaObject = controllers.next()->metaObject(); + + VERIFY_OR_DEBUG_ASSERT(metaObject) { + qCWarning(m_logger) << "Invalid meta object for controller"; + continue; + } + + QMetaMethod initFunction; + bool typed = false; + int methodIdx = metaObject->indexOfMethod(kQmlComponentInitFunctionUntypedSignature); + + if (methodIdx == -1 || !metaObject->method(methodIdx).isValid()) { + qCDebug(m_logger) << "QML controller has no valid untyped init method."; + methodIdx = metaObject->indexOfMethod(kQmlComponentFunctionTypedSignature); + typed = true; + } + + initFunction = metaObject->method(methodIdx); + + if (!initFunction.isValid()) { + qCDebug(m_logger) << "QML controller has no valid untyped init method; Skipping."; + continue; + } + + qCDebug(m_logger) << "Executing init on QML controller"; + if (typed) { + success &= initFunction.invoke(i.value().get(), + Qt::DirectConnection, + Q_ARG(QString, controllerName), + Q_ARG(bool, m_logger().isDebugEnabled())); + } else { + success &= initFunction.invoke(i.value().get(), + Qt::DirectConnection, + Q_ARG(QVariant, controllerName), + Q_ARG(QVariant, m_logger().isDebugEnabled())); + } + } + + return success && controllersSuccess; } #endif } @@ -439,10 +490,6 @@ bool ControllerScriptEngineLegacy::initialize() { qCWarning(m_logger) << "Controller mapping has QML library definitions but no " "QML files to use it. Ignoring."; } - - // If we encounter a failure while loading a scene, we will need to properly - // stop the screen threads before shutting down. - bool sceneBindingHasFailure = false; #endif for (const LegacyControllerMapping::ScriptFileInfo& script : std::as_const(m_scriptFiles)) { #ifdef MIXXX_USE_QML @@ -457,109 +504,13 @@ bool ControllerScriptEngineLegacy::initialize() { } #ifdef MIXXX_USE_QML } else { - if (script.identifier.isEmpty()) { - if (availableScreens.isEmpty()) { - QQmlComponent qmlComponent = QQmlComponent( - std::dynamic_pointer_cast(m_pJSEngine).get()); - - QFile scene = QFile(script.file.absoluteFilePath()); - - QDir dir(m_resourcePath + "/qml/"); - - scene.open(QIODevice::ReadOnly); - qmlComponent.setData(scene.readAll(), - // Obfuscate the scene filename to make it appear in the QML folder. - // This allows a smooth integration with QML components. - QUrl::fromLocalFile( - dir.absoluteFilePath(script.file.fileName()))); - scene.close(); - - while (qmlComponent.isLoading()) { - qCDebug(m_logger) << "Waiting for component " - << script.file.absoluteFilePath() - << " to be ready: " << qmlComponent.progress(); - QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents, 500); - } - - if (qmlComponent.isError()) { - const QList errorList = qmlComponent.errors(); - for (const QQmlError& error : errorList) { - qCWarning(m_logger) - << "Unable to load the QML scene:" - << error.url() << "at line" << error.line() - << ", error: " << error; - showQMLExceptionDialog(error, true); - } - } - - VERIFY_OR_DEBUG_ASSERT(qmlComponent.isReady()) { - qCWarning(m_logger) - << "QMLComponent isn't ready although " - "synchronous load was requested."; - } - - QObject* pRootObject = qmlComponent.createWithInitialProperties( - QVariantMap{}); - if (qmlComponent.isError()) { - const QList errorList = qmlComponent.errors(); - for (const QQmlError& error : errorList) { - qCWarning(m_logger) << error.url() << error.line() << error; - } - } - - std::shared_ptr rootItem = - std::shared_ptr(qobject_cast(pRootObject)); - if (!rootItem) { - qWarning("Oopsie run: Not a QQuickItem"); - delete pRootObject; - } - - watchFilePath(script.file.absoluteFilePath()); - - m_rootItems.insert("screenIdentifier", rootItem); - } - while (!availableScreens.isEmpty()) { - QString screenIdentifier(availableScreens.firstKey()); - if (!bindSceneToScreen(script, - screenIdentifier, - availableScreens.take(screenIdentifier))) { - sceneBindingHasFailure = true; - } - } - } else { - if (!availableScreens.contains(script.identifier)) { - qCCritical(m_logger) << "Not screen" << script.identifier << "found!"; + const auto result = instanciateQMLComponent(script, availableScreens); - sceneBindingHasFailure = true; - break; - } - if (!bindSceneToScreen(script, - script.identifier, - availableScreens.take(script.identifier))) { - sceneBindingHasFailure = true; - } + if (!result) { + shutdown(); + return false; } } - } - - if (!availableScreens.isEmpty()) { - if (!sceneBindingHasFailure) { - qCWarning(m_logger) - << "Found screen with no QML scene able to run on it. Ignoring" - << availableScreens.size() << "screens"; - } - - while (!availableScreens.isEmpty()) { - auto pScreen = availableScreens.take(availableScreens.firstKey()); - VERIFY_OR_DEBUG_ASSERT(!pScreen->isValid() || - !pScreen->isRunning() || pScreen->stop()) { - qCWarning(m_logger) << "Unable to stop the screen"; - }; - } - } - if (sceneBindingHasFailure) { - shutdown(); - return false; #endif } @@ -639,15 +590,9 @@ void ControllerScriptEngineLegacy::extractTransformFunction( } bool ControllerScriptEngineLegacy::bindSceneToScreen( - const LegacyControllerMapping::ScriptFileInfo& qmlFile, + const std::shared_ptr pScene, const QString& screenIdentifier, std::shared_ptr pScreen) { - // Like for Javascript, if the script is invalid, it should be watched so the user can fix it - // without having to restart Mixxx. So, add it to the watcher before - // evaluating it. - watchFilePath(qmlFile.file.absoluteFilePath()); - - auto pScene = loadQMLFile(qmlFile, pScreen); if (!pScene) { VERIFY_OR_DEBUG_ASSERT(!pScreen->isValid() || !pScreen->isRunning() || pScreen->stop()) { @@ -800,6 +745,7 @@ void ControllerScriptEngineLegacy::shutdown() { callShutdownFunction(); #ifdef MIXXX_USE_QML + m_engineThreadControl.setCanPause(false); // Wait till the splash off animation has finished rendering. std::chrono::milliseconds maxSplashOffDuration{}; @@ -923,34 +869,40 @@ bool ControllerScriptEngineLegacy::evaluateScriptFile(const QFileInfo& scriptFil } #ifdef MIXXX_USE_QML -std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( +bool ControllerScriptEngineLegacy::instanciateQMLComponent( const LegacyControllerMapping::ScriptFileInfo& qmlScript, - std::shared_ptr pScreen) { + QMap> + availableScreens) { + // Like for Javascript, if the script is invalid, it should be watched so the user can fix it + // without having to restart Mixxx. So, add it to the watcher before + // evaluating it. VERIFY_OR_DEBUG_ASSERT(m_pJSEngine || qmlScript.type != LegacyControllerMapping::ScriptFileInfo::Type::Qml) { - return nullptr; + return false; } + watchFilePath(qmlScript.file.absoluteFilePath()); + QQmlComponent qmlComponent = QQmlComponent( std::dynamic_pointer_cast(m_pJSEngine).get()); - QFile scene = QFile(qmlScript.file.absoluteFilePath()); - if (!scene.exists()) { - qCWarning(m_logger) << "Unable to load the QML scene:" << qmlScript.file.absoluteFilePath() + QFile file = QFile(qmlScript.file.absoluteFilePath()); + if (!file.exists()) { + qCWarning(m_logger) << "Unable to load the QML file:" << qmlScript.file.absoluteFilePath() << "does not exist."; - return nullptr; + return false; } QDir dir(m_resourcePath + "/qml/"); - scene.open(QIODevice::ReadOnly); - qmlComponent.setData(scene.readAll(), + file.open(QIODevice::ReadOnly); + qmlComponent.setData(file.readAll(), // Obfuscate the scene filename to make it appear in the QML folder. // This allows a smooth integration with QML components. QUrl::fromLocalFile( dir.absoluteFilePath(qmlScript.file.fileName()))); - scene.close(); + file.close(); while (qmlComponent.isLoading()) { qCDebug(m_logger) << "Waiting for component " @@ -962,47 +914,106 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( if (qmlComponent.isError()) { const QList errorList = qmlComponent.errors(); for (const QQmlError& error : errorList) { - qCWarning(m_logger) << "Unable to load the QML scene:" << error.url() + qCWarning(m_logger) << "Unable to load the QML file:" << error.url() << "at line" << error.line() << ", error: " << error; showQMLExceptionDialog(error, true); } - return nullptr; + return false; } VERIFY_OR_DEBUG_ASSERT(qmlComponent.isReady()) { qCWarning(m_logger) << "QMLComponent isn't ready although synchronous load was requested."; - return nullptr; + return false; } - QObject* pRootObject = qmlComponent.createWithInitialProperties( - QVariantMap{{"screenId", pScreen->info().identifier}}); - if (qmlComponent.isError()) { - const QList errorList = qmlComponent.errors(); - for (const QQmlError& error : errorList) { - qCWarning(m_logger) << error.url() << error.line() << error; + QObject* createdComp = qmlComponent.create(); + + const std::shared_ptr controller = + std::shared_ptr( + qobject_cast(createdComp)); + + if (controller) { + qmlComponent.setInitialProperties(controller.get(), + QVariantMap{{"controllerId", + m_pController ? m_pController->getName() + : QString{}}, + {"debugMode", m_logger().isDebugEnabled()}}); + qmlComponent.completeCreate(); + if (qmlComponent.isError()) { + const QList errorList = qmlComponent.errors(); + for (const QQmlError& error : errorList) { + qCWarning(m_logger) << error.url() << error.line() << error; + } + delete createdComp; + return false; + } + m_mixxxController.append(controller); + return true; + } + + qDebug() << qmlScript.file.absoluteFilePath() << "is not a MixxxController"; + + std::shared_ptr quickItem = + std::shared_ptr(qobject_cast(createdComp)); + + if (!quickItem) { + qWarning("run: Component is neither a MixxxController not a QQuickItem"); + delete createdComp; + return false; + } + + bool sceneBindingHasFailure = false; + + QString identifier = ""; + + if (qmlScript.identifier.isEmpty()) { + while (!availableScreens.isEmpty()) { + identifier = availableScreens.firstKey(); } - return nullptr; + } else { + identifier = qmlScript.identifier; } - std::shared_ptr rootItem = - std::shared_ptr(qobject_cast(pRootObject)); - if (!rootItem) { - qWarning("run: Not a QQuickItem"); - delete pRootObject; - return nullptr; + const auto pScreen = availableScreens.take(identifier); + if (!availableScreens.contains(qmlScript.identifier)) { + qCCritical(m_logger) << "Not screen" << qmlScript.identifier << "found!"; + delete createdComp; + return false; } - watchFilePath(qmlScript.file.absoluteFilePath()); + qmlComponent.setInitialProperties(quickItem.get(), + QVariantMap{{"screenId", pScreen->info().identifier}}); + qmlComponent.completeCreate(); + + if (qmlComponent.isError() || !bindSceneToScreen(quickItem, identifier, pScreen)) { + if (!availableScreens.isEmpty()) { + if (!sceneBindingHasFailure) { + qCWarning(m_logger) + << "Found screen with no QML scene able to run on it. Ignoring" + << availableScreens.size() << "screens"; + } + + while (!availableScreens.isEmpty()) { + VERIFY_OR_DEBUG_ASSERT(!pScreen->isValid() || + !pScreen->isRunning() || pScreen->stop()) { + qCWarning(m_logger) << "Unable to stop the screen"; + } + } + } + + delete createdComp; + return false; + } // The root item is ready. Associate it with the window. if (!m_bTesting) { - rootItem->setParentItem(pScreen->quickWindow()->contentItem()); + quickItem->setParentItem(pScreen->quickWindow()->contentItem()); - rootItem->setWidth(pScreen->quickWindow()->width()); - rootItem->setHeight(pScreen->quickWindow()->height()); + quickItem->setWidth(pScreen->quickWindow()->width()); + quickItem->setHeight(pScreen->quickWindow()->height()); } - return rootItem; + return true; } #endif diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index b0605f4b584e..3893785e6bd9 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -10,6 +10,7 @@ #include "controllers/legacycontrollermapping.h" #include "controllers/scripting/controllerscriptenginebase.h" +#include "qml/mixxxcontroller.h" #ifdef MIXXX_USE_QML class QQuickItem; @@ -79,14 +80,15 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { bool evaluateScriptFile(const QFileInfo& scriptFile); #ifdef MIXXX_USE_QML bool bindSceneToScreen( - const LegacyControllerMapping::ScriptFileInfo& qmlFile, + const std::shared_ptr pScene, const QString& screenIdentifier, std::shared_ptr pScreen); void extractTransformFunction(const QMetaObject* metaObject, const QString& screenIdentifier); - std::shared_ptr loadQMLFile( + bool instanciateQMLComponent( const LegacyControllerMapping::ScriptFileInfo& qmlScript, - std::shared_ptr pScreen); + QMap> + availableScreens); struct TransformScreenFrameFunction { QMetaMethod method; @@ -111,6 +113,7 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { QJSValue m_makeArrayBufferWrapperFunction; QList m_scriptFunctionPrefixes; #ifdef MIXXX_USE_QML + QList> m_mixxxController; QHash> m_renderingScreens; // Contains all the scenes loaded for this mapping. Key is the scene // identifier (LegacyControllerMapping::ScreenInfo::identifier), value in diff --git a/src/qml/mixxxcontroller.cpp b/src/qml/mixxxcontroller.cpp index 367bf9d0fad8..4419e74769eb 100644 --- a/src/qml/mixxxcontroller.cpp +++ b/src/qml/mixxxcontroller.cpp @@ -2,6 +2,11 @@ namespace mixxx { namespace qml { - +void MixxxController::init() { + metaObject()->invokeMethod(this, "init"); +} +void MixxxController::shutdown() { + metaObject()->invokeMethod(this, "shutdown"); +} } // namespace qml } // namespace mixxx diff --git a/src/qml/mixxxcontroller.h b/src/qml/mixxxcontroller.h index b4313dc51d26..32282cecc770 100644 --- a/src/qml/mixxxcontroller.h +++ b/src/qml/mixxxcontroller.h @@ -9,19 +9,18 @@ namespace mixxx { namespace qml { class MixxxController : public QObject { - struct ControllerInfo { - Q_GADGET - public: - QString name; - QString author; - QString forums; - QString manual; - // Other? - }; - Q_OBJECT QML_ELEMENT - Q_PROPERTY(ControllerInfo info READ info WRITE setInfo) + Q_PROPERTY(QString controllerId MEMBER m_controllerId) + Q_PROPERTY(bool debugMode MEMBER m_debugMode) + + public: + void init(); + void shutdown(); + + private: + QString m_controllerId; + bool m_debugMode; }; } // namespace qml From edd22544347fc32ed7b125f6bf23bb9b57948dc9 Mon Sep 17 00:00:00 2001 From: Christophe Henry Date: Mon, 15 Jul 2024 23:13:22 +0200 Subject: [PATCH 05/10] Implement screens --- CMakeLists.txt | 2 + res/controllers/Denon-DN-S3700.qml | 6 ++ .../legacy/controllerscriptenginelegacy.cpp | 8 +- src/qml/mixxxcontroller.h | 6 ++ src/qml/mixxxscreen.cpp | 6 ++ src/qml/mixxxscreen.h | 75 +++++++++++++++++++ 6 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 src/qml/mixxxscreen.cpp create mode 100644 src/qml/mixxxscreen.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c0904dc81551..75d3f94e8879 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2778,6 +2778,8 @@ if(QML) src/qml/qmlwaveformoverview.cpp src/qml/mixxxcontroller.cpp src/qml/mixxxcontroller.h + src/qml/mixxxscreen.cpp + src/qml/mixxxscreen.h # The following sources need to be in this target to get QML_ELEMENT properly interpreted src/control/controlmodel.cpp src/control/controlsortfiltermodel.cpp diff --git a/res/controllers/Denon-DN-S3700.qml b/res/controllers/Denon-DN-S3700.qml index f947454805cb..506d37698113 100644 --- a/res/controllers/Denon-DN-S3700.qml +++ b/res/controllers/Denon-DN-S3700.qml @@ -11,4 +11,10 @@ MixxxController { function shutdown() { console.error(`Shutting down ${controller.controllerId} with debug mode ${controller.debugMode}`); } + + MixxxScreen { + screenId: "screen 7" + splashOff: 5000 + Component.onCompleted: console.error(`MixxxScreen.identifier=${screenId} ${splashOff}`) + } } diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index e2ea75e17d39..64c00f30bf14 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -987,11 +987,9 @@ bool ControllerScriptEngineLegacy::instanciateQMLComponent( if (qmlComponent.isError() || !bindSceneToScreen(quickItem, identifier, pScreen)) { if (!availableScreens.isEmpty()) { - if (!sceneBindingHasFailure) { - qCWarning(m_logger) - << "Found screen with no QML scene able to run on it. Ignoring" - << availableScreens.size() << "screens"; - } + qCWarning(m_logger) + << "Found screen with no QML scene able to run on it. Ignoring" + << availableScreens.size() << "screens"; while (!availableScreens.isEmpty()) { VERIFY_OR_DEBUG_ASSERT(!pScreen->isValid() || diff --git a/src/qml/mixxxcontroller.h b/src/qml/mixxxcontroller.h index 32282cecc770..112c43ec6643 100644 --- a/src/qml/mixxxcontroller.h +++ b/src/qml/mixxxcontroller.h @@ -4,6 +4,9 @@ #include #include +#include + +#include "mixxxscreen.h" namespace mixxx { namespace qml { @@ -13,6 +16,8 @@ class MixxxController : public QObject { QML_ELEMENT Q_PROPERTY(QString controllerId MEMBER m_controllerId) Q_PROPERTY(bool debugMode MEMBER m_debugMode) + Q_PROPERTY(QQmlListProperty screens MEMBER m_screens) + Q_CLASSINFO("DefaultProperty", "screens") public: void init(); @@ -21,6 +26,7 @@ class MixxxController : public QObject { private: QString m_controllerId; bool m_debugMode; + QQmlListProperty m_screens; }; } // namespace qml diff --git a/src/qml/mixxxscreen.cpp b/src/qml/mixxxscreen.cpp new file mode 100644 index 000000000000..d1e2568a7c9d --- /dev/null +++ b/src/qml/mixxxscreen.cpp @@ -0,0 +1,6 @@ +#include "mixxxscreen.h" + +namespace mixxx { +namespace qml { +} // namespace qml +} // namespace mixxx diff --git a/src/qml/mixxxscreen.h b/src/qml/mixxxscreen.h new file mode 100644 index 000000000000..df90679a1782 --- /dev/null +++ b/src/qml/mixxxscreen.h @@ -0,0 +1,75 @@ +// +// Created by augier on 15/07/24. +// + +#ifndef MIXXX_MIXXXSCREEN_H +#define MIXXX_MIXXXSCREEN_H + +#include +#include +#include +#include + +namespace mixxx { +namespace qml { + +class MixxxScreen : public QObject { + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(QString screenId MEMBER m_screenId REQUIRED) + Q_PROPERTY(int width READ width WRITE setWidth) + Q_PROPERTY(int height READ height WRITE setHeight) + Q_PROPERTY(uint targetFps MEMBER m_targetFps) + Q_PROPERTY(uint msaa MEMBER m_msaa) + Q_PROPERTY(uint splashOff READ splashOff WRITE setSplashOff) + Q_PROPERTY(QImage::Format pixelType MEMBER m_pixelType) + Q_PROPERTY(ColorEndian endian MEMBER m_endian) + Q_PROPERTY(bool reversedColor MEMBER m_reversedColor) + Q_PROPERTY(bool rawData MEMBER m_rawData) + + public: + enum class ColorEndian { + Big = static_cast(std::endian::big), + Little = static_cast(std::endian::little), + }; + Q_ENUM(ColorEndian) + + int width() { + return m_size.width(); + } + void setWidth(int value) { + m_size = QSize(value, m_size.height()); + } + int height() { + return m_size.width(); + } + void setHeight(int value) { + m_size = QSize(m_size.width(), value); + } + uint splashOff() { + return m_splashOff.count(); + } + void setSplashOff(uint value) { + m_splashOff = std::chrono::milliseconds(value); + } + + private: + QString m_screenId; // The screen identifier. + QSize m_size = QSize(0, 0); // The size of the screen. + uint m_targetFps = 30; // The maximum FPS to render. + uint m_msaa = 1; // The MSAA value to use for render. + std::chrono::milliseconds m_splashOff = std::chrono::milliseconds( + 3000); // The rendering grace time given when the screen is + // requested to shutdown. + QImage::Format m_pixelType = + QImage::Format_RGB888; // The pixel encoding format. + ColorEndian m_endian = ColorEndian::Little; // The pixel endian format. + bool m_reversedColor = false; // Whether or not the RGB is swapped BGR. + bool m_rawData = false; // Whether or not the screen is allowed to receive + // bare data, not transformed. +}; + +} // namespace qml +} // namespace mixxx + +#endif // MIXXX_MIXXXSCREEN_H From 2e34045921809e39063c9dd93f426c907e1b07ee Mon Sep 17 00:00:00 2001 From: Christophe Henry Date: Tue, 16 Jul 2024 10:15:19 +0200 Subject: [PATCH 06/10] Transform component's init and shutdown to signals --- CMakeLists.txt | 2 - res/controllers/Denon-DN-S3700.midi.xml | 4 +- res/controllers/Denon-DN-S3700.qml | 10 ++-- .../legacy/controllerscriptenginelegacy.cpp | 47 ++----------------- src/qml/mixxxcontroller.cpp | 35 ++++++++++++-- src/qml/mixxxcontroller.h | 21 ++++++--- src/qml/mixxxscreen.cpp | 25 ++++++++++ src/qml/mixxxscreen.h | 31 +++++------- 8 files changed, 90 insertions(+), 85 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 75d3f94e8879..d48f3d88375c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2777,9 +2777,7 @@ if(QML) src/qml/qmlvisibleeffectsmodel.cpp src/qml/qmlwaveformoverview.cpp src/qml/mixxxcontroller.cpp - src/qml/mixxxcontroller.h src/qml/mixxxscreen.cpp - src/qml/mixxxscreen.h # The following sources need to be in this target to get QML_ELEMENT properly interpreted src/control/controlmodel.cpp src/control/controlsortfiltermodel.cpp diff --git a/res/controllers/Denon-DN-S3700.midi.xml b/res/controllers/Denon-DN-S3700.midi.xml index 80353f62e4c7..56a8794e3a41 100644 --- a/res/controllers/Denon-DN-S3700.midi.xml +++ b/res/controllers/Denon-DN-S3700.midi.xml @@ -7,9 +7,7 @@ - + - - diff --git a/res/controllers/Denon-DN-S3700.qml b/res/controllers/Denon-DN-S3700.qml index 506d37698113..715f1e218622 100644 --- a/res/controllers/Denon-DN-S3700.qml +++ b/res/controllers/Denon-DN-S3700.qml @@ -5,16 +5,12 @@ import "Mixxx" MixxxController { id: controller - function init() { - console.error(controller.controllerId, controller.debugMode); - } - function shutdown() { - console.error(`Shutting down ${controller.controllerId} with debug mode ${controller.debugMode}`); - } + onInit: console.error(`Starting controller ${controller.controllerId} with debug mode ${controller.debugMode}`) + onShutdown: console.error(`Shutting down ${controller.controllerId} with debug mode ${controller.debugMode}`) MixxxScreen { screenId: "screen 7" splashOff: 5000 - Component.onCompleted: console.error(`MixxxScreen.identifier=${screenId} ${splashOff}`) + onInit: console.error(`MixxxScreen.screenId=${screenId}, MixxxScreen.splashOff=${splashOff}`) } } diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 64c00f30bf14..029997ba7ada 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -129,7 +129,7 @@ bool ControllerScriptEngineLegacy::callShutdownFunction() { #ifdef MIXXX_USE_QML for (const auto& controller : m_mixxxController) { - controller->shutdown(); + emit controller->shutdown(); } if (!m_bQmlMode) { @@ -207,7 +207,7 @@ bool ControllerScriptEngineLegacy::callInitFunction() { } for (const auto& controller : m_mixxxController) { - controller->init(); + emit controller->init(); } QHashIterator> i(m_rootItems); @@ -261,48 +261,7 @@ bool ControllerScriptEngineLegacy::callInitFunction() { // ControllerScriptEngineBase::handleQMLErrors } - QListIterator> controllers(m_mixxxController); - bool controllersSuccess = true; - while (controllers.hasNext()) { - const QMetaObject* metaObject = controllers.next()->metaObject(); - - VERIFY_OR_DEBUG_ASSERT(metaObject) { - qCWarning(m_logger) << "Invalid meta object for controller"; - continue; - } - - QMetaMethod initFunction; - bool typed = false; - int methodIdx = metaObject->indexOfMethod(kQmlComponentInitFunctionUntypedSignature); - - if (methodIdx == -1 || !metaObject->method(methodIdx).isValid()) { - qCDebug(m_logger) << "QML controller has no valid untyped init method."; - methodIdx = metaObject->indexOfMethod(kQmlComponentFunctionTypedSignature); - typed = true; - } - - initFunction = metaObject->method(methodIdx); - - if (!initFunction.isValid()) { - qCDebug(m_logger) << "QML controller has no valid untyped init method; Skipping."; - continue; - } - - qCDebug(m_logger) << "Executing init on QML controller"; - if (typed) { - success &= initFunction.invoke(i.value().get(), - Qt::DirectConnection, - Q_ARG(QString, controllerName), - Q_ARG(bool, m_logger().isDebugEnabled())); - } else { - success &= initFunction.invoke(i.value().get(), - Qt::DirectConnection, - Q_ARG(QVariant, controllerName), - Q_ARG(QVariant, m_logger().isDebugEnabled())); - } - } - - return success && controllersSuccess; + return success; } #endif } diff --git a/src/qml/mixxxcontroller.cpp b/src/qml/mixxxcontroller.cpp index 4419e74769eb..afb26bb2d4f6 100644 --- a/src/qml/mixxxcontroller.cpp +++ b/src/qml/mixxxcontroller.cpp @@ -2,11 +2,38 @@ namespace mixxx { namespace qml { -void MixxxController::init() { - metaObject()->invokeMethod(this, "init"); + +MixxxController::MixxxController(QObject* parent) + : QObject(parent), m_pChildren(this, &m_children) { } -void MixxxController::shutdown() { - metaObject()->invokeMethod(this, "shutdown"); + +void MixxxController::classBegin() { } + +void MixxxController::componentComplete() { + QObject::connect(this, + &MixxxController::init, + this, + &MixxxController::initChildrenComponents); + QObject::connect(this, + &MixxxController::shutdown, + this, + &MixxxController::shutdownChildrenComponents); +} + +void MixxxController::initChildrenComponents() { + for (auto* childComponent : m_children) { + // Try emit init signal + QMetaObject::invokeMethod(childComponent, "init", Qt::DirectConnection); + } +} + +void MixxxController::shutdownChildrenComponents() { + for (auto* childComponent : m_children) { + // Try emit shutdown signal + QMetaObject::invokeMethod(childComponent, "shutdown", Qt::DirectConnection); + } +} + } // namespace qml } // namespace mixxx diff --git a/src/qml/mixxxcontroller.h b/src/qml/mixxxcontroller.h index 112c43ec6643..f4a68017c978 100644 --- a/src/qml/mixxxcontroller.h +++ b/src/qml/mixxxcontroller.h @@ -6,27 +6,36 @@ #include #include -#include "mixxxscreen.h" - namespace mixxx { namespace qml { -class MixxxController : public QObject { +class MixxxController : public QObject, public QQmlParserStatus { Q_OBJECT + Q_INTERFACES(QQmlParserStatus) QML_ELEMENT Q_PROPERTY(QString controllerId MEMBER m_controllerId) Q_PROPERTY(bool debugMode MEMBER m_debugMode) - Q_PROPERTY(QQmlListProperty screens MEMBER m_screens) - Q_CLASSINFO("DefaultProperty", "screens") + Q_PROPERTY(QQmlListProperty childComponents MEMBER m_pChildren) + Q_CLASSINFO("DefaultProperty", "childComponents") public: + explicit MixxxController(QObject* parent = nullptr); + void classBegin() override; + void componentComplete() override; + + signals: void init(); void shutdown(); private: QString m_controllerId; bool m_debugMode; - QQmlListProperty m_screens; + QList m_children; + QQmlListProperty m_pChildren; + + private slots: + void initChildrenComponents(); + void shutdownChildrenComponents(); }; } // namespace qml diff --git a/src/qml/mixxxscreen.cpp b/src/qml/mixxxscreen.cpp index d1e2568a7c9d..b3a72b83f920 100644 --- a/src/qml/mixxxscreen.cpp +++ b/src/qml/mixxxscreen.cpp @@ -2,5 +2,30 @@ namespace mixxx { namespace qml { + +int MixxxScreen::width() { + return m_size.width(); +} + +void MixxxScreen::setWidth(int value) { + m_size = QSize(value, m_size.height()); +} + +int MixxxScreen::height() { + return m_size.width(); +} + +void MixxxScreen::setHeight(int value) { + m_size = QSize(m_size.width(), value); +} + +uint MixxxScreen::splashOff() { + return m_splashOff.count(); +} + +void MixxxScreen::setSplashOff(uint value) { + m_splashOff = std::chrono::milliseconds(value); +} + } // namespace qml } // namespace mixxx diff --git a/src/qml/mixxxscreen.h b/src/qml/mixxxscreen.h index df90679a1782..636c10804728 100644 --- a/src/qml/mixxxscreen.h +++ b/src/qml/mixxxscreen.h @@ -5,10 +5,11 @@ #ifndef MIXXX_MIXXXSCREEN_H #define MIXXX_MIXXXSCREEN_H +#include + #include #include #include -#include namespace mixxx { namespace qml { @@ -34,24 +35,16 @@ class MixxxScreen : public QObject { }; Q_ENUM(ColorEndian) - int width() { - return m_size.width(); - } - void setWidth(int value) { - m_size = QSize(value, m_size.height()); - } - int height() { - return m_size.width(); - } - void setHeight(int value) { - m_size = QSize(m_size.width(), value); - } - uint splashOff() { - return m_splashOff.count(); - } - void setSplashOff(uint value) { - m_splashOff = std::chrono::milliseconds(value); - } + int width(); + void setWidth(int value); + int height(); + void setHeight(int value); + uint splashOff(); + void setSplashOff(uint value); + + signals: + void init(); + void shutdown(); private: QString m_screenId; // The screen identifier. From c469ca4316cd2b1ebe0304d34d8cc34d4676a371 Mon Sep 17 00:00:00 2001 From: Christophe Henry Date: Fri, 19 Jul 2024 14:56:56 +0200 Subject: [PATCH 07/10] Start moving scren rendering from ControllerScriptEngineLegacy to MixxxScreen --- .../legacy/controllerscriptenginelegacy.cpp | 9 +-- src/qml/mixxxcontroller.h | 4 +- src/qml/mixxxscreen.cpp | 59 ++++++++++++++++++ src/qml/mixxxscreen.h | 61 +++++++++++++++---- 4 files changed, 116 insertions(+), 17 deletions(-) diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 029997ba7ada..d35378af50cd 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -828,10 +828,11 @@ bool ControllerScriptEngineLegacy::evaluateScriptFile(const QFileInfo& scriptFil } #ifdef MIXXX_USE_QML -bool ControllerScriptEngineLegacy::instanciateQMLComponent( - const LegacyControllerMapping::ScriptFileInfo& qmlScript, - QMap> - availableScreens) { +bool ControllerScriptEngineLegacy:: + : instanciateQMLComponent( + const LegacyControllerMapping::ScriptFileInfo& qmlScript, + QMap> + availableScreens) { // Like for Javascript, if the script is invalid, it should be watched so the user can fix it // without having to restart Mixxx. So, add it to the watcher before // evaluating it. diff --git a/src/qml/mixxxcontroller.h b/src/qml/mixxxcontroller.h index f4a68017c978..bf392927f7d3 100644 --- a/src/qml/mixxxcontroller.h +++ b/src/qml/mixxxcontroller.h @@ -11,11 +11,13 @@ namespace qml { class MixxxController : public QObject, public QQmlParserStatus { Q_OBJECT - Q_INTERFACES(QQmlParserStatus) QML_ELEMENT + Q_INTERFACES(QQmlParserStatus) + Q_PROPERTY(QString controllerId MEMBER m_controllerId) Q_PROPERTY(bool debugMode MEMBER m_debugMode) Q_PROPERTY(QQmlListProperty childComponents MEMBER m_pChildren) + Q_CLASSINFO("DefaultProperty", "childComponents") public: diff --git a/src/qml/mixxxscreen.cpp b/src/qml/mixxxscreen.cpp index b3a72b83f920..c3d1a1979e96 100644 --- a/src/qml/mixxxscreen.cpp +++ b/src/qml/mixxxscreen.cpp @@ -3,6 +3,37 @@ namespace mixxx { namespace qml { +void MixxxScreen::classBegin() { +} + +void MixxxScreen::componentComplete() { + const auto* const meta = metaObject(); + int typedIdx = meta->indexOfMethod(kScreenTransformFunctionTypedSignature); + int untypedIdx = meta->indexOfMethod(kScreenTransformFunctionUntypedSignature); + if (typedIdx >= 0) { + auto transformMethod = meta->method(typedIdx); + if (!transformMethod.isValid()) { + // TODO + } else { + transform = [transformMethod, this](const QByteArray input, + const QDateTime& timestamp) -> QVariant { + return transform(transformMethod, input, timestamp, true); + }; + } + } else if (untypedIdx >= 0) { + auto transformMethod = meta->method(untypedIdx); + if (!transformMethod.isValid()) { + // TODO + } else { + transform = [transformMethod, this](const QByteArray input, + const QDateTime& timestamp) -> QVariant { + return transform(transformMethod, input, timestamp, false); + }; + } + } else { + } +} + int MixxxScreen::width() { return m_size.width(); } @@ -26,6 +57,34 @@ uint MixxxScreen::splashOff() { void MixxxScreen::setSplashOff(uint value) { m_splashOff = std::chrono::milliseconds(value); } +QVariant MixxxScreen::transform(QMetaMethod transformMethod, + const QByteArray input, + const QDateTime& timestamp, + bool typed) { + QVariant returnedValue; + const bool isSuccessful = transformMethod.invoke( + &m_item, + Qt::DirectConnection, + Q_RETURN_ARG(QVariant, returnedValue), + typed ? Q_ARG(QVariant, QVariant::fromValue(input)) : Q_ARG(QByteArray, input), + Q_ARG(QVariant, timestamp)); + if (!isSuccessful) { + // TODO + return {}; + } else if (returnedValue.isValid()) { + // TODO + return {}; + } + + if (returnedValue.canView()) { + return QVariant(returnedValue.view()); + } else if (returnedValue.canConvert()) { + return QVariant(returnedValue.toByteArray()); + } else { + // TODO + return {}; + } +} } // namespace qml } // namespace mixxx diff --git a/src/qml/mixxxscreen.h b/src/qml/mixxxscreen.h index 636c10804728..f289aeaf7c82 100644 --- a/src/qml/mixxxscreen.h +++ b/src/qml/mixxxscreen.h @@ -9,14 +9,17 @@ #include #include +#include #include namespace mixxx { namespace qml { -class MixxxScreen : public QObject { +class MixxxScreen : public QObject, public QQmlParserStatus { Q_OBJECT QML_ELEMENT + Q_INTERFACES(QQmlParserStatus) + Q_PROPERTY(QString screenId MEMBER m_screenId REQUIRED) Q_PROPERTY(int width READ width WRITE setWidth) Q_PROPERTY(int height READ height WRITE setHeight) @@ -28,6 +31,10 @@ class MixxxScreen : public QObject { Q_PROPERTY(bool reversedColor MEMBER m_reversedColor) Q_PROPERTY(bool rawData MEMBER m_rawData) + Q_PROPERTY(QQuickItem item MEMBER m_item) + + Q_CLASSINFO("DefaultProperty", "item") + public: enum class ColorEndian { Big = static_cast(std::endian::big), @@ -35,6 +42,9 @@ class MixxxScreen : public QObject { }; Q_ENUM(ColorEndian) + void classBegin() override; + void componentComplete() override; + int width(); void setWidth(int value); int height(); @@ -47,19 +57,46 @@ class MixxxScreen : public QObject { void shutdown(); private: - QString m_screenId; // The screen identifier. - QSize m_size = QSize(0, 0); // The size of the screen. - uint m_targetFps = 30; // The maximum FPS to render. - uint m_msaa = 1; // The MSAA value to use for render. + // The screen identifier. + QString m_screenId; + // The size of the screen. + QSize m_size = QSize(0, 0); + // The maximum FPS to render. + uint m_targetFps = 30; + // The MSAA value to use for render. + uint m_msaa = 1; + // The rendering grace time given when the + // screen is requested to shutdown. std::chrono::milliseconds m_splashOff = std::chrono::milliseconds( - 3000); // The rendering grace time given when the screen is - // requested to shutdown. + 3000); + // The pixel encoding format. QImage::Format m_pixelType = - QImage::Format_RGB888; // The pixel encoding format. - ColorEndian m_endian = ColorEndian::Little; // The pixel endian format. - bool m_reversedColor = false; // Whether or not the RGB is swapped BGR. - bool m_rawData = false; // Whether or not the screen is allowed to receive - // bare data, not transformed. + QImage::Format_RGB888; + // The pixel endian format. + ColorEndian m_endian = ColorEndian::Little; + // Whether or not the RGB is swapped BGR. + bool m_reversedColor = false; + // Whether or not the screen is allowed to receive bare data, + // not transformed. + bool m_rawData = false; + // The item to render + QQuickItem m_item; + // Transform function + std::function transform = + [](const QByteArray input, const QDateTime& timestamp) { + return QVariant(input); + }; + + inline static QByteArray kScreenTransformFunctionUntypedSignature = + QMetaObject::normalizedSignature( + "transformFrame(QVariant,QVariant)"); + inline static QByteArray kScreenTransformFunctionTypedSignature = + QMetaObject::normalizedSignature("transformFrame(QVariant,QDateTime)"); + + QVariant transform(QMetaMethod transformMethod, + const QByteArray input, + const QDateTime& timestamp, + bool typed); }; } // namespace qml From 111031cd1763a2bdc3765a33eb8068ef57b820c7 Mon Sep 17 00:00:00 2001 From: Christophe Henry Date: Mon, 22 Jul 2024 09:08:47 +0200 Subject: [PATCH 08/10] Some fixes --- src/qml/mixxxcontroller.cpp | 31 ++++++++++++++++++++----------- src/qml/mixxxcontroller.h | 8 ++++++-- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/qml/mixxxcontroller.cpp b/src/qml/mixxxcontroller.cpp index afb26bb2d4f6..f67432aa190a 100644 --- a/src/qml/mixxxcontroller.cpp +++ b/src/qml/mixxxcontroller.cpp @@ -3,13 +3,6 @@ namespace mixxx { namespace qml { -MixxxController::MixxxController(QObject* parent) - : QObject(parent), m_pChildren(this, &m_children) { -} - -void MixxxController::classBegin() { -} - void MixxxController::componentComplete() { QObject::connect(this, &MixxxController::init, @@ -23,15 +16,31 @@ void MixxxController::componentComplete() { void MixxxController::initChildrenComponents() { for (auto* childComponent : m_children) { - // Try emit init signal - QMetaObject::invokeMethod(childComponent, "init", Qt::DirectConnection); + const auto* const meta = childComponent->metaObject(); + const auto idx = meta->indexOfMethod(kInitSignature); + if (idx >= 0) { + const auto method = meta->method(idx); + if (method.isValid()) { + method.invoke(childComponent, Qt::DirectConnection); + } else { + // TODO: log? + } + } } } void MixxxController::shutdownChildrenComponents() { for (auto* childComponent : m_children) { - // Try emit shutdown signal - QMetaObject::invokeMethod(childComponent, "shutdown", Qt::DirectConnection); + const auto* const meta = childComponent->metaObject(); + const auto idx = meta->indexOfMethod(kShutdownSignature); + if (idx >= 0) { + const auto method = meta->method(idx); + if (method.isValid()) { + method.invoke(childComponent, Qt::DirectConnection); + } else { + // TODO: log? + } + } } } diff --git a/src/qml/mixxxcontroller.h b/src/qml/mixxxcontroller.h index bf392927f7d3..925059eb2443 100644 --- a/src/qml/mixxxcontroller.h +++ b/src/qml/mixxxcontroller.h @@ -21,8 +21,9 @@ class MixxxController : public QObject, public QQmlParserStatus { Q_CLASSINFO("DefaultProperty", "childComponents") public: - explicit MixxxController(QObject* parent = nullptr); - void classBegin() override; + explicit MixxxController(QObject* parent = nullptr) + : QObject(parent), m_pChildren(this, &m_children){}; + void classBegin() override{}; void componentComplete() override; signals: @@ -35,6 +36,9 @@ class MixxxController : public QObject, public QQmlParserStatus { QList m_children; QQmlListProperty m_pChildren; + static inline QByteArray kInitSignature = QMetaObject::normalizedSignature("init()"); + static inline QByteArray kShutdownSignature = QMetaObject::normalizedSignature("shutdown()"); + private slots: void initChildrenComponents(); void shutdownChildrenComponents(); From 893137579b7157493161289b7243ceb7ff9bf756 Mon Sep 17 00:00:00 2001 From: Christophe Henry Date: Wed, 24 Jul 2024 11:05:22 +0200 Subject: [PATCH 09/10] Change MixxxScreen::tranform from JS function to MixxxScreen::transformFrame and call method --- res/controllers/Denon-DN-S3700.qml | 4 + .../legacy/controllerscriptenginelegacy.cpp | 9 +- src/qml/mixxxscreen.cpp | 92 ++++++++----------- src/qml/mixxxscreen.h | 36 ++++---- 4 files changed, 66 insertions(+), 75 deletions(-) diff --git a/res/controllers/Denon-DN-S3700.qml b/res/controllers/Denon-DN-S3700.qml index 715f1e218622..05597dfcd521 100644 --- a/res/controllers/Denon-DN-S3700.qml +++ b/res/controllers/Denon-DN-S3700.qml @@ -12,5 +12,9 @@ MixxxController { screenId: "screen 7" splashOff: 5000 onInit: console.error(`MixxxScreen.screenId=${screenId}, MixxxScreen.splashOff=${splashOff}`) + transformFrame: (frame, timestamp, area) => { + console.error(frame) + return new ArrayBuffer(0) + } } } diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index d35378af50cd..029997ba7ada 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -828,11 +828,10 @@ bool ControllerScriptEngineLegacy::evaluateScriptFile(const QFileInfo& scriptFil } #ifdef MIXXX_USE_QML -bool ControllerScriptEngineLegacy:: - : instanciateQMLComponent( - const LegacyControllerMapping::ScriptFileInfo& qmlScript, - QMap> - availableScreens) { +bool ControllerScriptEngineLegacy::instanciateQMLComponent( + const LegacyControllerMapping::ScriptFileInfo& qmlScript, + QMap> + availableScreens) { // Like for Javascript, if the script is invalid, it should be watched so the user can fix it // without having to restart Mixxx. So, add it to the watcher before // evaluating it. diff --git a/src/qml/mixxxscreen.cpp b/src/qml/mixxxscreen.cpp index c3d1a1979e96..e25986d2d68b 100644 --- a/src/qml/mixxxscreen.cpp +++ b/src/qml/mixxxscreen.cpp @@ -1,37 +1,17 @@ #include "mixxxscreen.h" +#include "util/assert.h" + namespace mixxx { namespace qml { -void MixxxScreen::classBegin() { -} - void MixxxScreen::componentComplete() { - const auto* const meta = metaObject(); - int typedIdx = meta->indexOfMethod(kScreenTransformFunctionTypedSignature); - int untypedIdx = meta->indexOfMethod(kScreenTransformFunctionUntypedSignature); - if (typedIdx >= 0) { - auto transformMethod = meta->method(typedIdx); - if (!transformMethod.isValid()) { - // TODO - } else { - transform = [transformMethod, this](const QByteArray input, - const QDateTime& timestamp) -> QVariant { - return transform(transformMethod, input, timestamp, true); - }; - } - } else if (untypedIdx >= 0) { - auto transformMethod = meta->method(untypedIdx); - if (!transformMethod.isValid()) { - // TODO - } else { - transform = [transformMethod, this](const QByteArray input, - const QDateTime& timestamp) -> QVariant { - return transform(transformMethod, input, timestamp, false); - }; - } - } else { + auto* const context = QQmlEngine::contextForObject(this); + VERIFY_OR_DEBUG_ASSERT(context) { + return; } + + m_engine = context->engine(); } int MixxxScreen::width() { @@ -43,7 +23,7 @@ void MixxxScreen::setWidth(int value) { } int MixxxScreen::height() { - return m_size.width(); + return m_size.height(); } void MixxxScreen::setHeight(int value) { @@ -57,33 +37,41 @@ uint MixxxScreen::splashOff() { void MixxxScreen::setSplashOff(uint value) { m_splashOff = std::chrono::milliseconds(value); } -QVariant MixxxScreen::transform(QMetaMethod transformMethod, - const QByteArray input, - const QDateTime& timestamp, - bool typed) { - QVariant returnedValue; - const bool isSuccessful = transformMethod.invoke( - &m_item, - Qt::DirectConnection, - Q_RETURN_ARG(QVariant, returnedValue), - typed ? Q_ARG(QVariant, QVariant::fromValue(input)) : Q_ARG(QByteArray, input), - Q_ARG(QVariant, timestamp)); - if (!isSuccessful) { - // TODO - return {}; - } else if (returnedValue.isValid()) { - // TODO - return {}; + +QJSValue MixxxScreen::jsTransformFrame() { + return m_transformFunc; +} + +void MixxxScreen::setJsTransformFrame(QJSValue value) { + if (!value.isCallable()) { + qWarning() << "transformFrame is not a valid function"; + return; + } + + m_transformFunc = value; + emit jsTransformFrameChanged(); +} + +const std::unique_ptr MixxxScreen::transform( + const QByteArray& frame, QDateTime timestamp, QRect area) { + VERIFY_OR_DEBUG_ASSERT(m_engine) { + return std::make_unique(frame); } - if (returnedValue.canView()) { - return QVariant(returnedValue.view()); - } else if (returnedValue.canConvert()) { - return QVariant(returnedValue.toByteArray()); - } else { - // TODO - return {}; + if (m_transformFunc.isUndefined()) { // Default implementation + return std::make_unique(frame); + } + const auto transformResult = + m_transformFunc.call(QJSValueList{m_engine->toScriptValue(frame), + m_engine->toScriptValue(timestamp), + m_engine->toScriptValue(area)}); + if (!transformResult.isVariant()) { + qWarning() << "transformFrame did not return a valid result"; + return std::make_unique(frame); } + const auto result = transformResult.toVariant().toByteArray(); + qDebug() << "transformFrame returned a result of size" << result.length(); + return std::make_unique(result); } } // namespace qml diff --git a/src/qml/mixxxscreen.h b/src/qml/mixxxscreen.h index f289aeaf7c82..f0458e08cfbf 100644 --- a/src/qml/mixxxscreen.h +++ b/src/qml/mixxxscreen.h @@ -12,6 +12,8 @@ #include #include +#include "mixxxcontroller.h" + namespace mixxx { namespace qml { @@ -30,8 +32,10 @@ class MixxxScreen : public QObject, public QQmlParserStatus { Q_PROPERTY(ColorEndian endian MEMBER m_endian) Q_PROPERTY(bool reversedColor MEMBER m_reversedColor) Q_PROPERTY(bool rawData MEMBER m_rawData) + Q_PROPERTY(QJSValue transformFrame READ jsTransformFrame WRITE + setJsTransformFrame NOTIFY jsTransformFrameChanged) - Q_PROPERTY(QQuickItem item MEMBER m_item) + Q_PROPERTY(QQuickItem* item MEMBER m_item) Q_CLASSINFO("DefaultProperty", "item") @@ -42,7 +46,11 @@ class MixxxScreen : public QObject, public QQmlParserStatus { }; Q_ENUM(ColorEndian) - void classBegin() override; + explicit MixxxScreen(MixxxController* parent = nullptr) + : QObject(parent) { + } + + void classBegin() override{}; void componentComplete() override; int width(); @@ -51,12 +59,18 @@ class MixxxScreen : public QObject, public QQmlParserStatus { void setHeight(int value); uint splashOff(); void setSplashOff(uint value); + QJSValue jsTransformFrame(); + void setJsTransformFrame(QJSValue value); + const std::unique_ptr transform( + const QByteArray& frame, QDateTime timestamp, QRect area); signals: void init(); void shutdown(); + void jsTransformFrameChanged(); private: + QQmlEngine* m_engine = nullptr; // The screen identifier. QString m_screenId; // The size of the screen. @@ -80,23 +94,9 @@ class MixxxScreen : public QObject, public QQmlParserStatus { // not transformed. bool m_rawData = false; // The item to render - QQuickItem m_item; + QQuickItem* m_item; // Transform function - std::function transform = - [](const QByteArray input, const QDateTime& timestamp) { - return QVariant(input); - }; - - inline static QByteArray kScreenTransformFunctionUntypedSignature = - QMetaObject::normalizedSignature( - "transformFrame(QVariant,QVariant)"); - inline static QByteArray kScreenTransformFunctionTypedSignature = - QMetaObject::normalizedSignature("transformFrame(QVariant,QDateTime)"); - - QVariant transform(QMetaMethod transformMethod, - const QByteArray input, - const QDateTime& timestamp, - bool typed); + QJSValue m_transformFunc; }; } // namespace qml From 33b764989856f3aa9c57cc1d1b2e7d01b913dc1a Mon Sep 17 00:00:00 2001 From: Christophe Henry Date: Fri, 26 Jul 2024 21:32:05 +0200 Subject: [PATCH 10/10] API interface exposing some ControllerScriptEngineBase methods to QML components --- .../scripting/controllerscriptenginebase.cpp | 12 +++++++++ .../scripting/controllerscriptenginebase.h | 26 +++++++++++++++++++ .../legacy/controllerscriptenginelegacy.h | 2 ++ src/qml/mixxxscreen.cpp | 12 +++++++++ src/qml/mixxxscreen.h | 3 ++- 5 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index e31faad90887..e4ff0873fbb0 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -24,6 +24,7 @@ ControllerScriptEngineBase::ControllerScriptEngineBase( m_bAbortOnWarning(false), #ifdef MIXXX_USE_QML m_bQmlMode(false), + m_mixxxControllerEngineInterface(this), #endif m_bTesting(false) { // Handle error dialog buttons @@ -31,6 +32,10 @@ ControllerScriptEngineBase::ControllerScriptEngineBase( } #ifdef MIXXX_USE_QML +void ControllerScriptEngineBase::declareScreen(mixxx::qml::MixxxScreen& screen) { + std::make_shared(screen); +} + void ControllerScriptEngineBase::registerTrackCollectionManager( std::shared_ptr pTrackCollectionManager) { s_pTrackCollectionManager = std::move(pTrackCollectionManager); @@ -60,6 +65,7 @@ bool ControllerScriptEngineBase::initialize() { #ifdef MIXXX_USE_QML } else { auto pQmlEngine = std::make_shared(this); + pQmlEngine->setProperty("controllerEngineInterface", m_mixxxControllerEngineInterface); pQmlEngine->addImportPath(QStringLiteral(":/mixxx.org/imports")); if (s_pTrackCollectionManager) { mixxx::qml::AsyncImageProvider* pImageProvider = new mixxx::qml::AsyncImageProvider( @@ -288,3 +294,9 @@ void ControllerScriptEngineBase::errorDialogButton( void ControllerScriptEngineBase::throwJSError(const QString& message) { m_pJSEngine->throwError(message); } + +#ifdef MIXXX_USE_QML +void mixxx::qml::MixxxControllerEngineInterface::declareScreen(MixxxScreen& screen) { + m_controllerScriptEngine->declareScreen(screen); +} +#endif diff --git a/src/controllers/scripting/controllerscriptenginebase.h b/src/controllers/scripting/controllerscriptenginebase.h index 2129184b641b..d7d9fdf35e9b 100644 --- a/src/controllers/scripting/controllerscriptenginebase.h +++ b/src/controllers/scripting/controllerscriptenginebase.h @@ -10,12 +10,14 @@ #include "util/runtimeloggingcategory.h" #ifdef MIXXX_USE_QML #include "controllers/controllerenginethreadcontrol.h" +#include "qml/mixxxscreen.h" #endif class Controller; class QJSEngine; #ifdef MIXXX_USE_QML class TrackCollectionManager; +class MixxxControllerEngineInterface; #endif /// ControllerScriptEngineBase manages the JavaScript engine for controller scripts. @@ -38,6 +40,8 @@ class ControllerScriptEngineBase : public QObject { #ifdef MIXXX_USE_QML /// Precondition: QML.isValid() == true void showQMLExceptionDialog(const QQmlError& evaluationResult, bool bFatal = false); + + void declareScreen(mixxx::qml::MixxxScreen& screen); #endif void throwJSError(const QString& message); @@ -94,6 +98,8 @@ class ControllerScriptEngineBase : public QObject { #ifdef MIXXX_USE_QML private: static inline std::shared_ptr s_pTrackCollectionManager; + QHash> m_screens; + MixxxControllerEngineInterface m_mixxxControllerEngineInterface; protected: /// Pause the GUI main thread. Pause is required by rendering @@ -119,3 +125,23 @@ class ControllerScriptEngineBase : public QObject { friend class ColorMapperJSProxy; friend class MidiControllerTest; }; + +#ifdef MIXXX_USE_QML +namespace mixxx { +namespace qml { + +class MixxxControllerEngineInterface : QObject { + Q_OBJECT + public: + MixxxControllerEngineInterface(ControllerScriptEngineBase* controllerScriptEngine) + : QObject(controllerScriptEngine), m_controllerScriptEngine(controllerScriptEngine) { + } + void declareScreen(MixxxScreen& screen); + + private: + ControllerScriptEngineBase* m_controllerScriptEngine; +}; + +} // namespace qml +} // namespace mixxx +#endif diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index 3893785e6bd9..93f788df8e03 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -6,6 +6,8 @@ #include #ifdef MIXXX_USE_QML #include + +#include "qml/mixxxscreen.h" #endif #include "controllers/legacycontrollermapping.h" diff --git a/src/qml/mixxxscreen.cpp b/src/qml/mixxxscreen.cpp index e25986d2d68b..3bd7fe7adfaa 100644 --- a/src/qml/mixxxscreen.cpp +++ b/src/qml/mixxxscreen.cpp @@ -1,5 +1,6 @@ #include "mixxxscreen.h" +#include "controllers/scripting/controllerscriptenginebase.h" #include "util/assert.h" namespace mixxx { @@ -12,6 +13,17 @@ void MixxxScreen::componentComplete() { } m_engine = context->engine(); + const MixxxControllerEngineInterface controllerEngineInterface = + qobject_cast( + m_engine->property("controllerEngineInterface")); + VERIFY_OR_DEBUG_ASSERT(controllerEngineInterface) { + return; + } + controllerEngineInterface.declareScreen(this); +} + +QString MixxxScreen::screenId() { + return m_screenId; } int MixxxScreen::width() { diff --git a/src/qml/mixxxscreen.h b/src/qml/mixxxscreen.h index f0458e08cfbf..6aab3e914c1e 100644 --- a/src/qml/mixxxscreen.h +++ b/src/qml/mixxxscreen.h @@ -22,7 +22,7 @@ class MixxxScreen : public QObject, public QQmlParserStatus { QML_ELEMENT Q_INTERFACES(QQmlParserStatus) - Q_PROPERTY(QString screenId MEMBER m_screenId REQUIRED) + Q_PROPERTY(QString screenId READ screenId MEMBER m_screenId REQUIRED) Q_PROPERTY(int width READ width WRITE setWidth) Q_PROPERTY(int height READ height WRITE setHeight) Q_PROPERTY(uint targetFps MEMBER m_targetFps) @@ -53,6 +53,7 @@ class MixxxScreen : public QObject, public QQmlParserStatus { void classBegin() override{}; void componentComplete() override; + QString screenId(); int width(); void setWidth(int value); int height();