aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2020-10-28 16:48:53 +0100
committerUlf Hermann <ulf.hermann@qt.io>2020-11-02 14:00:17 +0100
commit6f968781c4c8a278a7743b0904fb2bdf4c6ddf1a (patch)
tree0d5944f74c10bf3223cb374234c7f6d78706714f
parent2b99430671e37427a990973df37e62ad8cdebe8b (diff)
QML: Allow namespaces as extensions to types
This way we can access the enums of namespaces as properties of the primary type. This is achieved by: 1. Making enums of extended types available in the base type 2. Allowing the extension to be specified as plain metaObject rather than as type name. 3. Refraining from creating the extension if the create function does not exist. The goal of this is to declare the Qt namespace in a civilized way, but will also help with cleaning up the QtQuick value types and their enums. Change-Id: I13399741d30ce38d1bff753cfa1b11e72ccfbf6a Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r--src/qml/doc/src/cppintegration/definetypes.qdoc3
-rw-r--r--src/qml/doc/src/qmlfunctions.qdoc24
-rw-r--r--src/qml/qml/qqml.cpp4
-rw-r--r--src/qml/qml/qqml.h25
-rw-r--r--src/qml/qml/qqmlmetatype.cpp2
-rw-r--r--src/qml/qml/qqmlprivate.h18
-rw-r--r--src/qml/qml/qqmlproxymetaobject.cpp2
-rw-r--r--src/qml/qml/qqmltype.cpp9
-rw-r--r--tests/auto/qml/qqmllanguage/testtypes.h29
-rw-r--r--tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp21
10 files changed, 116 insertions, 21 deletions
diff --git a/src/qml/doc/src/cppintegration/definetypes.qdoc b/src/qml/doc/src/cppintegration/definetypes.qdoc
index cbbbd9ba58..5098f74df8 100644
--- a/src/qml/doc/src/cppintegration/definetypes.qdoc
+++ b/src/qml/doc/src/cppintegration/definetypes.qdoc
@@ -310,6 +310,9 @@ QLineEdit, without modifying its source code.
The QML_EXTENDED(extended) macro is for registering extended types. The
argument is the name of another class to be used as extension.
+You can also use QML_EXTENDED_NAMESPACE(namespace) to register a namespace, and
+especially the enumerations declared within, as an extension to a type.
+
An extension class is a regular QObject, with a constructor that takes a QObject
pointer. However, the extension class creation is delayed until the first
extended property is accessed. The extension class is created and the target
diff --git a/src/qml/doc/src/qmlfunctions.qdoc b/src/qml/doc/src/qmlfunctions.qdoc
index c0c481b861..caca58f34a 100644
--- a/src/qml/doc/src/qmlfunctions.qdoc
+++ b/src/qml/doc/src/qmlfunctions.qdoc
@@ -230,10 +230,24 @@
\relates QQmlEngine
Declares that the enclosing type uses \a EXTENDED_TYPE as an extension to
- provide further properties and methods in QML. This takes effect if the type
+ provide further properties, methods, and enumerations in QML. This takes
+ effect if the type is exposed to QML using a \l QML_ELEMENT or
+ \l QML_NAMED_ELEMENT() macro.
+
+ \sa QML_ELEMENT, QML_NAMED_ELEMENT(), QML_EXTENDED_NAMEPSACE(),
+ {Registering Extension Objects}
+*/
+
+/*!
+ \macro QML_EXTENDED_NAMESPACE(EXTENDED_NAMESPACE)
+ \relates QQmlEngine
+
+ Declares that the enclosing type uses \a EXTENDED_NAMESPACE as an extension to
+ provide further enumerations in QML. This takes effect if the type
is exposed to QML using a \l QML_ELEMENT or \l QML_NAMED_ELEMENT() macro.
- \sa QML_ELEMENT, QML_NAMED_ELEMENT(), {Registering Extension Objects}
+ \sa QML_ELEMENT, QML_NAMED_ELEMENT(), QML_EXTENDED(),
+ {Registering Extension Objects}
*/
/*!
@@ -243,9 +257,9 @@
Declares that any \l QML_ELEMENT, \l QML_NAMED_ELEMENT(), \l QML_ANONYMOUS,
\l QML_INTERFACE, \l QML_UNCREATABLE(), \l QML_SINGLETON,
\l QML_ADDED_IN_MINOR_VERSION(), \l QML_REMOVED_IN_MINOR_VERSION(),
- \l QML_ATTACHED(), or \l QML_EXTENDED() macros in the enclosing C++ type do
- not apply to the enclosing type but instead to \a FOREIGN_TYPE. The enclosing
- type still needs to be registered with the
+ \l QML_ATTACHED(), \l QML_EXTENDED(), or \l QML_EXTENDED_NAMESPACE() macros
+ in the enclosing C++ type do not apply to the enclosing type but instead to
+ \a FOREIGN_TYPE. The enclosing type still needs to be registered with the
\l {The Meta-Object System}{meta object system} using a \l Q_GADGET or
\l Q_OBJECT macro.
diff --git a/src/qml/qml/qqml.cpp b/src/qml/qml/qqml.cpp
index e61cf417e3..5dd245f2a3 100644
--- a/src/qml/qml/qqml.cpp
+++ b/src/qml/qml/qqml.cpp
@@ -483,7 +483,7 @@ namespace QQmlPrivate {
template<>
void qmlRegisterTypeAndRevisions<QQmlTypeNotAvailable, void>(
const char *uri, int versionMajor, const QMetaObject *classInfoMetaObject,
- QVector<int> *qmlTypeIds)
+ QVector<int> *qmlTypeIds, const QMetaObject *extension)
{
using T = QQmlTypeNotAvailable;
@@ -509,7 +509,7 @@ namespace QQmlPrivate {
StaticCastSelector<T, QQmlPropertyValueSource>::cast(),
StaticCastSelector<T, QQmlPropertyValueInterceptor>::cast(),
- nullptr, nullptr, qmlCreateCustomParser<T>, qmlTypeIds
+ nullptr, extension, qmlCreateCustomParser<T>, qmlTypeIds
};
qmlregister(TypeAndRevisionsRegistration, &type);
diff --git a/src/qml/qml/qqml.h b/src/qml/qml/qqml.h
index be482c5277..9118b42390 100644
--- a/src/qml/qml/qqml.h
+++ b/src/qml/qml/qqml.h
@@ -119,6 +119,13 @@
template<typename T, typename... Args> \
friend void QML_REGISTER_TYPES_AND_REVISIONS(const char *uri, int versionMajor, QList<int> *);
+#define QML_EXTENDED_NAMESPACE(EXTENDED_NAMESPACE) \
+ Q_CLASSINFO("QML.Extended", #EXTENDED_NAMESPACE) \
+ static constexpr const QMetaObject *qmlExtendedNamespace() { return &EXTENDED_NAMESPACE::staticMetaObject; } \
+ template<class, class> friend struct QML_PRIVATE_NAMESPACE::QmlExtendedNamespace; \
+ template<typename T, typename... Args> \
+ friend void QML_REGISTER_TYPES_AND_REVISIONS(const char *uri, int versionMajor, QList<int> *);
+
#define QML_FOREIGN(FOREIGN_TYPE) \
Q_CLASSINFO("QML.Foreign", #FOREIGN_TYPE) \
using QmlForeignType = FOREIGN_TYPE; \
@@ -792,25 +799,30 @@ struct QmlTypeAndRevisionsRegistration;
template<class T, class Resolved, class Extended>
struct QmlTypeAndRevisionsRegistration<T, Resolved, Extended, false, false> {
- static void registerTypeAndRevisions(const char *uri, int versionMajor, QList<int> *qmlTypeIds)
+ static void registerTypeAndRevisions(const char *uri, int versionMajor, QList<int> *qmlTypeIds,
+ const QMetaObject *extension)
{
QQmlPrivate::qmlRegisterTypeAndRevisions<Resolved, Extended>(
- uri, versionMajor, QQmlPrivate::StaticMetaObject<T>::staticMetaObject(), qmlTypeIds);
+ uri, versionMajor, QQmlPrivate::StaticMetaObject<T>::staticMetaObject(),
+ qmlTypeIds, extension);
}
};
template<class T, class Resolved>
struct QmlTypeAndRevisionsRegistration<T, Resolved, void, true, false> {
- static void registerTypeAndRevisions(const char *uri, int versionMajor, QList<int> *qmlTypeIds)
+ static void registerTypeAndRevisions(const char *uri, int versionMajor, QList<int> *qmlTypeIds,
+ const QMetaObject *)
{
QQmlPrivate::qmlRegisterSingletonAndRevisions<Resolved>(
- uri, versionMajor, QQmlPrivate::StaticMetaObject<T>::staticMetaObject(), qmlTypeIds);
+ uri, versionMajor, QQmlPrivate::StaticMetaObject<T>::staticMetaObject(),
+ qmlTypeIds);
}
};
template<class T, class Resolved>
struct QmlTypeAndRevisionsRegistration<T, Resolved, void, false, true> {
- static void registerTypeAndRevisions(const char *uri, int versionMajor, QList<int> *qmlTypeIds)
+ static void registerTypeAndRevisions(const char *uri, int versionMajor, QList<int> *qmlTypeIds,
+ const QMetaObject *)
{
const int id = qmlRegisterInterface<Resolved>(uri, versionMajor);
if (qmlTypeIds)
@@ -830,7 +842,8 @@ void qmlRegisterTypesAndRevisions(const char *uri, int versionMajor, QList<int>
typename QQmlPrivate::QmlExtended<T>::Type,
QQmlPrivate::QmlSingleton<T>::Value,
QQmlPrivate::QmlInterface<T>::Value>
- ::registerTypeAndRevisions(uri, versionMajor, qmlTypeIds);
+ ::registerTypeAndRevisions(uri, versionMajor, qmlTypeIds,
+ QQmlPrivate::QmlExtendedNamespace<T>::metaObject());
qmlRegisterTypesAndRevisions<Args...>(uri, versionMajor, qmlTypeIds);
}
diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp
index ba7ffe2a91..df5c4d89c5 100644
--- a/src/qml/qml/qqmlmetatype.cpp
+++ b/src/qml/qml/qqmlmetatype.cpp
@@ -1610,7 +1610,7 @@ QList<QQmlProxyMetaObject::ProxyData> QQmlMetaType::proxyData(const QMetaObject
QQmlTypePrivate *t = data->metaObjectToType.value(mo);
if (t) {
if (t->regType == QQmlType::CppType) {
- if (t->extraData.cd->extFunc) {
+ if (t->extraData.cd->extMetaObject) {
QMetaObjectBuilder builder;
clone(builder, t->extraData.cd->extMetaObject, t->baseMetaObject, baseMetaObject);
builder.setFlags(MetaObjectFlag::DynamicMetaObject);
diff --git a/src/qml/qml/qqmlprivate.h b/src/qml/qml/qqmlprivate.h
index 6639ecbd26..2beeacd20f 100644
--- a/src/qml/qml/qqmlprivate.h
+++ b/src/qml/qml/qqmlprivate.h
@@ -625,6 +625,18 @@ namespace QQmlPrivate
};
template<class T, class = std::void_t<>>
+ struct QmlExtendedNamespace
+ {
+ static constexpr const QMetaObject *metaObject() { return nullptr; }
+ };
+
+ template<class T>
+ struct QmlExtendedNamespace<T, std::void_t<decltype(T::qmlExtendedNamespace())>>
+ {
+ static constexpr const QMetaObject *metaObject() { return T::qmlExtendedNamespace(); }
+ };
+
+ template<class T, class = std::void_t<>>
struct QmlResolved
{
using Type = T;
@@ -718,7 +730,7 @@ namespace QQmlPrivate
template<typename T, typename E>
void qmlRegisterTypeAndRevisions(const char *uri, int versionMajor,
const QMetaObject *classInfoMetaObject,
- QVector<int> *qmlTypeIds)
+ QVector<int> *qmlTypeIds, const QMetaObject *extension)
{
RegisterTypeAndRevisions type = {
0,
@@ -742,7 +754,7 @@ namespace QQmlPrivate
StaticCastSelector<T, QQmlPropertyValueInterceptor>::cast(),
ExtendedType<E>::createParent,
- ExtendedType<E>::staticMetaObject(),
+ extension ? extension : ExtendedType<E>::staticMetaObject(),
&qmlCreateCustomParser<T>,
qmlTypeIds
@@ -754,7 +766,7 @@ namespace QQmlPrivate
template<>
void Q_QML_EXPORT qmlRegisterTypeAndRevisions<QQmlTypeNotAvailable, void>(
const char *uri, int versionMajor, const QMetaObject *classInfoMetaObject,
- QVector<int> *qmlTypeIds);
+ QVector<int> *qmlTypeIds, const QMetaObject *);
} // namespace QQmlPrivate
diff --git a/src/qml/qml/qqmlproxymetaobject.cpp b/src/qml/qml/qqmlproxymetaobject.cpp
index e1500f70fb..c5cb2ec059 100644
--- a/src/qml/qml/qqmlproxymetaobject.cpp
+++ b/src/qml/qml/qqmlproxymetaobject.cpp
@@ -83,6 +83,8 @@ int QQmlProxyMetaObject::metaCall(QObject *o, QMetaObject::Call c, int id, void
}
if (!proxies[ii]) {
+ if (!data.createFunc)
+ continue;
QObject *proxy = data.createFunc(object);
const QMetaObject *metaObject = proxy->metaObject();
proxies[ii] = proxy;
diff --git a/src/qml/qml/qqmltype.cpp b/src/qml/qml/qqmltype.cpp
index a4f7c3f8b5..e5b3653d2d 100644
--- a/src/qml/qml/qqmltype.cpp
+++ b/src/qml/qml/qqmltype.cpp
@@ -217,7 +217,7 @@ void QQmlTypePrivate::init() const
if (regType == QQmlType::CppType) {
// Setup extended meta object
// XXX - very inefficient
- if (extraData.cd->extFunc) {
+ if (extraData.cd->extMetaObject) {
QMetaObjectBuilder builder;
QQmlMetaType::clone(builder, extraData.cd->extMetaObject, extraData.cd->extMetaObject,
extraData.cd->extMetaObject);
@@ -269,14 +269,15 @@ void QQmlTypePrivate::initEnums(QQmlEnginePrivate *engine) const
? compositePropertyCache(engine)
: nullptr;
+ // beware: It could be a singleton type without metaobject
const QMetaObject *metaObject = !isEnumFromBaseSetup.loadAcquire()
- ? baseMetaObject // beware: It could be a singleton type without metaobject
+ ? baseMetaObject
: nullptr;
if (!cache && !metaObject)
return;
- init();
+ init(); // init() can add to the metaObjects list. Therefore, check metaObjects only below
QMutexLocker lock(QQmlMetaType::typeRegistrationLock());
@@ -286,7 +287,7 @@ void QQmlTypePrivate::initEnums(QQmlEnginePrivate *engine) const
}
if (metaObject) {
- insertEnums(metaObject);
+ insertEnums(metaObjects.isEmpty() ? baseMetaObject : metaObjects.constFirst().metaObject);
isEnumFromBaseSetup.storeRelease(true);
}
}
diff --git a/tests/auto/qml/qqmllanguage/testtypes.h b/tests/auto/qml/qqmllanguage/testtypes.h
index 1156d265ed..518f52f4e7 100644
--- a/tests/auto/qml/qqmllanguage/testtypes.h
+++ b/tests/auto/qml/qqmllanguage/testtypes.h
@@ -1534,6 +1534,35 @@ class UncreatableElementNoReason : public QObject
QML_UNCREATABLE("")
};
+namespace ExtensionNamespace {
+Q_NAMESPACE
+
+enum Foo {
+ Bar = 9,
+ Baz = 12
+};
+Q_ENUM_NS(Foo)
+}
+
+class ExtendedByNamespace : public QObject
+{
+ Q_OBJECT
+ QML_ELEMENT
+ QML_EXTENDED_NAMESPACE(ExtensionNamespace)
+
+ Q_PROPERTY(int own READ own CONSTANT)
+public:
+
+ enum OwnEnum {
+ Moo = 16,
+ Maeh = 17
+ };
+ Q_ENUM(OwnEnum)
+
+ ExtendedByNamespace(QObject *parent = nullptr) : QObject(parent) {}
+ int own() const { return 93; }
+};
+
void registerTypes();
#endif // TESTTYPES_H
diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
index 4e8e668c18..74b7c0e64e 100644
--- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
+++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
@@ -340,6 +340,7 @@ private slots:
void checkURLtoURLObject();
void registerValueTypes();
+ void extendedNamespace();
private:
QQmlEngine engine;
@@ -6027,6 +6028,26 @@ void tst_qqmllanguage::accessNullPointerPropertyCache()
QVERIFY(!obj.isNull());
}
+void tst_qqmllanguage::extendedNamespace()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine);
+ c.setData("import StaticTest\n"
+ "import QtQml\n"
+ "ExtendedByNamespace {\n"
+ " property int mine: own\n"
+ " property int myEnum: ExtendedByNamespace.Moo\n"
+ " property int fromExtension: ExtendedByNamespace.Bar\n"
+ "}", QUrl());
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> obj(c.create());
+ QVERIFY(!obj.isNull());
+
+ QCOMPARE(obj->property("mine").toInt(), 93);
+ QCOMPARE(obj->property("myEnum").toInt(), 16);
+ QCOMPARE(obj->property("fromExtension").toInt(), 9);
+}
+
QTEST_MAIN(tst_qqmllanguage)
#include "tst_qqmllanguage.moc"