diff options
Diffstat (limited to 'src/qml/qml')
-rw-r--r-- | src/qml/qml/qqmlcomponent.cpp | 129 | ||||
-rw-r--r-- | src/qml/qml/qqmlcomponent_p.h | 7 | ||||
-rw-r--r-- | src/qml/qml/qqmlincubator.cpp | 48 | ||||
-rw-r--r-- | src/qml/qml/qqmlincubator.h | 5 | ||||
-rw-r--r-- | src/qml/qml/qqmlincubator_p.h | 8 | ||||
-rw-r--r-- | src/qml/qml/qqmlobjectcreator.cpp | 51 | ||||
-rw-r--r-- | src/qml/qml/qqmlobjectcreator_p.h | 25 |
7 files changed, 257 insertions, 16 deletions
diff --git a/src/qml/qml/qqmlcomponent.cpp b/src/qml/qml/qqmlcomponent.cpp index ed8c41a582..b72c745490 100644 --- a/src/qml/qml/qqmlcomponent.cpp +++ b/src/qml/qml/qqmlcomponent.cpp @@ -340,6 +340,11 @@ void QQmlComponentPrivate::fromTypeData(const QQmlRefPointer<QQmlTypeData> &data } } +RequiredProperties &QQmlComponentPrivate::requiredProperties() +{ + return state.creator->requiredProperties(); +} + void QQmlComponentPrivate::clear() { if (typeData) { @@ -364,8 +369,8 @@ QObject *QQmlComponentPrivate::doBeginCreate(QQmlComponent *q, QQmlContext *cont bool QQmlComponentPrivate::setInitialProperty(QObject *component, const QString& name, const QVariant &value) { - QQmlProperty prop(component, name); - auto privProp = QQmlPropertyPrivate::get(prop); + QQmlProperty prop = QQmlComponentPrivate::removePropertyFromRequired(component, name, requiredProperties()); + QQmlPropertyPrivate *privProp = QQmlPropertyPrivate::get(prop); if (!prop.isValid() || !privProp->writeValueProperty(value, nullptr)) { QQmlError error{}; error.setUrl(url); @@ -809,6 +814,10 @@ QObject *QQmlComponent::create(QQmlContext *context) QObject *rv = d->doBeginCreate(this, context); if (rv) completeCreate(); + if (rv && !d->requiredProperties().empty()) { + delete rv; + return nullptr; + } return rv; } @@ -828,6 +837,10 @@ QObject *QQmlComponent::createWithInitialProperties(const QVariantMap& initialPr setInitialProperties(rv, initialProperties); completeCreate(); } + if (!d->requiredProperties().empty()) { + d->requiredProperties().clear(); + return nullptr; + } return rv; } @@ -980,6 +993,57 @@ void QQmlComponentPrivate::complete(QQmlEnginePrivate *enginePriv, ConstructionS } /*! + * \internal + * Finds the matching toplevel property with name \a name of the component \a createdComponent. + * If it was a required property or an alias to a required property contained in \a + * requiredProperties, it is removed from it. + * + * If wasInRequiredProperties is non-null, the referenced boolean is set to true iff the property + * was found in requiredProperties. + * + * Returns the QQmlProperty with name \a name (which might be invalid if there is no such property), + * for further processing (for instance, actually setting the property value). + * + * Note: This method is used in QQmlComponent and QQmlIncubator to manage required properties. Most + * classes which create components should not need it and should only need to call + * setInitialProperties. + */ +QQmlProperty QQmlComponentPrivate::removePropertyFromRequired(QObject *createdComponent, const QString &name, RequiredProperties &requiredProperties, bool* wasInRequiredProperties) +{ + QQmlProperty prop(createdComponent, name); + auto privProp = QQmlPropertyPrivate::get(prop); + if (prop.isValid()) { + // resolve outstanding required properties + auto targetProp = &privProp->core; + if (targetProp->isAlias()) { + auto target = createdComponent; + QQmlPropertyIndex originalIndex(targetProp->coreIndex()); + QQmlPropertyIndex propIndex; + QQmlPropertyPrivate::findAliasTarget(target, originalIndex, &target, &propIndex); + QQmlData *data = QQmlData::get(target); + Q_ASSERT(data && data->propertyCache); + targetProp = data->propertyCache->property(propIndex.coreIndex()); + } else { + // we need to get the pointer from the property cache instead of directly using + // targetProp else the lookup will fail + QQmlData *data = QQmlData::get(createdComponent); + Q_ASSERT(data && data->propertyCache); + targetProp = data->propertyCache->property(targetProp->coreIndex()); + } + auto it = requiredProperties.find(targetProp); + if (it != requiredProperties.end()) { + if (wasInRequiredProperties) + *wasInRequiredProperties = true; + requiredProperties.erase(it); + } else { + if (wasInRequiredProperties) + *wasInRequiredProperties = false; + } + } + return prop; +} + +/*! This method provides advanced control over component instance creation. In general, programmers should use QQmlComponent::create() to create a component. @@ -998,6 +1062,11 @@ void QQmlComponent::completeCreate() void QQmlComponentPrivate::completeCreate() { + const RequiredProperties& unsetRequiredProperties = requiredProperties(); + for (const auto& unsetRequiredProperty: unsetRequiredProperties) { + QQmlError error = unsetRequiredPropertyToQQmlError(unsetRequiredProperty); + state.errors.push_back(error); + } if (state.completePending) { ++creationDepth.localData(); QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine); @@ -1185,7 +1254,7 @@ struct QmlIncubatorObject : public QV4::Object static ReturnedValue method_forceCompletion(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); void statusChanged(QQmlIncubator::Status); - void setInitialState(QObject *); + void setInitialState(QObject *, RequiredProperties &requiredProperties); }; } @@ -1210,7 +1279,8 @@ public: void setInitialState(QObject *o) override { QV4::Scope scope(incubatorObject.engine()); QV4::Scoped<QV4::QmlIncubatorObject> i(scope, incubatorObject.as<QV4::QmlIncubatorObject>()); - i->setInitialState(o); + auto d = QQmlIncubatorPrivate::get(this); + i->setInitialState(o, d->requiredProperties()); } QV4::PersistentValue incubatorObject; // keep a strong internal reference while incubating @@ -1282,7 +1352,7 @@ static void QQmlComponent_setQmlParent(QObject *me, QObject *parent) */ -void QQmlComponentPrivate::setInitialProperties(QV4::ExecutionEngine *engine, QV4::QmlContext *qmlContext, const QV4::Value &o, const QV4::Value &v) +void QQmlComponentPrivate::setInitialProperties(QV4::ExecutionEngine *engine, QV4::QmlContext *qmlContext, const QV4::Value &o, const QV4::Value &v, RequiredProperties &requiredProperties, QObject *createdComponent) { QV4::Scope scope(engine); QV4::ScopedObject object(scope); @@ -1301,6 +1371,7 @@ void QQmlComponentPrivate::setInitialProperties(QV4::ExecutionEngine *engine, QV break; object = o; const QStringList properties = name->toQString().split(QLatin1Char('.')); + bool isTopLevelProperty = properties.size() == 1; for (int i = 0; i < properties.length() - 1; ++i) { name = engine->newString(properties.at(i)); object = object->get(name); @@ -1317,12 +1388,40 @@ void QQmlComponentPrivate::setInitialProperties(QV4::ExecutionEngine *engine, QV if (engine->hasException) { engine->hasException = false; continue; + } else if (isTopLevelProperty) { + auto prop = removePropertyFromRequired(createdComponent, name->toQString(), requiredProperties); } } engine->hasException = false; } +QQmlError QQmlComponentPrivate::unsetRequiredPropertyToQQmlError(const RequiredPropertyInfo &unsetRequiredProperty) +{ + QQmlError error; + QString description = QLatin1String("Required property %1 was not initialized").arg(unsetRequiredProperty.propertyName); + switch (unsetRequiredProperty.aliasesToRequired.size()) { + case 0: + break; + case 1: { + const auto info = unsetRequiredProperty.aliasesToRequired.first(); + description += QLatin1String("\nIt can be set via the alias property %1 from %2\n").arg(info.propertyName, info.fileUrl.toString()); + break; + } + default: + description += QLatin1String("\nIt can be set via one of the following alias properties:"); + for (auto aliasInfo: unsetRequiredProperty.aliasesToRequired) { + description += QLatin1String("\n- %1 (%2)").arg(aliasInfo.propertyName, aliasInfo.fileUrl.toString()); + } + description += QLatin1Char('\n'); + } + error.setDescription(description); + error.setUrl(unsetRequiredProperty.fileUrl); + error.setLine(unsetRequiredProperty.location.line); + error.setColumn(unsetRequiredProperty.location.column); + return error; +} + /*! \internal */ @@ -1370,7 +1469,17 @@ void QQmlComponent::createObject(QQmlV4Function *args) if (!valuemap->isUndefined()) { QV4::Scoped<QV4::QmlContext> qmlContext(scope, v4->qmlContext()); - QQmlComponentPrivate::setInitialProperties(v4, qmlContext, object, valuemap); + QQmlComponentPrivate::setInitialProperties(v4, qmlContext, object, valuemap, d->requiredProperties(), rv); + } + if (!d->requiredProperties().empty()) { + QList<QQmlError> errors; + for (const auto &requiredProperty: d->requiredProperties()) { + errors.push_back(QQmlComponentPrivate::unsetRequiredPropertyToQQmlError(requiredProperty)); + } + qmlWarning(rv, errors); + args->setReturnValue(QV4::Encode::null()); + delete rv; + return; } d->completeCreate(); @@ -1502,7 +1611,7 @@ void QQmlComponent::incubateObject(QQmlV4Function *args) } // XXX used by QSGLoader -void QQmlComponentPrivate::initializeObjectWithInitialProperties(QV4::QmlContext *qmlContext, const QV4::Value &valuemap, QObject *toCreate) +void QQmlComponentPrivate::initializeObjectWithInitialProperties(QV4::QmlContext *qmlContext, const QV4::Value &valuemap, QObject *toCreate, RequiredProperties &requiredProperties) { QV4::ExecutionEngine *v4engine = engine->handle(); QV4::Scope scope(v4engine); @@ -1511,7 +1620,7 @@ void QQmlComponentPrivate::initializeObjectWithInitialProperties(QV4::QmlContext Q_ASSERT(object->as<QV4::Object>()); if (!valuemap.isUndefined()) - setInitialProperties(v4engine, qmlContext, object, valuemap); + setInitialProperties(v4engine, qmlContext, object, valuemap, requiredProperties, toCreate); } QQmlComponentExtension::QQmlComponentExtension(QV4::ExecutionEngine *v4) @@ -1601,7 +1710,7 @@ void QV4::Heap::QmlIncubatorObject::destroy() { Object::destroy(); } -void QV4::QmlIncubatorObject::setInitialState(QObject *o) +void QV4::QmlIncubatorObject::setInitialState(QObject *o, RequiredProperties &requiredProperties) { QQmlComponent_setQmlParent(o, d()->parent); @@ -1610,7 +1719,7 @@ void QV4::QmlIncubatorObject::setInitialState(QObject *o) QV4::Scope scope(v4); QV4::ScopedObject obj(scope, QV4::QObjectWrapper::wrap(v4, o)); QV4::Scoped<QV4::QmlContext> qmlCtxt(scope, d()->qmlContext); - QQmlComponentPrivate::setInitialProperties(v4, qmlCtxt, obj, d()->valuemap); + QQmlComponentPrivate::setInitialProperties(v4, qmlCtxt, obj, d()->valuemap, requiredProperties, o); } } diff --git a/src/qml/qml/qqmlcomponent_p.h b/src/qml/qml/qqmlcomponent_p.h index 2170646b89..8ae7672c19 100644 --- a/src/qml/qml/qqmlcomponent_p.h +++ b/src/qml/qml/qqmlcomponent_p.h @@ -86,8 +86,9 @@ public: QObject *beginCreate(QQmlContextData *); void completeCreate(); - void initializeObjectWithInitialProperties(QV4::QmlContext *qmlContext, const QV4::Value &valuemap, QObject *toCreate); - static void setInitialProperties(QV4::ExecutionEngine *engine, QV4::QmlContext *qmlContext, const QV4::Value &o, const QV4::Value &v); + void initializeObjectWithInitialProperties(QV4::QmlContext *qmlContext, const QV4::Value &valuemap, QObject *toCreate, RequiredProperties &requiredProperties); + static void setInitialProperties(QV4::ExecutionEngine *engine, QV4::QmlContext *qmlContext, const QV4::Value &o, const QV4::Value &v, RequiredProperties &requiredProperties, QObject *createdComponent); + static QQmlError unsetRequiredPropertyToQQmlError(const RequiredPropertyInfo &unsetRequiredProperty); virtual void incubateObject( QQmlIncubator *incubationTask, @@ -106,6 +107,7 @@ public: qreal progress; int start; + RequiredProperties& requiredProperties(); QQmlRefPointer<QV4::ExecutableCompilationUnit> compilationUnit; struct ConstructionState { @@ -134,6 +136,7 @@ public: static void completeDeferred(QQmlEnginePrivate *enginePriv, DeferredState *deferredState); static void complete(QQmlEnginePrivate *enginePriv, ConstructionState *state); + static QQmlProperty removePropertyFromRequired(QObject *createdComponent, const QString &name, RequiredProperties& requiredProperties, bool *wasInRequiredProperties = nullptr); QQmlEngine *engine; QQmlGuardedContextData creationContext; diff --git a/src/qml/qml/qqmlincubator.cpp b/src/qml/qml/qqmlincubator.cpp index bc06226cbf..7b3ae31c08 100644 --- a/src/qml/qml/qqmlincubator.cpp +++ b/src/qml/qml/qqmlincubator.cpp @@ -43,6 +43,7 @@ #include "qqmlexpression_p.h" #include "qqmlobjectcreator_p.h" +#include <private/qqmlcomponent_p.h> void QQmlEnginePrivate::incubate(QQmlIncubator &i, QQmlContextData *forContext) { @@ -296,6 +297,20 @@ void QQmlIncubatorPrivate::incubate(QQmlInstantiationInterrupt &i) tresult = creator->create(subComponentToCreate, /*parent*/nullptr, &i); if (!tresult) errors = creator->errors; + else { + RequiredProperties& requiredProperties = creator->requiredProperties(); + for (auto it = initialProperties.cbegin(); it != initialProperties.cend(); ++it) { + auto component = tresult; + auto name = it.key(); + QQmlProperty prop = QQmlComponentPrivate::removePropertyFromRequired(component, name, requiredProperties); + if (!prop.isValid() || !prop.write(it.value())) { + QQmlError error{}; + error.setUrl(compilationUnit->url()); + error.setDescription(QLatin1String("Could not set property %1").arg(name)); + errors.push_back(error); + } + } + } enginePriv->dereferenceScarceResources(); if (watcher.hasRecursed()) @@ -312,8 +327,14 @@ void QQmlIncubatorPrivate::incubate(QQmlInstantiationInterrupt &i) ddata->indestructible = true; ddata->explicitIndestructibleSet = true; ddata->rootObjectInCreation = false; - if (q) + if (q) { q->setInitialState(result); + if (!creator->requiredProperties().empty()) { + const auto& unsetRequiredProperties = creator->requiredProperties(); + for (const auto& unsetRequiredProperty: unsetRequiredProperties) + errors << QQmlComponentPrivate::unsetRequiredPropertyToQQmlError(unsetRequiredProperty); + } + } } if (watcher.hasRecursed()) @@ -657,6 +678,31 @@ QObject *QQmlIncubator::object() const } /*! +Return a list of properties which are required but haven't been set yet. +This list can be modified, so that subclasses which implement special logic +setInitialProperties can mark properties set there as no longer required. + +\sa QQmlIncubator::setInitialProperties +\since 5.15 +*/ +RequiredProperties &QQmlIncubatorPrivate::requiredProperties() +{ + return creator->requiredProperties(); +} + +/*! +Stores a mapping from property names to initial values with which the incubated +component will be initialized + +\sa QQmlComponent::setInitialProperties +\since 5.15 +*/ +void QQmlIncubator::setInitialProperties(const QVariantMap &initialProperties) +{ + d->initialProperties = initialProperties; +} + +/*! Called when the status of the incubator changes. \a status is the new status. The default implementation does nothing. diff --git a/src/qml/qml/qqmlincubator.h b/src/qml/qml/qqmlincubator.h index e68f6e3c45..f075407e73 100644 --- a/src/qml/qml/qqmlincubator.h +++ b/src/qml/qml/qqmlincubator.h @@ -47,6 +47,9 @@ QT_BEGIN_NAMESPACE class QQmlEngine; +class QQmlPropertyData; +class QVariant; +using QVariantMap = QMap<QString, QVariant>; class QQmlIncubatorPrivate; class Q_QML_EXPORT QQmlIncubator @@ -84,6 +87,8 @@ public: QObject *object() const; + void setInitialProperties(const QVariantMap &initialProperties); + protected: virtual void statusChanged(Status); virtual void setInitialState(QObject *); diff --git a/src/qml/qml/qqmlincubator_p.h b/src/qml/qml/qqmlincubator_p.h index 57ec8249cb..731db7aad3 100644 --- a/src/qml/qml/qqmlincubator_p.h +++ b/src/qml/qml/qqmlincubator_p.h @@ -61,8 +61,12 @@ QT_BEGIN_NAMESPACE +class QQmlPropertyData; +struct RequiredPropertyInfo; +using RequiredProperties = QHash<QQmlPropertyData*, RequiredPropertyInfo>; + class QQmlIncubator; -class QQmlIncubatorPrivate : public QQmlEnginePrivate::Incubator +class Q_QML_PRIVATE_EXPORT QQmlIncubatorPrivate : public QQmlEnginePrivate::Incubator { public: QQmlIncubatorPrivate(QQmlIncubator *q, QQmlIncubator::IncubationMode m); @@ -97,11 +101,13 @@ public: QIntrusiveList<QIPBase, &QIPBase::nextWaitingFor> waitingFor; QRecursionNode recursion; + QVariantMap initialProperties; void clear(); void forceCompletion(QQmlInstantiationInterrupt &i); void incubate(QQmlInstantiationInterrupt &i); + RequiredProperties &requiredProperties(); }; QT_END_NAMESPACE diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp index f89608cd5d..b723ddb381 100644 --- a/src/qml/qml/qqmlobjectcreator.cpp +++ b/src/qml/qml/qqmlobjectcreator.cpp @@ -783,6 +783,23 @@ void QQmlObjectCreator::setupBindings(bool applyDeferredBindings) const QV4::CompiledData::Binding *binding = _compiledObject->bindingTable(); for (quint32 i = 0; i < _compiledObject->nBindings; ++i, ++binding) { + QQmlPropertyData *const property = propertyData.at(i); + if (property) { + QQmlPropertyData* targetProperty = property; + if (targetProperty->isAlias()) { + // follow alias + auto target = _bindingTarget; + QQmlPropertyIndex originalIndex(targetProperty->coreIndex(), _valueTypeProperty ? _valueTypeProperty->coreIndex() : -1); + QQmlPropertyIndex propIndex; + QQmlPropertyPrivate::findAliasTarget(target, originalIndex, &target, &propIndex); + QQmlData *data = QQmlData::get(target); + Q_ASSERT(data && data->propertyCache); + targetProperty = data->propertyCache->property(propIndex.coreIndex()); + } + sharedState->requiredProperties.remove(targetProperty); + } + + if (binding->flags & QV4::CompiledData::Binding::IsCustomParserBinding) continue; @@ -794,8 +811,6 @@ void QQmlObjectCreator::setupBindings(bool applyDeferredBindings) continue; } - const QQmlPropertyData *property = propertyData.at(i); - if (property && property->isQList()) { if (property->coreIndex() != currentListPropertyIndex) { void *argv[1] = { (void*)&_currentList }; @@ -1504,10 +1519,42 @@ bool QQmlObjectCreator::populateInstance(int index, QObject *instance, QObject * if (_compiledObject->flags & QV4::CompiledData::Object::HasDeferredBindings) _ddata->deferData(_compiledObjectIndex, compilationUnit, context); + for (int propertyIndex = 0; propertyIndex != _compiledObject->propertyCount(); ++propertyIndex) { + const QV4::CompiledData::Property* property = _compiledObject->propertiesBegin() + propertyIndex; + QQmlPropertyData *propertyData = _propertyCache->property(_propertyCache->propertyOffset() + propertyIndex); + if (property->isRequired) { + sharedState->requiredProperties.insert(propertyData, + RequiredPropertyInfo {compilationUnit->stringAt(property->nameIndex), compilationUnit->finalUrl(), property->location, {}}); + } + } + if (_compiledObject->nFunctions > 0) setupFunctions(); setupBindings(); + for (int aliasIndex = 0; aliasIndex != _compiledObject->aliasCount(); ++aliasIndex) { + const QV4::CompiledData::Alias* alias = _compiledObject->aliasesBegin() + aliasIndex; + const auto originalAlias = alias; + while (alias->aliasToLocalAlias) + alias = _compiledObject->aliasesBegin() + alias->localAliasIndex; + Q_ASSERT(alias->flags & QV4::CompiledData::Alias::Resolved); + if (!context->idValues->wasSet()) + continue; + QObject *target = context->idValues[alias->targetObjectId].data(); + if (!target) + continue; + QQmlData *targetDData = QQmlData::get(target, /*create*/false); + if (!targetDData) + continue; + int coreIndex = QQmlPropertyIndex::fromEncoded(alias->encodedMetaPropertyIndex).coreIndex(); + QQmlPropertyData *const targetProperty = targetDData->propertyCache->property(coreIndex); + if (!targetProperty) + continue; + auto it = sharedState->requiredProperties.find(targetProperty); + if (it != sharedState->requiredProperties.end()) + it->aliasesToRequired.push_back(AliasToRequiredInfo {compilationUnit->stringAt(originalAlias->nameIndex), compilationUnit->finalUrl()}); + } + qSwap(_vmeMetaObject, vmeMetaObject); qSwap(_bindingTarget, bindingTarget); qSwap(_ddata, declarativeData); diff --git a/src/qml/qml/qqmlobjectcreator_p.h b/src/qml/qml/qqmlobjectcreator_p.h index ecdbcc56dd..ee1d82d4e3 100644 --- a/src/qml/qml/qqmlobjectcreator_p.h +++ b/src/qml/qml/qqmlobjectcreator_p.h @@ -66,6 +66,28 @@ class QQmlAbstractBinding; class QQmlInstantiationInterrupt; class QQmlIncubatorPrivate; +struct AliasToRequiredInfo { + QString propertyName; + QUrl fileUrl; +}; + +/*! +\internal +This struct contains information solely used for displaying error messages +\variable aliasesToRequired allows us to give the user a way to know which (aliasing) properties +can be set to set the required property +\sa QQmlComponentPrivate::unsetRequiredPropertyToQQmlError +*/ +struct RequiredPropertyInfo +{ + QString propertyName; + QUrl fileUrl; + QV4::CompiledData::Location location; + QVector<AliasToRequiredInfo> aliasesToRequired; +}; + +using RequiredProperties = QHash<QQmlPropertyData*, RequiredPropertyInfo>; + struct QQmlObjectCreatorSharedState : public QSharedData { QQmlContextData *rootContext; @@ -78,6 +100,7 @@ struct QQmlObjectCreatorSharedState : public QSharedData QList<QQmlEnginePrivate::FinalizeCallback> finalizeCallbacks; QQmlVmeProfiler profiler; QRecursionNode recursionNode; + RequiredProperties requiredProperties; }; class Q_QML_PRIVATE_EXPORT QQmlObjectCreator @@ -102,6 +125,8 @@ public: QQmlContextData *parentContextData() const { return parentContext.contextData(); } QFiniteStack<QPointer<QObject> > &allCreatedObjects() { return sharedState->allCreatedObjects; } + RequiredProperties &requiredProperties() {return sharedState->requiredProperties;} + private: QQmlObjectCreator(QQmlContextData *contextData, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, QQmlObjectCreatorSharedState *inheritedSharedState); |