diff options
Diffstat (limited to 'src/qmlmodels/qqmldelegatemodel.cpp')
-rw-r--r-- | src/qmlmodels/qqmldelegatemodel.cpp | 119 |
1 files changed, 116 insertions, 3 deletions
diff --git a/src/qmlmodels/qqmldelegatemodel.cpp b/src/qmlmodels/qqmldelegatemodel.cpp index e7868517cd..691b4b3c29 100644 --- a/src/qmlmodels/qqmldelegatemodel.cpp +++ b/src/qmlmodels/qqmldelegatemodel.cpp @@ -48,7 +48,6 @@ #include <private/qqmlchangeset_p.h> #include <private/qqmlengine_p.h> #include <private/qqmlcomponent_p.h> -#include <private/qqmlincubator_p.h> #include <private/qv4value_p.h> #include <private/qv4functionobject_p.h> @@ -887,6 +886,117 @@ static bool isDoneIncubating(QQmlIncubator::Status status) return status == QQmlIncubator::Ready || status == QQmlIncubator::Error; } +PropertyUpdater::PropertyUpdater(QObject *parent) : + QObject(parent) {} + +void PropertyUpdater::doUpdate() +{ + auto sender = QObject::sender(); + auto mo = sender->metaObject(); + auto signalIndex = QObject::senderSignalIndex(); + ++updateCount; + // start at 0 instead of propertyOffset to handle properties from parent hierarchy + for (auto i = 0; i < mo->propertyCount() + mo->propertyOffset(); ++i) { + auto property = mo->property(i); + if (property.notifySignal().methodIndex() == signalIndex) { + // we synchronize between required properties and model rolenames by name + // that's why the QQmlProperty and the metaobject property must have the same name + QQmlProperty qmlProp(parent(), QString::fromLatin1(property.name())); + qmlProp.write(property.read(QObject::sender())); + return; + } + } +} + +void PropertyUpdater::breakBinding() +{ + auto it = senderToConnection.find(QObject::senderSignalIndex()); + if (it == senderToConnection.end()) + return; + if (updateCount == 0) { + QObject::disconnect(*it); + QQmlError warning; + warning.setUrl(qmlContext(QObject::sender())->baseUrl()); + auto signalName = QString::fromLatin1(QObject::sender()->metaObject()->method(QObject::senderSignalIndex()).name()); + signalName.chop(sizeof("changed")-1); + QString propName = signalName; + propName[0] = propName[0].toLower(); + warning.setDescription(QString::fromUtf8("Writing to \"%1\" broke the binding to the underlying model").arg(propName)); + qmlWarning(this, warning); + senderToConnection.erase(it); + } else { + --updateCount; + } +} + +void QQDMIncubationTask::initializeRequiredProperties(QQmlDelegateModelItem *modelItemToIncubate, QObject *object) +{ + auto incubatorPriv = QQmlIncubatorPrivate::get(this); + QQmlData *d = QQmlData::get(object); + auto contextData = d ? d->context : nullptr; + if (contextData) { + contextData->hasExtraObject = true; + contextData->extraObject = modelItemToIncubate; + } + if (incubatorPriv->hadRequiredProperties()) { + if (incubatorPriv->requiredProperties().empty()) + return; + RequiredProperties &requiredProperties = incubatorPriv->requiredProperties(); + + auto qmlMetaObject = modelItemToIncubate->metaObject(); + // if a required property was not in the model, it might still be a static property of the + // QQmlDelegateModelItem or one of its derived classes this is the case for index, row, + // column, model and more + // the most derived subclass of QQmlDelegateModelItem is QQmlDMAbstractModelData at depth 2, + // so 4 should be plenty + QVarLengthArray<const QMetaObject *, 4> mos; + // we first check the dynamic meta object for properties originating from the model + mos.push_back(qmlMetaObject); // contains abstractitemmodelproperties + auto delegateModelItemSubclassMO = qmlMetaObject->superClass(); + mos.push_back(delegateModelItemSubclassMO); + + while (strcmp(delegateModelItemSubclassMO->className(), modelItemToIncubate->staticMetaObject.className())) { + delegateModelItemSubclassMO = delegateModelItemSubclassMO->superClass(); + mos.push_back(delegateModelItemSubclassMO); + } + if (proxiedObject) + mos.push_back(proxiedObject->metaObject()); + + auto updater = new PropertyUpdater(object); + for (const QMetaObject *mo : mos) { + for (int i = mo->propertyOffset(); i < mo->propertyCount() + mo->propertyOffset(); ++i) { + auto prop = mo->property(i); + if (!prop.name()) + continue; + auto propName = QString::fromUtf8(prop.name()); + bool wasInRequired = false; + QQmlProperty componentProp = QQmlComponentPrivate::removePropertyFromRequired( + object, propName, requiredProperties, &wasInRequired); + // only write to property if it was actually requested by the component + if (wasInRequired && prop.hasNotifySignal()) { + QMetaMethod changeSignal = prop.notifySignal(); + static QMetaMethod updateSlot = PropertyUpdater::staticMetaObject.method(PropertyUpdater::staticMetaObject.indexOfSlot("doUpdate()")); + QMetaObject::Connection conn = QObject::connect(modelItemToIncubate, changeSignal, updater, updateSlot); + auto propIdx = object->metaObject()->indexOfProperty(propName.toUtf8()); + QMetaMethod writeToPropSignal = object->metaObject()->property(propIdx).notifySignal(); + updater->senderToConnection[writeToPropSignal.methodIndex()] = conn; + static QMetaMethod breakBinding = PropertyUpdater::staticMetaObject.method(PropertyUpdater::staticMetaObject.indexOfSlot("breakBinding()")); + componentProp.write(prop.read(modelItemToIncubate)); + // the connection needs to established after the write, + // else the signal gets triggered by it and breakBinding will remove the connection + QObject::connect(object, writeToPropSignal, updater, breakBinding); + } + else if (wasInRequired) // we still have to write, even if there is no change signal + componentProp.write(prop.read(modelItemToIncubate)); + } + } + } else { + modelItemToIncubate->contextData->contextObject = modelItemToIncubate; + if (proxiedObject) + proxyContext->contextObject = proxiedObject; + } +} + void QQDMIncubationTask::statusChanged(Status status) { if (vdm) { @@ -987,6 +1097,7 @@ void QQDMIncubationTask::setInitialState(QObject *o) void QQmlDelegateModelPrivate::setInitialState(QQDMIncubationTask *incubationTask, QObject *o) { QQmlDelegateModelItem *cacheItem = incubationTask->incubating; + incubationTask->initializeRequiredProperties(incubationTask->incubating, o); cacheItem->object = o; if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(cacheItem->object)) @@ -1053,7 +1164,6 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQ QQmlContextData *ctxt = new QQmlContextData; ctxt->setParent(QQmlContextData::get(creationContext ? creationContext : m_context.data())); - ctxt->contextObject = cacheItem; cacheItem->contextData = ctxt; if (m_adaptorModel.hasProxyObject()) { @@ -1062,7 +1172,8 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQ ctxt = new QQmlContextData; ctxt->setParent(cacheItem->contextData, /*stronglyReferencedByParent*/true); QObject *proxied = proxy->proxiedObject(); - ctxt->contextObject = proxied; + cacheItem->incubationTask->proxiedObject = proxied; + cacheItem->incubationTask->proxyContext = ctxt; // We don't own the proxied object. We need to clear it if it goes away. QObject::connect(proxied, &QObject::destroyed, cacheItem, &QQmlDelegateModelItem::childContextObjectDestroyed); @@ -2166,6 +2277,8 @@ QQmlDelegateModelItem *QQmlDelegateModelItem::dataForObject(QObject *object) { QQmlData *d = QQmlData::get(object); QQmlContextData *context = d ? d->context : nullptr; + if (context && context->hasExtraObject) + return qobject_cast<QQmlDelegateModelItem *>(context->extraObject); for (context = context ? context->parent : nullptr; context; context = context->parent) { if (QQmlDelegateModelItem *cacheItem = qobject_cast<QQmlDelegateModelItem *>( context->contextObject)) { |