From e826636a0bebd0d73e6c17038b24ee2a42d92ead Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Mon, 2 Nov 2020 10:57:59 +0100 Subject: 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 Reviewed-by: Maximilian Goldstein --- src/qml/doc/src/qmlfunctions.qdoc | 14 +++--- src/qml/qml/qqmlprivate.h | 64 +++++++++++++++++++++--- tests/auto/qml/qqmllanguage/testtypes.h | 20 ++++++++ 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> + struct HasSingletonFactory + { + static constexpr bool value = false; + }; + template - constexpr bool isConstructible() + struct HasSingletonFactory(nullptr), + static_cast(nullptr)))>> { - return std::is_default_constructible::value && std::is_base_of::value; + static constexpr bool value = std::is_same_v< + decltype(T::create(static_cast(nullptr), + static_cast(nullptr))), T *>; + }; + + template + constexpr ConstructionMode constructionMode() + { + if constexpr (!std::is_base_of::value) + return ConstructionMode::None; + if constexpr (std::is_default_constructible::value) + return ConstructionMode::Constructor; + if constexpr (HasSingletonFactory::value) + return ConstructionMode::Factory; + + return ConstructionMode::None; } template void createInto(void *memory, void *) { new (memory) QQmlElement; } - template - QObject *createSingletonInstance(QQmlEngine *, QJSEngine *) { return new T; } + template + 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 QObject *createParent(QObject *p) { return new T(p); } @@ -165,25 +205,33 @@ namespace QQmlPrivate using CreateParentFunction = QObject *(*)(QObject *); using CreateValueTypeFunction = QVariant (*)(const QJSValue &); - template()> + template()> struct Constructors; template - struct Constructors + struct Constructors { static constexpr CreateIntoFunction createInto = QQmlPrivate::createInto; static constexpr CreateSingletonFunction createSingletonInstance - = QQmlPrivate::createSingletonInstance; + = QQmlPrivate::createSingletonInstance; }; template - struct Constructors + struct Constructors { static constexpr CreateIntoFunction createInto = nullptr; static constexpr CreateSingletonFunction createSingletonInstance = nullptr; }; + template + struct Constructors + { + static constexpr CreateIntoFunction createInto = nullptr; + static constexpr CreateSingletonFunction createSingletonInstance + = QQmlPrivate::createSingletonInstance; + }; + template::value, bool IsGadget = QtPrivate::IsGadgetHelper::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 obj(c.create()); + QVERIFY(!obj.isNull()); + + QCOMPARE(obj->property("mine").toInt(), 314); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" -- cgit v1.2.3