diff options
author | Richard Weickelt <richard@weickelt.de> | 2018-05-31 21:41:47 +0200 |
---|---|---|
committer | Richard Weickelt <richard@weickelt.de> | 2018-06-21 18:00:11 +0000 |
commit | 73fdeb9c1c70079e54104c93811b5d7ff9e4ee0b (patch) | |
tree | c6c170c7a27fa8fa1715c035515c3ba849c097c9 | |
parent | f44782d0cdbdb800d9c31d5aff712fbf29d52edc (diff) |
Provide API to access singletons associated with a QQmlEngine
This patch adds allows C++ code to retrieve the instance of a registered
singleton type. Until now this required a deturn via QML expression.
Two methods are added to QQmlEngine: A generic one that encapsulates all
singleton objects in a QJSValue and a template function for QObject-derived
singleton types.
An additional convenience function is added to query the QML type id. This
function may also be used for other purposes in the future.
[ChangeLog][QtQml][QQmlEngine] Added API to access singletons associated with
a QQmlEngine.
Task-number: QTBUG-39970
Change-Id: I67c132ede35f80b9aaf1c5e5456715cf4f1b0848
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
-rw-r--r-- | src/qml/doc/src/qmlfunctions.qdoc | 22 | ||||
-rw-r--r-- | src/qml/qml/qqml.h | 2 | ||||
-rw-r--r-- | src/qml/qml/qqmlengine.cpp | 65 | ||||
-rw-r--r-- | src/qml/qml/qqmlengine.h | 16 | ||||
-rw-r--r-- | src/qml/qml/qqmlmetatype.cpp | 39 | ||||
-rw-r--r-- | src/qml/qml/qqmlmetatype_p.h | 7 | ||||
-rw-r--r-- | tests/auto/qml/qqmlengine/tst_qqmlengine.cpp | 91 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp | 22 |
8 files changed, 255 insertions, 9 deletions
diff --git a/src/qml/doc/src/qmlfunctions.qdoc b/src/qml/doc/src/qmlfunctions.qdoc index 6445a003a1..62c0f5d81b 100644 --- a/src/qml/doc/src/qmlfunctions.qdoc +++ b/src/qml/doc/src/qmlfunctions.qdoc @@ -638,3 +638,25 @@ are registered for that version. This is particularly useful for keeping the versions of related modules in sync. */ + +/*! + \since 5.12 + \fn int qmlTypeId(const char* uri, int versionMajor, int versionMinor, const char *qmlName); + \relates QQmlEngine + + Returns the QML type id of a type that was registered with the + name \a qmlName in a particular \a uri and a version specified in \a + versionMajor and \a versionMinor. + + This function returns the same value as the QML type registration functions + such as qmlRegisterType() and qmlRegisterSingletonType(). + + If \a qmlName, \a uri and \a versionMajor match a registered type, but the + specified minor version in \a versionMinor is higher, then the id of the type + with the closest minor version is returned. + + Returns -1 if no matching type was found or one of the given parameters + was invalid. + + \sa qmlRegisterType(), qmlRegisterSingletonType() +*/ diff --git a/src/qml/qml/qqml.h b/src/qml/qml/qqml.h index 213f23cd98..2a8e236905 100644 --- a/src/qml/qml/qqml.h +++ b/src/qml/qml/qqml.h @@ -645,6 +645,8 @@ inline int qmlRegisterType(const QUrl &url, const char *uri, int versionMajor, i return QQmlPrivate::qmlregister(QQmlPrivate::CompositeRegistration, &type); } +int Q_QML_EXPORT qmlTypeId(const char *uri, int versionMajor, int versionMinor, const char *qmlName); + QT_END_NAMESPACE QML_DECLARE_TYPE(QObject) diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index d62d91456f..61cf2a8994 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -1374,6 +1374,71 @@ void QQmlEngine::setOutputWarningsToStandardError(bool enabled) } /*! + \fn template<typename T> T QQmlEngine::singletonInstance(int qmlTypeId) + + Returns the instance of a singleton type that was registered under \a qmlTypeId. + + The template argument \e T may be either QJSValue or a pointer to a QObject-derived + type and depends on how the singleton was registered. If no instance of \e T has been + created yet, it is created now. If \a qmlTypeId does not represent a valid singleton + type, either a default constructed QJSValue or a \c nullptr is returned. + + QObject* example: + \code + class MySingleton : public QObject { + Q_OBJECT + static int typeId; + // ... + }; + + // Register with QObject* callback + MySingleton::typeId = qmlRegisterSingletonType<MySingleton>(...); + + // Retrieve as QObject* + QQmlEngine engine; + MySingleton* instance = engine.singletonInstance<MySingleton*>(MySingleton::typeId); + \endcode + + QJSValue example: + \code + // Register with QJSValue callback + int typeId = qmlRegisterSingletonType(...); + + // Retrieve as QJSValue + QQmlEngine engine; + QJSValue instance = engine.singletonInstance<QJSValue>(typeId); + \endcode + + It is recommended to store the QML type id during registration, e.g. as a static member + in the singleton class. Otherwise, a costly lookup via qmlTypeId() has to be performed + at run-time. + + \sa qmlRegisterSingletonType(), qmlTypeId() + \since 5.12 +*/ +template<> +QJSValue QQmlEngine::singletonInstance<QJSValue>(int qmlTypeId) +{ + QQmlType type = QQmlMetaType::qmlType(qmlTypeId, QQmlMetaType::TypeIdCategory::QmlType); + + if (!type.isValid() || !type.isSingleton()) + return QJSValue(); + + QQmlType::SingletonInstanceInfo* info = type.singletonInstanceInfo(); + info->init(this); + + if (QObject* o = info->qobjectApi(this)) + return this->newQObject(o); + else { + QJSValue value = info->scriptApi(this); + if (!value.isUndefined()) + return value; + } + + return QJSValue(); +} + +/*! Refreshes all binding expressions that use strings marked for translation. Call this function after you have installed a new translator with diff --git a/src/qml/qml/qqmlengine.h b/src/qml/qml/qqmlengine.h index 73ad2754c8..871e6bd9b4 100644 --- a/src/qml/qml/qqmlengine.h +++ b/src/qml/qml/qqmlengine.h @@ -143,6 +143,9 @@ public: bool outputWarningsToStandardError() const; void setOutputWarningsToStandardError(bool); + template<typename T> + T singletonInstance(int qmlTypeId); + public Q_SLOTS: void retranslate(); @@ -167,6 +170,19 @@ private: Q_DECLARE_PRIVATE(QQmlEngine) }; +template<> +Q_QML_EXPORT QJSValue QQmlEngine::singletonInstance<QJSValue>(int qmlTypeId); + +template<typename T> +T QQmlEngine::singletonInstance(int qmlTypeId) { + QJSValue instance = singletonInstance<QJSValue>(qmlTypeId); + if (!instance.isQObject()) + return nullptr; + + QObject *object = instance.toQObject(); + return qobject_cast<T>(object); +} + QT_END_NAMESPACE #endif // QQMLENGINE_H diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp index 307f9a9ec6..dd027818cd 100644 --- a/src/qml/qml/qqmlmetatype.cpp +++ b/src/qml/qml/qqmlmetatype.cpp @@ -1863,6 +1863,23 @@ void qmlRegisterModule(const char *uri, int versionMajor, int versionMinor) p->maxMinorVersion = qMax(p->maxMinorVersion, versionMinor); } +//From qqml.h +int qmlTypeId(const char *uri, int versionMajor, int versionMinor, const char *qmlName) +{ + QMutexLocker lock(metaTypeDataLock()); + QQmlMetaTypeData *data = metaTypeData(); + + QQmlTypeModule *module = getTypeModule(QString::fromUtf8(uri), versionMajor, data); + if (!module) + return -1; + + QQmlType type = module->type(QHashedStringRef(qmlName), versionMinor); + if (!type.isValid()) + return -1; + + return type.index(); +} + bool QQmlMetaType::namespaceContainsRegistrations(const QString &uri, int majorVersion) { const QQmlMetaTypeData *data = metaTypeData(); @@ -2242,19 +2259,25 @@ QQmlType QQmlMetaType::qmlType(const QMetaObject *metaObject, const QHashedStrin } /*! - Returns the type (if any) that corresponds to the QVariant::Type \a userType. - Returns null if no type is registered. + Returns the type (if any) that corresponds to \a typeId. Depending on \a category, the + \a typeId is interpreted either as QVariant::Type or as QML type id returned by one of the + qml type registration functions. Returns null if no type is registered. */ -QQmlType QQmlMetaType::qmlType(int userType) +QQmlType QQmlMetaType::qmlType(int typeId, TypeIdCategory category) { QMutexLocker lock(metaTypeDataLock()); QQmlMetaTypeData *data = metaTypeData(); - QQmlTypePrivate *type = data->idToType.value(userType); - if (type && type->typeId == userType) - return QQmlType(type); - else - return QQmlType(); + if (category == TypeIdCategory::MetaType) { + QQmlTypePrivate *type = data->idToType.value(typeId); + if (type && type->typeId == typeId) + return QQmlType(type); + } else if (category == TypeIdCategory::QmlType) { + QQmlType type = data->types.value(typeId); + if (type.isValid()) + return type; + } + return QQmlType(); } /*! diff --git a/src/qml/qml/qqmlmetatype_p.h b/src/qml/qml/qqmlmetatype_p.h index 51bf485a3e..6df439cd7a 100644 --- a/src/qml/qml/qqmlmetatype_p.h +++ b/src/qml/qml/qqmlmetatype_p.h @@ -91,11 +91,16 @@ public: static QList<QQmlType> qmlSingletonTypes(); static QList<QQmlType> qmlAllTypes(); + enum class TypeIdCategory { + MetaType, + QmlType + }; + static QQmlType qmlType(const QString &qualifiedName, int, int); static QQmlType qmlType(const QHashedStringRef &name, const QHashedStringRef &module, int, int); static QQmlType qmlType(const QMetaObject *); static QQmlType qmlType(const QMetaObject *metaObject, const QHashedStringRef &module, int version_major, int version_minor); - static QQmlType qmlType(int); + static QQmlType qmlType(int typeId, TypeIdCategory category = TypeIdCategory::MetaType); static QQmlType qmlType(const QUrl &unNormalizedUrl, bool includeNonFileImports = false); static QQmlPropertyCache *propertyCache(const QMetaObject *metaObject); diff --git a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp index f179093a3d..f58ae38264 100644 --- a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp +++ b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp @@ -79,6 +79,7 @@ private slots: void componentFromEval(); void qrcUrls(); void cppSignalAndEval(); + void singletonInstance(); public slots: QObject *createAQObjectForOwnershipTest () @@ -952,6 +953,96 @@ void tst_qqmlengine::cppSignalAndEval() QCOMPARE(object->property("r"), 1.1234); } +class CppSingleton : public QObject { + Q_OBJECT +public: + CppSingleton() {} + + static QObject *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine) + { + Q_UNUSED(qmlEngine); + Q_UNUSED(jsEngine); + return new CppSingleton(); + } +}; + +class JsSingleton : public QObject { + Q_OBJECT +public: + JsSingleton() {} + + static QJSValue create(QQmlEngine *qmlEngine, QJSEngine *jsEngine) + { + Q_UNUSED(qmlEngine); + QJSValue value = jsEngine->newQObject(new JsSingleton()); + return value; + } +}; + +class SomeQObjectClass : public QObject { + Q_OBJECT +public: + SomeQObjectClass() : QObject(nullptr){} +}; + +void tst_qqmlengine::singletonInstance() +{ + QQmlEngine engine; + + int cppSingletonTypeId = qmlRegisterSingletonType<CppSingleton>("Test", 1, 0, "CppSingleton", &CppSingleton::create); + int jsValueSingletonTypeId = qmlRegisterSingletonType("Test", 1, 0, "JsSingleton", &JsSingleton::create); + + { + // Cpp QObject singleton type + QJSValue value = engine.singletonInstance<QJSValue>(cppSingletonTypeId); + QVERIFY(!value.isUndefined()); + QVERIFY(value.isQObject()); + QObject *instance = value.toQObject(); + QVERIFY(instance); + QCOMPARE(instance->metaObject()->className(), "CppSingleton"); + } + + { + // QJSValue QObject singleton type + QJSValue value = engine.singletonInstance<QJSValue>(jsValueSingletonTypeId); + QVERIFY(!value.isUndefined()); + QVERIFY(value.isQObject()); + QObject *instance = value.toQObject(); + QVERIFY(instance); + QCOMPARE(instance->metaObject()->className(), "JsSingleton"); + } + + { + // Invalid types + QJSValue value; + value = engine.singletonInstance<QJSValue>(-4711); + QVERIFY(value.isUndefined()); + value = engine.singletonInstance<QJSValue>(1701); + QVERIFY(value.isUndefined()); + } + + { + // Valid, but non-singleton type + int typeId = qmlRegisterType<CppSingleton>("Test", 1, 0, "NotASingleton"); + QJSValue value = engine.singletonInstance<QJSValue>(typeId); + QVERIFY(value.isUndefined()); + } + + { + // Cpp QObject singleton type + CppSingleton *instance = engine.singletonInstance<CppSingleton*>(cppSingletonTypeId); + QVERIFY(instance); + QCOMPARE(instance->metaObject()->className(), "CppSingleton"); + QCOMPARE(instance, engine.singletonInstance<QJSValue>(cppSingletonTypeId).toQObject()); + } + + { + // Wrong destination type + SomeQObjectClass * instance = engine.singletonInstance<SomeQObjectClass*>(cppSingletonTypeId); + QVERIFY(!instance); + } +} + QTEST_MAIN(tst_qqmlengine) #include "tst_qqmlengine.moc" diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 39f973f3fd..f3569c2efe 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -290,6 +290,8 @@ private slots: void valueTypeGroupPropertiesInBehavior(); + void retrieveQmlTypeId(); + private: QQmlEngine engine; QStringList defaultImportPathList; @@ -4956,6 +4958,26 @@ void tst_qqmllanguage::valueTypeGroupPropertiesInBehavior() QCOMPARE(animation->property("easing").value<QEasingCurve>().type(), QEasingCurve::InOutQuad); } +void tst_qqmllanguage::retrieveQmlTypeId() +{ + // Register in reverse order to provoke wrong minor version matching. + int id2 = qmlRegisterType<QObject>("Test", 2, 3, "SomeTestType"); + int id1 = qmlRegisterType<QObject>("Test", 2, 1, "SomeTestType"); + QCOMPARE(qmlTypeId("Test", 2, 1, "SomeTestType"), id1); + QCOMPARE(qmlTypeId("Test", 2, 2, "SomeTestType"), id1); + QCOMPARE(qmlTypeId("Test", 2, 3, "SomeTestType"), id2); + + // Error cases + QCOMPARE(qmlTypeId("Test", 2, 0, "SomeTestType"), -1); + QCOMPARE(qmlTypeId("Test", 2, 3, "DoesNotExist"), -1); + QCOMPARE(qmlTypeId("DoesNotExist", 2, 3, "SomeTestType"), -1); + + // Must also work for other types (defined in testtpes.cpp) + QVERIFY(qmlTypeId("Test", 1, 0, "MyExtendedUncreateableBaseClass") >= 0); + QVERIFY(qmlTypeId("Test", 1, 0, "MyUncreateableBaseClass") >= 0); + QVERIFY(qmlTypeId("Test", 1, 0, "MyTypeObjectSingleton") >= 0); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" |