Revision control

++ // Copyright (C) 2018 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant
++
++ #include "qqmltableinstancemodel_p.h"
++ #include "qqmlabstractdelegatecomponent_p.h"
++
++ #include <QtCore/QTimer>
++
++ #include <QtQml/private/qqmlincubator_p.h>
++ #include <QtQmlModels/private/qqmlchangeset_p.h>
++ #include <QtQml/private/qqmlcomponent_p.h>
++
++ #include <QtCore/qtyperevision.h>
++
++ QT_BEGIN_NAMESPACE
++
++ const char* kModelItemTag = "_tableinstancemodel_modelItem";
++
++ bool QQmlTableInstanceModel::isDoneIncubating(QQmlDelegateModelItem *modelItem)
++ {
++ QQDMIncubationTask *incubationTask = modelItem->incubationTask();
++ if (!incubationTask)
++ return true;
++
++ switch (incubationTask->status()) {
++ case QQmlIncubator::Ready:
++ case QQmlIncubator::Error:
++ return true;
++ default:
++ break;
++ }
++
++ return false;
++ }
++
++ void QQmlTableInstanceModel::deleteModelItemLater(QQmlDelegateModelItem *modelItem)
++ {
++ Q_ASSERT(modelItem);
++
++ modelItem->destroyObject();
++ modelItem->deleteLater();
++ }
++
++ QQmlTableInstanceModel::QQmlTableInstanceModel(QQmlContext *qmlContext, QObject *parent)
++ : QQmlInstanceModel(*(new QObjectPrivate()), parent)
++ , m_qmlContext(qmlContext)
++ , m_metaType(QQml::makeRefPointer<QQmlDelegateModelItemMetaType>(
++ m_qmlContext->engine()->handle(), this))
++ {
++ }
++
++ void QQmlTableInstanceModel::useImportVersion(QTypeRevision version)
++ {
++ m_adaptorModel.useImportVersion(version);
++ }
++
++ QQmlTableInstanceModel::~QQmlTableInstanceModel()
++ {
++ for (const auto modelItem : m_modelItems) {
++ // No item in m_modelItems should be referenced at this point. The view
++ // should release all its items before it deletes this model. Only model items
++ // that are still being incubated should be left for us to delete.
++ // We can't rely on that, though. So we only check the strong ref.
++ Q_ASSERT(modelItem->objectStrongRef() == 0);
++ Q_ASSERT(modelItem->incubationTask());
++ // Check that we are not being deleted while we're
++ // in the process of e.g emitting a created signal.
++ Q_ASSERT(modelItem->scriptRef() == 0);
++
++ modelItem->destroyObject();
++ }
++
++ deleteAllFinishedIncubationTasks();
++ qDeleteAll(m_modelItems);
++ drainReusableItemsPool(0);
++
++ // We don't expect there to be any released items at this point.
++ // The view must explicitly call commitReleasedItems() after each
++ // rebuild of the viewport, and long before the model is destructed.
++ Q_ASSERT(m_releasedItems.isEmpty());
++ }
++
++ QQmlComponent *QQmlTableInstanceModel::resolveDelegate(int index)
++ {
++ if (m_delegateChooser) {
++ const int row = m_adaptorModel.rowAt(index);
++ const int column = m_adaptorModel.columnAt(index);
++ QQmlComponent *delegate = nullptr;
++ QQmlAbstractDelegateComponent *chooser = m_delegateChooser;
++ do {
++ delegate = chooser->delegate(&m_adaptorModel, row, column);
++ chooser = qobject_cast<QQmlAbstractDelegateComponent *>(delegate);
++ } while (chooser);
++ return delegate;
++ }
++
++ return m_delegate;
++ }
++
++ QQmlDelegateModelItem *QQmlTableInstanceModel::resolveModelItem(int flatIndex, const QModelIndex &modelIndex)
++ {
++ Q_ASSERT(flatIndex >= 0 && flatIndex < m_adaptorModel.count());
++
++ // Check if an item for the given index is already loaded and ready
++ if (QQmlDelegateModelItem *modelItem = m_modelItems.value(flatIndex, nullptr))
++ return modelItem;
++
++ QQmlComponent *delegate = resolveDelegate(flatIndex);
++ if (!delegate)
++ return nullptr;
++
++ // Check if the temporary release cache contains the requested item
++ if (modelIndex.isValid()) {
++ const auto it = std::find_if(m_releasedItems.cbegin(), m_releasedItems.cend(),
++ [&modelIndex](const QQmlDelegateModelItem *item) {
++ return item->persistentModelIndex() == modelIndex;
++ });
++
++ if (it != m_releasedItems.cend()) {
++ QQmlDelegateModelItem *modelItem = *it;
++ // Note: a rebuild can change which delegate a DelegateChooser assigns to a cell, so the
++ // released item may no longer use the right delegate, even if it has the correct index.
++ if (modelItem->delegate() == delegate) {
++ m_releasedItems.erase(it);
++ restoreFromReleasedItemsCache(modelItem, flatIndex);
++ return modelItem;
++ }
++ }
++ }
++
++ // Check if the pool contains an item with the same delegate that can be reused
++ if (QQmlDelegateModelItem *modelItem = m_reusableItemsPool.takeItem(delegate, flatIndex)) {
++ m_modelItems.insert(flatIndex, modelItem);
++ reuseItem(modelItem, flatIndex);
++ return modelItem;
++ }
++
++ // Create a new item from scratch
++ if (QQmlDelegateModelItem *modelItem = m_adaptorModel.createItem(m_metaType.data(), flatIndex)) {
++ modelItem->setDelegate(delegate);
++ m_modelItems.insert(flatIndex, modelItem);
++ return modelItem;
++ }
++
++ qWarning() << Q_FUNC_INFO << "failed creating a model item for index: " << flatIndex << modelIndex;
++ return nullptr;
++ }
++
++ void QQmlTableInstanceModel::commitReleasedItems()
++ {
++ // Transfer all released items from the temporary cache to the reuse pool. From
++ // now on, they can only be reused based on delegate type and not model index.
++ for (auto *item : std::as_const(m_releasedItems)) {
++ item->setPersistentModelIndex(QModelIndex());
++ if (m_reusableItemsPool.insertItem(item))
++ emit itemPooled(item->modelIndex(), item->object());
++ else
++ destroyModelItem(item, Deferred);
++ }
++ m_releasedItems.clear();
++ }
++
++ QObject *QQmlTableInstanceModel::object(int index, QQmlIncubator::IncubationMode incubationMode)
++ {
++ Q_ASSERT(m_delegate);
++
++ QModelIndex modelIndex;
++ if (const QAbstractItemModel *aim = abstractItemModel()) {
++ // A valid QModelIndex is needed for resolveModelItem() to match
++ // items in the release cache rather than just delegate type alone.
++ const int row = m_adaptorModel.rowAt(index);
++ const int column = m_adaptorModel.columnAt(index);
++ modelIndex = aim->index(row, column);
++ }
++
++ if (QQmlDelegateModelItem *modelItem = resolveModelItem(index, modelIndex)) {
++ // Return the incubated object, or start an async incubation task and return nullptr for now
++ return incubateModelItemIfNeeded(modelItem, incubationMode);
++ }
++
++ return nullptr;
++ }
++
++ QObject *QQmlTableInstanceModel::incubateModelItemIfNeeded(QQmlDelegateModelItem *modelItem, QQmlIncubator::IncubationMode incubationMode)
++ {
++ if (modelItem->object()) {
++ // The model item has already been incubated. So
++ // just bump the ref-count and return it.
++ return modelItem->referenceObjectWeak();
++ }
++
++ // The object is not ready, and needs to be incubated
++ incubateModelItem(modelItem, incubationMode);
++ if (!isDoneIncubating(modelItem))
++ return nullptr;
++
++ // Incubation is done, so the task should be removed
++ Q_ASSERT(!modelItem->incubationTask());
++
++ // Incubation was completed sync and successful
++ if (modelItem->object())
++ return modelItem->referenceObjectWeak();
++
++ // The object was incubated synchronously (otherwise we would return above). But since
++ // we have no object, the incubation must have failed. And when we have no object, there
++ // should be no object references either. And there should also not be any internal script
++ // refs at this point. So we delete the model item.
++ Q_ASSERT(!modelItem->isObjectReferenced());
++ Q_ASSERT(!modelItem->isScriptReferenced());
++ m_modelItems.remove(modelItem->modelIndex());
++ delete modelItem;
++ return nullptr;
++ }
++
++ QQmlInstanceModel::ReleaseFlags QQmlTableInstanceModel::release(QObject *object, ReusableFlag reusable)
++ {
++ Q_ASSERT(object);
++ auto modelItem = qvariant_cast<QQmlDelegateModelItem *>(object->property(kModelItemTag));
++ Q_ASSERT(modelItem);
++ // Ensure that the object was incubated by this QQmlTableInstanceModel
++ Q_ASSERT(m_modelItems.contains(modelItem->modelIndex()));
++ Q_ASSERT(m_modelItems[modelItem->modelIndex()]->object() == object);
++
++ if (!modelItem->releaseObjectWeak())
++ return QQmlDelegateModel::Referenced;
++
++ if (modelItem->isScriptReferenced()) {
++ // We still have an internal reference to this object, which means that we are told to release an
++ // object while the createdItem signal for it is still on the stack. This can happen when objects
++ // are e.g delivered async, and the user flicks back and forth quicker than the loading can catch
++ // up with. The view might then find that the object is no longer visible and should be released.
++ // We detect this case in incubatorStatusChanged(), and delete it there instead. But from the callers
++ // point of view, it should consider it destroyed.
++ return QQmlDelegateModel::Destroyed;
++ }
++
++ // The item is not referenced by anyone
++ m_modelItems.remove(modelItem->modelIndex());
++
++ if (reusable == Reusable) {
++ // Stage the item in a temporary cache rather than moving it directly to the reuse pool.
++ // The view calls commitReleasedItems() once the rebuild is done, which transfers the cache
++ // to the pool. Keeping a temporary cache lets resolveModelItem() match a staged item by
++ // persistent model index first, during viewport builds, and hand back the exact same
++ // delegate item that was showing that cell. That way we can avoid the otherwise full
++ // reuse of an item, which includes emitting pooled signals etc. This also preserves
++ // ongoing visual state such as animations.
++ m_releasedItems.append(modelItem);
++ return QQmlInstanceModel::Pooled;
++ }
++
++ // The item is not reused or referenced by anyone, so just delete it
++ destroyModelItem(modelItem, Deferred);
++ return QQmlInstanceModel::Destroyed;
++ }
++
++ void QQmlTableInstanceModel::destroyModelItem(QQmlDelegateModelItem *modelItem, DestructionMode mode)
++ {
++ emit destroyingItem(modelItem->object());
++ if (mode == Deferred)
++ modelItem->destroyObjectLater();
++ else
++ modelItem->destroyObject();
++ delete modelItem;
++ }
++
++ void QQmlTableInstanceModel::dispose(QObject *object)
++ {
++ Q_ASSERT(object);
++ auto modelItem = qvariant_cast<QQmlDelegateModelItem *>(object->property(kModelItemTag));
++ Q_ASSERT(modelItem);
++
++ modelItem->releaseObjectWeak();
++
++ // The item is not referenced by anyone
++ Q_ASSERT(!modelItem->isObjectReferenced());
++ Q_ASSERT(!modelItem->isScriptReferenced());
++ // Ensure that the object was incubated by this QQmlTableInstanceModel
++ Q_ASSERT(m_modelItems.contains(modelItem->modelIndex()));
++ Q_ASSERT(m_modelItems[modelItem->modelIndex()]->object() == object);
++
++ m_modelItems.remove(modelItem->modelIndex());
++
++ emit destroyingItem(object);
++ modelItem->destroyObject();
++
++ delete modelItem;
++ }
++
++ void QQmlTableInstanceModel::cancel(int index)
++ {
++ auto modelItem = m_modelItems.value(index);
++ Q_ASSERT(modelItem);
++
++ // Since the view expects the item to be incubating, there should be
++ // an incubation task. And since the incubation is not done, no-one
++ // should yet have received, and therfore hold a reference to, the object.
++ Q_ASSERT(modelItem->incubationTask());
++ Q_ASSERT(!modelItem->isObjectReferenced());
++
++ m_modelItems.remove(index);
++
++ modelItem->destroyObject();
++
++ // modelItem->incubationTask will be deleted from the modelItems destructor
++ delete modelItem;
++ }
++
++ void QQmlTableInstanceModel::drainReusableItemsPool(int maxPoolTime)
++ {
++ m_reusableItemsPool.drain(maxPoolTime, [this](QQmlDelegateModelItem *modelItem) {
++ destroyModelItem(modelItem, Immediate);
++ });
++ }
++
++ void QQmlTableInstanceModel::restoreFromReleasedItemsCache(QQmlDelegateModelItem *item, int newFlatIndex)
++ {
++ // Row/column shifts elsewhere in the table may have changed this item's
++ // position, so update its index before handing it back from the released
++ // items cache. Also notify the view so it can refresh any index-dependent
++ // required properties (such as 'expanded' and 'hasChildren').
++ m_modelItems.insert(newFlatIndex, item);
++ const bool alwaysEmit = true;
++ const bool init = false;
++ const int row = m_adaptorModel.rowAt(newFlatIndex);
++ const int column = m_adaptorModel.columnAt(newFlatIndex);
++ item->setModelIndex(newFlatIndex, row, column, alwaysEmit);
++ emit updateItemProperties(newFlatIndex, item->object(), init);
++ }
++
++ void QQmlTableInstanceModel::reuseItem(QQmlDelegateModelItem *item, int newModelIndex)
++ {
++ // Update the context properties index, row and column on
++ // the delegate item, and inform the application about it.
++ // Note that we set alwaysEmit to true, to force all bindings
++ // to be reevaluated, even if the index didn't change (since
++ // the model can have changed size since last usage).
++ const bool alwaysEmit = true;
++ const int newRow = m_adaptorModel.rowAt(newModelIndex);
++ const int newColumn = m_adaptorModel.columnAt(newModelIndex);
++ item->setModelIndex(newModelIndex, newRow, newColumn, alwaysEmit);
++ if (auto *qaim = abstractItemModel())
++ item->setPersistentModelIndex(qaim->index(newRow, newColumn));
++
++ // Notify the application that all 'dynamic'/role-based context data has
++ // changed as well (their getter function will use the updated index).
++ auto const itemAsList = QList<QQmlDelegateModelItem *>() << item;
++ auto const updateAllRoles = QList<int>();
++ m_adaptorModel.notify(itemAsList, newModelIndex, 1, updateAllRoles);
++
++ // Inform the view that the item is recycled. This will typically result
++ // in the view updating its own attached delegate item properties.
++ const bool init = false;
++ emit updateItemProperties(newModelIndex, item->object(), init);
++ emit itemReused(newModelIndex, item->object());
++ }
++
++ void QQmlTableInstanceModel::incubateModelItem(QQmlDelegateModelItem *modelItem, QQmlIncubator::IncubationMode incubationMode)
++ {
++ // Guard the model item temporarily so that it's not deleted from
++ // incubatorStatusChanged(), in case the incubation is done synchronously.
++ QQmlDelegateModelItem::ScriptReference scriptRef(modelItem);
++
++ if (QQDMIncubationTask *incubationTask = modelItem->incubationTask()) {
++ // We're already incubating the model item from a previous request. If the previous call requested
++ // the item async, but the current request needs it sync, we need to force-complete the incubation.
++ const bool sync = (incubationMode == QQmlIncubator::Synchronous || incubationMode == QQmlIncubator::AsynchronousIfNested);
++ if (sync && incubationTask->incubationMode() == QQmlIncubator::Asynchronous)
++ incubationTask->forceCompletion();
++ } else if (m_qmlContext && m_qmlContext->isValid()) {
++ modelItem->setIncubationTask(
++ new QQmlTableInstanceModelIncubationTask(this, modelItem, incubationMode));
++ // TODO: In order to retain compatibility, we cannot allow the incubation task to clear the
++ // context object in the presence of required properties. This results in the context
++ // properties still being available in the delegate even though they shouldn't.
++ // modelItem->incubationTask->incubating = modelItem;
++
++ QQmlComponent *delegate = modelItem->delegate();
++ QQmlContext *creationContext = delegate->creationContext();
++ const QQmlRefPointer<QQmlContextData> componentContext
++ = QQmlContextData::get(creationContext ? creationContext : m_qmlContext.data());
++
++ QQmlComponentPrivate *cp = QQmlComponentPrivate::get(delegate);
++ if (cp->isBound()) {
++ modelItem->setContextData(componentContext);
++
++ // Ignore return value of initProxy. We want to know the proxy when assigning required
++ // properties, but we don't want it to pollute our context. The context is bound.
++ if (m_adaptorModel.hasProxyObject())
++ modelItem->initProxy();
++
++ cp->incubateObject(
++ modelItem->incubationTask(), delegate, m_qmlContext->engine(), componentContext,
++ QQmlContextData::get(m_qmlContext));
++ } else {
++ QQmlRefPointer<QQmlContextData> ctxt = QQmlContextData::createRefCounted(
++ QQmlContextData::get(creationContext ? creationContext : m_qmlContext.data()));
++ ctxt->setContextObject(modelItem);
++ modelItem->setContextData(ctxt);
++
++ // If the model is read-only we cannot just expose the object as context
++ // We actually need a separate model object to moderate access.
++ if (m_adaptorModel.hasProxyObject()) {
++ if (m_adaptorModel.delegateModelAccess == QQmlDelegateModel::ReadOnly)
++ modelItem->initProxy();
++ else
++ ctxt = modelItem->initProxy();
++ }
++
++ cp->incubateObject(
++ modelItem->incubationTask(), modelItem->delegate(), m_qmlContext->engine(),
++ ctxt, QQmlContextData::get(m_qmlContext));
++ }
++ }
++ }
++
++ void QQmlTableInstanceModel::incubatorStatusChanged(QQmlTableInstanceModelIncubationTask *incubationTask, QQmlIncubator::Status status)
++ {
++ QQmlDelegateModelItem *modelItem = incubationTask->modelItemToIncubate;
++ Q_ASSERT(modelItem->incubationTask());
++
++ modelItem->clearIncubationTask();
++ incubationTask->modelItemToIncubate = nullptr;
++
++ if (status == QQmlIncubator::Ready) {
++ QObject *object = modelItem->object();
++ Q_ASSERT(object);
++
++ if (auto *qaim = abstractItemModel()) {
++ // Optimization: store a persistent model index so that resolveModelItem(QModelIndex)
++ // can match this item by index after a rebuild, even if its flat index has shifted
++ // due to row or column insertions/removals.
++ modelItem->setPersistentModelIndex(qaim->index(modelItem->modelRow(), modelItem->modelColumn()));
++ }
++
++ // Tag the incubated object with the model item for easy retrieval upon release etc.
++ object->setProperty(kModelItemTag, QVariant::fromValue(modelItem));
++
++ // Emit that the item has been created. What normally happens next is that the view
++ // upon receiving the signal asks for the model item once more. And since the item is
++ // now in the map, it will be returned directly.
++ QQmlDelegateModelItem::ScriptReference scriptRef(modelItem);
++ emit createdItem(modelItem->modelIndex(), object);
++ } else if (status == QQmlIncubator::Error) {
++ qWarning() << "Error incubating delegate:" << incubationTask->errors();
++ }
++
++ if (!modelItem->isScriptReferenced() && !modelItem->isObjectReferenced()) {
++ // We have no internal reference to the model item, and the view has no
++ // reference to the incubated object. So just delete the model item.
++ // Note that being here means that the object was incubated _async_
++ // (otherwise modelItem->isReferenced() would be true).
++ m_modelItems.remove(modelItem->modelIndex());
++
++ if (QObject *object = modelItem->object()) {
++ QQmlDelegateModelItem::ScriptReference scriptRef(modelItem);
++ emit destroyingItem(object);
++ }
++
++ Q_ASSERT(!modelItem->isScriptReferenced());
++ deleteModelItemLater(modelItem);
++ }
++
++ deleteIncubationTaskLater(incubationTask);
++ }
++
++ QQmlIncubator::Status QQmlTableInstanceModel::incubationStatus(int index) {
++ const auto modelItem = m_modelItems.value(index, nullptr);
++ if (!modelItem)
++ return QQmlIncubator::Null;
++
++ if (QQDMIncubationTask *incubationTask = modelItem->incubationTask())
++ return incubationTask->status();
++
++ // Since we clear the incubation task when we're done
++ // incubating, it means that the status is Ready.
++ return QQmlIncubator::Ready;
++ }
++
++ bool QQmlTableInstanceModel::setRequiredProperty(int index, const QString &name, const QVariant &value)
++ {
++ // This function can be called from the view upon
++ // receiving the initItem signal. It can be used to
++ // give all required delegate properties used by the
++ // view an initial value.
++ const auto modelItem = m_modelItems.value(index, nullptr);
++ if (!modelItem)
++ return false;
++ if (!modelItem->object())
++ return false;
++ if (!modelItem->incubationTask())
++ return false;
++
++ bool wasInRequired = false;
++ const auto task = QQmlIncubatorPrivate::get(modelItem->incubationTask());
++ RequiredProperties *props = task->requiredProperties();
++ if (props->empty())
++ return false;
++
++ QQmlProperty componentProp = QQmlComponentPrivate::removePropertyFromRequired(
++ modelItem->object(), name, props, QQmlEnginePrivate::get(task->enginePriv),
++ &wasInRequired);
++ if (wasInRequired)
++ componentProp.write(value);
++ return wasInRequired;
++ }
++
++ QQmlDelegateModelItem *QQmlTableInstanceModel::getModelItem(int index)
++ {
++ return m_modelItems.value(index, nullptr);
++ }
++
++ void QQmlTableInstanceModel::deleteIncubationTaskLater(QQmlIncubator *incubationTask)
++ {
++ // We often need to post-delete incubation tasks, since we cannot
++ // delete them while we're in the middle of an incubation change callback.
++ Q_ASSERT(!m_finishedIncubationTasks.contains(incubationTask));
++ m_finishedIncubationTasks.append(incubationTask);
++ if (m_finishedIncubationTasks.size() == 1)
++ QTimer::singleShot(1, this, &QQmlTableInstanceModel::deleteAllFinishedIncubationTasks);
++ }
++
++ void QQmlTableInstanceModel::deleteAllFinishedIncubationTasks()
++ {
++ qDeleteAll(m_finishedIncubationTasks);
++ m_finishedIncubationTasks.clear();
++ }
++
++ QVariant QQmlTableInstanceModel::model() const
++ {
++ return m_adaptorModel.model();
++ }
++
++ void QQmlTableInstanceModel::forceSetModel(const QVariant &model)
++ {
++ // Pooled items are still accessible/alive for the application, and
++ // needs to stay in sync with the model. So we need to drain the pool
++ // completely when the model changes.
++ drainReusableItemsPool(0);
++ if (auto const aim = abstractItemModel()) {
++ disconnect(aim, &QAbstractItemModel::dataChanged, this, &QQmlTableInstanceModel::dataChangedCallback);
++ disconnect(aim, &QAbstractItemModel::modelAboutToBeReset, this, &QQmlTableInstanceModel::modelAboutToBeResetCallback);
++ }
++ m_adaptorModel.setModel(model);
++ if (auto const aim = abstractItemModel()) {
++ connect(aim, &QAbstractItemModel::dataChanged, this, &QQmlTableInstanceModel::dataChangedCallback);
++ connect(aim, &QAbstractItemModel::modelAboutToBeReset, this, &QQmlTableInstanceModel::modelAboutToBeResetCallback);
++ }
++ }
++
++ void QQmlTableInstanceModel::setModel(const QVariant &model)
++ {
++ if (m_adaptorModel.model() == model)
++ return;
++
++ forceSetModel(model);
++
++ emit modelChanged();
++ }
++
++ void QQmlTableInstanceModel::dataChangedCallback(const QModelIndex &begin, const QModelIndex &end, const QList<int> &roles)
++ {
++ // This function is called when model data has changed. We iterate over the affected cells,
++ // find the delegate item (if any) currently showing each cell via its persistent model index,
++ // and notify the adaptor model so it can update role-based QML bindings in that item.
++ // NOTE: A model may sometimes emit layoutChanged first (e.g. after sorting), and then
++ // emit dataChanged for some of the affected items (e.g. because a data change triggered
++ // the sort in the first place). This causes a problem here, because TableView hasn't
++ // processed the layout change yet (it will do so on the next updatePolish()), so the
++ // items in this QQmlTableInstanceModel are index-wise out-of-sync with QAbstractItemModel.
++ // When we detect this, we temporarily correct an item's flat index before notifying, so
++ // that the right QModelIndex is read from QAbstractItemModel. Afterwards we restore the
++ // old flat index to stay in sync with what TableView expects. When TableView later
++ // rebuilds in response to the layout change, all flat indices will be corrected.
++ for (int row = begin.row(); row <= end.row(); ++row) {
++ for (int column = begin.column(); column <= end.column(); ++column) {
++ const QModelIndex modelIndex = abstractItemModel()->index(row, column);
++ const auto it = std::find_if(m_modelItems.cbegin(), m_modelItems.cend(),
++ [&modelIndex](const QQmlDelegateModelItem *item) {
++ return item->persistentModelIndex() == modelIndex;
++ });
++ if (it == m_modelItems.cend())
++ continue;
++
++ QQmlDelegateModelItem *modelItem = *it;
++ const int oldFlatIndex = modelItem->modelIndex();
++ const int newFlatIndex = row + column * rows();
++ if (oldFlatIndex != newFlatIndex)
++ modelItem->setModelIndex(newFlatIndex, row, column, false);
++ m_adaptorModel.notify({modelItem}, newFlatIndex, 1, roles);
++ if (oldFlatIndex != newFlatIndex)
++ modelItem->setModelIndex(oldFlatIndex, row, column, false);
++ }
++ }
++ }
++
++ void QQmlTableInstanceModel::modelAboutToBeResetCallback()
++ {
++ // When the model is reset, we can no longer rely on any of the data it has
++ // provided us so far. Normally it's enough for the view to recreate all the
++ // delegate items in that case, except if the model roles has changed as well
++ // (since those are cached by QQmlAdaptorModel / Accessors). For the latter case, we
++ // simply set the model once more in the delegate model to rebuild everything.
++ auto const aim = abstractItemModel();
++ auto oldRoleNames = aim->roleNames();
++ QObject::connect(aim, &QAbstractItemModel::modelReset, this, [this, aim, oldRoleNames](){
++ if (oldRoleNames != aim->roleNames()) {
++ // We refresh the model, but without sending any signals. The actual model object
++ // stays the same after all.
++ forceSetModel(model());
++ }
++ }, Qt::SingleShotConnection);
++ }
++
++ QQmlComponent *QQmlTableInstanceModel::delegate() const
++ {
++ return m_delegate;
++ }
++
++ void QQmlTableInstanceModel::setDelegate(QQmlComponent *delegate)
++ {
++ if (m_delegate == delegate)
++ return;
++
++ m_delegateChooser = nullptr;
++ if (delegate) {
++ QQmlAbstractDelegateComponent *adc =
++ qobject_cast<QQmlAbstractDelegateComponent *>(delegate);
++ if (adc)
++ m_delegateChooser = adc;
++ }
++
++ m_delegate = delegate;
++ }
++
++ const QAbstractItemModel *QQmlTableInstanceModel::abstractItemModel() const
++ {
++ return m_adaptorModel.adaptsAim() ? m_adaptorModel.aim() : nullptr;
++ }
++
++ // --------------------------------------------------------
++
++ void QQmlTableInstanceModelIncubationTask::setInitialState(QObject *object)
++ {
++ initializeRequiredProperties(
++ modelItemToIncubate, object, tableInstanceModel->delegateModelAccess());
++ modelItemToIncubate->setObject(object);
++
++ const bool init = true;
++ const int flatIndex = modelItemToIncubate->modelIndex();
++ emit tableInstanceModel->initItem(flatIndex, object);
++ emit tableInstanceModel->updateItemProperties(flatIndex, object, init);
++
++ if (!QQmlIncubatorPrivate::get(this)->requiredProperties()->empty())
++ modelItemToIncubate->destroyObjectLater();
++ }
++
++ void QQmlTableInstanceModelIncubationTask::statusChanged(QQmlIncubator::Status status)
++ {
++ if (!QQmlTableInstanceModel::isDoneIncubating(modelItemToIncubate))
++ return;
++
++ // We require the view to cancel any ongoing load
++ // requests before the tableInstanceModel is destructed.
++ Q_ASSERT(tableInstanceModel);
++
++ tableInstanceModel->incubatorStatusChanged(this, status);
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qqmltableinstancemodel_p.cpp"
++