aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabian Kosmale <fabian.kosmale@qt.io>2022-03-31 16:28:24 +0200
committerFabian Kosmale <fabian.kosmale@qt.io>2022-11-28 17:10:07 +0100
commit6656567a4085e3d6de01226fb7b1ec16ee7ba08c (patch)
tree9d021e9f3c17d468dcc6bc1be52c0e595019a662
parent6cd8d209ec472a658a330f25b84f92cd61e0d4cf (diff)
Introduce type based overload of Qt.createComponent
Add type based createComponent overloads based on QQmlComponent::loadFromModule. They provide a way to instantiate a component by its type, specified by a URI and typename pair. This enables creating Components for inline component types and C++ only types. Support for directly passing the type is deferred to a later commit, as it needs some care to work together with the compiler. As the string based overload matches a mistaken call to the URL based createComponent with parent and mode swapped (due to anything being convertible to string), we add a check to detect this specific error case and give a helpful error message. [ChangeLog][QtQml] Qt.createComponent now supports creating a Component from its module and type name (passed as strings). Task-number: QTBUG-97156 Fixes: QTBUG-26278 Change-Id: I89e7430fe02d52f57230bfa1b0bfbcbfd0ead4b7 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
-rw-r--r--src/qml/qml/qqmlbuiltinfunctions.cpp85
-rw-r--r--src/qml/qml/qqmlbuiltinfunctions_p.h12
-rw-r--r--tests/auto/qml/qqmlecmascript/data/componentCreationForType.qml48
-rw-r--r--tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp4
4 files changed, 141 insertions, 8 deletions
diff --git a/src/qml/qml/qqmlbuiltinfunctions.cpp b/src/qml/qml/qqmlbuiltinfunctions.cpp
index 79475435bf..4a1bb5c8c2 100644
--- a/src/qml/qml/qqmlbuiltinfunctions.cpp
+++ b/src/qml/qml/qqmlbuiltinfunctions.cpp
@@ -271,6 +271,22 @@ QtObject::QtObject(ExecutionEngine *engine)
{
}
+QtObject::Contexts QtObject::getContexts() const
+{
+ QQmlEngine *engine = qmlEngine();
+ if (!engine)
+ return {};
+
+ QQmlRefPointer<QQmlContextData> context = v4Engine()->callingQmlContext();
+ if (!context)
+ context = QQmlContextData::get(QQmlEnginePrivate::get(engine)->rootContext);
+
+ Q_ASSERT(context);
+ QQmlRefPointer<QQmlContextData> effectiveContext
+ = context->isPragmaLibraryContext() ? nullptr : context;
+ return {context, effectiveContext};
+}
+
QtObject *QtObject::create(QQmlEngine *, QJSEngine *jsEngine)
{
QV4::ExecutionEngine *v4 = jsEngine->handle();
@@ -1288,6 +1304,32 @@ See \l {Dynamic QML Object Creation from JavaScript} for more information on usi
To create a QML object from an arbitrary string of QML (instead of a file),
use \l{QtQml::Qt::createQmlObject()}{Qt.createQmlObject()}.
*/
+
+/*!
+\qmlmethod Component Qt::createComponent(string moduleUri, string typeName, enumeration mode, QtObject parent)
+\overload
+Returns a \l Component object created for the type specified by \a moduleUri and \a typeName.
+\qml
+import QtQuick
+QtObject {
+ id: root
+ property Component myComponent: Qt.createComponent(Rectangle, root)
+}
+\endqml
+This overload mostly behaves as the \c url based version, but can be used
+to instantiate types which do not have an URL (e.g. C++ types registered
+via \l {QML_ELEMENT}).
+\note In some cases, passing \c Component.Asynchronous won't have any
+effect:
+\list
+\li The type is implemented in C++
+\li The type is an inline component.
+\endlist
+If the optional \a parent parameter is given, it should refer to the object
+that will become the parent for the created \l Component object. If no mode
+was passed, this can be the second argument.
+*/
+
QQmlComponent *QtObject::createComponent(const QUrl &url, QObject *parent) const
{
return createComponent(url, QQmlComponent::PreferSynchronous, parent);
@@ -1308,13 +1350,9 @@ QQmlComponent *QtObject::createComponent(const QUrl &url, QQmlComponent::Compila
if (!engine)
return nullptr;
- QQmlRefPointer<QQmlContextData> context = v4Engine()->callingQmlContext();
+ auto [context, effectiveContext] = getContexts();
if (!context)
- context = QQmlContextData::get(QQmlEnginePrivate::get(engine)->rootContext);
-
- Q_ASSERT(context);
- QQmlRefPointer<QQmlContextData> effectiveContext
- = context->isPragmaLibraryContext() ? nullptr : context;
+ return nullptr;
QQmlComponent *c = new QQmlComponent(engine, context->resolvedUrl(url), mode, parent);
QQmlComponentPrivate::get(c)->creationContext = effectiveContext;
@@ -1323,6 +1361,41 @@ QQmlComponent *QtObject::createComponent(const QUrl &url, QQmlComponent::Compila
return c;
}
+QQmlComponent *QtObject::createComponent(const QString &moduleUri, const QString &typeName,
+ QObject *parent) const
+{
+ return createComponent(moduleUri, typeName, QQmlComponent::PreferSynchronous, parent);
+}
+
+QQmlComponent *QtObject::createComponent(const QString &moduleUri, const QString &typeName, QQmlComponent::CompilationMode mode, QObject *parent) const
+{
+ if (mode != QQmlComponent::Asynchronous && mode != QQmlComponent::PreferSynchronous) {
+ v4Engine()->throwError(QStringLiteral("Invalid compilation mode %1").arg(mode));
+ return nullptr;
+ }
+
+ QQmlEngine *engine = qmlEngine();
+ if (!engine)
+ return nullptr;
+
+ if (moduleUri.isEmpty() || typeName.isEmpty())
+ return nullptr;
+
+ auto [context, effectiveContext] = getContexts();
+ if (!context)
+ return nullptr;
+
+ QQmlComponent *c = new QQmlComponent(engine, moduleUri, typeName, mode, parent);
+ if (c->isError() && !parent && moduleUri.endsWith(u".qml")) {
+ v4Engine()->throwTypeError(
+ QStringLiteral("Invalid arguments; did you swap mode and parent"));
+ }
+ QQmlComponentPrivate::get(c)->creationContext = effectiveContext;
+ QQmlData::get(c, true)->explicitIndestructibleSet = false;
+ QQmlData::get(c)->indestructible = false;
+ return c;
+}
+
#if QT_CONFIG(translation)
QString QtObject::uiLanguage() const
{
diff --git a/src/qml/qml/qqmlbuiltinfunctions_p.h b/src/qml/qml/qqmlbuiltinfunctions_p.h
index b961fe6e56..2739524516 100644
--- a/src/qml/qml/qqmlbuiltinfunctions_p.h
+++ b/src/qml/qml/qqmlbuiltinfunctions_p.h
@@ -131,6 +131,12 @@ public:
const QUrl &url, QQmlComponent::CompilationMode mode = QQmlComponent::PreferSynchronous,
QObject *parent = nullptr) const;
+ Q_INVOKABLE QQmlComponent *createComponent(const QString &moduleUri,
+ const QString &typeName, QObject *parent) const;
+ Q_INVOKABLE QQmlComponent *createComponent(const QString &moduleUri, const QString &typeName,
+ QQmlComponent::CompilationMode mode = QQmlComponent::PreferSynchronous,
+ QObject *parent = nullptr) const;
+
Q_INVOKABLE QJSValue binding(const QJSValue &function) const;
Q_INVOKABLE void callLater(QQmlV4Function *args);
@@ -156,6 +162,12 @@ private:
QJSEngine *jsEngine() const { return m_engine->jsEngine(); }
QV4::ExecutionEngine *v4Engine() const { return m_engine; }
+ struct Contexts {
+ QQmlRefPointer<QQmlContextData> context;
+ QQmlRefPointer<QQmlContextData> effectiveContext;
+ };
+ Contexts getContexts() const;
+
QQmlPlatform *m_platform = nullptr;
QQmlApplication *m_application = nullptr;
diff --git a/tests/auto/qml/qqmlecmascript/data/componentCreationForType.qml b/tests/auto/qml/qqmlecmascript/data/componentCreationForType.qml
new file mode 100644
index 0000000000..66b06d4f5c
--- /dev/null
+++ b/tests/auto/qml/qqmlecmascript/data/componentCreationForType.qml
@@ -0,0 +1,48 @@
+import QtQml
+
+QtObject {
+ id: root
+ property int status: -1
+ Component.onCompleted: {
+ let comp
+ comp = Qt.createComponent("QtQml", "QtObject")
+ if (comp.status !== Component.Ready) {
+ root.status = 1
+ return
+ }
+
+ comp = Qt.createComponent("QtQml", "QtObject", root)
+ if (comp.status !== Component.Ready) {
+ root.status = 2
+ return
+ }
+
+ comp = Qt.createComponent("QtQml", "QtObject", Component.PreferSynchronous, root)
+ if (comp.status !== Component.Ready) {
+ root.status = 3
+ return
+ }
+
+
+ comp = Qt.createComponent("QtQml", "QtObject", Component.Asynchronous)
+ // C++ component will _always_ be ready, even if we request async loading
+ if (comp.status !== Component.Ready) {
+ root.status = 4
+ return
+ }
+
+ comp = Qt.createComponent("QtQml", "DoesNotExist")
+ if (comp.status !== Component.Error) {
+ root.status = 5
+ return
+ }
+
+ comp = Qt.createComponent("NoSuchModule", "QtObject")
+ if (comp.status !== Component.Error) {
+ root.status = 6
+ return
+ }
+
+ root.status = 0
+ }
+}
diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp
index 2922b02643..f591a87dcb 100644
--- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp
+++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp
@@ -1964,11 +1964,11 @@ void tst_qqmlecmascript::componentCreation_data()
<< "null";
QTest::newRow("invalidSecondArg")
<< "invalidSecondArg"
- << "" // We cannot catch this case as coercing a string to a number is valid in JavaScript
+ << ":40: TypeError: Invalid arguments; did you swap mode and parent"
<< "";
QTest::newRow("invalidThirdArg")
<< "invalidThirdArg"
- << ":45: TypeError: Passing incompatible arguments to C++ functions from JavaScript is not allowed."
+ << ":45: TypeError: Invalid arguments; did you swap mode and parent"
<< "";
QTest::newRow("invalidMode")
<< "invalidMode"