diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2020-11-02 10:57:59 +0100 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2020-11-02 14:00:23 +0100 |
commit | e826636a0bebd0d73e6c17038b24ee2a42d92ead (patch) | |
tree | 0a2ea355eb83af6ac636bec0e5fb0028b81ffbe2 | |
parent | 6f968781c4c8a278a7743b0904fb2bdf4c6ddf1a (diff) |
QML: Allow singletons to be created with a factory function
This is in line with what we do on qmlRegisterSingletonType(), and it
allows us to return singleton objects created independently of the
instantiation mechanism.
Change-Id: Ia6a077f5d22241593acd8cc87b3f65ae20f95667
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Maximilian Goldstein <max.goldstein@qt.io>
-rw-r--r-- | src/qml/doc/src/qmlfunctions.qdoc | 14 | ||||
-rw-r--r-- | src/qml/qml/qqmlprivate.h | 64 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/testtypes.h | 20 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp | 17 |
4 files changed, 101 insertions, 14 deletions
diff --git a/src/qml/doc/src/qmlfunctions.qdoc b/src/qml/doc/src/qmlfunctions.qdoc index caca58f34a..25cdd8142a 100644 --- a/src/qml/doc/src/qmlfunctions.qdoc +++ b/src/qml/doc/src/qmlfunctions.qdoc @@ -163,12 +163,14 @@ Declares the enclosing type to be a singleton in QML. This only takes effect if the type is available in QML, by having a \l QML_ELEMENT or \l QML_NAMED_ELEMENT() macro. By default, each QQmlEngine will try to create a - singleton instance using the type's default constructor when the type is first - accessed. If there is no default constructor the singleton is initially - inaccessible. This behavior can be overridden by calling - \l qmlRegisterSingletonType() with a specific factory function or - \l qmlRegisterSingletonInstance() with a specific instance for the same class - and the same type namespace and version. + singleton instance using either the type's default constructor or a static + factory function of the signature \a{T *create(QQmlEngine *, QJSEngine *)} + when the type is first accessed. If both do exist and are accessible, the + default constructor is preferred. If there is no default constructor and no + factory function the singleton is initially inaccessible. This behavior can be + overridden by calling \l qmlRegisterSingletonType() with a specific (external) + factory function or \l qmlRegisterSingletonInstance() with a specific instance + for the same class and the same type namespace and version. \sa QML_ELEMENT, QML_NAMED_ELEMENT(), qmlRegisterSingletonInstance(). */ diff --git a/src/qml/qml/qqmlprivate.h b/src/qml/qml/qqmlprivate.h index 2beeacd20f..4fcfd65c09 100644 --- a/src/qml/qml/qqmlprivate.h +++ b/src/qml/qml/qqmlprivate.h @@ -145,17 +145,57 @@ namespace QQmlPrivate } }; + enum class ConstructionMode + { + None, + Constructor, + Factory + }; + + template<typename T, typename = std::void_t<>> + struct HasSingletonFactory + { + static constexpr bool value = false; + }; + template<typename T> - constexpr bool isConstructible() + struct HasSingletonFactory<T, std::void_t<decltype(T::create( + static_cast<QQmlEngine *>(nullptr), + static_cast<QJSEngine *>(nullptr)))>> { - return std::is_default_constructible<T>::value && std::is_base_of<QObject, T>::value; + static constexpr bool value = std::is_same_v< + decltype(T::create(static_cast<QQmlEngine *>(nullptr), + static_cast<QJSEngine *>(nullptr))), T *>; + }; + + template<typename T> + constexpr ConstructionMode constructionMode() + { + if constexpr (!std::is_base_of<QObject, T>::value) + return ConstructionMode::None; + if constexpr (std::is_default_constructible<T>::value) + return ConstructionMode::Constructor; + if constexpr (HasSingletonFactory<T>::value) + return ConstructionMode::Factory; + + return ConstructionMode::None; } template<typename T> void createInto(void *memory, void *) { new (memory) QQmlElement<T>; } - template<typename T> - QObject *createSingletonInstance(QQmlEngine *, QJSEngine *) { return new T; } + template<typename T, ConstructionMode Mode> + QObject *createSingletonInstance(QQmlEngine *q, QJSEngine *j) + { + Q_UNUSED(q); + Q_UNUSED(j); + if constexpr (Mode == ConstructionMode::Constructor) + return new T; + else if constexpr (Mode == ConstructionMode::Factory) + return T::create(q, j); + else + return nullptr; + } template<typename T> QObject *createParent(QObject *p) { return new T(p); } @@ -165,25 +205,33 @@ namespace QQmlPrivate using CreateParentFunction = QObject *(*)(QObject *); using CreateValueTypeFunction = QVariant (*)(const QJSValue &); - template<typename T, bool Constructible = isConstructible<T>()> + template<typename T, ConstructionMode Mode = constructionMode<T>()> struct Constructors; template<typename T> - struct Constructors<T, true> + struct Constructors<T, ConstructionMode::Constructor> { static constexpr CreateIntoFunction createInto = QQmlPrivate::createInto<T>; static constexpr CreateSingletonFunction createSingletonInstance - = QQmlPrivate::createSingletonInstance<T>; + = QQmlPrivate::createSingletonInstance<T, ConstructionMode::Constructor>; }; template<typename T> - struct Constructors<T, false> + struct Constructors<T, ConstructionMode::None> { static constexpr CreateIntoFunction createInto = nullptr; static constexpr CreateSingletonFunction createSingletonInstance = nullptr; }; + template<typename T> + struct Constructors<T, ConstructionMode::Factory> + { + static constexpr CreateIntoFunction createInto = nullptr; + static constexpr CreateSingletonFunction createSingletonInstance + = QQmlPrivate::createSingletonInstance<T, ConstructionMode::Factory>; + }; + template<typename T, bool IsObject = std::is_base_of<QObject, T>::value, bool IsGadget = QtPrivate::IsGadgetHelper<T>::IsRealGadget> diff --git a/tests/auto/qml/qqmllanguage/testtypes.h b/tests/auto/qml/qqmllanguage/testtypes.h index 518f52f4e7..cdcc5faed8 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.h +++ b/tests/auto/qml/qqmllanguage/testtypes.h @@ -1563,6 +1563,26 @@ public: int own() const { return 93; } }; +class FactorySingleton : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + + Q_PROPERTY(int foo READ foo CONSTANT) +public: + + static FactorySingleton *create(QQmlEngine *, QJSEngine *) + { + return new FactorySingleton; + } + + int foo() const { return 314; } + +private: + FactorySingleton() = default; +}; + void registerTypes(); #endif // TESTTYPES_H diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 74b7c0e64e..40a2ee887a 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -341,6 +341,7 @@ private slots: void checkURLtoURLObject(); void registerValueTypes(); void extendedNamespace(); + void factorySingleton(); private: QQmlEngine engine; @@ -6048,6 +6049,22 @@ void tst_qqmllanguage::extendedNamespace() QCOMPARE(obj->property("fromExtension").toInt(), 9); } +void tst_qqmllanguage::factorySingleton() +{ + QQmlEngine engine; + QQmlComponent c(&engine); + c.setData("import StaticTest\n" + "import QtQml\n" + "QtObject {\n" + " property int mine: FactorySingleton.foo\n" + "}", QUrl()); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> obj(c.create()); + QVERIFY(!obj.isNull()); + + QCOMPARE(obj->property("mine").toInt(), 314); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" |