diff options
Diffstat (limited to 'src/qmlmodels/qqmltableinstancemodel.cpp')
-rw-r--r-- | src/qmlmodels/qqmltableinstancemodel.cpp | 295 |
1 files changed, 139 insertions, 156 deletions
diff --git a/src/qmlmodels/qqmltableinstancemodel.cpp b/src/qmlmodels/qqmltableinstancemodel.cpp index a538ae4a1f..dcc15f90a5 100644 --- a/src/qmlmodels/qqmltableinstancemodel.cpp +++ b/src/qmlmodels/qqmltableinstancemodel.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 #include "qqmltableinstancemodel_p.h" #include "qqmlabstractdelegatecomponent_p.h" @@ -65,26 +29,21 @@ void QQmlTableInstanceModel::deleteModelItemLater(QQmlDelegateModelItem *modelIt delete modelItem->object; modelItem->object = nullptr; - - if (modelItem->contextData) { - modelItem->contextData->invalidate(); - Q_ASSERT(modelItem->contextData->refCount == 1); - modelItem->contextData = nullptr; - } - + modelItem->contextData.reset(); modelItem->deleteLater(); } QQmlTableInstanceModel::QQmlTableInstanceModel(QQmlContext *qmlContext, QObject *parent) : QQmlInstanceModel(*(new QObjectPrivate()), parent) , m_qmlContext(qmlContext) - , m_metaType(new QQmlDelegateModelItemMetaType(m_qmlContext->engine()->handle(), nullptr, QStringList())) + , m_metaType(new QQmlDelegateModelItemMetaType(m_qmlContext->engine()->handle(), nullptr, QStringList()), + QQmlRefPointer<QQmlDelegateModelItemMetaType>::Adopt) { } -void QQmlTableInstanceModel::useImportVersion(int minorVersion) +void QQmlTableInstanceModel::useImportVersion(QTypeRevision version) { - m_adaptorModel.useImportVersion(minorVersion); + m_adaptorModel.useImportVersion(version); } QQmlTableInstanceModel::~QQmlTableInstanceModel() @@ -102,8 +61,7 @@ QQmlTableInstanceModel::~QQmlTableInstanceModel() if (modelItem->object) { delete modelItem->object; modelItem->object = nullptr; - modelItem->contextData->invalidate(); - modelItem->contextData = nullptr; + modelItem->contextData.reset(); } } @@ -141,7 +99,7 @@ QQmlDelegateModelItem *QQmlTableInstanceModel::resolveModelItem(int index) return nullptr; // Check if the pool contains an item that can be reused - modelItem = takeFromReusableItemsPool(delegate); + modelItem = m_reusableItemsPool.takeItem(delegate, index); if (modelItem) { reuseItem(modelItem, index); m_modelItems.insert(index, modelItem); @@ -149,7 +107,7 @@ QQmlDelegateModelItem *QQmlTableInstanceModel::resolveModelItem(int index) } // Create a new item from scratch - modelItem = m_adaptorModel.createItem(m_metaType, index); + modelItem = m_adaptorModel.createItem(m_metaType.data(), index); if (modelItem) { modelItem->delegate = delegate; m_modelItems.insert(index, modelItem); @@ -207,6 +165,9 @@ QQmlInstanceModel::ReleaseFlags QQmlTableInstanceModel::release(QObject *object, 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->index)); + Q_ASSERT(m_modelItems[modelItem->index]->object == object); if (!modelItem->releaseObject()) return QQmlDelegateModel::Referenced; @@ -225,16 +186,46 @@ QQmlInstanceModel::ReleaseFlags QQmlTableInstanceModel::release(QObject *object, m_modelItems.remove(modelItem->index); if (reusable == Reusable) { - insertIntoReusableItemsPool(modelItem); - return QQmlInstanceModel::Referenced; + m_reusableItemsPool.insertItem(modelItem); + emit itemPooled(modelItem->index, modelItem->object); + return QQmlInstanceModel::Pooled; } // The item is not reused or referenced by anyone, so just delete it - modelItem->destroyObject(); - emit destroyingItem(object); + destroyModelItem(modelItem, Deferred); + return QQmlInstanceModel::Destroyed; +} +void QQmlTableInstanceModel::destroyModelItem(QQmlDelegateModelItem *modelItem, DestructionMode mode) +{ + emit destroyingItem(modelItem->object); + if (mode == Deferred) + modelItem->destroyObject(); + else + delete modelItem->object; + delete modelItem; +} + +void QQmlTableInstanceModel::dispose(QObject *object) +{ + Q_ASSERT(object); + auto modelItem = qvariant_cast<QQmlDelegateModelItem *>(object->property(kModelItemTag)); + Q_ASSERT(modelItem); + + modelItem->releaseObject(); + + // The item is not referenced by anyone + Q_ASSERT(!modelItem->isObjectReferenced()); + Q_ASSERT(!modelItem->isReferenced()); + // Ensure that the object was incubated by this QQmlTableInstanceModel + Q_ASSERT(m_modelItems.contains(modelItem->index)); + Q_ASSERT(m_modelItems[modelItem->index]->object == object); + + m_modelItems.remove(modelItem->index); + + emit destroyingItem(object); + delete object; delete modelItem; - return QQmlInstanceModel::Destroyed; } void QQmlTableInstanceModel::cancel(int index) @@ -257,94 +248,24 @@ void QQmlTableInstanceModel::cancel(int index) delete modelItem; } -void QQmlTableInstanceModel::insertIntoReusableItemsPool(QQmlDelegateModelItem *modelItem) -{ - // Currently, the only way for a view to reuse items is to call QQmlTableInstanceModel::release() - // with the second argument explicitly set to QQmlTableInstanceModel::Reusable. If the released - // item is no longer referenced, it will be added to the pool. Reusing of items can be specified - // per item, in case certain items cannot be recycled. - // A QQmlDelegateModelItem knows which delegate its object was created from. So when we are - // about to create a new item, we first check if the pool contains an item based on the same - // delegate from before. If so, we take it out of the pool (instead of creating a new item), and - // update all its context-, and attached properties. - // When a view is recycling items, it should call QQmlTableInstanceModel::drainReusableItemsPool() - // regularly. As there is currently no logic to 'hibernate' items in the pool, they are only - // meant to rest there for a short while, ideally only from the time e.g a row is unloaded - // on one side of the view, and until a new row is loaded on the opposite side. In-between - // this time, the application will see the item as fully functional and 'alive' (just not - // visible on screen). Since this time is supposed to be short, we don't take any action to - // notify the application about it, since we don't want to trigger any bindings that can - // disturb performance. - // A recommended time for calling drainReusableItemsPool() is each time a view has finished - // loading e.g a new row or column. If there are more items in the pool after that, it means - // that the view most likely doesn't need them anytime soon. Those items should be destroyed to - // not consume resources. - // Depending on if a view is a list or a table, it can sometimes be performant to keep - // items in the pool for a bit longer than one "row out/row in" cycle. E.g for a table, if the - // number of visible rows in a view is much larger than the number of visible columns. - // In that case, if you flick out a row, and then flick in a column, you would throw away a lot - // of items in the pool if completely draining it. The reason is that unloading a row places more - // items in the pool than what ends up being recycled when loading a new column. And then, when you - // next flick in a new row, you would need to load all those drained items again from scratch. For - // that reason, you can specify a maxPoolTime to the drainReusableItemsPool() that allows you to keep - // items in the pool for a bit longer, effectively keeping more items in circulation. - // A recommended maxPoolTime would be equal to the number of dimenstions in the view, which - // means 1 for a list view and 2 for a table view. If you specify 0, all items will be drained. - Q_ASSERT(!modelItem->incubationTask); - Q_ASSERT(!modelItem->isObjectReferenced()); - Q_ASSERT(!modelItem->isReferenced()); - Q_ASSERT(modelItem->object); - - modelItem->poolTime = 0; - m_reusableItemsPool.append(modelItem); - emit itemPooled(modelItem->index, modelItem->object); -} - -QQmlDelegateModelItem *QQmlTableInstanceModel::takeFromReusableItemsPool(const QQmlComponent *delegate) -{ - // Find the oldest item in the pool that was made from the same delegate as - // the given argument, remove it from the pool, and return it. - if (m_reusableItemsPool.isEmpty()) - return nullptr; - - for (auto it = m_reusableItemsPool.begin(); it != m_reusableItemsPool.end(); ++it) { - if ((*it)->delegate != delegate) - continue; - auto modelItem = *it; - m_reusableItemsPool.erase(it); - return modelItem; - } - - return nullptr; -} - void QQmlTableInstanceModel::drainReusableItemsPool(int maxPoolTime) { - // Rather than releasing all pooled items upon a call to this function, each - // item has a poolTime. The poolTime specifies for how many loading cycles an item - // has been resting in the pool. And for each invocation of this function, poolTime - // will increase. If poolTime is equal to, or exceeds, maxPoolTime, it will be removed - // from the pool and released. This way, the view can tweak a bit for how long - // items should stay in "circulation", even if they are not recycled right away. - for (auto it = m_reusableItemsPool.begin(); it != m_reusableItemsPool.end();) { - auto modelItem = *it; - modelItem->poolTime++; - if (modelItem->poolTime <= maxPoolTime) { - ++it; - } else { - it = m_reusableItemsPool.erase(it); - release(modelItem->object, NotReusable); - } - } + m_reusableItemsPool.drain(maxPoolTime, [this](QQmlDelegateModelItem *modelItem) { + destroyModelItem(modelItem, Immediate); + }); } 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); + item->setModelIndex(newModelIndex, newRow, newColumn, alwaysEmit); // Notify the application that all 'dynamic'/role-based context data has // changed as well (their getter function will use the updated index). @@ -372,18 +293,32 @@ void QQmlTableInstanceModel::incubateModelItem(QQmlDelegateModelItem *modelItem, } else { modelItem->incubationTask = new QQmlTableInstanceModelIncubationTask(this, modelItem, incubationMode); - QQmlContextData *ctxt = new QQmlContextData; QQmlContext *creationContext = modelItem->delegate->creationContext(); - ctxt->setParent(QQmlContextData::get(creationContext ? creationContext : m_qmlContext.data())); - ctxt->contextObject = modelItem; - modelItem->contextData = ctxt; - - QQmlComponentPrivate::get(modelItem->delegate)->incubateObject( - modelItem->incubationTask, - modelItem->delegate, - m_qmlContext->engine(), - ctxt, - QQmlContextData::get(m_qmlContext)); + const QQmlRefPointer<QQmlContextData> componentContext + = QQmlContextData::get(creationContext ? creationContext : m_qmlContext.data()); + + QQmlComponentPrivate *cp = QQmlComponentPrivate::get(modelItem->delegate); + if (cp->isBound()) { + modelItem->contextData = componentContext; + cp->incubateObject( + modelItem->incubationTask, + modelItem->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->contextData = ctxt; + + cp->incubateObject( + modelItem->incubationTask, + modelItem->delegate, + m_qmlContext->engine(), + ctxt, + QQmlContextData::get(m_qmlContext)); + } } // Remove the temporary guard @@ -446,13 +381,41 @@ QQmlIncubator::Status QQmlTableInstanceModel::incubationStatus(int index) { 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; +} + 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.count() == 1) + if (m_finishedIncubationTasks.size() == 1) QTimer::singleShot(1, this, &QQmlTableInstanceModel::deleteAllFinishedIncubationTasks); } @@ -473,11 +436,15 @@ void QQmlTableInstanceModel::setModel(const QVariant &model) // 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()) + if (auto const aim = abstractItemModel()) { disconnect(aim, &QAbstractItemModel::dataChanged, this, &QQmlTableInstanceModel::dataChangedCallback); - m_adaptorModel.setModel(model, this, m_qmlContext->engine()); - if (auto const aim = abstractItemModel()) + 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::dataChangedCallback(const QModelIndex &begin, const QModelIndex &end, const QVector<int> &roles) @@ -495,6 +462,21 @@ void QQmlTableInstanceModel::dataChangedCallback(const QModelIndex &begin, const } } +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()) + setModel(model()); + }, Qt::SingleShotConnection); +} + QQmlComponent *QQmlTableInstanceModel::delegate() const { return m_delegate; @@ -526,10 +508,11 @@ const QAbstractItemModel *QQmlTableInstanceModel::abstractItemModel() const void QQmlTableInstanceModelIncubationTask::setInitialState(QObject *object) { initializeRequiredProperties(modelItemToIncubate, object); - if (QQmlIncubatorPrivate::get(this)->requiredProperties().empty()) { - modelItemToIncubate->object = object; - emit tableInstanceModel->initItem(modelItemToIncubate->index, object); - } else { + modelItemToIncubate->object = object; + emit tableInstanceModel->initItem(modelItemToIncubate->index, object); + + if (!QQmlIncubatorPrivate::get(this)->requiredProperties()->empty()) { + modelItemToIncubate->object = nullptr; object->deleteLater(); } } @@ -546,7 +529,7 @@ void QQmlTableInstanceModelIncubationTask::statusChanged(QQmlIncubator::Status s tableInstanceModel->incubatorStatusChanged(this, status); } -#include "moc_qqmltableinstancemodel_p.cpp" - QT_END_NAMESPACE +#include "moc_qqmltableinstancemodel_p.cpp" + |