diff options
-rw-r--r-- | src/ivicore/ivicore.pro | 4 | ||||
-rw-r--r-- | src/ivicore/qivisimulationengine.cpp | 278 | ||||
-rw-r--r-- | src/ivicore/qivisimulationengine.h | 71 | ||||
-rw-r--r-- | src/ivicore/qivisimulationproxy.cpp | 246 | ||||
-rw-r--r-- | src/ivicore/qivisimulationproxy.h | 196 |
5 files changed, 795 insertions, 0 deletions
diff --git a/src/ivicore/ivicore.pro b/src/ivicore/ivicore.pro index ab74365..f0c640c 100644 --- a/src/ivicore/ivicore.pro +++ b/src/ivicore/ivicore.pro @@ -56,6 +56,8 @@ HEADERS += \ qividefaultpropertyoverrider_p.h \ qivipendingreply.h \ qivipendingreply_p.h \ + qivisimulationengine.h \ + qivisimulationproxy.h \ qtivicoremodule.h SOURCES += \ @@ -79,6 +81,8 @@ SOURCES += \ qividefaultpropertyoverrider.cpp \ qiviqmlconversion_helper.cpp \ qivipendingreply.cpp \ + qivisimulationengine.cpp \ + qivisimulationproxy.cpp \ qtivicoremodule.cpp include(queryparser/queryparser.pri) diff --git a/src/ivicore/qivisimulationengine.cpp b/src/ivicore/qivisimulationengine.cpp new file mode 100644 index 0000000..dd3ff90 --- /dev/null +++ b/src/ivicore/qivisimulationengine.cpp @@ -0,0 +1,278 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtIvi module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite licenses may use +** this file in accordance with the commercial license agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and The Qt Company. For +** licensing terms and conditions see https://www.qt.io/terms-conditions. +** For further information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +** SPDX-License-Identifier: LGPL-3.0 +** +****************************************************************************/ + +#include "qivisimulationengine.h" + +#include <QFile> + +QT_BEGIN_NAMESPACE + +/*! + \class QIviSimulationEngine + \inmodule QtIviCore + \brief QIviSimulationEngine provides a way to script a simulation backend from QML. + + This class is an extended QQmlApplicationEngine which can be used to load QML files. It is made + especially for \l {Dynamic Backend System}{simulation backends} to script the behavior of a + simulation backend from QML. + + In contrast to a normal QQmlEngine the QIviSimulationEngine provides an extra template function + called registerSimulationInstance(). + Using this function it is possible to register an class instance as a QML type. Inside a QML + file this QML type can be used to define the behavior for function calls, update properties or + emit signals. + + \section1 Registering an instance + + Any instance of a class derived from QObject can be registered to the QIviSimulationEngine by + calling the registerSimulationInstance function. Similar to qmlRegisterTypes, the provided uri, + version and name is used for importing the type from within QML. + + \code + class MyClass : public QObject + { + Q_OBJECT + Q_PROPERTY(int currentTemperature READ currentTemperature WRITE setCurrentTemperature NOTIFY currentTemperatureChanged) + + ... + } + \endcode + + An instance of this simple class can be registered like this: + + \code + QIviSimulationEngine engine; + MyClass myClass; + engine.registerSimulationInstance<MyClass>(&myClass, "Test", 1, 0, "MyClass"); + engine.loadSimulation("simulation.qml") + \endcode + + The registered instance has the same constraints as other C++ classes exposed to QML and needs + to use Q_PROPERTY, Q_INVOKABLE or slots to make the functionality available to QML. + + \section1 Using the type from QML + + Once an instance is registered to the engine, the type can be used like any other QML element + in a declarative form: + + \qml + import QtQuick 2.0 + import Test 1.0 + + Item { + MyClass { + id: myClass + + Component.onCompleted: currentTemperature = 10; + } + + Timer { + running: true + repeat: true + interval: 1000 + onTriggered: myClass.currentTemperature++; + } + } + \endqml + + This QML file will initialize the \c currentTemperature of \c myClass with a value of \e 10 and + increase it every second. + + In the same way values can be updated from the C++ side and the QML side can react to the + change. E.g. the following QML prints the \c currentTemperature whenever it changed: + + \qml + import QtQuick 2.0 + import Test 1.0 + + MyClass { + onCurrentTemperatureChanged: print(currentTemperature) + } + \endqml + + The slot will be called once the myClass variable is updated: + + \code + QIviSimulationEngine engine; + MyClass myClass; + engine.registerSimulationInstance<MyClass>(&myClass, "Test", 1, 0, "MyClass"); + engine.loadSimulation("simulation.qml") + ... + myClass.setCurrentTemperature(100); + \endcode + + \section1 Forwarding calls from the instance to the engine + + Providing the behavior for invokable functions in QML can be done as well, but for this the + exposed class needs to be extended. + + E.g. by adding the following line to the \c setCurrentTemperature setter: + + \code + void MyClass::setCurrentTemperature(int currentTemperature) + { + QIVI_SIMULATION_TRY_CALL(MyClass, "setCurrentTemperature", void, currentTemperature); + + if (m_currentTemperature == currentTemperature) + return; + m_currentTemperature = currentTemperature; + emit currentTemperatureChanged(m_currentTemperature); + } + \endcode + + Calling the setCurrentTemperature() function will now try to forward the call to the QML + instance if a function matching the signature is defined in QML and when successful, use its + returned value and skip the execution of the original C++ function. + + By using the following QML snippet the execution of the C++ setter is skipped and only an error + is emitted on the console: + + \qml + import QtQuick 2.0 + import Test 1.0 + + MyClass { + function setCurrentTemperature(temperature) { + print("Updating the temperature is not possible") + } + } + \endqml + + \section1 Reusing existing behavior in the instance + + Replacing the C++ functionality with a QML behavior is not always what is desired. Because of + this, it is also possible to call the original C++ behavior from QML. For this the original C++ + function needs to be a Q_INVOKABLE or a slot and the functionality works like function + overriding in C++. In the C++ world the functionality of the overridden function can be + accessed by calling \c <BaseClass>::<function>. In the exposed QML type this is possible by + calling the function in the \e Base object. + + \qml + import QtQuick 2.0 + import Test 1.0 + + MyClass { + function setCurrentTemperature(temperature) { + print("Updating the temperature: " + temperature ) + Base.setCurrentTemperature(temperature) + } + } + \endqml + + This QML code overrides the setCurrentTemperature behavior in QML and prints an debug message + for the new value. The original C++ behavior is called by using \c + Base.setCurrentTemperature(temperature). + + \section1 Multiple QML instances + + The registered instance is exposed as a normal QML type. This makes it possible to have + multiple declarations in QML and by that have multiple QML instances linked to the same C++ + instance. Updating and reacting to property changes and signal emissions is possible in all + instances, but should be used with care as this can result in property update loops and other + issues. + + Forwarding C++ function calls to QML is limited. Every call is forwarded to only one QML + instance as the return value is used from this call. If multiple QML instances define the same + method, the C++ call is always forwarded to the first registered QML instance. +*/ + +QIviSimulationEngine::QIviSimulationEngine(QObject *parent) + : QQmlApplicationEngine (parent) +{ +} + +/*! + Loads the QML \a file as the simulation behavior. + + In addition to QQmlApplicationEngine::load(), this function provides functionality to change + the used simulation file by using an environment variable. +*/ +void QIviSimulationEngine::loadSimulation(const QString &file) +{ + if (QFile::exists(file)) + load(file); +} + +/*! + \fn template <typename T> void QIviSimulationEngine::registerSimulationInstance(T* instance, const char *uri, int versionMajor, int versionMinor, const char *qmlName) + + Registers the provided \a instance in the QML system with the name \a qmlName, in the library + imported from \a uri having the version number composed from \a versionMajor and \a + versionMinor. + + \note The registered instance is only available to this instance of the QIviSimulationEngine. + Using it from another QIviSimulationEngine or a QQmlEngine will not work and produce an error. + + \sa qmlRegisterType +*/ + +/*! + \macro QIVI_SIMULATION_TRY_CALL_FUNC(instance_type, function, ret_func, ...) + \relates QIviSimulationEngine + + Tries to call \a function in the QML instances registered for the instance of type \a + instance_type. The variadic arguments are passed as arguments to the function in QML. + + If the call was successful the code passed in \a ret_func will be executed. This can be useful + for situations when the return value needs to be converted first. The original return value is + available as \c return_value. + + \code + QIVI_SIMULATION_TRY_CALL_FUNC(MyClass, "contactList", return return_value.toStringList()); + \endcode + + \sa QIVI_SIMULATION_TRY_CALL {Forwarding calls from the instance to the engine} +*/ + +/*! + \macro QIVI_SIMULATION_TRY_CALL(instance_type, function, ret_type, ...) + \relates QIviSimulationEngine + + Tries to call \a function in the QML instances registered for the instance of type \a + instance_type. The variadic arguments are passed as arguments to the function in QML. + + If the call was successful the return value of type \a ret_type will be returned and all code + after this macro will \b not be executed. + + \sa QIVI_SIMULATION_TRY_CALL_FUNC {Forwarding calls from the instance to the engine} +*/ + +QT_END_NAMESPACE diff --git a/src/ivicore/qivisimulationengine.h b/src/ivicore/qivisimulationengine.h new file mode 100644 index 0000000..988fd9a --- /dev/null +++ b/src/ivicore/qivisimulationengine.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtIvi module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite licenses may use +** this file in accordance with the commercial license agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and The Qt Company. For +** licensing terms and conditions see https://www.qt.io/terms-conditions. +** For further information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +** SPDX-License-Identifier: LGPL-3.0 +** +****************************************************************************/ + +#ifndef QIVISIMULATIONENGINE_H +#define QIVISIMULATIONENGINE_H + +#include <QtIviCore/QtIviCoreModule> + +#include <QtQml/QQmlApplicationEngine> +#include <QtIviCore/qivisimulationproxy.h> + +QT_BEGIN_NAMESPACE + +class Q_QTIVICORE_EXPORT QIviSimulationEngine : public QQmlApplicationEngine +{ + Q_OBJECT +public: + QIviSimulationEngine(QObject *parent = nullptr); + + template <typename T> void registerSimulationInstance(T* instance, const char *uri, int versionMajor, int versionMinor, const char *qmlName) + { + //pass engine here to check that it's only used in this engine + qtivi_private::QIviSimulationProxy<T>::registerInstance(this, instance); + //@uri BridgetTest + qmlRegisterType< qtivi_private::QIviSimulationProxy<T> >(uri, versionMajor, versionMinor, qmlName); + } + + void loadSimulation(const QString &file); +}; + +QT_END_NAMESPACE + +#endif // QIVISIMULATIONENGINE_H diff --git a/src/ivicore/qivisimulationproxy.cpp b/src/ivicore/qivisimulationproxy.cpp new file mode 100644 index 0000000..b3ce374 --- /dev/null +++ b/src/ivicore/qivisimulationproxy.cpp @@ -0,0 +1,246 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtIvi module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite licenses may use +** this file in accordance with the commercial license agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and The Qt Company. For +** licensing terms and conditions see https://www.qt.io/terms-conditions. +** For further information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +** SPDX-License-Identifier: LGPL-3.0 +** +****************************************************************************/ + +#include "qivisimulationproxy.h" +#include "qivisimulationengine.h" + +#include <QDebug> +#include <QLoggingCategory> +#include <QQmlInfo> + +#include <private/qmetaobjectbuilder_p.h> + +QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(qLcIviSimulationEngine, "qt.ivi.simulationengine"); + +namespace qtivi_private { + +QIviSimulationProxyBase::QIviSimulationProxyBase(QMetaObject *staticMetaObject, QObject *instance, const QHash<int, int> &methodMap, QObject *parent) + : QObject(parent) + , m_noSimulationEngine(false) + , m_instance(instance) + , m_staticMetaObject(staticMetaObject) + , m_methodMap(methodMap) +{ +} + +const QMetaObject *QIviSimulationProxyBase::metaObject() const +{ + // Copied from moc_ class code + // A dynamicMetaObject is created when the type is used from QML and new functions/properties + // are added. This makes sure that we can access these from C++ as well + return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : m_staticMetaObject; +} + +void *QIviSimulationProxyBase::qt_metacast(const char *classname) +{ + if (!classname) + return nullptr; + return m_instance->qt_metacast(classname); +} + +int QIviSimulationProxyBase::qt_metacall(QMetaObject::Call call, int methodId, void **a) +{ + if (m_noSimulationEngine) + return -1; + + if (call == QMetaObject::InvokeMetaMethod) { + // When a forwarded signal from the registered instance gets in. Directly call the signal here as well + if (sender() == m_instance) { + // The static MetaObject uses local ids, so we need to subtract the offset + QMetaObject::activate(this, m_staticMetaObject, methodId - m_staticMetaObject->methodOffset(), a); + return 0; + } + return m_instance->qt_metacall(call, m_methodMap.key(methodId), a); + } + return m_instance->qt_metacall(call, methodId, a); +} + +void QIviSimulationProxyBase::classBegin() +{ +} + +void QIviSimulationProxyBase::componentComplete() +{ + setProperty("Base", QVariant::fromValue(m_instance)); +} + +QMetaObject QIviSimulationProxyBase::buildObject(const QMetaObject *metaObject, QHash<int, int> &methodMap, QIviSimulationProxyBase::StaticMetacallFunction metaCallFunction) +{ + QMetaObjectBuilder builder; + const QString name = QString(QStringLiteral("QIviSimulationProxy_%1")).arg(QLatin1String(metaObject->className())); + builder.setClassName(qPrintable(name)); + builder.setSuperClass(&QObject::staticMetaObject); + builder.setStaticMetacallFunction(metaCallFunction); + + // Build the MetaObject ourself + // This is needed as QML uses the static_metacall for reading the properties and every QMetaObject + // has its own set. But as we need to intercept this to forward it to the registered instance, we + // build our MetaObject completely and have one static_metacall function for all properties + const QMetaObject *mo = metaObject; + + //Search for the QObject base class to know which offset we need to start building from + const QMetaObject *superClass = mo->superClass(); + while (qstrcmp(superClass->className(), "QObject") != 0) { + superClass = superClass->superClass(); + } + const int methodOffset = superClass->methodCount(); + const int propertyOffset = superClass->propertyCount(); + + //Fill the mapping for all QObject methods. + for (int i=0; i<methodOffset; ++i) + methodMap.insert(i, i); + + //Add all signals + qCDebug(qLcIviSimulationEngine) << "Signal Mapping: Original -> Proxy"; + for (int index = methodOffset; index < mo->methodCount(); ++index) { + QMetaMethod mm = mo->method(index); + if (mm.methodType() == QMetaMethod::Signal) { + auto mb = builder.addMethod(mm); + qCDebug(qLcIviSimulationEngine) << index << "->" << methodOffset + mb.index(); + methodMap.insert(index, methodOffset + mb.index()); + } + } + + //Add all other methods + qCDebug(qLcIviSimulationEngine) << "Method Mapping: Original -> Proxy"; + for (int index = methodOffset; index < mo->methodCount(); ++index) { + QMetaMethod mm = mo->method(index); + if (mm.methodType() != QMetaMethod::Signal) { + auto mb = builder.addMethod(mm); + qCDebug(qLcIviSimulationEngine) << index << "->" << methodOffset + mb.index(); + methodMap.insert(index, methodOffset + mb.index()); + } + } + + //Add all properties + for (int index = propertyOffset; index < mo->propertyCount(); ++index) { + QMetaProperty prop = mo->property(index); + builder.addProperty(prop); + } + //Add a Base property which works like a attached property + builder.addProperty("Base", "QObject *"); + + //Debugging output + if (qLcIviSimulationEngine().isDebugEnabled()) { + qCDebug(qLcIviSimulationEngine) << "Original Object:"; + for (int i=0; i < mo->methodCount(); i++) { + QMetaMethod method = mo->method(i); + qCDebug(qLcIviSimulationEngine) << "method: " << method.methodIndex() << method.methodSignature(); + } + for (int i=0; i < mo->propertyCount(); i++) { + QMetaProperty prop = mo->property(i); + qCDebug(qLcIviSimulationEngine) << "property:" << prop.propertyIndex() << prop.name(); + QMetaMethod method = prop.notifySignal(); + qCDebug(qLcIviSimulationEngine) << "signal: " << method.methodIndex() << method.methodSignature(); + } + + qCDebug(qLcIviSimulationEngine) << "Proxy Object:"; + mo = builder.toMetaObject(); + for (int i=0; i < mo->methodCount(); i++) { + QMetaMethod method = mo->method(i); + qCDebug(qLcIviSimulationEngine) << "method: " << method.methodIndex() << method.methodSignature(); + } + for (int i=0; i < mo->propertyCount(); i++) { + QMetaProperty prop = mo->property(i); + qCDebug(qLcIviSimulationEngine) << "property:" << prop.propertyIndex() << prop.name(); + QMetaMethod method = prop.notifySignal(); + qCDebug(qLcIviSimulationEngine) << "signal: " << method.methodIndex() << method.methodSignature(); + } + } + + return *builder.toMetaObject(); +} + +bool QIviSimulationProxyBase::callQmlMethod(const char *function, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9) +{ + if (m_noSimulationEngine) + return false; + + //Prevent recursion + static bool recursionGuard = false; + if (recursionGuard) + return false; + + recursionGuard = true; + + bool functionExecuted = false; + const QMetaObject *mo = metaObject(); + + // Only invoke the functions declared in QML. + // Once a function/property is added to a type a new MetaObject gets created which contains + // _QML_ in the name. + if (QString::fromLatin1(mo->className()).contains(QLatin1String("_QML_"))) { + for (int i=mo->methodOffset(); i<mo->methodCount(); i++) { + //qDebug() << "CHECKING FOR: " << function << mo->method(i).name(); + if (mo->method(i).name() != function) + continue; + //qDebug() << "EXECUTING"; + functionExecuted = QMetaObject::invokeMethod(this, function, ret, val0, val1, val2, val3, val4, val5, val6, val7, val8, val9); + break; + } + } + recursionGuard = false; + return functionExecuted; +} + +void QIviSimulationProxyBase::setup(QIviSimulationEngine *engine) +{ + if (engine != qmlEngine(this)) { + qmlWarning(this) << "QIviSimulationProxy can only be used in the same Engine it is registered in"; + m_noSimulationEngine = true; + return; + } + + // Connect all signals from the instance to the signals of this metaobject. + // This is needed to relay the signals from the instance to this instance and to QML + const QMetaObject *mo = m_instance->metaObject(); + for (int i=0; i<mo->methodCount(); i++) { + QMetaMethod mm = mo->method(i); + if (mm.methodType() != QMetaMethod::Signal) + continue; + connect(m_instance, mm, this, m_staticMetaObject->method(m_methodMap.value(i))); + } +} + +} //namespace + +QT_END_NAMESPACE diff --git a/src/ivicore/qivisimulationproxy.h b/src/ivicore/qivisimulationproxy.h new file mode 100644 index 0000000..911c2e3 --- /dev/null +++ b/src/ivicore/qivisimulationproxy.h @@ -0,0 +1,196 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtIvi module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite licenses may use +** this file in accordance with the commercial license agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and The Qt Company. For +** licensing terms and conditions see https://www.qt.io/terms-conditions. +** For further information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +** SPDX-License-Identifier: LGPL-3.0 +** +****************************************************************************/ + +#ifndef QIVISIMULATIONPROXY_H +#define QIVISIMULATIONPROXY_H + +#include <QtIviCore/QtIviCoreModule> + +#include <QtCore/QObject> +#include <QtCore/QVariant> +#include <QtCore/QMetaObject> +#include <QtQml/QQmlParserStatus> + +QT_BEGIN_NAMESPACE + +class QIviSimulationEngine; + +// The classes here can't be moved to a private header as they are used in macros in the user code +// They are still considered private as they shouldn't be used directly by the user. +namespace qtivi_private { + + // This is needed as QVariant doesn't support returning void + // It is used to cast the variant to the needed return type and use it in the return statement. + template <typename T> struct QIviReturnValueHelper { + static T value(const QVariant &var) + { + return var.value<T>(); + } + }; + + template <> struct QIviReturnValueHelper <void> { + static void value(const QVariant &var) + { + Q_UNUSED(var); + return; + } + }; + + class Q_QTIVICORE_EXPORT QIviSimulationProxyBase : public QObject, public QQmlParserStatus + { + Q_INTERFACES(QQmlParserStatus) + + public: + QIviSimulationProxyBase(QMetaObject *staticMetaObject, QObject *instance, const QHash<int, int> &methodMap, QObject *parent=nullptr); + + virtual const QMetaObject *metaObject() const override; + virtual void *qt_metacast(const char *classname) override; + virtual int qt_metacall(QMetaObject::Call call, int methodId, void **a) override; + + void classBegin() override; + void componentComplete() override; + + typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **); + static QMetaObject buildObject(const QMetaObject *metaObject, QHash<int, int> &methodMap, QIviSimulationProxyBase::StaticMetacallFunction metaCallFunction); + + bool callQmlMethod(const char* function, + QGenericReturnArgument ret, + QGenericArgument val0 = QGenericArgument(nullptr), + QGenericArgument val1 = QGenericArgument(), + QGenericArgument val2 = QGenericArgument(), + QGenericArgument val3 = QGenericArgument(), + QGenericArgument val4 = QGenericArgument(), + QGenericArgument val5 = QGenericArgument(), + QGenericArgument val6 = QGenericArgument(), + QGenericArgument val7 = QGenericArgument(), + QGenericArgument val8 = QGenericArgument(), + QGenericArgument val9 = QGenericArgument()); + + template<typename... Ts> + bool callQmlMethod(const char* function, QVariant &returnValue, Ts... args) + { + return QIviSimulationProxyBase::callQmlMethod(function, Q_RETURN_ARG(QVariant, returnValue), Q_ARG(QVariant, QVariant::fromValue(args))...); + } + + protected: + void setup(QIviSimulationEngine *engine); + + private: + bool m_noSimulationEngine; + QObject *m_instance; + QMetaObject *m_staticMetaObject; + QHash<int, int> m_methodMap; + }; + + template <typename T> class Q_QTIVICORE_EXPORT QIviSimulationProxy: public QIviSimulationProxyBase + { + public: + QIviSimulationProxy(QObject *parent=nullptr) + : QIviSimulationProxyBase(&staticMetaObject, m_instance, m_methodMap, parent) + { + Q_ASSERT_X(m_instance, "QIviSimulationProxy()", "QIviSimulationProxy::registerInstance needs to be called first"); + } + + ~QIviSimulationProxy() + { + proxies.removeAll(this); + } + + void classBegin() override + { + QIviSimulationProxyBase::setup(m_engine); + proxies.append(this); + } + + // Function is used from QML when reading a property. The static QMetaObject has this function set + // as the handler for all static meta calls + static void qt_static_metacall(QObject *obj, QMetaObject::Call call, int methodId, void **a) + { + Q_UNUSED(obj); + Q_ASSERT_X(m_instance, "qt_static_metacall()", "QIviSimulationProxy::registerInstance needs to be called first"); + // As the class acts as a proxy, forward all calls here to the registered instance + if (call == QMetaObject::ReadProperty) { + void *_v = a[0]; + *reinterpret_cast< T**>(_v) = m_instance; + obj->qt_metacall(call, methodId + staticMetaObject.propertyOffset(), a); + return; + } + + obj->qt_metacall(call, methodId, a); + } + + static void registerInstance(QIviSimulationEngine *engine, T *instance) + { + m_engine = engine; + m_instance = instance; + } + + static QMetaObject staticMetaObject; + static QList<QIviSimulationProxy<T> *> proxies; + + private: + static QIviSimulationEngine *m_engine; + static T *m_instance; + static QHash<int, int> m_methodMap; + }; + + template <typename T> QMetaObject QIviSimulationProxy<T>::staticMetaObject = QIviSimulationProxy<T>::buildObject(&T::staticMetaObject, QIviSimulationProxy::m_methodMap, &QIviSimulationProxy<T>::qt_static_metacall); + template <typename T> T *QIviSimulationProxy<T>::m_instance = nullptr; + template <typename T> QIviSimulationEngine *QIviSimulationProxy<T>::m_engine = nullptr; + template <typename T> QList<QIviSimulationProxy<T> *> QIviSimulationProxy<T>::proxies = QList<QIviSimulationProxy<T> *>(); + template <typename T> QHash<int, int> QIviSimulationProxy<T>::m_methodMap = QHash<int, int>(); +} + +#define QIVI_SIMULATION_TRY_CALL_FUNC(instance_type, function, ret_func, ...) \ +for (auto _qivi_instance : qtivi_private::QIviSimulationProxy<instance_type>::proxies) { \ + QVariant return_value; \ + if (_qivi_instance->callQmlMethod(function, return_value, ##__VA_ARGS__)) { \ + ret_func; \ + } \ +} \ + + +#define QIVI_SIMULATION_TRY_CALL(instance_type, function, ret_type, ...) \ +QIVI_SIMULATION_TRY_CALL_FUNC(instance_type, function, return qtivi_private::QIviReturnValueHelper<ret_type>::value(return_value);, ##__VA_ARGS__) \ + +QT_END_NAMESPACE + +#endif // QIVISIMULATIONPROXY_H |