aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2020-11-02 10:57:59 +0100
committerUlf Hermann <ulf.hermann@qt.io>2020-11-02 14:00:23 +0100
commite826636a0bebd0d73e6c17038b24ee2a42d92ead (patch)
tree0a2ea355eb83af6ac636bec0e5fb0028b81ffbe2
parent6f968781c4c8a278a7743b0904fb2bdf4c6ddf1a (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.qdoc14
-rw-r--r--src/qml/qml/qqmlprivate.h64
-rw-r--r--tests/auto/qml/qqmllanguage/testtypes.h20
-rw-r--r--tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp17
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"