diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2021-08-13 18:03:05 +0200 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2021-10-28 17:49:52 +0200 |
commit | 1c5a7ebcde782739cfe02e56a65d274e32bd14fa (patch) | |
tree | c61750ca7291fac00e3ff38feddbe790f22a651c /src/qml/qml | |
parent | 9b2ba45c1886ee88e856f49acb6b4e2ddb983421 (diff) |
Introduce generalized grouped properties
You can now use IDs to set up bindings on other objects than the one
currently being constructed. Such bindings are only accepted if the name
given as ID is a deferred property in the surrounding object. The best
way to achieve this is by giving ImmediatePropertyNames, as that will
make all non-immediate names deferred.
You are then expected to explicitly handle the resulting deferred
bindings, for example in componentComplete(). In order to simply apply
the bindings, you can call qmlExecuteDeferred().
Task-number: QTBUG-95117
Change-Id: Iedcf07543426f8f14c23cf53f6f3bcb186a342b0
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Maximilian Goldstein <max.goldstein@qt.io>
Reviewed-by: Andrei Golubev <andrei.golubev@qt.io>
Diffstat (limited to 'src/qml/qml')
-rw-r--r-- | src/qml/qml/qqmlengine.cpp | 4 | ||||
-rw-r--r-- | src/qml/qml/qqmlobjectcreator.cpp | 85 | ||||
-rw-r--r-- | src/qml/qml/qqmlobjectcreator_p.h | 53 | ||||
-rw-r--r-- | src/qml/qml/qqmlpropertycachecreator.cpp | 14 | ||||
-rw-r--r-- | src/qml/qml/qqmlpropertycachecreator_p.h | 47 | ||||
-rw-r--r-- | src/qml/qml/qqmlpropertyvalidator.cpp | 15 | ||||
-rw-r--r-- | src/qml/qml/qqmltypecompiler.cpp | 40 |
7 files changed, 165 insertions, 93 deletions
diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index ed7687cc40..39499bc06d 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -1414,8 +1414,8 @@ void QQmlData::deferData( const QV4::CompiledData::Binding *binding = compiledObject->bindingTable(); for (quint32 i = 0; i < compiledObject->nBindings; ++i, ++binding) { const QQmlPropertyData *property = propertyData.at(i); - if (property && binding->flags & QV4::CompiledData::Binding::IsDeferredBinding) - deferData->bindings.insert(property->coreIndex(), binding); + if (binding->flags & QV4::CompiledData::Binding::IsDeferredBinding) + deferData->bindings.insert(property ? property->coreIndex() : -1, binding); } deferredData.append(deferData); diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp index 81299ea0e0..f258304f88 100644 --- a/src/qml/qml/qqmlobjectcreator.cpp +++ b/src/qml/qml/qqmlobjectcreator.cpp @@ -261,37 +261,7 @@ void QQmlObjectCreator::populateDeferred(QObject *instance, int deferredIndex, const QQmlPropertyPrivate *qmlProperty, const QV4::CompiledData::Binding *binding) { - QQmlData *declarativeData = QQmlData::get(instance); - QObject *bindingTarget = instance; - - QQmlRefPointer<QQmlPropertyCache> cache = declarativeData->propertyCache; - QQmlVMEMetaObject *vmeMetaObject = QQmlVMEMetaObject::get(instance); - - QObject *scopeObject = instance; - qSwap(_scopeObject, scopeObject); - - QV4::Scope valueScope(v4); - QScopedValueRollback<QV4::Value*> jsObjectGuard(sharedState->allJavaScriptObjects, - valueScope.alloc(compilationUnit->totalObjectCount())); - - Q_ASSERT(topLevelCreator); - QV4::QmlContext *qmlContext = static_cast<QV4::QmlContext *>(valueScope.alloc()); - - qSwap(_qmlContext, qmlContext); - - qSwap(_propertyCache, cache); - qSwap(_qobject, instance); - - int objectIndex = deferredIndex; - qSwap(_compiledObjectIndex, objectIndex); - - const QV4::CompiledData::Object *obj = compilationUnit->objectAt(_compiledObjectIndex); - qSwap(_compiledObject, obj); - qSwap(_ddata, declarativeData); - qSwap(_bindingTarget, bindingTarget); - qSwap(_vmeMetaObject, vmeMetaObject); - - if (binding) { + doPopulateDeferred(instance, deferredIndex, [this, qmlProperty, binding]() { Q_ASSERT(qmlProperty); Q_ASSERT(binding->flags & QV4::CompiledData::Binding::IsDeferredBinding); @@ -310,20 +280,12 @@ void QQmlObjectCreator::populateDeferred(QObject *instance, int deferredIndex, setPropertyBinding(&property, binding); qSwap(_currentList, savedList); - } else { - setupBindings(/*applyDeferredBindings=*/true); - } - - qSwap(_vmeMetaObject, vmeMetaObject); - qSwap(_bindingTarget, bindingTarget); - qSwap(_ddata, declarativeData); - qSwap(_compiledObject, obj); - qSwap(_compiledObjectIndex, objectIndex); - qSwap(_qobject, instance); - qSwap(_propertyCache, cache); + }); +} - qSwap(_qmlContext, qmlContext); - qSwap(_scopeObject, scopeObject); +void QQmlObjectCreator::populateDeferred(QObject *instance, int deferredIndex) +{ + doPopulateDeferred(instance, deferredIndex, [this]() { setupBindings(true); }); } bool QQmlObjectCreator::populateDeferredProperties(QObject *instance, @@ -338,8 +300,12 @@ bool QQmlObjectCreator::populateDeferredProperties(QObject *instance, void QQmlObjectCreator::populateDeferredBinding(const QQmlProperty &qmlProperty, int deferredIndex, const QV4::CompiledData::Binding *binding) { - populateDeferred(qmlProperty.object(), deferredIndex, QQmlPropertyPrivate::get(qmlProperty), - binding); + if (binding) { + populateDeferred(qmlProperty.object(), deferredIndex, QQmlPropertyPrivate::get(qmlProperty), + binding); + } else { + populateDeferred(qmlProperty.object(), deferredIndex); + } } void QQmlObjectCreator::finalizePopulateDeferred() @@ -835,9 +801,6 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper return false; } - if (!bindingProperty) // ### error - return true; - if (binding->type == QV4::CompiledData::Binding::Type_GroupProperty) { const QV4::CompiledData::Object *obj = compilationUnit->objectAt(binding->value.objectIndex); if (stringAt(obj->inheritedTypeNameIndex).isEmpty()) { @@ -846,8 +809,19 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper QQmlGadgetPtrWrapper *valueType = nullptr; const QQmlPropertyData *valueTypeProperty = nullptr; QObject *bindingTarget = _bindingTarget; - - if (QQmlMetaType::isValueType(bindingProperty->propType())) { + int groupObjectIndex = binding->value.objectIndex; + + if (!bindingProperty) { + for (int i = 0, end = compilationUnit->objectCount(); i != end; ++i) { + const QV4::CompiledData::Object *external = compilationUnit->objectAt(i); + if (external->idNameIndex == binding->propertyNameIndex) { + bindingTarget = groupObject = context->idValue(external->id); + break; + } + } + if (!groupObject) + return true; + } else if (QQmlMetaType::isValueType(bindingProperty->propType())) { valueType = QQmlGadgetPtrWrapper::instance(engine, bindingProperty->propType()); if (!valueType) { recordError(binding->location, tr("Cannot set properties on %1 as it is null").arg(stringAt(binding->propertyNameIndex))); @@ -869,9 +843,10 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper bindingTarget = groupObject; } - if (!populateInstance(binding->value.objectIndex, groupObject, bindingTarget, - valueTypeProperty, binding)) + if (!populateInstance(groupObjectIndex, groupObject, bindingTarget, valueTypeProperty, + binding)) { return false; + } if (valueType) valueType->write(_qobject, bindingProperty->coreIndex(), QQmlPropertyData::BypassInterceptor); @@ -880,6 +855,9 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper } } + if (!bindingProperty) // ### error + return true; + const bool allowedToRemoveBinding = !(binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression) && !(binding->flags & QV4::CompiledData::Binding::IsOnAssignment) && !(binding->flags & QV4::CompiledData::Binding::IsPropertyObserver) @@ -1565,6 +1543,7 @@ bool QQmlObjectCreator::populateInstance(int index, QObject *instance, QObject * qSwap(_propertyCache, cache); qSwap(_vmeMetaObject, vmeMetaObject); + _ddata->compilationUnit = compilationUnit; if (_compiledObject->flags & QV4::CompiledData::Object::HasDeferredBindings) _ddata->deferData(_compiledObjectIndex, compilationUnit, context); diff --git a/src/qml/qml/qqmlobjectcreator_p.h b/src/qml/qml/qqmlobjectcreator_p.h index 03e1ad7169..c48b1285a5 100644 --- a/src/qml/qml/qqmlobjectcreator_p.h +++ b/src/qml/qml/qqmlobjectcreator_p.h @@ -59,6 +59,7 @@ #include <private/qv4qmlcontext_p.h> #include <private/qqmlguardedcontextdata_p.h> #include <private/qqmlfinalizer_p.h> +#include <private/qqmlvmemetaobject_p.h> #include <qpointer.h> @@ -165,9 +166,10 @@ private: const QV4::CompiledData::Binding *binding = nullptr); // If qmlProperty and binding are null, populate all properties, otherwise only the given one. + void populateDeferred(QObject *instance, int deferredIndex); void populateDeferred(QObject *instance, int deferredIndex, - const QQmlPropertyPrivate *qmlProperty = nullptr, - const QV4::CompiledData::Binding *binding = nullptr); + const QQmlPropertyPrivate *qmlProperty, + const QV4::CompiledData::Binding *binding); void setupBindings(bool applyDeferredBindings = false); bool setPropertyBinding(const QQmlPropertyData *property, const QV4::CompiledData::Binding *binding); @@ -222,6 +224,53 @@ private: typedef std::function<bool(QQmlObjectCreatorSharedState *sharedState)> PendingAliasBinding; std::vector<PendingAliasBinding> pendingAliasBindings; + + template<typename Functor> + void doPopulateDeferred(QObject *instance, int deferredIndex, Functor f) + { + QQmlData *declarativeData = QQmlData::get(instance); + QObject *bindingTarget = instance; + + QQmlRefPointer<QQmlPropertyCache> cache = declarativeData->propertyCache; + QQmlVMEMetaObject *vmeMetaObject = QQmlVMEMetaObject::get(instance); + + QObject *scopeObject = instance; + qSwap(_scopeObject, scopeObject); + + QV4::Scope valueScope(v4); + QScopedValueRollback<QV4::Value*> jsObjectGuard(sharedState->allJavaScriptObjects, + valueScope.alloc(compilationUnit->totalObjectCount())); + + Q_ASSERT(topLevelCreator); + QV4::QmlContext *qmlContext = static_cast<QV4::QmlContext *>(valueScope.alloc()); + + qSwap(_qmlContext, qmlContext); + + qSwap(_propertyCache, cache); + qSwap(_qobject, instance); + + int objectIndex = deferredIndex; + qSwap(_compiledObjectIndex, objectIndex); + + const QV4::CompiledData::Object *obj = compilationUnit->objectAt(_compiledObjectIndex); + qSwap(_compiledObject, obj); + qSwap(_ddata, declarativeData); + qSwap(_bindingTarget, bindingTarget); + qSwap(_vmeMetaObject, vmeMetaObject); + + f(); + + qSwap(_vmeMetaObject, vmeMetaObject); + qSwap(_bindingTarget, bindingTarget); + qSwap(_ddata, declarativeData); + qSwap(_compiledObject, obj); + qSwap(_compiledObjectIndex, objectIndex); + qSwap(_qobject, instance); + qSwap(_propertyCache, cache); + + qSwap(_qmlContext, qmlContext); + qSwap(_scopeObject, scopeObject); + } }; struct QQmlObjectCreatorRecursionWatcher diff --git a/src/qml/qml/qqmlpropertycachecreator.cpp b/src/qml/qml/qqmlpropertycachecreator.cpp index 8bd66177a9..d542175647 100644 --- a/src/qml/qml/qqmlpropertycachecreator.cpp +++ b/src/qml/qml/qqmlpropertycachecreator.cpp @@ -147,11 +147,15 @@ void QQmlPendingGroupPropertyBindings::resolveMissingPropertyCaches(QQmlEnginePr if (propertyCaches->at(groupPropertyObjectIndex)) continue; - if (!pendingBinding.resolveInstantiatingProperty()) - continue; - - auto cache = pendingBinding.instantiatingPropertyCache(enginePrivate); - propertyCaches->set(groupPropertyObjectIndex, cache); + if (pendingBinding.referencingObjectPropertyCache) { + if (!pendingBinding.resolveInstantiatingProperty()) + continue; + auto cache = pendingBinding.instantiatingPropertyCache(enginePrivate); + propertyCaches->set(groupPropertyObjectIndex, cache); + } else { + auto cache = propertyCaches->at(pendingBinding.referencingObjectIndex); + propertyCaches->set(groupPropertyObjectIndex, cache); + } } } diff --git a/src/qml/qml/qqmlpropertycachecreator_p.h b/src/qml/qml/qqmlpropertycachecreator_p.h index 363a9ff4e2..cabaff38fc 100644 --- a/src/qml/qml/qqmlpropertycachecreator_p.h +++ b/src/qml/qml/qqmlpropertycachecreator_p.h @@ -389,22 +389,41 @@ inline QQmlRefPointer<QQmlPropertyCache> QQmlPropertyCacheCreator<ObjectContaine } return typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); - } else if (context.instantiatingBinding && context.instantiatingBinding->isAttachedProperty()) { - auto *typeRef = objectContainer->resolvedType( - context.instantiatingBinding->propertyNameIndex); - Q_ASSERT(typeRef); - QQmlType qmltype = typeRef->type(); - if (!qmltype.isValid()) { - imports->resolveType(stringAt(context.instantiatingBinding->propertyNameIndex), - &qmltype, nullptr, nullptr, nullptr); - } + } else if (const QV4::CompiledData::Binding *binding = context.instantiatingBinding) { + if (binding->isAttachedProperty()) { + auto *typeRef = objectContainer->resolvedType( + binding->propertyNameIndex); + Q_ASSERT(typeRef); + QQmlType qmltype = typeRef->type(); + if (!qmltype.isValid()) { + imports->resolveType(stringAt(binding->propertyNameIndex), + &qmltype, nullptr, nullptr, nullptr); + } - const QMetaObject *attachedMo = qmltype.attachedPropertiesType(enginePrivate); - if (!attachedMo) { - *error = qQmlCompileError(context.instantiatingBinding->location, QQmlPropertyCacheCreatorBase::tr("Non-existent attached object")); - return nullptr; + const QMetaObject *attachedMo = qmltype.attachedPropertiesType(enginePrivate); + if (!attachedMo) { + *error = qQmlCompileError(binding->location, QQmlPropertyCacheCreatorBase::tr("Non-existent attached object")); + return nullptr; + } + return enginePrivate->cache(attachedMo); + } else if (binding->isGroupProperty()) { + const auto *obj = objectContainer->objectAt(binding->value.objectIndex); + if (!stringAt(obj->inheritedTypeNameIndex).isEmpty()) + return nullptr; + + for (int i = 0, end = objectContainer->objectCount(); i != end; ++i) { + const auto *ext = objectContainer->objectAt(i); + if (ext->idNameIndex != binding->propertyNameIndex) + continue; + + if (ext->inheritedTypeNameIndex == 0) + return nullptr; + + QQmlBindingInstantiationContext pendingContext(i, &(*binding), QString(), nullptr); + pendingGroupPropertyBindings->append(pendingContext); + return nullptr; + } } - return enginePrivate->cache(attachedMo); } return nullptr; } diff --git a/src/qml/qml/qqmlpropertyvalidator.cpp b/src/qml/qml/qqmlpropertyvalidator.cpp index 72e0275a53..6e0a578f6c 100644 --- a/src/qml/qml/qqmlpropertyvalidator.cpp +++ b/src/qml/qml/qqmlpropertyvalidator.cpp @@ -228,7 +228,8 @@ QVector<QQmlError> QQmlPropertyValidator::validateObject( return recordError(binding->location, tr("Invalid attached object assignment")); } - if (binding->type >= QV4::CompiledData::Binding::Type_Object && (pd || binding->isAttachedProperty())) { + if (binding->type >= QV4::CompiledData::Binding::Type_Object + && (pd || binding->isAttachedProperty() || binding->isGroupProperty())) { const bool populatingValueTypeGroupProperty = pd && QQmlMetaType::metaObjectForValueType(pd->propType()) @@ -247,9 +248,15 @@ QVector<QQmlError> QQmlPropertyValidator::validateObject( continue; } - if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) { - if (instantiatingBinding && (instantiatingBinding->isAttachedProperty() || instantiatingBinding->isGroupProperty())) { - return recordError(binding->location, tr("Attached properties cannot be used here")); + if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty + || (!pd && binding->type == QV4::CompiledData::Binding::Type_GroupProperty)) { + if (instantiatingBinding && (instantiatingBinding->isAttachedProperty() + || instantiatingBinding->isGroupProperty())) { + return recordError( + binding->location, tr("%1 properties cannot be used here") + .arg(binding->type == QV4::CompiledData::Binding::Type_AttachedProperty + ? QStringLiteral("Attached") + : QStringLiteral("Group"))); } continue; } diff --git a/src/qml/qml/qqmltypecompiler.cpp b/src/qml/qml/qqmltypecompiler.cpp index 415a4eb7e9..554ece779b 100644 --- a/src/qml/qml/qqmltypecompiler.cpp +++ b/src/qml/qml/qqmltypecompiler.cpp @@ -1310,24 +1310,27 @@ bool QQmlDeferredAndCustomParserBindingScanner::scanObject(int objectIndex) } bool seenSubObjectWithId = false; - - if (binding->type >= QV4::CompiledData::Binding::Type_Object && (pd || binding->isAttachedProperty())) { - qSwap(_seenObjectWithId, seenSubObjectWithId); - const bool subObjectValid = scanObject(binding->value.objectIndex); - qSwap(_seenObjectWithId, seenSubObjectWithId); - if (!subObjectValid) - return false; - _seenObjectWithId |= seenSubObjectWithId; + bool isExternal = false; + if (binding->type >= QV4::CompiledData::Binding::Type_Object) { + const bool isOwnProperty = pd || binding->isAttachedProperty(); + isExternal = !isOwnProperty && binding->isGroupProperty(); + if (isOwnProperty || isExternal) { + qSwap(_seenObjectWithId, seenSubObjectWithId); + const bool subObjectValid = scanObject(binding->value.objectIndex); + qSwap(_seenObjectWithId, seenSubObjectWithId); + if (!subObjectValid) + return false; + _seenObjectWithId |= seenSubObjectWithId; + } } + bool isDeferred = false; if (!immediatePropertyNames.isEmpty() && !immediatePropertyNames.contains(name)) { if (seenSubObjectWithId) { COMPILE_EXCEPTION(binding, tr("You cannot assign an id to an object assigned " "to a deferred property.")); } - - binding->flags |= QV4::CompiledData::Binding::IsDeferredBinding; - obj->flags |= QV4::CompiledData::Object::HasDeferredBindings; + isDeferred = true; } else if (!deferredPropertyNames.isEmpty() && deferredPropertyNames.contains(name)) { if (seenSubObjectWithId) { qWarning("Binding on %s is not deferred as requested by the DeferredPropertyNames " @@ -1337,11 +1340,22 @@ bool QQmlDeferredAndCustomParserBindingScanner::scanObject(int objectIndex) qWarning("Binding on %s is not deferred as requested by the DeferredPropertyNames " "class info because it constitutes a group property.", qPrintable(name)); } else { - binding->flags |= QV4::CompiledData::Binding::IsDeferredBinding; - obj->flags |= QV4::CompiledData::Object::HasDeferredBindings; + isDeferred = true; } } + if (binding->type >= QV4::CompiledData::Binding::Type_Object) { + if (isExternal && !isDeferred && !customParser) { + COMPILE_EXCEPTION( + binding, tr("Cannot assign to non-existent property \"%1\"").arg(name)); + } + } + + if (isDeferred) { + binding->flags |= QV4::CompiledData::Binding::IsDeferredBinding; + obj->flags |= QV4::CompiledData::Object::HasDeferredBindings; + } + if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression || binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject || binding->flags & QV4::CompiledData::Binding::IsPropertyObserver) |