aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2021-10-19 15:51:36 +0200
committerUlf Hermann <ulf.hermann@qt.io>2021-10-21 09:48:00 +0200
commitc837be7beab1c217b8f163b9a2d53ca12fd2c95e (patch)
tree2f59fbf8503872f5c6bc50af31a83061ca77c15e
parent9021f46cc0ce1d89d5d62ccb622085437c8350a8 (diff)
Provide a non-QQmlV4Function version of Component::createObject()
It actually just takes a parent object and a list of parameters, both optional. We don't need varargs for that. It now warns if you pass more parameters, but that is a good thing. As the QQmlV4Function variant accepts a number of questionable values in addition to QObject* and QVariantMap, we need to retain it. Change-Id: Id39c07b890e4d2964ddb2cc498f56f2fe483e887 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io> Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io> Reviewed-by: Andrei Golubev <andrei.golubev@qt.io>
-rw-r--r--src/qml/qml/qqmlcomponent.cpp102
-rw-r--r--src/qml/qml/qqmlcomponent.h7
-rw-r--r--tests/auto/qml/qqmlcomponent/data/createObjectClean.qml14
-rw-r--r--tests/auto/qml/qqmlcomponent/data/createObjectDirty.qml11
-rw-r--r--tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp38
5 files changed, 163 insertions, 9 deletions
diff --git a/src/qml/qml/qqmlcomponent.cpp b/src/qml/qml/qqmlcomponent.cpp
index e81834ef1a..8293f17e7f 100644
--- a/src/qml/qml/qqmlcomponent.cpp
+++ b/src/qml/qml/qqmlcomponent.cpp
@@ -374,24 +374,52 @@ QObject *QQmlComponentPrivate::doBeginCreate(QQmlComponent *q, QQmlContext *cont
return q->beginCreate(context);
}
-bool QQmlComponentPrivate::setInitialProperty(QObject *component, const QString& name, const QVariant &value)
+bool QQmlComponentPrivate::setInitialProperty(
+ QObject *base, const QString &name, const QVariant &value)
{
+ const QStringList properties = name.split(u'.');
+
+ if (properties.size() > 1) {
+ QV4::Scope scope(engine->handle());
+ QV4::ScopedObject object(scope, QV4::QObjectWrapper::wrap(scope.engine, base));
+ QV4::ScopedString segment(scope);
+
+ for (int i = 0; i < properties.length() - 1; ++i) {
+ segment = scope.engine->newString(properties.at(i));
+ object = object->get(segment);
+ if (scope.engine->hasException)
+ break;
+ }
+ segment = scope.engine->newString(properties.last());
+ object->put(segment, scope.engine->metaTypeToJS(value.metaType(), value.constData()));
+ if (scope.engine->hasException) {
+ state.errors.push_back(scope.engine->catchExceptionAsQmlError());
+ scope.engine->hasException = false;
+ return false;
+ }
+ return true;
+ }
+
QQmlProperty prop = QQmlComponentPrivate::removePropertyFromRequired(
- component, name, requiredProperties(), engine);
+ base, name, requiredProperties(), engine);
QQmlPropertyPrivate *privProp = QQmlPropertyPrivate::get(prop);
const bool isValid = prop.isValid();
if (!isValid || !privProp->writeValueProperty(value, {})) {
QQmlError error{};
error.setUrl(url);
- if (isValid)
- error.setDescription(QLatin1String("Could not set initial property %1").arg(name));
- else
- error.setDescription(QLatin1String("Setting initial properties failed: %2 does not have a property called %1").arg(name,
- QQmlMetaType::prettyTypeName(component)));
+ if (isValid) {
+ error.setDescription(QStringLiteral("Could not set initial property %1").arg(name));
+ } else {
+ error.setDescription(QStringLiteral("Setting initial properties failed: "
+ "%2 does not have a property called %1")
+ .arg(name, QQmlMetaType::prettyTypeName(base)));
+ }
state.errors.push_back(error);
return false;
- } else
- return true;
+ }
+
+ return true;
+
}
/*!
@@ -1455,6 +1483,7 @@ QQmlError QQmlComponentPrivate::unsetRequiredPropertyToQQmlError(const RequiredP
return error;
}
+#if QT_DEPRECATED_SINCE(6, 3)
/*!
\internal
*/
@@ -1464,6 +1493,10 @@ void QQmlComponent::createObject(QQmlV4Function *args)
Q_ASSERT(d->engine);
Q_ASSERT(args);
+ qmlWarning(this) << "Unsuitable arguments passed to createObject(). The first argument should "
+ "be a QObject* or null, and the second argument should be a JavaScript "
+ "object or a QVariantMap";
+
QObject *parent = nullptr;
QV4::ExecutionEngine *v4 = args->v4engine();
QV4::Scope scope(v4);
@@ -1523,6 +1556,57 @@ void QQmlComponent::createObject(QQmlV4Function *args)
args->setReturnValue(object->asReturnedValue());
}
+#endif
+
+/*!
+ \internal
+ */
+QObject *QQmlComponent::createObject(QObject *parent, const QVariantMap &properties)
+{
+ Q_D(QQmlComponent);
+ Q_ASSERT(d->engine);
+
+ QQmlContext *ctxt = creationContext();
+ if (!ctxt)
+ ctxt = d->engine->rootContext();
+
+ QObject *rv = beginCreate(ctxt);
+ if (!rv)
+ return nullptr;
+
+ Q_ASSERT(d->state.errors.isEmpty()); // otherwise beginCreate() would return nullptr
+
+ QQmlComponent_setQmlParent(rv, parent);
+
+ if (!properties.isEmpty()) {
+ setInitialProperties(rv, properties);
+ if (!d->state.errors.isEmpty()) {
+ qmlWarning(rv, d->state.errors);
+ d->state.errors.clear();
+ }
+ }
+
+ if (!d->requiredProperties().empty()) {
+ QList<QQmlError> errors;
+ for (const auto &requiredProperty: qAsConst(d->requiredProperties())) {
+ errors.push_back(QQmlComponentPrivate::unsetRequiredPropertyToQQmlError(
+ requiredProperty));
+ }
+ if (!errors.isEmpty())
+ qmlWarning(rv, errors);
+ delete rv;
+ return nullptr;
+ }
+
+ d->completeCreate();
+
+ QQmlData *qmlData = QQmlData::get(rv);
+ Q_ASSERT(qmlData);
+ qmlData->explicitIndestructibleSet = false;
+ qmlData->indestructible = false;
+
+ return rv;
+}
/*!
\qmlmethod object Component::incubateObject(Item parent, object properties, enumeration mode)
diff --git a/src/qml/qml/qqmlcomponent.h b/src/qml/qml/qqmlcomponent.h
index f0d51931a4..d60c2cdc9f 100644
--- a/src/qml/qml/qqmlcomponent.h
+++ b/src/qml/qml/qqmlcomponent.h
@@ -127,7 +127,14 @@ Q_SIGNALS:
protected:
QQmlComponent(QQmlComponentPrivate &dd, QObject* parent);
+
+#if QT_DEPRECATED_SINCE(6, 3)
+ QT_DEPRECATED_X("Use the overload with proper arguments")
Q_INVOKABLE void createObject(QQmlV4Function *);
+#endif
+
+ Q_INVOKABLE QObject *createObject(
+ QObject *parent = nullptr, const QVariantMap &properties = {});
Q_INVOKABLE void incubateObject(QQmlV4Function *);
private:
diff --git a/tests/auto/qml/qqmlcomponent/data/createObjectClean.qml b/tests/auto/qml/qqmlcomponent/data/createObjectClean.qml
new file mode 100644
index 0000000000..10c2c11bfb
--- /dev/null
+++ b/tests/auto/qml/qqmlcomponent/data/createObjectClean.qml
@@ -0,0 +1,14 @@
+import QtQml
+
+QtObject {
+ id: self
+
+ property Component t: Component {
+ id: t
+ QtObject {}
+ }
+ property QtObject a: t.createObject()
+ property QtObject b: t.createObject(null)
+ property QtObject c: t.createObject(self)
+ property QtObject d: t.createObject(self, ({objectName: "foo"}))
+}
diff --git a/tests/auto/qml/qqmlcomponent/data/createObjectDirty.qml b/tests/auto/qml/qqmlcomponent/data/createObjectDirty.qml
new file mode 100644
index 0000000000..bb3bdce57d
--- /dev/null
+++ b/tests/auto/qml/qqmlcomponent/data/createObjectDirty.qml
@@ -0,0 +1,11 @@
+import QtQml
+
+QtObject {
+ id: self
+
+ property Component t: Component {
+ id: t
+ QtObject {}
+ }
+ property QtObject a: t.createObject("foobar")
+}
diff --git a/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp b/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp
index 64b2207201..581c060d36 100644
--- a/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp
+++ b/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp
@@ -130,6 +130,8 @@ private slots:
void qmlCreateObjectAutoParent_data();
void qmlCreateObjectAutoParent();
void qmlCreateObjectWithProperties();
+ void qmlCreateObjectClean();
+ void qmlCreateObjectDirty();
void qmlIncubateObject();
void qmlCreateParentReference();
void async();
@@ -331,6 +333,42 @@ void tst_qqmlcomponent::qmlCreateObjectWithProperties()
}
}
+void tst_qqmlcomponent::qmlCreateObjectClean()
+{
+ QQmlEngine engine;
+ QVERIFY(engine.outputWarningsToStandardError());
+ QObject::connect(&engine, &QQmlEngine::warnings, [](const QList<QQmlError> &) {
+ QFAIL("Calls with suitable parameters should not generate any warnings.");
+ });
+ QQmlComponent component(&engine, testFileUrl("createObjectClean.qml"));
+ QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+
+ QVERIFY(qvariant_cast<QObject *>(object->property("a")) != nullptr);
+ QVERIFY(qvariant_cast<QObject *>(object->property("b")) != nullptr);
+ QVERIFY(qvariant_cast<QObject *>(object->property("c")) != nullptr);
+ QVERIFY(qvariant_cast<QObject *>(object->property("d")) != nullptr);
+}
+
+void tst_qqmlcomponent::qmlCreateObjectDirty()
+{
+ QQmlEngine engine;
+ engine.setOutputWarningsToStandardError(false);
+ QObject::connect(&engine, &QQmlEngine::warnings, [](const QList<QQmlError> &warnings) {
+ QCOMPARE(warnings.count(), 1);
+ QCOMPARE(warnings[0].description(),
+ "QML Component: Unsuitable arguments passed to createObject(). The first argument "
+ "should be a QObject* or null, and the second argument should be a JavaScript "
+ "object or a QVariantMap");
+ });
+ QQmlComponent component(&engine, testFileUrl("createObjectDirty.qml"));
+ QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QVERIFY(qvariant_cast<QObject *>(object->property("a")) != nullptr);
+}
+
void tst_qqmlcomponent::qmlCreateParentReference()
{
QQmlEngine engine;