From c837be7beab1c217b8f163b9a2d53ca12fd2c95e Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Tue, 19 Oct 2021 15:51:36 +0200 Subject: 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 Reviewed-by: Fabian Kosmale Reviewed-by: Andrei Golubev --- src/qml/qml/qqmlcomponent.cpp | 102 +++++++++++++++++++-- src/qml/qml/qqmlcomponent.h | 7 ++ .../qml/qqmlcomponent/data/createObjectClean.qml | 14 +++ .../qml/qqmlcomponent/data/createObjectDirty.qml | 11 +++ tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp | 38 ++++++++ 5 files changed, 163 insertions(+), 9 deletions(-) create mode 100644 tests/auto/qml/qqmlcomponent/data/createObjectClean.qml create mode 100644 tests/auto/qml/qqmlcomponent/data/createObjectDirty.qml 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 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 &) { + QFAIL("Calls with suitable parameters should not generate any warnings."); + }); + QQmlComponent component(&engine, testFileUrl("createObjectClean.qml")); + QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8()); + QScopedPointer object(component.create()); + QVERIFY(!object.isNull()); + + QVERIFY(qvariant_cast(object->property("a")) != nullptr); + QVERIFY(qvariant_cast(object->property("b")) != nullptr); + QVERIFY(qvariant_cast(object->property("c")) != nullptr); + QVERIFY(qvariant_cast(object->property("d")) != nullptr); +} + +void tst_qqmlcomponent::qmlCreateObjectDirty() +{ + QQmlEngine engine; + engine.setOutputWarningsToStandardError(false); + QObject::connect(&engine, &QQmlEngine::warnings, [](const QList &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 object(component.create()); + QVERIFY(!object.isNull()); + QVERIFY(qvariant_cast(object->property("a")) != nullptr); +} + void tst_qqmlcomponent::qmlCreateParentReference() { QQmlEngine engine; -- cgit v1.2.3