aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/qml
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2021-08-13 18:03:05 +0200
committerUlf Hermann <ulf.hermann@qt.io>2021-10-28 17:49:52 +0200
commit1c5a7ebcde782739cfe02e56a65d274e32bd14fa (patch)
treec61750ca7291fac00e3ff38feddbe790f22a651c /src/qml/qml
parent9b2ba45c1886ee88e856f49acb6b4e2ddb983421 (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.cpp4
-rw-r--r--src/qml/qml/qqmlobjectcreator.cpp85
-rw-r--r--src/qml/qml/qqmlobjectcreator_p.h53
-rw-r--r--src/qml/qml/qqmlpropertycachecreator.cpp14
-rw-r--r--src/qml/qml/qqmlpropertycachecreator_p.h47
-rw-r--r--src/qml/qml/qqmlpropertyvalidator.cpp15
-rw-r--r--src/qml/qml/qqmltypecompiler.cpp40
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)