| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 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$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qqmldelegatemodel_p_p.h" |
| |
| #include <QtQml/qqmlinfo.h> |
| |
| #include <private/qqmlabstractdelegatecomponent_p.h> |
| #include <private/qquickpackage_p.h> |
| #include <private/qmetaobjectbuilder_p.h> |
| #include <private/qqmladaptormodel_p.h> |
| #include <private/qqmlchangeset_p.h> |
| #include <private/qqmlengine_p.h> |
| #include <private/qqmlcomponent_p.h> |
| |
| #include <private/qv4value_p.h> |
| #include <private/qv4functionobject_p.h> |
| #include <private/qv4objectiterator_p.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| Q_LOGGING_CATEGORY(lcItemViewDelegateRecycling, "qt.quick.itemview.delegaterecycling") |
| |
| class QQmlDelegateModelItem; |
| |
| namespace QV4 { |
| |
| namespace Heap { |
| |
| struct DelegateModelGroupFunction : FunctionObject { |
| void init(QV4::ExecutionContext *scope, uint flag, QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg)); |
| |
| QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg); |
| uint flag; |
| }; |
| |
| struct QQmlDelegateModelGroupChange : Object { |
| void init() { Object::init(); } |
| |
| QQmlChangeSet::ChangeData change; |
| }; |
| |
| struct QQmlDelegateModelGroupChangeArray : Object { |
| void init(const QVector<QQmlChangeSet::Change> &changes); |
| void destroy() { |
| delete changes; |
| Object::destroy(); |
| } |
| |
| QVector<QQmlChangeSet::Change> *changes; |
| }; |
| |
| |
| } |
| |
| struct DelegateModelGroupFunction : QV4::FunctionObject |
| { |
| V4_OBJECT2(DelegateModelGroupFunction, FunctionObject) |
| |
| static Heap::DelegateModelGroupFunction *create(QV4::ExecutionContext *scope, uint flag, QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg)) |
| { |
| return scope->engine()->memoryManager->allocate<DelegateModelGroupFunction>(scope, flag, code); |
| } |
| |
| static ReturnedValue virtualCall(const QV4::FunctionObject *that, const Value *thisObject, const Value *argv, int argc) |
| { |
| QV4::Scope scope(that->engine()); |
| QV4::Scoped<DelegateModelGroupFunction> f(scope, static_cast<const DelegateModelGroupFunction *>(that)); |
| QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject); |
| if (!o) |
| return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object")); |
| |
| QV4::ScopedValue v(scope, argc ? argv[0] : Value::undefinedValue()); |
| return f->d()->code(o->d()->item, f->d()->flag, v); |
| } |
| }; |
| |
| void Heap::DelegateModelGroupFunction::init(QV4::ExecutionContext *scope, uint flag, QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg)) |
| { |
| QV4::Heap::FunctionObject::init(scope, QStringLiteral("DelegateModelGroupFunction")); |
| this->flag = flag; |
| this->code = code; |
| } |
| |
| } |
| |
| DEFINE_OBJECT_VTABLE(QV4::DelegateModelGroupFunction); |
| |
| |
| |
| class QQmlDelegateModelEngineData : public QV4::ExecutionEngine::Deletable |
| { |
| public: |
| QQmlDelegateModelEngineData(QV4::ExecutionEngine *v4); |
| ~QQmlDelegateModelEngineData(); |
| |
| QV4::ReturnedValue array(QV4::ExecutionEngine *engine, |
| const QVector<QQmlChangeSet::Change> &changes); |
| |
| QV4::PersistentValue changeProto; |
| }; |
| |
| V4_DEFINE_EXTENSION(QQmlDelegateModelEngineData, engineData) |
| |
| |
| void QQmlDelegateModelPartsMetaObject::propertyCreated(int, QMetaPropertyBuilder &prop) |
| { |
| prop.setWritable(false); |
| } |
| |
| QVariant QQmlDelegateModelPartsMetaObject::initialValue(int id) |
| { |
| QQmlDelegateModelParts *parts = static_cast<QQmlDelegateModelParts *>(object()); |
| QQmlPartsModel *m = new QQmlPartsModel( |
| parts->model, QString::fromUtf8(name(id)), parts); |
| parts->models.append(m); |
| return QVariant::fromValue(static_cast<QObject *>(m)); |
| } |
| |
| QQmlDelegateModelParts::QQmlDelegateModelParts(QQmlDelegateModel *parent) |
| : QObject(parent), model(parent) |
| { |
| new QQmlDelegateModelPartsMetaObject(this); |
| } |
| |
| //--------------------------------------------------------------------------- |
| |
| /*! |
| \qmltype DelegateModel |
| \instantiates QQmlDelegateModel |
| \inqmlmodule QtQml.Models |
| \brief Encapsulates a model and delegate. |
| |
| The DelegateModel type encapsulates a model and the delegate that will |
| be instantiated for items in the model. |
| |
| It is usually not necessary to create a DelegateModel. |
| However, it can be useful for manipulating and accessing the \l modelIndex |
| when a QAbstractItemModel subclass is used as the |
| model. Also, DelegateModel is used together with \l Package to |
| provide delegates to multiple views, and with DelegateModelGroup to sort and filter |
| delegate items. |
| |
| The example below illustrates using a DelegateModel with a ListView. |
| |
| \snippet delegatemodel/delegatemodel.qml 0 |
| */ |
| |
| QQmlDelegateModelPrivate::QQmlDelegateModelPrivate(QQmlContext *ctxt) |
| : m_delegateChooser(nullptr) |
| , m_cacheMetaType(nullptr) |
| , m_context(ctxt) |
| , m_parts(nullptr) |
| , m_filterGroup(QStringLiteral("items")) |
| , m_count(0) |
| , m_groupCount(Compositor::MinimumGroupCount) |
| , m_compositorGroup(Compositor::Cache) |
| , m_complete(false) |
| , m_delegateValidated(false) |
| , m_reset(false) |
| , m_transaction(false) |
| , m_incubatorCleanupScheduled(false) |
| , m_waitingToFetchMore(false) |
| , m_cacheItems(nullptr) |
| , m_items(nullptr) |
| , m_persistedItems(nullptr) |
| { |
| } |
| |
| QQmlDelegateModelPrivate::~QQmlDelegateModelPrivate() |
| { |
| qDeleteAll(m_finishedIncubating); |
| |
| // Free up all items in the pool |
| drainReusableItemsPool(0); |
| |
| if (m_cacheMetaType) |
| m_cacheMetaType->release(); |
| } |
| |
| int QQmlDelegateModelPrivate::adaptorModelCount() const |
| { |
| // QQmlDelegateModel currently only support list models. |
| // So even if a model is a table model, only the first |
| // column will be used. |
| return m_adaptorModel.rowCount(); |
| } |
| |
| void QQmlDelegateModelPrivate::requestMoreIfNecessary() |
| { |
| Q_Q(QQmlDelegateModel); |
| if (!m_waitingToFetchMore && m_adaptorModel.canFetchMore()) { |
| m_waitingToFetchMore = true; |
| QCoreApplication::postEvent(q, new QEvent(QEvent::UpdateRequest)); |
| } |
| } |
| |
| void QQmlDelegateModelPrivate::init() |
| { |
| Q_Q(QQmlDelegateModel); |
| m_compositor.setRemoveGroups(Compositor::GroupMask & ~Compositor::PersistedFlag); |
| |
| m_items = new QQmlDelegateModelGroup(QStringLiteral("items"), q, Compositor::Default, q); |
| m_items->setDefaultInclude(true); |
| m_persistedItems = new QQmlDelegateModelGroup(QStringLiteral("persistedItems"), q, Compositor::Persisted, q); |
| QQmlDelegateModelGroupPrivate::get(m_items)->emitters.insert(this); |
| } |
| |
| QQmlDelegateModel::QQmlDelegateModel() |
| : QQmlDelegateModel(nullptr, nullptr) |
| { |
| } |
| |
| QQmlDelegateModel::QQmlDelegateModel(QQmlContext *ctxt, QObject *parent) |
| : QQmlInstanceModel(*(new QQmlDelegateModelPrivate(ctxt)), parent) |
| { |
| Q_D(QQmlDelegateModel); |
| d->init(); |
| } |
| |
| QQmlDelegateModel::~QQmlDelegateModel() |
| { |
| Q_D(QQmlDelegateModel); |
| d->disconnectFromAbstractItemModel(); |
| d->m_adaptorModel.setObject(nullptr, this); |
| |
| for (QQmlDelegateModelItem *cacheItem : qAsConst(d->m_cache)) { |
| if (cacheItem->object) { |
| delete cacheItem->object; |
| |
| cacheItem->object = nullptr; |
| cacheItem->contextData->invalidate(); |
| Q_ASSERT(cacheItem->contextData->refCount == 1); |
| cacheItem->contextData = nullptr; |
| cacheItem->scriptRef -= 1; |
| } else if (cacheItem->incubationTask) { |
| // Both the incubationTask and the object may hold a scriptRef, |
| // but if both are present, only one scriptRef is held in total. |
| cacheItem->scriptRef -= 1; |
| } |
| |
| cacheItem->groups &= ~Compositor::UnresolvedFlag; |
| cacheItem->objectRef = 0; |
| |
| if (cacheItem->incubationTask) { |
| d->releaseIncubator(cacheItem->incubationTask); |
| cacheItem->incubationTask->vdm = nullptr; |
| cacheItem->incubationTask = nullptr; |
| } |
| |
| if (!cacheItem->isReferenced()) |
| delete cacheItem; |
| } |
| } |
| |
| |
| void QQmlDelegateModel::classBegin() |
| { |
| Q_D(QQmlDelegateModel); |
| if (!d->m_context) |
| d->m_context = qmlContext(this); |
| } |
| |
| void QQmlDelegateModel::componentComplete() |
| { |
| Q_D(QQmlDelegateModel); |
| d->m_complete = true; |
| |
| int defaultGroups = 0; |
| QStringList groupNames; |
| groupNames.append(QStringLiteral("items")); |
| groupNames.append(QStringLiteral("persistedItems")); |
| if (QQmlDelegateModelGroupPrivate::get(d->m_items)->defaultInclude) |
| defaultGroups |= Compositor::DefaultFlag; |
| if (QQmlDelegateModelGroupPrivate::get(d->m_persistedItems)->defaultInclude) |
| defaultGroups |= Compositor::PersistedFlag; |
| for (int i = Compositor::MinimumGroupCount; i < d->m_groupCount; ++i) { |
| QString name = d->m_groups[i]->name(); |
| if (name.isEmpty()) { |
| d->m_groups[i] = d->m_groups[d->m_groupCount - 1]; |
| --d->m_groupCount; |
| --i; |
| } else if (name.at(0).isUpper()) { |
| qmlWarning(d->m_groups[i]) << QQmlDelegateModelGroup::tr("Group names must start with a lower case letter"); |
| d->m_groups[i] = d->m_groups[d->m_groupCount - 1]; |
| --d->m_groupCount; |
| --i; |
| } else { |
| groupNames.append(name); |
| |
| QQmlDelegateModelGroupPrivate *group = QQmlDelegateModelGroupPrivate::get(d->m_groups[i]); |
| group->setModel(this, Compositor::Group(i)); |
| if (group->defaultInclude) |
| defaultGroups |= (1 << i); |
| } |
| } |
| |
| d->m_cacheMetaType = new QQmlDelegateModelItemMetaType( |
| d->m_context->engine()->handle(), this, groupNames); |
| |
| d->m_compositor.setGroupCount(d->m_groupCount); |
| d->m_compositor.setDefaultGroups(defaultGroups); |
| d->updateFilterGroup(); |
| |
| while (!d->m_pendingParts.isEmpty()) |
| static_cast<QQmlPartsModel *>(d->m_pendingParts.first())->updateFilterGroup(); |
| |
| QVector<Compositor::Insert> inserts; |
| d->m_count = d->adaptorModelCount(); |
| d->m_compositor.append( |
| &d->m_adaptorModel, |
| 0, |
| d->m_count, |
| defaultGroups | Compositor::AppendFlag | Compositor::PrependFlag, |
| &inserts); |
| d->itemsInserted(inserts); |
| d->emitChanges(); |
| d->requestMoreIfNecessary(); |
| } |
| |
| /*! |
| \qmlproperty model QtQml.Models::DelegateModel::model |
| This property holds the model providing data for the DelegateModel. |
| |
| The model provides a set of data that is used to create the items |
| for a view. For large or dynamic datasets the model is usually |
| provided by a C++ model object. The C++ model object must be a \l |
| {QAbstractItemModel} subclass or a simple list. |
| |
| Models can also be created directly in QML, using a \l{ListModel} or |
| \l{QtQuick.XmlListModel::XmlListModel}{XmlListModel}. |
| |
| \sa {qml-data-models}{Data Models} |
| \keyword dm-model-property |
| */ |
| QVariant QQmlDelegateModel::model() const |
| { |
| Q_D(const QQmlDelegateModel); |
| return d->m_adaptorModel.model(); |
| } |
| |
| void QQmlDelegateModelPrivate::connectToAbstractItemModel() |
| { |
| Q_Q(QQmlDelegateModel); |
| if (!m_adaptorModel.adaptsAim()) |
| return; |
| |
| auto aim = m_adaptorModel.aim(); |
| |
| qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsInserted(QModelIndex,int,int)), |
| q, QQmlDelegateModel, SLOT(_q_rowsInserted(QModelIndex,int,int))); |
| qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), |
| q, QQmlDelegateModel, SLOT(_q_rowsRemoved(QModelIndex,int,int))); |
| qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), |
| q, QQmlDelegateModel, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int))); |
| qmlobject_connect(aim, QAbstractItemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)), |
| q, QQmlDelegateModel, SLOT(_q_dataChanged(QModelIndex,QModelIndex,QVector<int>))); |
| qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), |
| q, QQmlDelegateModel, SLOT(_q_rowsMoved(QModelIndex,int,int,QModelIndex,int))); |
| qmlobject_connect(aim, QAbstractItemModel, SIGNAL(modelReset()), |
| q, QQmlDelegateModel, SLOT(_q_modelReset())); |
| qmlobject_connect(aim, QAbstractItemModel, SIGNAL(layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)), |
| q, QQmlDelegateModel, SLOT(_q_layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint))); |
| } |
| |
| void QQmlDelegateModelPrivate::disconnectFromAbstractItemModel() |
| { |
| Q_Q(QQmlDelegateModel); |
| if (!m_adaptorModel.adaptsAim()) |
| return; |
| |
| auto aim = m_adaptorModel.aim(); |
| |
| QObject::disconnect(aim, SIGNAL(rowsInserted(QModelIndex,int,int)), |
| q, SLOT(_q_rowsInserted(QModelIndex,int,int))); |
| QObject::disconnect(aim, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), |
| q, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int))); |
| QObject::disconnect(aim, SIGNAL(rowsRemoved(QModelIndex,int,int)), |
| q, SLOT(_q_rowsRemoved(QModelIndex,int,int))); |
| QObject::disconnect(aim, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)), |
| q, SLOT(_q_dataChanged(QModelIndex,QModelIndex,QVector<int>))); |
| QObject::disconnect(aim, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), |
| q, SLOT(_q_rowsMoved(QModelIndex,int,int,QModelIndex,int))); |
| QObject::disconnect(aim, SIGNAL(modelReset()), |
| q, SLOT(_q_modelReset())); |
| QObject::disconnect(aim, SIGNAL(layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)), |
| q, SLOT(_q_layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint))); |
| } |
| |
| void QQmlDelegateModel::setModel(const QVariant &model) |
| { |
| Q_D(QQmlDelegateModel); |
| |
| if (d->m_complete) |
| _q_itemsRemoved(0, d->m_count); |
| |
| d->disconnectFromAbstractItemModel(); |
| d->m_adaptorModel.setModel(model, this, d->m_context->engine()); |
| d->connectToAbstractItemModel(); |
| |
| d->m_adaptorModel.replaceWatchedRoles(QList<QByteArray>(), d->m_watchedRoles); |
| for (int i = 0; d->m_parts && i < d->m_parts->models.count(); ++i) { |
| d->m_adaptorModel.replaceWatchedRoles( |
| QList<QByteArray>(), d->m_parts->models.at(i)->watchedRoles()); |
| } |
| |
| if (d->m_complete) { |
| _q_itemsInserted(0, d->adaptorModelCount()); |
| d->requestMoreIfNecessary(); |
| } |
| } |
| |
| /*! |
| \qmlproperty Component QtQml.Models::DelegateModel::delegate |
| |
| The delegate provides a template defining each item instantiated by a view. |
| The index is exposed as an accessible \c index property. Properties of the |
| model are also available depending upon the type of \l {qml-data-models}{Data Model}. |
| */ |
| QQmlComponent *QQmlDelegateModel::delegate() const |
| { |
| Q_D(const QQmlDelegateModel); |
| return d->m_delegate; |
| } |
| |
| void QQmlDelegateModel::setDelegate(QQmlComponent *delegate) |
| { |
| Q_D(QQmlDelegateModel); |
| if (d->m_transaction) { |
| qmlWarning(this) << tr("The delegate of a DelegateModel cannot be changed within onUpdated."); |
| return; |
| } |
| if (d->m_delegate == delegate) |
| return; |
| if (d->m_complete) |
| _q_itemsRemoved(0, d->m_count); |
| d->m_delegate.setObject(delegate, this); |
| d->m_delegateValidated = false; |
| if (d->m_delegateChooser) |
| QObject::disconnect(d->m_delegateChooserChanged); |
| |
| d->m_delegateChooser = nullptr; |
| if (delegate) { |
| QQmlAbstractDelegateComponent *adc = |
| qobject_cast<QQmlAbstractDelegateComponent *>(delegate); |
| if (adc) { |
| d->m_delegateChooser = adc; |
| d->m_delegateChooserChanged = connect(adc, &QQmlAbstractDelegateComponent::delegateChanged, |
| [d](){ d->delegateChanged(); }); |
| } |
| } |
| if (d->m_complete) { |
| _q_itemsInserted(0, d->adaptorModelCount()); |
| d->requestMoreIfNecessary(); |
| } |
| emit delegateChanged(); |
| } |
| |
| /*! |
| \qmlproperty QModelIndex QtQml.Models::DelegateModel::rootIndex |
| |
| QAbstractItemModel provides a hierarchical tree of data, whereas |
| QML only operates on list data. \c rootIndex allows the children of |
| any node in a QAbstractItemModel to be provided by this model. |
| |
| This property only affects models of type QAbstractItemModel that |
| are hierarchical (e.g, a tree model). |
| |
| For example, here is a simple interactive file system browser. |
| When a directory name is clicked, the view's \c rootIndex is set to the |
| QModelIndex node of the clicked directory, thus updating the view to show |
| the new directory's contents. |
| |
| \c main.cpp: |
| \snippet delegatemodel/delegatemodel_rootindex/main.cpp 0 |
| |
| \c view.qml: |
| \snippet delegatemodel/delegatemodel_rootindex/view.qml 0 |
| |
| If the \l {dm-model-property}{model} is a QAbstractItemModel subclass, |
| the delegate can also reference a \c hasModelChildren property (optionally |
| qualified by a \e model. prefix) that indicates whether the delegate's |
| model item has any child nodes. |
| |
| \sa modelIndex(), parentModelIndex() |
| */ |
| QVariant QQmlDelegateModel::rootIndex() const |
| { |
| Q_D(const QQmlDelegateModel); |
| return QVariant::fromValue(QModelIndex(d->m_adaptorModel.rootIndex)); |
| } |
| |
| void QQmlDelegateModel::setRootIndex(const QVariant &root) |
| { |
| Q_D(QQmlDelegateModel); |
| |
| QModelIndex modelIndex = qvariant_cast<QModelIndex>(root); |
| const bool changed = d->m_adaptorModel.rootIndex != modelIndex; |
| if (changed || !d->m_adaptorModel.isValid()) { |
| const int oldCount = d->m_count; |
| d->m_adaptorModel.rootIndex = modelIndex; |
| if (!d->m_adaptorModel.isValid() && d->m_adaptorModel.aim()) { |
| // The previous root index was invalidated, so we need to reconnect the model. |
| d->disconnectFromAbstractItemModel(); |
| d->m_adaptorModel.setModel(d->m_adaptorModel.list.list(), this, d->m_context->engine()); |
| d->connectToAbstractItemModel(); |
| } |
| if (d->m_adaptorModel.canFetchMore()) |
| d->m_adaptorModel.fetchMore(); |
| if (d->m_complete) { |
| const int newCount = d->adaptorModelCount(); |
| if (oldCount) |
| _q_itemsRemoved(0, oldCount); |
| if (newCount) |
| _q_itemsInserted(0, newCount); |
| } |
| if (changed) |
| emit rootIndexChanged(); |
| } |
| } |
| |
| /*! |
| \qmlmethod QModelIndex QtQml.Models::DelegateModel::modelIndex(int index) |
| |
| QAbstractItemModel provides a hierarchical tree of data, whereas |
| QML only operates on list data. This function assists in using |
| tree models in QML. |
| |
| Returns a QModelIndex for the specified \a index. |
| This value can be assigned to rootIndex. |
| |
| \sa rootIndex |
| */ |
| QVariant QQmlDelegateModel::modelIndex(int idx) const |
| { |
| Q_D(const QQmlDelegateModel); |
| return d->m_adaptorModel.modelIndex(idx); |
| } |
| |
| /*! |
| \qmlmethod QModelIndex QtQml.Models::DelegateModel::parentModelIndex() |
| |
| QAbstractItemModel provides a hierarchical tree of data, whereas |
| QML only operates on list data. This function assists in using |
| tree models in QML. |
| |
| Returns a QModelIndex for the parent of the current rootIndex. |
| This value can be assigned to rootIndex. |
| |
| \sa rootIndex |
| */ |
| QVariant QQmlDelegateModel::parentModelIndex() const |
| { |
| Q_D(const QQmlDelegateModel); |
| return d->m_adaptorModel.parentModelIndex(); |
| } |
| |
| /*! |
| \qmlproperty int QtQml.Models::DelegateModel::count |
| */ |
| |
| int QQmlDelegateModel::count() const |
| { |
| Q_D(const QQmlDelegateModel); |
| if (!d->m_delegate) |
| return 0; |
| return d->m_compositor.count(d->m_compositorGroup); |
| } |
| |
| QQmlDelegateModel::ReleaseFlags QQmlDelegateModelPrivate::release(QObject *object, QQmlInstanceModel::ReusableFlag reusableFlag) |
| { |
| if (!object) |
| return QQmlDelegateModel::ReleaseFlags{}; |
| |
| QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(object); |
| if (!cacheItem) |
| return QQmlDelegateModel::ReleaseFlags{}; |
| |
| if (!cacheItem->releaseObject()) |
| return QQmlDelegateModel::Referenced; |
| |
| if (reusableFlag == QQmlInstanceModel::Reusable) { |
| removeCacheItem(cacheItem); |
| m_reusableItemsPool.insertItem(cacheItem); |
| emit q_func()->itemPooled(cacheItem->index, cacheItem->object); |
| return QQmlInstanceModel::Pooled; |
| } |
| |
| destroyCacheItem(cacheItem); |
| return QQmlInstanceModel::Destroyed; |
| } |
| |
| void QQmlDelegateModelPrivate::destroyCacheItem(QQmlDelegateModelItem *cacheItem) |
| { |
| emitDestroyingItem(cacheItem->object); |
| cacheItem->destroyObject(); |
| if (cacheItem->incubationTask) { |
| releaseIncubator(cacheItem->incubationTask); |
| cacheItem->incubationTask = nullptr; |
| } |
| cacheItem->Dispose(); |
| } |
| |
| /* |
| Returns ReleaseStatus flags. |
| */ |
| QQmlDelegateModel::ReleaseFlags QQmlDelegateModel::release(QObject *item, QQmlInstanceModel::ReusableFlag reusableFlag) |
| { |
| Q_D(QQmlDelegateModel); |
| QQmlInstanceModel::ReleaseFlags stat = d->release(item, reusableFlag); |
| return stat; |
| } |
| |
| // Cancel a requested async item |
| void QQmlDelegateModel::cancel(int index) |
| { |
| Q_D(QQmlDelegateModel); |
| if (index < 0 || index >= d->m_compositor.count(d->m_compositorGroup)) { |
| qWarning() << "DelegateModel::cancel: index out range" << index << d->m_compositor.count(d->m_compositorGroup); |
| return; |
| } |
| |
| Compositor::iterator it = d->m_compositor.find(d->m_compositorGroup, index); |
| QQmlDelegateModelItem *cacheItem = it->inCache() ? d->m_cache.at(it.cacheIndex) : 0; |
| if (cacheItem) { |
| if (cacheItem->incubationTask && !cacheItem->isObjectReferenced()) { |
| d->releaseIncubator(cacheItem->incubationTask); |
| cacheItem->incubationTask = nullptr; |
| |
| if (cacheItem->object) { |
| QObject *object = cacheItem->object; |
| cacheItem->destroyObject(); |
| if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(object)) |
| d->emitDestroyingPackage(package); |
| else |
| d->emitDestroyingItem(object); |
| } |
| |
| cacheItem->scriptRef -= 1; |
| } |
| if (!cacheItem->isReferenced()) { |
| d->m_compositor.clearFlags(Compositor::Cache, it.cacheIndex, 1, Compositor::CacheFlag); |
| d->m_cache.removeAt(it.cacheIndex); |
| delete cacheItem; |
| Q_ASSERT(d->m_cache.count() == d->m_compositor.count(Compositor::Cache)); |
| } |
| } |
| } |
| |
| void QQmlDelegateModelPrivate::group_append( |
| QQmlListProperty<QQmlDelegateModelGroup> *property, QQmlDelegateModelGroup *group) |
| { |
| QQmlDelegateModelPrivate *d = static_cast<QQmlDelegateModelPrivate *>(property->data); |
| if (d->m_complete) |
| return; |
| if (d->m_groupCount == Compositor::MaximumGroupCount) { |
| qmlWarning(d->q_func()) << QQmlDelegateModel::tr("The maximum number of supported DelegateModelGroups is 8"); |
| return; |
| } |
| d->m_groups[d->m_groupCount] = group; |
| d->m_groupCount += 1; |
| } |
| |
| int QQmlDelegateModelPrivate::group_count( |
| QQmlListProperty<QQmlDelegateModelGroup> *property) |
| { |
| QQmlDelegateModelPrivate *d = static_cast<QQmlDelegateModelPrivate *>(property->data); |
| return d->m_groupCount - 1; |
| } |
| |
| QQmlDelegateModelGroup *QQmlDelegateModelPrivate::group_at( |
| QQmlListProperty<QQmlDelegateModelGroup> *property, int index) |
| { |
| QQmlDelegateModelPrivate *d = static_cast<QQmlDelegateModelPrivate *>(property->data); |
| return index >= 0 && index < d->m_groupCount - 1 |
| ? d->m_groups[index + 1] |
| : nullptr; |
| } |
| |
| /*! |
| \qmlproperty list<DelegateModelGroup> QtQml.Models::DelegateModel::groups |
| |
| This property holds a delegate model's group definitions. |
| |
| Groups define a sub-set of the items in a delegate model and can be used to filter |
| a model. |
| |
| For every group defined in a DelegateModel two attached properties are added to each |
| delegate item. The first of the form DelegateModel.in\e{GroupName} holds whether the |
| item belongs to the group and the second DelegateModel.\e{groupName}Index holds the |
| index of the item in that group. |
| |
| The following example illustrates using groups to select items in a model. |
| |
| \snippet delegatemodel/delegatemodelgroup.qml 0 |
| \keyword dm-groups-property |
| */ |
| |
| QQmlListProperty<QQmlDelegateModelGroup> QQmlDelegateModel::groups() |
| { |
| Q_D(QQmlDelegateModel); |
| return QQmlListProperty<QQmlDelegateModelGroup>( |
| this, |
| d, |
| QQmlDelegateModelPrivate::group_append, |
| QQmlDelegateModelPrivate::group_count, |
| QQmlDelegateModelPrivate::group_at, |
| nullptr, nullptr, nullptr); |
| } |
| |
| /*! |
| \qmlproperty DelegateModelGroup QtQml.Models::DelegateModel::items |
| |
| This property holds default group to which all new items are added. |
| */ |
| |
| QQmlDelegateModelGroup *QQmlDelegateModel::items() |
| { |
| Q_D(QQmlDelegateModel); |
| return d->m_items; |
| } |
| |
| /*! |
| \qmlproperty DelegateModelGroup QtQml.Models::DelegateModel::persistedItems |
| |
| This property holds delegate model's persisted items group. |
| |
| Items in this group are not destroyed when released by a view, instead they are persisted |
| until removed from the group. |
| |
| An item can be removed from the persistedItems group by setting the |
| DelegateModel.inPersistedItems property to false. If the item is not referenced by a view |
| at that time it will be destroyed. Adding an item to this group will not create a new |
| instance. |
| |
| Items returned by the \l QtQml.Models::DelegateModelGroup::create() function are automatically added |
| to this group. |
| */ |
| |
| QQmlDelegateModelGroup *QQmlDelegateModel::persistedItems() |
| { |
| Q_D(QQmlDelegateModel); |
| return d->m_persistedItems; |
| } |
| |
| /*! |
| \qmlproperty string QtQml.Models::DelegateModel::filterOnGroup |
| |
| This property holds name of the group that is used to filter the delegate model. |
| |
| Only items that belong to this group are visible to a view. |
| |
| By default this is the \l items group. |
| */ |
| |
| QString QQmlDelegateModel::filterGroup() const |
| { |
| Q_D(const QQmlDelegateModel); |
| return d->m_filterGroup; |
| } |
| |
| void QQmlDelegateModel::setFilterGroup(const QString &group) |
| { |
| Q_D(QQmlDelegateModel); |
| |
| if (d->m_transaction) { |
| qmlWarning(this) << tr("The group of a DelegateModel cannot be changed within onChanged"); |
| return; |
| } |
| |
| if (d->m_filterGroup != group) { |
| d->m_filterGroup = group; |
| d->updateFilterGroup(); |
| emit filterGroupChanged(); |
| } |
| } |
| |
| void QQmlDelegateModel::resetFilterGroup() |
| { |
| setFilterGroup(QStringLiteral("items")); |
| } |
| |
| void QQmlDelegateModelPrivate::updateFilterGroup() |
| { |
| Q_Q(QQmlDelegateModel); |
| if (!m_cacheMetaType) |
| return; |
| |
| QQmlListCompositor::Group previousGroup = m_compositorGroup; |
| m_compositorGroup = Compositor::Default; |
| for (int i = 1; i < m_groupCount; ++i) { |
| if (m_filterGroup == m_cacheMetaType->groupNames.at(i - 1)) { |
| m_compositorGroup = Compositor::Group(i); |
| break; |
| } |
| } |
| |
| QQmlDelegateModelGroupPrivate::get(m_groups[m_compositorGroup])->emitters.insert(this); |
| if (m_compositorGroup != previousGroup) { |
| QVector<QQmlChangeSet::Change> removes; |
| QVector<QQmlChangeSet::Change> inserts; |
| m_compositor.transition(previousGroup, m_compositorGroup, &removes, &inserts); |
| |
| QQmlChangeSet changeSet; |
| changeSet.move(removes, inserts); |
| emit q->modelUpdated(changeSet, false); |
| |
| if (changeSet.difference() != 0) |
| emit q->countChanged(); |
| |
| if (m_parts) { |
| auto partsCopy = m_parts->models; // deliberate; this may alter m_parts |
| for (QQmlPartsModel *model : qAsConst(partsCopy)) |
| model->updateFilterGroup(m_compositorGroup, changeSet); |
| } |
| } |
| } |
| |
| /*! |
| \qmlproperty object QtQml.Models::DelegateModel::parts |
| |
| The \a parts property selects a DelegateModel which creates |
| delegates from the part named. This is used in conjunction with |
| the \l Package type. |
| |
| For example, the code below selects a model which creates |
| delegates named \e list from a \l Package: |
| |
| \code |
| DelegateModel { |
| id: visualModel |
| delegate: Package { |
| Item { Package.name: "list" } |
| } |
| model: myModel |
| } |
| |
| ListView { |
| width: 200; height:200 |
| model: visualModel.parts.list |
| } |
| \endcode |
| |
| \sa Package |
| */ |
| |
| QObject *QQmlDelegateModel::parts() |
| { |
| Q_D(QQmlDelegateModel); |
| if (!d->m_parts) |
| d->m_parts = new QQmlDelegateModelParts(this); |
| return d->m_parts; |
| } |
| |
| const QAbstractItemModel *QQmlDelegateModel::abstractItemModel() const |
| { |
| Q_D(const QQmlDelegateModel); |
| return d->m_adaptorModel.adaptsAim() ? d->m_adaptorModel.aim() : nullptr; |
| } |
| |
| void QQmlDelegateModelPrivate::emitCreatedPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package) |
| { |
| for (int i = 1; i < m_groupCount; ++i) |
| QQmlDelegateModelGroupPrivate::get(m_groups[i])->createdPackage(incubationTask->index[i], package); |
| } |
| |
| void QQmlDelegateModelPrivate::emitInitPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package) |
| { |
| for (int i = 1; i < m_groupCount; ++i) |
| QQmlDelegateModelGroupPrivate::get(m_groups[i])->initPackage(incubationTask->index[i], package); |
| } |
| |
| void QQmlDelegateModelPrivate::emitDestroyingPackage(QQuickPackage *package) |
| { |
| for (int i = 1; i < m_groupCount; ++i) |
| QQmlDelegateModelGroupPrivate::get(m_groups[i])->destroyingPackage(package); |
| } |
| |
| 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; |
| auto property = mo->property(changeSignalIndexToPropertyIndex[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())); |
| } |
| |
| void PropertyUpdater::breakBinding() |
| { |
| auto it = senderToConnection.find(QObject::senderSignalIndex()); |
| if (it == senderToConnection.end()) |
| return; |
| if (updateCount == 0) { |
| QObject::disconnect(*it); |
| senderToConnection.erase(it); |
| QQmlError warning; |
| if (auto context = qmlContext(QObject::sender())) |
| warning.setUrl(context->baseUrl()); |
| else |
| return; |
| 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); |
| } else { |
| --updateCount; |
| } |
| } |
| |
| void QQDMIncubationTask::initializeRequiredProperties(QQmlDelegateModelItem *modelItemToIncubate, QObject *object) |
| { |
| auto incubatorPriv = QQmlIncubatorPrivate::get(this); |
| if (incubatorPriv->hadRequiredProperties()) { |
| QQmlData *d = QQmlData::get(object); |
| auto contextData = d ? d->context : nullptr; |
| if (contextData) { |
| contextData->hasExtraObject = true; |
| contextData->extraObject = modelItemToIncubate; |
| } |
| |
| 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<QPair<const QMetaObject *, QObject *>, 4> mos; |
| // we first check the dynamic meta object for properties originating from the model |
| // contains abstractitemmodelproperties |
| mos.push_back(qMakePair(qmlMetaObject, modelItemToIncubate)); |
| auto delegateModelItemSubclassMO = qmlMetaObject->superClass(); |
| mos.push_back(qMakePair(delegateModelItemSubclassMO, modelItemToIncubate)); |
| |
| while (strcmp(delegateModelItemSubclassMO->className(), |
| modelItemToIncubate->staticMetaObject.className())) { |
| delegateModelItemSubclassMO = delegateModelItemSubclassMO->superClass(); |
| mos.push_back(qMakePair(delegateModelItemSubclassMO, modelItemToIncubate)); |
| } |
| if (proxiedObject) |
| mos.push_back(qMakePair(proxiedObject->metaObject(), proxiedObject)); |
| |
| auto updater = new PropertyUpdater(object); |
| for (const auto &metaObjectAndObject : mos) { |
| const QMetaObject *mo = metaObjectAndObject.first; |
| QObject *itemOrProxy = metaObjectAndObject.second; |
| 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(itemOrProxy, changeSignal, |
| updater, updateSlot); |
| updater->changeSignalIndexToPropertyIndex[changeSignal.methodIndex()] = i; |
| 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(itemOrProxy)); |
| // 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(itemOrProxy)); |
| } |
| } |
| } else { |
| modelItemToIncubate->contextData->contextObject = modelItemToIncubate; |
| if (proxiedObject) |
| proxyContext->contextObject = proxiedObject; |
| } |
| } |
| |
| void QQDMIncubationTask::statusChanged(Status status) |
| { |
| if (vdm) { |
| vdm->incubatorStatusChanged(this, status); |
| } else if (isDoneIncubating(status)) { |
| Q_ASSERT(incubating); |
| // The model was deleted from under our feet, cleanup ourselves |
| delete incubating->object; |
| incubating->object = nullptr; |
| if (incubating->contextData) { |
| incubating->contextData->invalidate(); |
| Q_ASSERT(incubating->contextData->refCount == 1); |
| incubating->contextData = nullptr; |
| } |
| incubating->scriptRef = 0; |
| incubating->deleteLater(); |
| } |
| } |
| |
| void QQmlDelegateModelPrivate::releaseIncubator(QQDMIncubationTask *incubationTask) |
| { |
| Q_Q(QQmlDelegateModel); |
| if (!incubationTask->isError()) |
| incubationTask->clear(); |
| m_finishedIncubating.append(incubationTask); |
| if (!m_incubatorCleanupScheduled) { |
| m_incubatorCleanupScheduled = true; |
| QCoreApplication::postEvent(q, new QEvent(QEvent::User)); |
| } |
| } |
| |
| void QQmlDelegateModelPrivate::reuseItem(QQmlDelegateModelItem *item, int newModelIndex, int newGroups) |
| { |
| Q_ASSERT(item->object); |
| |
| // Update/reset which groups the item belongs to |
| item->groups = newGroups; |
| |
| // Update context property index (including row and column) on the delegate |
| // item, and inform the application about it. For a list, the row is the same |
| // as the index, and the column is always 0. We set alwaysEmit to true, to |
| // force all bindings to be reevaluated, even if the index didn't change. |
| const bool alwaysEmit = true; |
| item->setModelIndex(newModelIndex, newModelIndex, 0, alwaysEmit); |
| |
| // 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 = QVector<int>(); |
| m_adaptorModel.notify(itemAsList, newModelIndex, 1, updateAllRoles); |
| |
| if (QQmlDelegateModelAttached *att = static_cast<QQmlDelegateModelAttached *>( |
| qmlAttachedPropertiesObject<QQmlDelegateModel>(item->object, false))) { |
| // Update currentIndex of the attached DelegateModel object |
| // to the index the item has in the cache. |
| att->resetCurrentIndex(); |
| // emitChanges will emit both group-, and index changes to the application |
| att->emitChanges(); |
| } |
| |
| // Inform the view that the item is recycled. This will typically result |
| // in the view updating its own attached delegate item properties. |
| emit q_func()->itemReused(newModelIndex, item->object); |
| } |
| |
| void QQmlDelegateModelPrivate::drainReusableItemsPool(int maxPoolTime) |
| { |
| m_reusableItemsPool.drain(maxPoolTime, [=](QQmlDelegateModelItem *cacheItem){ destroyCacheItem(cacheItem); }); |
| } |
| |
| void QQmlDelegateModel::drainReusableItemsPool(int maxPoolTime) |
| { |
| d_func()->drainReusableItemsPool(maxPoolTime); |
| } |
| |
| int QQmlDelegateModel::poolSize() |
| { |
| return d_func()->m_reusableItemsPool.size(); |
| } |
| |
| QQmlComponent *QQmlDelegateModelPrivate::resolveDelegate(int index) |
| { |
| if (!m_delegateChooser) |
| return m_delegate; |
| |
| QQmlComponent *delegate = nullptr; |
| QQmlAbstractDelegateComponent *chooser = m_delegateChooser; |
| |
| do { |
| delegate = chooser->delegate(&m_adaptorModel, index); |
| chooser = qobject_cast<QQmlAbstractDelegateComponent *>(delegate); |
| } while (chooser); |
| |
| return delegate; |
| } |
| |
| void QQmlDelegateModelPrivate::addCacheItem(QQmlDelegateModelItem *item, Compositor::iterator it) |
| { |
| m_cache.insert(it.cacheIndex, item); |
| m_compositor.setFlags(it, 1, Compositor::CacheFlag); |
| Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); |
| } |
| |
| void QQmlDelegateModelPrivate::removeCacheItem(QQmlDelegateModelItem *cacheItem) |
| { |
| int cidx = m_cache.lastIndexOf(cacheItem); |
| if (cidx >= 0) { |
| m_compositor.clearFlags(Compositor::Cache, cidx, 1, Compositor::CacheFlag); |
| m_cache.removeAt(cidx); |
| } |
| Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); |
| } |
| |
| void QQmlDelegateModelPrivate::incubatorStatusChanged(QQDMIncubationTask *incubationTask, QQmlIncubator::Status status) |
| { |
| if (!isDoneIncubating(status)) |
| return; |
| |
| const QList<QQmlError> incubationTaskErrors = incubationTask->errors(); |
| |
| QQmlDelegateModelItem *cacheItem = incubationTask->incubating; |
| cacheItem->incubationTask = nullptr; |
| incubationTask->incubating = nullptr; |
| releaseIncubator(incubationTask); |
| |
| if (status == QQmlIncubator::Ready) { |
| cacheItem->referenceObject(); |
| if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(cacheItem->object)) |
| emitCreatedPackage(incubationTask, package); |
| else |
| emitCreatedItem(incubationTask, cacheItem->object); |
| cacheItem->releaseObject(); |
| } else if (status == QQmlIncubator::Error) { |
| qmlInfo(m_delegate, incubationTaskErrors + m_delegate->errors()) << "Cannot create delegate"; |
| } |
| |
| if (!cacheItem->isObjectReferenced()) { |
| if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(cacheItem->object)) |
| emitDestroyingPackage(package); |
| else |
| emitDestroyingItem(cacheItem->object); |
| delete cacheItem->object; |
| cacheItem->object = nullptr; |
| cacheItem->scriptRef -= 1; |
| if (cacheItem->contextData) { |
| cacheItem->contextData->invalidate(); |
| Q_ASSERT(cacheItem->contextData->refCount == 1); |
| } |
| cacheItem->contextData = nullptr; |
| |
| if (!cacheItem->isReferenced()) { |
| removeCacheItem(cacheItem); |
| delete cacheItem; |
| } |
| } |
| } |
| |
| void QQDMIncubationTask::setInitialState(QObject *o) |
| { |
| vdm->setInitialState(this, 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)) |
| emitInitPackage(incubationTask, package); |
| else |
| emitInitItem(incubationTask, cacheItem->object); |
| } |
| |
| QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQmlIncubator::IncubationMode incubationMode) |
| { |
| if (!m_delegate || index < 0 || index >= m_compositor.count(group)) { |
| qWarning() << "DelegateModel::item: index out range" << index << m_compositor.count(group); |
| return nullptr; |
| } else if (!m_context || !m_context->isValid()) { |
| return nullptr; |
| } |
| |
| Compositor::iterator it = m_compositor.find(group, index); |
| const auto flags = it->flags; |
| const auto modelIndex = it.modelIndex(); |
| |
| QQmlDelegateModelItem *cacheItem = it->inCache() ? m_cache.at(it.cacheIndex) : 0; |
| |
| if (!cacheItem || !cacheItem->delegate) { |
| QQmlComponent *delegate = resolveDelegate(modelIndex); |
| if (!delegate) |
| return nullptr; |
| |
| if (!cacheItem) { |
| cacheItem = m_reusableItemsPool.takeItem(delegate, index); |
| if (cacheItem) { |
| // Move the pooled item back into the cache, update |
| // all related properties, and return the object (which |
| // has already been incubated, otherwise it wouldn't be in the pool). |
| addCacheItem(cacheItem, it); |
| reuseItem(cacheItem, index, flags); |
| cacheItem->referenceObject(); |
| return cacheItem->object; |
| } |
| |
| // Since we could't find an available item in the pool, we create a new one |
| cacheItem = m_adaptorModel.createItem(m_cacheMetaType, modelIndex); |
| if (!cacheItem) |
| return nullptr; |
| |
| cacheItem->groups = flags; |
| addCacheItem(cacheItem, it); |
| } |
| |
| cacheItem->delegate = delegate; |
| } |
| |
| // Bump the reference counts temporarily so neither the content data or the delegate object |
| // are deleted if incubatorStatusChanged() is called synchronously. |
| cacheItem->scriptRef += 1; |
| cacheItem->referenceObject(); |
| |
| if (cacheItem->incubationTask) { |
| bool sync = (incubationMode == QQmlIncubator::Synchronous || incubationMode == QQmlIncubator::AsynchronousIfNested); |
| if (sync && cacheItem->incubationTask->incubationMode() == QQmlIncubator::Asynchronous) { |
| // previously requested async - now needed immediately |
| cacheItem->incubationTask->forceCompletion(); |
| } |
| } else if (!cacheItem->object) { |
| QQmlContext *creationContext = cacheItem->delegate->creationContext(); |
| |
| cacheItem->scriptRef += 1; |
| |
| cacheItem->incubationTask = new QQDMIncubationTask(this, incubationMode); |
| cacheItem->incubationTask->incubating = cacheItem; |
| cacheItem->incubationTask->clear(); |
| |
| for (int i = 1; i < m_groupCount; ++i) |
| cacheItem->incubationTask->index[i] = it.index[i]; |
| |
| QQmlContextData *ctxt = new QQmlContextData; |
| ctxt->setParent(QQmlContextData::get(creationContext ? creationContext : m_context.data())); |
| cacheItem->contextData = ctxt; |
| |
| if (m_adaptorModel.hasProxyObject()) { |
| if (QQmlAdaptorModelProxyInterface *proxy |
| = qobject_cast<QQmlAdaptorModelProxyInterface *>(cacheItem)) { |
| ctxt = new QQmlContextData; |
| ctxt->setParent(cacheItem->contextData, /*stronglyReferencedByParent*/true); |
| QObject *proxied = proxy->proxiedObject(); |
| 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); |
| } |
| } |
| |
| QQmlComponentPrivate *cp = QQmlComponentPrivate::get(cacheItem->delegate); |
| cp->incubateObject( |
| cacheItem->incubationTask, |
| cacheItem->delegate, |
| m_context->engine(), |
| ctxt, |
| QQmlContextData::get(m_context)); |
| } |
| |
| if (index == m_compositor.count(group) - 1) |
| requestMoreIfNecessary(); |
| |
| // Remove the temporary reference count. |
| cacheItem->scriptRef -= 1; |
| if (cacheItem->object && (!cacheItem->incubationTask || isDoneIncubating(cacheItem->incubationTask->status()))) |
| return cacheItem->object; |
| |
| cacheItem->releaseObject(); |
| if (!cacheItem->isReferenced()) { |
| removeCacheItem(cacheItem); |
| delete cacheItem; |
| } |
| |
| return nullptr; |
| } |
| |
| /* |
| If asynchronous is true or the component is being loaded asynchronously due |
| to an ancestor being loaded asynchronously, object() may return 0. In this |
| case createdItem() will be emitted when the object is available. The object |
| at this stage does not have any references, so object() must be called again |
| to ensure a reference is held. Any call to object() which returns a valid object |
| must be matched by a call to release() in order to destroy the object. |
| */ |
| QObject *QQmlDelegateModel::object(int index, QQmlIncubator::IncubationMode incubationMode) |
| { |
| Q_D(QQmlDelegateModel); |
| if (!d->m_delegate || index < 0 || index >= d->m_compositor.count(d->m_compositorGroup)) { |
| qWarning() << "DelegateModel::item: index out range" << index << d->m_compositor.count(d->m_compositorGroup); |
| return nullptr; |
| } |
| |
| return d->object(d->m_compositorGroup, index, incubationMode); |
| } |
| |
| QQmlIncubator::Status QQmlDelegateModel::incubationStatus(int index) |
| { |
| Q_D(QQmlDelegateModel); |
| Compositor::iterator it = d->m_compositor.find(d->m_compositorGroup, index); |
| if (!it->inCache()) |
| return QQmlIncubator::Null; |
| |
| if (auto incubationTask = d->m_cache.at(it.cacheIndex)->incubationTask) |
| return incubationTask->status(); |
| |
| return QQmlIncubator::Ready; |
| } |
| |
| QVariant QQmlDelegateModelPrivate::variantValue(QQmlListCompositor::Group group, int index, const QString &name) |
| { |
| Compositor::iterator it = m_compositor.find(group, index); |
| if (QQmlAdaptorModel *model = it.list<QQmlAdaptorModel>()) { |
| QString role = name; |
| int dot = name.indexOf(QLatin1Char('.')); |
| if (dot > 0) |
| role = name.left(dot); |
| QVariant value = model->value(it.modelIndex(), role); |
| while (dot > 0) { |
| QObject *obj = qvariant_cast<QObject*>(value); |
| if (!obj) |
| return QVariant(); |
| const int from = dot + 1; |
| dot = name.indexOf(QLatin1Char('.'), from); |
| value = obj->property(name.midRef(from, dot - from).toUtf8()); |
| } |
| return value; |
| } |
| return QVariant(); |
| } |
| |
| QVariant QQmlDelegateModel::variantValue(int index, const QString &role) |
| { |
| Q_D(QQmlDelegateModel); |
| return d->variantValue(d->m_compositorGroup, index, role); |
| } |
| |
| int QQmlDelegateModel::indexOf(QObject *item, QObject *) const |
| { |
| Q_D(const QQmlDelegateModel); |
| if (QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(item)) |
| return cacheItem->groupIndex(d->m_compositorGroup); |
| return -1; |
| } |
| |
| void QQmlDelegateModel::setWatchedRoles(const QList<QByteArray> &roles) |
| { |
| Q_D(QQmlDelegateModel); |
| d->m_adaptorModel.replaceWatchedRoles(d->m_watchedRoles, roles); |
| d->m_watchedRoles = roles; |
| } |
| |
| void QQmlDelegateModelPrivate::addGroups( |
| Compositor::iterator from, int count, Compositor::Group group, int groupFlags) |
| { |
| QVector<Compositor::Insert> inserts; |
| m_compositor.setFlags(from, count, group, groupFlags, &inserts); |
| itemsInserted(inserts); |
| emitChanges(); |
| } |
| |
| void QQmlDelegateModelPrivate::removeGroups( |
| Compositor::iterator from, int count, Compositor::Group group, int groupFlags) |
| { |
| QVector<Compositor::Remove> removes; |
| m_compositor.clearFlags(from, count, group, groupFlags, &removes); |
| itemsRemoved(removes); |
| emitChanges(); |
| } |
| |
| void QQmlDelegateModelPrivate::setGroups( |
| Compositor::iterator from, int count, Compositor::Group group, int groupFlags) |
| { |
| QVector<Compositor::Remove> removes; |
| QVector<Compositor::Insert> inserts; |
| |
| m_compositor.setFlags(from, count, group, groupFlags, &inserts); |
| itemsInserted(inserts); |
| const int removeFlags = ~groupFlags & Compositor::GroupMask; |
| |
| from = m_compositor.find(from.group, from.index[from.group]); |
| m_compositor.clearFlags(from, count, group, removeFlags, &removes); |
| itemsRemoved(removes); |
| emitChanges(); |
| } |
| |
| bool QQmlDelegateModel::event(QEvent *e) |
| { |
| Q_D(QQmlDelegateModel); |
| if (e->type() == QEvent::UpdateRequest) { |
| d->m_waitingToFetchMore = false; |
| d->m_adaptorModel.fetchMore(); |
| } else if (e->type() == QEvent::User) { |
| d->m_incubatorCleanupScheduled = false; |
| qDeleteAll(d->m_finishedIncubating); |
| d->m_finishedIncubating.clear(); |
| } |
| return QQmlInstanceModel::event(e); |
| } |
| |
| void QQmlDelegateModelPrivate::itemsChanged(const QVector<Compositor::Change> &changes) |
| { |
| if (!m_delegate) |
| return; |
| |
| QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> translatedChanges(m_groupCount); |
| |
| for (const Compositor::Change &change : changes) { |
| for (int i = 1; i < m_groupCount; ++i) { |
| if (change.inGroup(i)) { |
| translatedChanges[i].append(QQmlChangeSet::Change(change.index[i], change.count)); |
| } |
| } |
| } |
| |
| for (int i = 1; i < m_groupCount; ++i) |
| QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.change(translatedChanges.at(i)); |
| } |
| |
| void QQmlDelegateModel::_q_itemsChanged(int index, int count, const QVector<int> &roles) |
| { |
| Q_D(QQmlDelegateModel); |
| if (count <= 0 || !d->m_complete) |
| return; |
| |
| if (d->m_adaptorModel.notify(d->m_cache, index, count, roles)) { |
| QVector<Compositor::Change> changes; |
| d->m_compositor.listItemsChanged(&d->m_adaptorModel, index, count, &changes); |
| d->itemsChanged(changes); |
| d->emitChanges(); |
| } |
| } |
| |
| static void incrementIndexes(QQmlDelegateModelItem *cacheItem, int count, const int *deltas) |
| { |
| if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) { |
| for (int i = 1; i < count; ++i) |
| incubationTask->index[i] += deltas[i]; |
| } |
| if (QQmlDelegateModelAttached *attached = cacheItem->attached) { |
| for (int i = 1; i < qMin<int>(count, Compositor::MaximumGroupCount); ++i) |
| attached->m_currentIndex[i] += deltas[i]; |
| } |
| } |
| |
| void QQmlDelegateModelPrivate::itemsInserted( |
| const QVector<Compositor::Insert> &inserts, |
| QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> *translatedInserts, |
| QHash<int, QList<QQmlDelegateModelItem *> > *movedItems) |
| { |
| int cacheIndex = 0; |
| |
| int inserted[Compositor::MaximumGroupCount]; |
| for (int i = 1; i < m_groupCount; ++i) |
| inserted[i] = 0; |
| |
| for (const Compositor::Insert &insert : inserts) { |
| for (; cacheIndex < insert.cacheIndex; ++cacheIndex) |
| incrementIndexes(m_cache.at(cacheIndex), m_groupCount, inserted); |
| |
| for (int i = 1; i < m_groupCount; ++i) { |
| if (insert.inGroup(i)) { |
| (*translatedInserts)[i].append( |
| QQmlChangeSet::Change(insert.index[i], insert.count, insert.moveId)); |
| inserted[i] += insert.count; |
| } |
| } |
| |
| if (!insert.inCache()) |
| continue; |
| |
| if (movedItems && insert.isMove()) { |
| QList<QQmlDelegateModelItem *> items = movedItems->take(insert.moveId); |
| Q_ASSERT(items.count() == insert.count); |
| m_cache = m_cache.mid(0, insert.cacheIndex) + items + m_cache.mid(insert.cacheIndex); |
| } |
| if (insert.inGroup()) { |
| for (int offset = 0; cacheIndex < insert.cacheIndex + insert.count; ++cacheIndex, ++offset) { |
| QQmlDelegateModelItem *cacheItem = m_cache.at(cacheIndex); |
| cacheItem->groups |= insert.flags & Compositor::GroupMask; |
| |
| if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) { |
| for (int i = 1; i < m_groupCount; ++i) |
| incubationTask->index[i] = cacheItem->groups & (1 << i) |
| ? insert.index[i] + offset |
| : insert.index[i]; |
| } |
| if (QQmlDelegateModelAttached *attached = cacheItem->attached) { |
| for (int i = 1; i < m_groupCount; ++i) |
| attached->m_currentIndex[i] = cacheItem->groups & (1 << i) |
| ? insert.index[i] + offset |
| : insert.index[i]; |
| } |
| } |
| } else { |
| cacheIndex = insert.cacheIndex + insert.count; |
| } |
| } |
| for (const QList<QQmlDelegateModelItem *> cache = m_cache; cacheIndex < cache.count(); ++cacheIndex) |
| incrementIndexes(cache.at(cacheIndex), m_groupCount, inserted); |
| } |
| |
| void QQmlDelegateModelPrivate::itemsInserted(const QVector<Compositor::Insert> &inserts) |
| { |
| QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> translatedInserts(m_groupCount); |
| itemsInserted(inserts, &translatedInserts); |
| Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); |
| if (!m_delegate) |
| return; |
| |
| for (int i = 1; i < m_groupCount; ++i) |
| QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.insert(translatedInserts.at(i)); |
| } |
| |
| void QQmlDelegateModel::_q_itemsInserted(int index, int count) |
| { |
| |
| Q_D(QQmlDelegateModel); |
| if (count <= 0 || !d->m_complete) |
| return; |
| |
| d->m_count += count; |
| |
| const QList<QQmlDelegateModelItem *> cache = d->m_cache; |
| for (int i = 0, c = cache.count(); i < c; ++i) { |
| QQmlDelegateModelItem *item = cache.at(i); |
| // layout change triggered by changing the modelIndex might have |
| // already invalidated this item in d->m_cache and deleted it. |
| if (!d->m_cache.isSharedWith(cache) && !d->m_cache.contains(item)) |
| continue; |
| |
| if (item->modelIndex() >= index) { |
| const int newIndex = item->modelIndex() + count; |
| const int row = newIndex; |
| const int column = 0; |
| item->setModelIndex(newIndex, row, column); |
| } |
| } |
| |
| QVector<Compositor::Insert> inserts; |
| d->m_compositor.listItemsInserted(&d->m_adaptorModel, index, count, &inserts); |
| d->itemsInserted(inserts); |
| d->emitChanges(); |
| } |
| |
| //### This method should be split in two. It will remove delegates, and it will re-render the list. |
| // When e.g. QQmlListModel::remove is called, the removal of the delegates should be done on |
| // QAbstractItemModel::rowsAboutToBeRemoved, and the re-rendering on |
| // QAbstractItemModel::rowsRemoved. Currently both are done on the latter signal. The problem is |
| // that the destruction of an item will emit a changed signal that ends up at the delegate, which |
| // in turn will try to load the data from the model (which should have already freed it), resulting |
| // in a use-after-free. See QTBUG-59256. |
| void QQmlDelegateModelPrivate::itemsRemoved( |
| const QVector<Compositor::Remove> &removes, |
| QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> *translatedRemoves, |
| QHash<int, QList<QQmlDelegateModelItem *> > *movedItems) |
| { |
| int cacheIndex = 0; |
| int removedCache = 0; |
| |
| int removed[Compositor::MaximumGroupCount]; |
| for (int i = 1; i < m_groupCount; ++i) |
| removed[i] = 0; |
| |
| for (const Compositor::Remove &remove : removes) { |
| for (; cacheIndex < remove.cacheIndex; ++cacheIndex) |
| incrementIndexes(m_cache.at(cacheIndex), m_groupCount, removed); |
| |
| for (int i = 1; i < m_groupCount; ++i) { |
| if (remove.inGroup(i)) { |
| (*translatedRemoves)[i].append( |
| QQmlChangeSet::Change(remove.index[i], remove.count, remove.moveId)); |
| removed[i] -= remove.count; |
| } |
| } |
| |
| if (!remove.inCache()) |
| continue; |
| |
| if (movedItems && remove.isMove()) { |
| movedItems->insert(remove.moveId, m_cache.mid(remove.cacheIndex, remove.count)); |
| QList<QQmlDelegateModelItem *>::iterator begin = m_cache.begin() + remove.cacheIndex; |
| QList<QQmlDelegateModelItem *>::iterator end = begin + remove.count; |
| m_cache.erase(begin, end); |
| } else { |
| for (; cacheIndex < remove.cacheIndex + remove.count - removedCache; ++cacheIndex) { |
| QQmlDelegateModelItem *cacheItem = m_cache.at(cacheIndex); |
| if (remove.inGroup(Compositor::Persisted) && cacheItem->objectRef == 0 && cacheItem->object) { |
| QObject *object = cacheItem->object; |
| cacheItem->destroyObject(); |
| if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(object)) |
| emitDestroyingPackage(package); |
| else |
| emitDestroyingItem(object); |
| cacheItem->scriptRef -= 1; |
| } |
| if (!cacheItem->isReferenced()) { |
| m_compositor.clearFlags(Compositor::Cache, cacheIndex, 1, Compositor::CacheFlag); |
| m_cache.removeAt(cacheIndex); |
| delete cacheItem; |
| --cacheIndex; |
| ++removedCache; |
| Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); |
| } else if (remove.groups() == cacheItem->groups) { |
| cacheItem->groups = 0; |
| if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) { |
| for (int i = 1; i < m_groupCount; ++i) |
| incubationTask->index[i] = -1; |
| } |
| if (QQmlDelegateModelAttached *attached = cacheItem->attached) { |
| for (int i = 1; i < m_groupCount; ++i) |
| attached->m_currentIndex[i] = -1; |
| } |
| } else { |
| if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) { |
| if (!cacheItem->isObjectReferenced()) { |
| releaseIncubator(cacheItem->incubationTask); |
| cacheItem->incubationTask = nullptr; |
| if (cacheItem->object) { |
| QObject *object = cacheItem->object; |
| cacheItem->destroyObject(); |
| if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(object)) |
| emitDestroyingPackage(package); |
| else |
| emitDestroyingItem(object); |
| } |
| cacheItem->scriptRef -= 1; |
| } else { |
| for (int i = 1; i < m_groupCount; ++i) { |
| if (remove.inGroup(i)) |
| incubationTask->index[i] = remove.index[i]; |
| } |
| } |
| } |
| if (QQmlDelegateModelAttached *attached = cacheItem->attached) { |
| for (int i = 1; i < m_groupCount; ++i) { |
| if (remove.inGroup(i)) |
| attached->m_currentIndex[i] = remove.index[i]; |
| } |
| } |
| cacheItem->groups &= ~remove.flags; |
| } |
| } |
| } |
| } |
| |
| for (const QList<QQmlDelegateModelItem *> cache = m_cache; cacheIndex < cache.count(); ++cacheIndex) |
| incrementIndexes(cache.at(cacheIndex), m_groupCount, removed); |
| } |
| |
| void QQmlDelegateModelPrivate::itemsRemoved(const QVector<Compositor::Remove> &removes) |
| { |
| QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> translatedRemoves(m_groupCount); |
| itemsRemoved(removes, &translatedRemoves); |
| Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); |
| if (!m_delegate) |
| return; |
| |
| for (int i = 1; i < m_groupCount; ++i) |
| QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.remove(translatedRemoves.at(i)); |
| } |
| |
| void QQmlDelegateModel::_q_itemsRemoved(int index, int count) |
| { |
| Q_D(QQmlDelegateModel); |
| if (count <= 0|| !d->m_complete) |
| return; |
| |
| d->m_count -= count; |
| const QList<QQmlDelegateModelItem *> cache = d->m_cache; |
| //Prevents items being deleted in remove loop |
| for (QQmlDelegateModelItem *item : cache) |
| item->referenceObject(); |
| |
| for (int i = 0, c = cache.count(); i < c; ++i) { |
| QQmlDelegateModelItem *item = cache.at(i); |
| // layout change triggered by removal of a previous item might have |
| // already invalidated this item in d->m_cache and deleted it |
| if (!d->m_cache.isSharedWith(cache) && !d->m_cache.contains(item)) |
| continue; |
| |
| if (item->modelIndex() >= index + count) { |
| const int newIndex = item->modelIndex() - count; |
| const int row = newIndex; |
| const int column = 0; |
| item->setModelIndex(newIndex, row, column); |
| } else if (item->modelIndex() >= index) { |
| item->setModelIndex(-1, -1, -1); |
| } |
| } |
| //Release items which are referenced before the loop |
| for (QQmlDelegateModelItem *item : cache) |
| item->releaseObject(); |
| |
| QVector<Compositor::Remove> removes; |
| d->m_compositor.listItemsRemoved(&d->m_adaptorModel, index, count, &removes); |
| d->itemsRemoved(removes); |
| |
| d->emitChanges(); |
| } |
| |
| void QQmlDelegateModelPrivate::itemsMoved( |
| const QVector<Compositor::Remove> &removes, const QVector<Compositor::Insert> &inserts) |
| { |
| QHash<int, QList<QQmlDelegateModelItem *> > movedItems; |
| |
| QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> translatedRemoves(m_groupCount); |
| itemsRemoved(removes, &translatedRemoves, &movedItems); |
| |
| QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> translatedInserts(m_groupCount); |
| itemsInserted(inserts, &translatedInserts, &movedItems); |
| Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); |
| Q_ASSERT(movedItems.isEmpty()); |
| if (!m_delegate) |
| return; |
| |
| for (int i = 1; i < m_groupCount; ++i) { |
| QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.move( |
| translatedRemoves.at(i), |
| translatedInserts.at(i)); |
| } |
| } |
| |
| void QQmlDelegateModel::_q_itemsMoved(int from, int to, int count) |
| { |
| Q_D(QQmlDelegateModel); |
| if (count <= 0 || !d->m_complete) |
| return; |
| |
| const int minimum = qMin(from, to); |
| const int maximum = qMax(from, to) + count; |
| const int difference = from > to ? count : -count; |
| |
| const QList<QQmlDelegateModelItem *> cache = d->m_cache; |
| for (int i = 0, c = cache.count(); i < c; ++i) { |
| QQmlDelegateModelItem *item = cache.at(i); |
| // layout change triggered by changing the modelIndex might have |
| // already invalidated this item in d->m_cache and deleted it. |
| if (!d->m_cache.isSharedWith(cache) && !d->m_cache.contains(item)) |
| continue; |
| |
| if (item->modelIndex() >= from && item->modelIndex() < from + count) { |
| const int newIndex = item->modelIndex() - from + to; |
| const int row = newIndex; |
| const int column = 0; |
| item->setModelIndex(newIndex, row, column); |
| } else if (item->modelIndex() >= minimum && item->modelIndex() < maximum) { |
| const int newIndex = item->modelIndex() + difference; |
| const int row = newIndex; |
| const int column = 0; |
| item->setModelIndex(newIndex, row, column); |
| } |
| } |
| |
| QVector<Compositor::Remove> removes; |
| QVector<Compositor::Insert> inserts; |
| d->m_compositor.listItemsMoved(&d->m_adaptorModel, from, to, count, &removes, &inserts); |
| d->itemsMoved(removes, inserts); |
| d->emitChanges(); |
| } |
| |
| void QQmlDelegateModelPrivate::emitModelUpdated(const QQmlChangeSet &changeSet, bool reset) |
| { |
| Q_Q(QQmlDelegateModel); |
| emit q->modelUpdated(changeSet, reset); |
| if (changeSet.difference() != 0) |
| emit q->countChanged(); |
| } |
| |
| void QQmlDelegateModelPrivate::delegateChanged(bool add, bool remove) |
| { |
| Q_Q(QQmlDelegateModel); |
| if (!m_complete) |
| return; |
| |
| if (m_transaction) { |
| qmlWarning(q) << QQmlDelegateModel::tr("The delegates of a DelegateModel cannot be changed within onUpdated."); |
| return; |
| } |
| |
| if (remove) { |
| for (int i = 1; i < m_groupCount; ++i) { |
| QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.remove( |
| 0, m_compositor.count(Compositor::Group(i))); |
| } |
| } |
| if (add) { |
| for (int i = 1; i < m_groupCount; ++i) { |
| QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.insert( |
| 0, m_compositor.count(Compositor::Group(i))); |
| } |
| } |
| emitChanges(); |
| } |
| |
| void QQmlDelegateModelPrivate::emitChanges() |
| { |
| if (m_transaction || !m_complete || !m_context || !m_context->isValid()) |
| return; |
| |
| m_transaction = true; |
| QV4::ExecutionEngine *engine = m_context->engine()->handle(); |
| for (int i = 1; i < m_groupCount; ++i) |
| QQmlDelegateModelGroupPrivate::get(m_groups[i])->emitChanges(engine); |
| m_transaction = false; |
| |
| const bool reset = m_reset; |
| m_reset = false; |
| for (int i = 1; i < m_groupCount; ++i) |
| QQmlDelegateModelGroupPrivate::get(m_groups[i])->emitModelUpdated(reset); |
| |
| auto cacheCopy = m_cache; // deliberate; emitChanges may alter m_cache |
| for (QQmlDelegateModelItem *cacheItem : qAsConst(cacheCopy)) { |
| if (cacheItem->attached) |
| cacheItem->attached->emitChanges(); |
| } |
| } |
| |
| void QQmlDelegateModel::_q_modelReset() |
| { |
| Q_D(QQmlDelegateModel); |
| if (!d->m_delegate) |
| return; |
| |
| int oldCount = d->m_count; |
| d->m_adaptorModel.rootIndex = QModelIndex(); |
| |
| if (d->m_complete) { |
| d->m_count = d->adaptorModelCount(); |
| |
| const QList<QQmlDelegateModelItem *> cache = d->m_cache; |
| for (int i = 0, c = cache.count(); i < c; ++i) { |
| QQmlDelegateModelItem *item = cache.at(i); |
| // layout change triggered by changing the modelIndex might have |
| // already invalidated this item in d->m_cache and deleted it. |
| if (!d->m_cache.isSharedWith(cache) && !d->m_cache.contains(item)) |
| continue; |
| |
| if (item->modelIndex() != -1) |
| item->setModelIndex(-1, -1, -1); |
| } |
| |
| QVector<Compositor::Remove> removes; |
| QVector<Compositor::Insert> inserts; |
| if (oldCount) |
| d->m_compositor.listItemsRemoved(&d->m_adaptorModel, 0, oldCount, &removes); |
| if (d->m_count) |
| d->m_compositor.listItemsInserted(&d->m_adaptorModel, 0, d->m_count, &inserts); |
| d->itemsMoved(removes, inserts); |
| d->m_reset = true; |
| |
| if (d->m_adaptorModel.canFetchMore()) |
| d->m_adaptorModel.fetchMore(); |
| |
| d->emitChanges(); |
| } |
| emit rootIndexChanged(); |
| } |
| |
| void QQmlDelegateModel::_q_rowsInserted(const QModelIndex &parent, int begin, int end) |
| { |
| Q_D(QQmlDelegateModel); |
| if (parent == d->m_adaptorModel.rootIndex) |
| _q_itemsInserted(begin, end - begin + 1); |
| } |
| |
| void QQmlDelegateModel::_q_rowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end) |
| { |
| Q_D(QQmlDelegateModel); |
| if (!d->m_adaptorModel.rootIndex.isValid()) |
| return; |
| const QModelIndex index = d->m_adaptorModel.rootIndex; |
| if (index.parent() == parent && index.row() >= begin && index.row() <= end) { |
| const int oldCount = d->m_count; |
| d->m_count = 0; |
| d->disconnectFromAbstractItemModel(); |
| d->m_adaptorModel.invalidateModel(); |
| |
| if (d->m_complete && oldCount > 0) { |
| QVector<Compositor::Remove> removes; |
| d->m_compositor.listItemsRemoved(&d->m_adaptorModel, 0, oldCount, &removes); |
| d->itemsRemoved(removes); |
| d->emitChanges(); |
| } |
| } |
| } |
| |
| void QQmlDelegateModel::_q_rowsRemoved(const QModelIndex &parent, int begin, int end) |
| { |
| Q_D(QQmlDelegateModel); |
| if (parent == d->m_adaptorModel.rootIndex) |
| _q_itemsRemoved(begin, end - begin + 1); |
| } |
| |
| void QQmlDelegateModel::_q_rowsMoved( |
| const QModelIndex &sourceParent, int sourceStart, int sourceEnd, |
| const QModelIndex &destinationParent, int destinationRow) |
| { |
| Q_D(QQmlDelegateModel); |
| const int count = sourceEnd - sourceStart + 1; |
| if (destinationParent == d->m_adaptorModel.rootIndex && sourceParent == d->m_adaptorModel.rootIndex) { |
| _q_itemsMoved(sourceStart, sourceStart > destinationRow ? destinationRow : destinationRow - count, count); |
| } else if (sourceParent == d->m_adaptorModel.rootIndex) { |
| _q_itemsRemoved(sourceStart, count); |
| } else if (destinationParent == d->m_adaptorModel.rootIndex) { |
| _q_itemsInserted(destinationRow, count); |
| } |
| } |
| |
| void QQmlDelegateModel::_q_dataChanged(const QModelIndex &begin, const QModelIndex &end, const QVector<int> &roles) |
| { |
| Q_D(QQmlDelegateModel); |
| if (begin.parent() == d->m_adaptorModel.rootIndex) |
| _q_itemsChanged(begin.row(), end.row() - begin.row() + 1, roles); |
| } |
| |
| bool QQmlDelegateModel::isDescendantOf(const QPersistentModelIndex& desc, const QList< QPersistentModelIndex >& parents) const |
| { |
| for (int i = 0, c = parents.count(); i < c; ++i) { |
| for (QPersistentModelIndex parent = desc; parent.isValid(); parent = parent.parent()) { |
| if (parent == parents[i]) |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| void QQmlDelegateModel::_q_layoutChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint) |
| { |
| Q_D(QQmlDelegateModel); |
| if (!d->m_complete) |
| return; |
| |
| if (hint == QAbstractItemModel::VerticalSortHint) { |
| if (!parents.isEmpty() && d->m_adaptorModel.rootIndex.isValid() && !isDescendantOf(d->m_adaptorModel.rootIndex, parents)) { |
| return; |
| } |
| |
| // mark all items as changed |
| _q_itemsChanged(0, d->m_count, QVector<int>()); |
| |
| } else if (hint == QAbstractItemModel::HorizontalSortHint) { |
| // Ignored |
| } else { |
| // We don't know what's going on, so reset the model |
| _q_modelReset(); |
| } |
| } |
| |
| QQmlDelegateModelAttached *QQmlDelegateModel::qmlAttachedProperties(QObject *obj) |
| { |
| if (QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(obj)) { |
| if (cacheItem->object == obj) { // Don't create attached item for child objects. |
| cacheItem->attached = new QQmlDelegateModelAttached(cacheItem, obj); |
| return cacheItem->attached; |
| } |
| } |
| return new QQmlDelegateModelAttached(obj); |
| } |
| |
| bool QQmlDelegateModelPrivate::insert(Compositor::insert_iterator &before, const QV4::Value &object, int groups) |
| { |
| if (!m_context || !m_context->isValid()) |
| return false; |
| |
| QQmlDelegateModelItem *cacheItem = m_adaptorModel.createItem(m_cacheMetaType, -1); |
| if (!cacheItem) |
| return false; |
| if (!object.isObject()) |
| return false; |
| |
| QV4::ExecutionEngine *v4 = object.as<QV4::Object>()->engine(); |
| QV4::Scope scope(v4); |
| QV4::ScopedObject o(scope, object); |
| if (!o) |
| return false; |
| |
| QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly); |
| QV4::ScopedValue propertyName(scope); |
| QV4::ScopedValue v(scope); |
| while (1) { |
| propertyName = it.nextPropertyNameAsString(v); |
| if (propertyName->isNull()) |
| break; |
| cacheItem->setValue(propertyName->toQStringNoThrow(), scope.engine->toVariant(v, QMetaType::UnknownType)); |
| } |
| |
| cacheItem->groups = groups | Compositor::UnresolvedFlag | Compositor::CacheFlag; |
| |
| // Must be before the new object is inserted into the cache or its indexes will be adjusted too. |
| itemsInserted(QVector<Compositor::Insert>(1, Compositor::Insert(before, 1, cacheItem->groups & ~Compositor::CacheFlag))); |
| |
| before = m_compositor.insert(before, nullptr, 0, 1, cacheItem->groups); |
| m_cache.insert(before.cacheIndex, cacheItem); |
| |
| return true; |
| } |
| |
| //============================================================================ |
| |
| QQmlDelegateModelItemMetaType::QQmlDelegateModelItemMetaType( |
| QV4::ExecutionEngine *engine, QQmlDelegateModel *model, const QStringList &groupNames) |
| : model(model) |
| , groupCount(groupNames.count() + 1) |
| , v4Engine(engine) |
| , metaObject(nullptr) |
| , groupNames(groupNames) |
| { |
| } |
| |
| QQmlDelegateModelItemMetaType::~QQmlDelegateModelItemMetaType() |
| { |
| if (metaObject) |
| metaObject->release(); |
| } |
| |
| void QQmlDelegateModelItemMetaType::initializeMetaObject() |
| { |
| QMetaObjectBuilder builder; |
| builder.setFlags(QMetaObjectBuilder::DynamicMetaObject); |
| builder.setClassName(QQmlDelegateModelAttached::staticMetaObject.className()); |
| builder.setSuperClass(&QQmlDelegateModelAttached::staticMetaObject); |
| |
| int notifierId = 0; |
| for (int i = 0; i < groupNames.count(); ++i, ++notifierId) { |
| QString propertyName = QLatin1String("in") + groupNames.at(i); |
| propertyName.replace(2, 1, propertyName.at(2).toUpper()); |
| builder.addSignal("__" + propertyName.toUtf8() + "Changed()"); |
| QMetaPropertyBuilder propertyBuilder = builder.addProperty( |
| propertyName.toUtf8(), "bool", notifierId); |
| propertyBuilder.setWritable(true); |
| } |
| for (int i = 0; i < groupNames.count(); ++i, ++notifierId) { |
| const QString propertyName = groupNames.at(i) + QLatin1String("Index"); |
| builder.addSignal("__" + propertyName.toUtf8() + "Changed()"); |
| QMetaPropertyBuilder propertyBuilder = builder.addProperty( |
| propertyName.toUtf8(), "int", notifierId); |
| propertyBuilder.setWritable(true); |
| } |
| |
| metaObject = new QQmlDelegateModelAttachedMetaObject(this, builder.toMetaObject()); |
| } |
| |
| void QQmlDelegateModelItemMetaType::initializePrototype() |
| { |
| QV4::Scope scope(v4Engine); |
| |
| QV4::ScopedObject proto(scope, v4Engine->newObject()); |
| proto->defineAccessorProperty(QStringLiteral("model"), QQmlDelegateModelItem::get_model, nullptr); |
| proto->defineAccessorProperty(QStringLiteral("groups"), QQmlDelegateModelItem::get_groups, QQmlDelegateModelItem::set_groups); |
| QV4::ScopedString s(scope); |
| QV4::ScopedProperty p(scope); |
| |
| s = v4Engine->newString(QStringLiteral("isUnresolved")); |
| QV4::ScopedFunctionObject f(scope); |
| QV4::ExecutionContext *global = scope.engine->rootContext(); |
| p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, 30, QQmlDelegateModelItem::get_member))); |
| p->setSetter(nullptr); |
| proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); |
| |
| s = v4Engine->newString(QStringLiteral("inItems")); |
| p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Default, QQmlDelegateModelItem::get_member))); |
| p->setSetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Default, QQmlDelegateModelItem::set_member))); |
| proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); |
| |
| s = v4Engine->newString(QStringLiteral("inPersistedItems")); |
| p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Persisted, QQmlDelegateModelItem::get_member))); |
| p->setSetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Persisted, QQmlDelegateModelItem::set_member))); |
| proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); |
| |
| s = v4Engine->newString(QStringLiteral("itemsIndex")); |
| p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Default, QQmlDelegateModelItem::get_index))); |
| proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); |
| |
| s = v4Engine->newString(QStringLiteral("persistedItemsIndex")); |
| p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Persisted, QQmlDelegateModelItem::get_index))); |
| p->setSetter(nullptr); |
| proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); |
| |
| for (int i = 2; i < groupNames.count(); ++i) { |
| QString propertyName = QLatin1String("in") + groupNames.at(i); |
| propertyName.replace(2, 1, propertyName.at(2).toUpper()); |
| s = v4Engine->newString(propertyName); |
| p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, i + 1, QQmlDelegateModelItem::get_member))); |
| p->setSetter((f = QV4::DelegateModelGroupFunction::create(global, i + 1, QQmlDelegateModelItem::set_member))); |
| proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); |
| } |
| for (int i = 2; i < groupNames.count(); ++i) { |
| const QString propertyName = groupNames.at(i) + QLatin1String("Index"); |
| s = v4Engine->newString(propertyName); |
| p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, i + 1, QQmlDelegateModelItem::get_index))); |
| p->setSetter(nullptr); |
| proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); |
| } |
| modelItemProto.set(v4Engine, proto); |
| } |
| |
| int QQmlDelegateModelItemMetaType::parseGroups(const QStringList &groups) const |
| { |
| int groupFlags = 0; |
| for (const QString &groupName : groups) { |
| int index = groupNames.indexOf(groupName); |
| if (index != -1) |
| groupFlags |= 2 << index; |
| } |
| return groupFlags; |
| } |
| |
| int QQmlDelegateModelItemMetaType::parseGroups(const QV4::Value &groups) const |
| { |
| int groupFlags = 0; |
| QV4::Scope scope(v4Engine); |
| |
| QV4::ScopedString s(scope, groups); |
| if (s) { |
| const QString groupName = s->toQString(); |
| int index = groupNames.indexOf(groupName); |
| if (index != -1) |
| groupFlags |= 2 << index; |
| return groupFlags; |
| } |
| |
| QV4::ScopedArrayObject array(scope, groups); |
| if (array) { |
| QV4::ScopedValue v(scope); |
| uint arrayLength = array->getLength(); |
| for (uint i = 0; i < arrayLength; ++i) { |
| v = array->get(i); |
| const QString groupName = v->toQString(); |
| int index = groupNames.indexOf(groupName); |
| if (index != -1) |
| groupFlags |= 2 << index; |
| } |
| } |
| return groupFlags; |
| } |
| |
| QV4::ReturnedValue QQmlDelegateModelItem::get_model(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) |
| { |
| QV4::Scope scope(b); |
| QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>()); |
| if (!o) |
| return b->engine()->throwTypeError(QStringLiteral("Not a valid DelegateModel object")); |
| if (!o->d()->item->metaType->model) |
| RETURN_UNDEFINED(); |
| |
| return o->d()->item->get(); |
| } |
| |
| QV4::ReturnedValue QQmlDelegateModelItem::get_groups(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) |
| { |
| QV4::Scope scope(b); |
| QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>()); |
| if (!o) |
| return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object")); |
| |
| QStringList groups; |
| for (int i = 1; i < o->d()->item->metaType->groupCount; ++i) { |
| if (o->d()->item->groups & (1 << i)) |
| groups.append(o->d()->item->metaType->groupNames.at(i - 1)); |
| } |
| |
| return scope.engine->fromVariant(groups); |
| } |
| |
| QV4::ReturnedValue QQmlDelegateModelItem::set_groups(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc) |
| { |
| QV4::Scope scope(b); |
| QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>()); |
| if (!o) |
| return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object")); |
| |
| if (!argc) |
| THROW_TYPE_ERROR(); |
| |
| if (!o->d()->item->metaType->model) |
| RETURN_UNDEFINED(); |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(o->d()->item->metaType->model); |
| |
| const int groupFlags = model->m_cacheMetaType->parseGroups(argv[0]); |
| const int cacheIndex = model->m_cache.indexOf(o->d()->item); |
| Compositor::iterator it = model->m_compositor.find(Compositor::Cache, cacheIndex); |
| model->setGroups(it, 1, Compositor::Cache, groupFlags); |
| return QV4::Encode::undefined(); |
| } |
| |
| QV4::ReturnedValue QQmlDelegateModelItem::get_member(QQmlDelegateModelItem *thisItem, uint flag, const QV4::Value &) |
| { |
| return QV4::Encode(bool(thisItem->groups & (1 << flag))); |
| } |
| |
| QV4::ReturnedValue QQmlDelegateModelItem::set_member(QQmlDelegateModelItem *cacheItem, uint flag, const QV4::Value &arg) |
| { |
| if (!cacheItem->metaType->model) |
| return QV4::Encode::undefined(); |
| |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(cacheItem->metaType->model); |
| |
| bool member = arg.toBoolean(); |
| uint groupFlag = (1 << flag); |
| if (member == ((cacheItem->groups & groupFlag) != 0)) |
| return QV4::Encode::undefined(); |
| |
| const int cacheIndex = model->m_cache.indexOf(cacheItem); |
| Compositor::iterator it = model->m_compositor.find(Compositor::Cache, cacheIndex); |
| if (member) |
| model->addGroups(it, 1, Compositor::Cache, groupFlag); |
| else |
| model->removeGroups(it, 1, Compositor::Cache, groupFlag); |
| return QV4::Encode::undefined(); |
| } |
| |
| QV4::ReturnedValue QQmlDelegateModelItem::get_index(QQmlDelegateModelItem *thisItem, uint flag, const QV4::Value &) |
| { |
| return QV4::Encode((int)thisItem->groupIndex(Compositor::Group(flag))); |
| } |
| |
| void QQmlDelegateModelItem::childContextObjectDestroyed(QObject *childContextObject) |
| { |
| if (!contextData) |
| return; |
| |
| for (QQmlContextData *ctxt = contextData->childContexts; ctxt; ctxt = ctxt->nextChild) { |
| if (ctxt->contextObject == childContextObject) |
| ctxt->contextObject = nullptr; |
| } |
| } |
| |
| |
| //--------------------------------------------------------------------------- |
| |
| DEFINE_OBJECT_VTABLE(QQmlDelegateModelItemObject); |
| |
| void QV4::Heap::QQmlDelegateModelItemObject::destroy() |
| { |
| item->Dispose(); |
| Object::destroy(); |
| } |
| |
| |
| QQmlDelegateModelItem::QQmlDelegateModelItem( |
| const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType, |
| QQmlAdaptorModel::Accessors *accessor, |
| int modelIndex, int row, int column) |
| : v4(metaType->v4Engine) |
| , metaType(metaType) |
| , contextData(nullptr) |
| , object(nullptr) |
| , attached(nullptr) |
| , incubationTask(nullptr) |
| , delegate(nullptr) |
| , poolTime(0) |
| , objectRef(0) |
| , scriptRef(0) |
| , groups(0) |
| , index(modelIndex) |
| , row(row) |
| , column(column) |
| { |
| if (accessor->propertyCache) { |
| // The property cache in the accessor is common for all the model |
| // items in the model it wraps. It describes available model roles, |
| // together with revisioned properties like row, column and index, all |
| // which should be available in the delegate. We assign this cache to the |
| // model item so that the QML engine can use the revision information |
| // when resolving the properties (rather than falling back to just |
| // inspecting the QObject in the model item directly). |
| QQmlData *qmldata = QQmlData::get(this, true); |
| if (qmldata->propertyCache) |
| qmldata->propertyCache->release(); |
| qmldata->propertyCache = accessor->propertyCache.data(); |
| qmldata->propertyCache->addref(); |
| } |
| } |
| |
| QQmlDelegateModelItem::~QQmlDelegateModelItem() |
| { |
| Q_ASSERT(scriptRef == 0); |
| Q_ASSERT(objectRef == 0); |
| Q_ASSERT(!object); |
| |
| if (incubationTask) { |
| if (metaType->model) |
| QQmlDelegateModelPrivate::get(metaType->model)->releaseIncubator(incubationTask); |
| else |
| delete incubationTask; |
| } |
| } |
| |
| void QQmlDelegateModelItem::Dispose() |
| { |
| --scriptRef; |
| if (isReferenced()) |
| return; |
| |
| if (metaType->model) { |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(metaType->model); |
| model->removeCacheItem(this); |
| } |
| delete this; |
| } |
| |
| void QQmlDelegateModelItem::setModelIndex(int idx, int newRow, int newColumn, bool alwaysEmit) |
| { |
| const int prevIndex = index; |
| const int prevRow = row; |
| const int prevColumn = column; |
| |
| index = idx; |
| row = newRow; |
| column = newColumn; |
| |
| if (idx != prevIndex || alwaysEmit) |
| emit modelIndexChanged(); |
| if (row != prevRow || alwaysEmit) |
| emit rowChanged(); |
| if (column != prevColumn || alwaysEmit) |
| emit columnChanged(); |
| } |
| |
| void QQmlDelegateModelItem::destroyObject() |
| { |
| Q_ASSERT(object); |
| Q_ASSERT(contextData); |
| |
| QQmlData *data = QQmlData::get(object); |
| Q_ASSERT(data); |
| if (data->ownContext) { |
| data->ownContext->clearContext(); |
| if (data->ownContext->contextObject == object) |
| data->ownContext->contextObject = nullptr; |
| data->ownContext = nullptr; |
| data->context = nullptr; |
| } |
| object->deleteLater(); |
| |
| if (attached) { |
| attached->m_cacheItem = nullptr; |
| attached = nullptr; |
| } |
| |
| contextData->invalidate(); |
| contextData = nullptr; |
| object = nullptr; |
| } |
| |
| 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)) { |
| return cacheItem; |
| } |
| } |
| return nullptr; |
| } |
| |
| int QQmlDelegateModelItem::groupIndex(Compositor::Group group) |
| { |
| if (QQmlDelegateModelPrivate * const model = metaType->model |
| ? QQmlDelegateModelPrivate::get(metaType->model) |
| : nullptr) { |
| return model->m_compositor.find(Compositor::Cache, model->m_cache.indexOf(this)).index[group]; |
| } |
| return -1; |
| } |
| |
| //--------------------------------------------------------------------------- |
| |
| QQmlDelegateModelAttachedMetaObject::QQmlDelegateModelAttachedMetaObject( |
| QQmlDelegateModelItemMetaType *metaType, QMetaObject *metaObject) |
| : metaType(metaType) |
| , metaObject(metaObject) |
| , memberPropertyOffset(QQmlDelegateModelAttached::staticMetaObject.propertyCount()) |
| , indexPropertyOffset(QQmlDelegateModelAttached::staticMetaObject.propertyCount() + metaType->groupNames.count()) |
| { |
| // Don't reference count the meta-type here as that would create a circular reference. |
| // Instead we rely the fact that the meta-type's reference count can't reach 0 without first |
| // destroying all delegates with attached objects. |
| *static_cast<QMetaObject *>(this) = *metaObject; |
| } |
| |
| QQmlDelegateModelAttachedMetaObject::~QQmlDelegateModelAttachedMetaObject() |
| { |
| ::free(metaObject); |
| } |
| |
| void QQmlDelegateModelAttachedMetaObject::objectDestroyed(QObject *) |
| { |
| release(); |
| } |
| |
| int QQmlDelegateModelAttachedMetaObject::metaCall(QObject *object, QMetaObject::Call call, int _id, void **arguments) |
| { |
| QQmlDelegateModelAttached *attached = static_cast<QQmlDelegateModelAttached *>(object); |
| if (call == QMetaObject::ReadProperty) { |
| if (_id >= indexPropertyOffset) { |
| Compositor::Group group = Compositor::Group(_id - indexPropertyOffset + 1); |
| *static_cast<int *>(arguments[0]) = attached->m_currentIndex[group]; |
| return -1; |
| } else if (_id >= memberPropertyOffset) { |
| Compositor::Group group = Compositor::Group(_id - memberPropertyOffset + 1); |
| *static_cast<bool *>(arguments[0]) = attached->m_cacheItem->groups & (1 << group); |
| return -1; |
| } |
| } else if (call == QMetaObject::WriteProperty) { |
| if (_id >= memberPropertyOffset) { |
| if (!metaType->model) |
| return -1; |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(metaType->model); |
| Compositor::Group group = Compositor::Group(_id - memberPropertyOffset + 1); |
| const int groupFlag = 1 << group; |
| const bool member = attached->m_cacheItem->groups & groupFlag; |
| if (member && !*static_cast<bool *>(arguments[0])) { |
| Compositor::iterator it = model->m_compositor.find( |
| group, attached->m_currentIndex[group]); |
| model->removeGroups(it, 1, group, groupFlag); |
| } else if (!member && *static_cast<bool *>(arguments[0])) { |
| for (int i = 1; i < metaType->groupCount; ++i) { |
| if (attached->m_cacheItem->groups & (1 << i)) { |
| Compositor::iterator it = model->m_compositor.find( |
| Compositor::Group(i), attached->m_currentIndex[i]); |
| model->addGroups(it, 1, Compositor::Group(i), groupFlag); |
| break; |
| } |
| } |
| } |
| return -1; |
| } |
| } |
| return attached->qt_metacall(call, _id, arguments); |
| } |
| |
| QQmlDelegateModelAttached::QQmlDelegateModelAttached(QObject *parent) |
| : m_cacheItem(nullptr) |
| , m_previousGroups(0) |
| { |
| QQml_setParent_noEvent(this, parent); |
| } |
| |
| QQmlDelegateModelAttached::QQmlDelegateModelAttached( |
| QQmlDelegateModelItem *cacheItem, QObject *parent) |
| : m_cacheItem(cacheItem) |
| , m_previousGroups(cacheItem->groups) |
| { |
| QQml_setParent_noEvent(this, parent); |
| resetCurrentIndex(); |
| // Let m_previousIndex be equal to m_currentIndex |
| std::copy(std::begin(m_currentIndex), std::end(m_currentIndex), std::begin(m_previousIndex)); |
| |
| if (!cacheItem->metaType->metaObject) |
| cacheItem->metaType->initializeMetaObject(); |
| |
| QObjectPrivate::get(this)->metaObject = cacheItem->metaType->metaObject; |
| cacheItem->metaType->metaObject->addref(); |
| } |
| |
| void QQmlDelegateModelAttached::resetCurrentIndex() |
| { |
| if (QQDMIncubationTask *incubationTask = m_cacheItem->incubationTask) { |
| for (int i = 1; i < qMin<int>(m_cacheItem->metaType->groupCount, Compositor::MaximumGroupCount); ++i) |
| m_currentIndex[i] = incubationTask->index[i]; |
| } else { |
| QQmlDelegateModelPrivate * const model = QQmlDelegateModelPrivate::get(m_cacheItem->metaType->model); |
| Compositor::iterator it = model->m_compositor.find( |
| Compositor::Cache, model->m_cache.indexOf(m_cacheItem)); |
| for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i) |
| m_currentIndex[i] = it.index[i]; |
| } |
| } |
| |
| /*! |
| \qmlattachedproperty model QtQml.Models::DelegateModel::model |
| |
| This attached property holds the data model this delegate instance belongs to. |
| |
| It is attached to each instance of the delegate. |
| */ |
| |
| QQmlDelegateModel *QQmlDelegateModelAttached::model() const |
| { |
| return m_cacheItem ? m_cacheItem->metaType->model : nullptr; |
| } |
| |
| /*! |
| \qmlattachedproperty stringlist QtQml.Models::DelegateModel::groups |
| |
| This attached property holds the name of DelegateModelGroups the item belongs to. |
| |
| It is attached to each instance of the delegate. |
| */ |
| |
| QStringList QQmlDelegateModelAttached::groups() const |
| { |
| QStringList groups; |
| |
| if (!m_cacheItem) |
| return groups; |
| for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i) { |
| if (m_cacheItem->groups & (1 << i)) |
| groups.append(m_cacheItem->metaType->groupNames.at(i - 1)); |
| } |
| return groups; |
| } |
| |
| void QQmlDelegateModelAttached::setGroups(const QStringList &groups) |
| { |
| if (!m_cacheItem) |
| return; |
| |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_cacheItem->metaType->model); |
| |
| const int groupFlags = model->m_cacheMetaType->parseGroups(groups); |
| const int cacheIndex = model->m_cache.indexOf(m_cacheItem); |
| Compositor::iterator it = model->m_compositor.find(Compositor::Cache, cacheIndex); |
| model->setGroups(it, 1, Compositor::Cache, groupFlags); |
| } |
| |
| /*! |
| \qmlattachedproperty bool QtQml.Models::DelegateModel::isUnresolved |
| |
| This attached property indicates whether the visual item is bound to a data model index. |
| Returns true if the item is not bound to the model, and false if it is. |
| |
| An unresolved item can be bound to the data model using the DelegateModelGroup::resolve() |
| function. |
| |
| It is attached to each instance of the delegate. |
| */ |
| |
| bool QQmlDelegateModelAttached::isUnresolved() const |
| { |
| if (!m_cacheItem) |
| return false; |
| |
| return m_cacheItem->groups & Compositor::UnresolvedFlag; |
| } |
| |
| /*! |
| \qmlattachedproperty bool QtQml.Models::DelegateModel::inItems |
| |
| This attached property holds whether the item belongs to the default \l items |
| DelegateModelGroup. |
| |
| Changing this property will add or remove the item from the items group. |
| |
| It is attached to each instance of the delegate. |
| */ |
| |
| /*! |
| \qmlattachedproperty int QtQml.Models::DelegateModel::itemsIndex |
| |
| This attached property holds the index of the item in the default \l items DelegateModelGroup. |
| |
| It is attached to each instance of the delegate. |
| */ |
| |
| /*! |
| \qmlattachedproperty bool QtQml.Models::DelegateModel::inPersistedItems |
| |
| This attached property holds whether the item belongs to the \l persistedItems |
| DelegateModelGroup. |
| |
| Changing this property will add or remove the item from the items group. Change with caution |
| as removing an item from the persistedItems group will destroy the current instance if it is |
| not referenced by a model. |
| |
| It is attached to each instance of the delegate. |
| */ |
| |
| /*! |
| \qmlattachedproperty int QtQml.Models::DelegateModel::persistedItemsIndex |
| |
| This attached property holds the index of the item in the \l persistedItems DelegateModelGroup. |
| |
| It is attached to each instance of the delegate. |
| */ |
| |
| void QQmlDelegateModelAttached::emitChanges() |
| { |
| const int groupChanges = m_previousGroups ^ m_cacheItem->groups; |
| m_previousGroups = m_cacheItem->groups; |
| |
| int indexChanges = 0; |
| for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i) { |
| if (m_previousIndex[i] != m_currentIndex[i]) { |
| m_previousIndex[i] = m_currentIndex[i]; |
| indexChanges |= (1 << i); |
| } |
| } |
| |
| int notifierId = 0; |
| const QMetaObject *meta = metaObject(); |
| for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i, ++notifierId) { |
| if (groupChanges & (1 << i)) |
| QMetaObject::activate(this, meta, notifierId, nullptr); |
| } |
| for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i, ++notifierId) { |
| if (indexChanges & (1 << i)) |
| QMetaObject::activate(this, meta, notifierId, nullptr); |
| } |
| |
| if (groupChanges) |
| emit groupsChanged(); |
| } |
| |
| //============================================================================ |
| |
| void QQmlDelegateModelGroupPrivate::setModel(QQmlDelegateModel *m, Compositor::Group g) |
| { |
| Q_ASSERT(!model); |
| model = m; |
| group = g; |
| } |
| |
| bool QQmlDelegateModelGroupPrivate::isChangedConnected() |
| { |
| Q_Q(QQmlDelegateModelGroup); |
| IS_SIGNAL_CONNECTED(q, QQmlDelegateModelGroup, changed, (const QJSValue &,const QJSValue &)); |
| } |
| |
| void QQmlDelegateModelGroupPrivate::emitChanges(QV4::ExecutionEngine *v4) |
| { |
| Q_Q(QQmlDelegateModelGroup); |
| if (isChangedConnected() && !changeSet.isEmpty()) { |
| emit q->changed(QJSValue(v4, engineData(v4)->array(v4, changeSet.removes())), |
| QJSValue(v4, engineData(v4)->array(v4, changeSet.inserts()))); |
| } |
| if (changeSet.difference() != 0) |
| emit q->countChanged(); |
| } |
| |
| void QQmlDelegateModelGroupPrivate::emitModelUpdated(bool reset) |
| { |
| for (QQmlDelegateModelGroupEmitterList::iterator it = emitters.begin(); it != emitters.end(); ++it) |
| it->emitModelUpdated(changeSet, reset); |
| changeSet.clear(); |
| } |
| |
| typedef QQmlDelegateModelGroupEmitterList::iterator GroupEmitterListIt; |
| |
| void QQmlDelegateModelGroupPrivate::createdPackage(int index, QQuickPackage *package) |
| { |
| for (GroupEmitterListIt it = emitters.begin(), end = emitters.end(); it != end; ++it) |
| it->createdPackage(index, package); |
| } |
| |
| void QQmlDelegateModelGroupPrivate::initPackage(int index, QQuickPackage *package) |
| { |
| for (GroupEmitterListIt it = emitters.begin(), end = emitters.end(); it != end; ++it) |
| it->initPackage(index, package); |
| } |
| |
| void QQmlDelegateModelGroupPrivate::destroyingPackage(QQuickPackage *package) |
| { |
| for (GroupEmitterListIt it = emitters.begin(), end = emitters.end(); it != end; ++it) |
| it->destroyingPackage(package); |
| } |
| |
| /*! |
| \qmltype DelegateModelGroup |
| \instantiates QQmlDelegateModelGroup |
| \inqmlmodule QtQml.Models |
| \ingroup qtquick-models |
| \brief Encapsulates a filtered set of visual data items. |
| |
| The DelegateModelGroup type provides a means to address the model data of a |
| DelegateModel's delegate items, as well as sort and filter these delegate |
| items. |
| |
| The initial set of instantiable delegate items in a DelegateModel is represented |
| by its \l {QtQml.Models::DelegateModel::items}{items} group, which normally directly reflects |
| the contents of the model assigned to DelegateModel::model. This set can be changed to |
| the contents of any other member of DelegateModel::groups by assigning the \l name of that |
| DelegateModelGroup to the DelegateModel::filterOnGroup property. |
| |
| The data of an item in a DelegateModelGroup can be accessed using the get() function, which returns |
| information about group membership and indexes as well as model data. In combination |
| with the move() function this can be used to implement view sorting, with remove() to filter |
| items out of a view, or with setGroups() and \l Package delegates to categorize items into |
| different views. Different groups can only be sorted independently if they are disjunct. Moving |
| an item in one group will also move it in all other groups it is a part of. |
| |
| Data from models can be supplemented by inserting data directly into a DelegateModelGroup |
| with the insert() function. This can be used to introduce mock items into a view, or |
| placeholder items that are later \l {resolve()}{resolved} to real model data when it becomes |
| available. |
| |
| Delegate items can also be instantiated directly from a DelegateModelGroup using the |
| create() function, making it possible to use DelegateModel without an accompanying view |
| type or to cherry-pick specific items that should be instantiated irregardless of whether |
| they're currently within a view's visible area. |
| |
| \sa {QML Dynamic View Ordering Tutorial} |
| */ |
| QQmlDelegateModelGroup::QQmlDelegateModelGroup(QObject *parent) |
| : QObject(*new QQmlDelegateModelGroupPrivate, parent) |
| { |
| } |
| |
| QQmlDelegateModelGroup::QQmlDelegateModelGroup( |
| const QString &name, QQmlDelegateModel *model, int index, QObject *parent) |
| : QQmlDelegateModelGroup(parent) |
| { |
| Q_D(QQmlDelegateModelGroup); |
| d->name = name; |
| d->setModel(model, Compositor::Group(index)); |
| } |
| |
| QQmlDelegateModelGroup::~QQmlDelegateModelGroup() |
| { |
| } |
| |
| /*! |
| \qmlproperty string QtQml.Models::DelegateModelGroup::name |
| |
| This property holds the name of the group. |
| |
| Each group in a model must have a unique name starting with a lower case letter. |
| */ |
| |
| QString QQmlDelegateModelGroup::name() const |
| { |
| Q_D(const QQmlDelegateModelGroup); |
| return d->name; |
| } |
| |
| void QQmlDelegateModelGroup::setName(const QString &name) |
| { |
| Q_D(QQmlDelegateModelGroup); |
| if (d->model) |
| return; |
| if (d->name != name) { |
| d->name = name; |
| emit nameChanged(); |
| } |
| } |
| |
| /*! |
| \qmlproperty int QtQml.Models::DelegateModelGroup::count |
| |
| This property holds the number of items in the group. |
| */ |
| |
| int QQmlDelegateModelGroup::count() const |
| { |
| Q_D(const QQmlDelegateModelGroup); |
| if (!d->model) |
| return 0; |
| return QQmlDelegateModelPrivate::get(d->model)->m_compositor.count(d->group); |
| } |
| |
| /*! |
| \qmlproperty bool QtQml.Models::DelegateModelGroup::includeByDefault |
| |
| This property holds whether new items are assigned to this group by default. |
| */ |
| |
| bool QQmlDelegateModelGroup::defaultInclude() const |
| { |
| Q_D(const QQmlDelegateModelGroup); |
| return d->defaultInclude; |
| } |
| |
| void QQmlDelegateModelGroup::setDefaultInclude(bool include) |
| { |
| Q_D(QQmlDelegateModelGroup); |
| if (d->defaultInclude != include) { |
| d->defaultInclude = include; |
| |
| if (d->model) { |
| if (include) |
| QQmlDelegateModelPrivate::get(d->model)->m_compositor.setDefaultGroup(d->group); |
| else |
| QQmlDelegateModelPrivate::get(d->model)->m_compositor.clearDefaultGroup(d->group); |
| } |
| emit defaultIncludeChanged(); |
| } |
| } |
| |
| /*! |
| \qmlmethod object QtQml.Models::DelegateModelGroup::get(int index) |
| |
| Returns a javascript object describing the item at \a index in the group. |
| |
| The returned object contains the same information that is available to a delegate from the |
| DelegateModel attached as well as the model for that item. It has the properties: |
| |
| \list |
| \li \b model The model data of the item. This is the same as the model context property in |
| a delegate |
| \li \b groups A list the of names of groups the item is a member of. This property can be |
| written to change the item's membership. |
| \li \b inItems Whether the item belongs to the \l {QtQml.Models::DelegateModel::items}{items} group. |
| Writing to this property will add or remove the item from the group. |
| \li \b itemsIndex The index of the item within the \l {QtQml.Models::DelegateModel::items}{items} group. |
| \li \b {in<GroupName>} Whether the item belongs to the dynamic group \e groupName. Writing to |
| this property will add or remove the item from the group. |
| \li \b {<groupName>Index} The index of the item within the dynamic group \e groupName. |
| \li \b isUnresolved Whether the item is bound to an index in the model assigned to |
| DelegateModel::model. Returns true if the item is not bound to the model, and false if it is. |
| \endlist |
| */ |
| |
| QJSValue QQmlDelegateModelGroup::get(int index) |
| { |
| Q_D(QQmlDelegateModelGroup); |
| if (!d->model) |
| return QJSValue(); |
| |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); |
| if (!model->m_context || !model->m_context->isValid()) { |
| return QJSValue(); |
| } else if (index < 0 || index >= model->m_compositor.count(d->group)) { |
| qmlWarning(this) << tr("get: index out of range"); |
| return QJSValue(); |
| } |
| |
| Compositor::iterator it = model->m_compositor.find(d->group, index); |
| QQmlDelegateModelItem *cacheItem = it->inCache() |
| ? model->m_cache.at(it.cacheIndex) |
| : 0; |
| |
| if (!cacheItem) { |
| cacheItem = model->m_adaptorModel.createItem( |
| model->m_cacheMetaType, it.modelIndex()); |
| if (!cacheItem) |
| return QJSValue(); |
| cacheItem->groups = it->flags; |
| |
| model->m_cache.insert(it.cacheIndex, cacheItem); |
| model->m_compositor.setFlags(it, 1, Compositor::CacheFlag); |
| } |
| |
| if (model->m_cacheMetaType->modelItemProto.isUndefined()) |
| model->m_cacheMetaType->initializePrototype(); |
| QV4::ExecutionEngine *v4 = model->m_cacheMetaType->v4Engine; |
| QV4::Scope scope(v4); |
| ++cacheItem->scriptRef; |
| QV4::ScopedObject o(scope, v4->memoryManager->allocate<QQmlDelegateModelItemObject>(cacheItem)); |
| QV4::ScopedObject p(scope, model->m_cacheMetaType->modelItemProto.value()); |
| o->setPrototypeOf(p); |
| |
| return QJSValue(v4, o->asReturnedValue()); |
| } |
| |
| bool QQmlDelegateModelGroupPrivate::parseIndex(const QV4::Value &value, int *index, Compositor::Group *group) const |
| { |
| if (value.isNumber()) { |
| *index = value.toInt32(); |
| return true; |
| } |
| |
| if (!value.isObject()) |
| return false; |
| |
| QV4::ExecutionEngine *v4 = value.as<QV4::Object>()->engine(); |
| QV4::Scope scope(v4); |
| QV4::Scoped<QQmlDelegateModelItemObject> object(scope, value); |
| |
| if (object) { |
| QQmlDelegateModelItem * const cacheItem = object->d()->item; |
| if (QQmlDelegateModelPrivate *model = cacheItem->metaType->model |
| ? QQmlDelegateModelPrivate::get(cacheItem->metaType->model) |
| : nullptr) { |
| *index = model->m_cache.indexOf(cacheItem); |
| *group = Compositor::Cache; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /*! |
| \qmlmethod QtQml.Models::DelegateModelGroup::insert(int index, jsdict data, array groups = undefined) |
| \qmlmethod QtQml.Models::DelegateModelGroup::insert(jsdict data, var groups = undefined) |
| |
| Creates a new entry at \a index in a DelegateModel with the values from \a data that |
| correspond to roles in the model assigned to DelegateModel::model. |
| |
| If no index is supplied the data is appended to the model. |
| |
| The optional \a groups parameter identifies the groups the new entry should belong to, |
| if unspecified this is equal to the group insert was called on. |
| |
| Data inserted into a DelegateModel can later be merged with an existing entry in |
| DelegateModel::model using the \l resolve() function. This can be used to create placeholder |
| items that are later replaced by actual data. |
| */ |
| |
| void QQmlDelegateModelGroup::insert(QQmlV4Function *args) |
| { |
| Q_D(QQmlDelegateModelGroup); |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); |
| |
| int index = model->m_compositor.count(d->group); |
| Compositor::Group group = d->group; |
| |
| if (args->length() == 0) |
| return; |
| |
| int i = 0; |
| QV4::Scope scope(args->v4engine()); |
| QV4::ScopedValue v(scope, (*args)[i]); |
| if (d->parseIndex(v, &index, &group)) { |
| if (index < 0 || index > model->m_compositor.count(group)) { |
| qmlWarning(this) << tr("insert: index out of range"); |
| return; |
| } |
| if (++i == args->length()) |
| return; |
| v = (*args)[i]; |
| } |
| |
| Compositor::insert_iterator before = index < model->m_compositor.count(group) |
| ? model->m_compositor.findInsertPosition(group, index) |
| : model->m_compositor.end(); |
| |
| int groups = 1 << d->group; |
| if (++i < args->length()) { |
| QV4::ScopedValue val(scope, (*args)[i]); |
| groups |= model->m_cacheMetaType->parseGroups(val); |
| } |
| |
| if (v->as<QV4::ArrayObject>()) { |
| return; |
| } else if (v->as<QV4::Object>()) { |
| model->insert(before, v, groups); |
| model->emitChanges(); |
| } |
| } |
| |
| /*! |
| \qmlmethod QtQml.Models::DelegateModelGroup::create(int index) |
| \qmlmethod QtQml.Models::DelegateModelGroup::create(int index, jsdict data, array groups = undefined) |
| \qmlmethod QtQml.Models::DelegateModelGroup::create(jsdict data, array groups = undefined) |
| |
| Returns a reference to the instantiated item at \a index in the group. |
| |
| If a \a data object is provided it will be \l {insert}{inserted} at \a index and an item |
| referencing this new entry will be returned. The optional \a groups parameter identifies |
| the groups the new entry should belong to, if unspecified this is equal to the group create() |
| was called on. |
| |
| All items returned by create are added to the |
| \l {QtQml.Models::DelegateModel::persistedItems}{persistedItems} group. Items in this |
| group remain instantiated when not referenced by any view. |
| */ |
| |
| void QQmlDelegateModelGroup::create(QQmlV4Function *args) |
| { |
| Q_D(QQmlDelegateModelGroup); |
| if (!d->model) |
| return; |
| |
| if (args->length() == 0) |
| return; |
| |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); |
| |
| int index = model->m_compositor.count(d->group); |
| Compositor::Group group = d->group; |
| |
| int i = 0; |
| QV4::Scope scope(args->v4engine()); |
| QV4::ScopedValue v(scope, (*args)[i]); |
| if (d->parseIndex(v, &index, &group)) |
| ++i; |
| |
| if (i < args->length() && index >= 0 && index <= model->m_compositor.count(group)) { |
| v = (*args)[i]; |
| if (v->as<QV4::Object>()) { |
| int groups = 1 << d->group; |
| if (++i < args->length()) { |
| QV4::ScopedValue val(scope, (*args)[i]); |
| groups |= model->m_cacheMetaType->parseGroups(val); |
| } |
| |
| Compositor::insert_iterator before = index < model->m_compositor.count(group) |
| ? model->m_compositor.findInsertPosition(group, index) |
| : model->m_compositor.end(); |
| |
| index = before.index[d->group]; |
| group = d->group; |
| |
| if (!model->insert(before, v, groups)) { |
| return; |
| } |
| } |
| } |
| if (index < 0 || index >= model->m_compositor.count(group)) { |
| qmlWarning(this) << tr("create: index out of range"); |
| return; |
| } |
| |
| QObject *object = model->object(group, index, QQmlIncubator::AsynchronousIfNested); |
| if (object) { |
| QVector<Compositor::Insert> inserts; |
| Compositor::iterator it = model->m_compositor.find(group, index); |
| model->m_compositor.setFlags(it, 1, d->group, Compositor::PersistedFlag, &inserts); |
| model->itemsInserted(inserts); |
| model->m_cache.at(it.cacheIndex)->releaseObject(); |
| } |
| |
| args->setReturnValue(QV4::QObjectWrapper::wrap(args->v4engine(), object)); |
| model->emitChanges(); |
| } |
| |
| /*! |
| \qmlmethod QtQml.Models::DelegateModelGroup::resolve(int from, int to) |
| |
| Binds an unresolved item at \a from to an item in DelegateModel::model at index \a to. |
| |
| Unresolved items are entries whose data has been \l {insert()}{inserted} into a DelegateModelGroup |
| instead of being derived from a DelegateModel::model index. Resolving an item will replace |
| the item at the target index with the unresolved item. A resolved an item will reflect the data |
| of the source model at its bound index and will move when that index moves like any other item. |
| |
| If a new item is replaced in the DelegateModelGroup onChanged() handler its insertion and |
| replacement will be communicated to views as an atomic operation, creating the appearance |
| that the model contents have not changed, or if the unresolved and model item are not adjacent |
| that the previously unresolved item has simply moved. |
| |
| */ |
| void QQmlDelegateModelGroup::resolve(QQmlV4Function *args) |
| { |
| Q_D(QQmlDelegateModelGroup); |
| if (!d->model) |
| return; |
| |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); |
| |
| if (args->length() < 2) |
| return; |
| |
| int from = -1; |
| int to = -1; |
| Compositor::Group fromGroup = d->group; |
| Compositor::Group toGroup = d->group; |
| |
| QV4::Scope scope(args->v4engine()); |
| QV4::ScopedValue v(scope, (*args)[0]); |
| if (d->parseIndex(v, &from, &fromGroup)) { |
| if (from < 0 || from >= model->m_compositor.count(fromGroup)) { |
| qmlWarning(this) << tr("resolve: from index out of range"); |
| return; |
| } |
| } else { |
| qmlWarning(this) << tr("resolve: from index invalid"); |
| return; |
| } |
| |
| v = (*args)[1]; |
| if (d->parseIndex(v, &to, &toGroup)) { |
| if (to < 0 || to >= model->m_compositor.count(toGroup)) { |
| qmlWarning(this) << tr("resolve: to index out of range"); |
| return; |
| } |
| } else { |
| qmlWarning(this) << tr("resolve: to index invalid"); |
| return; |
| } |
| |
| Compositor::iterator fromIt = model->m_compositor.find(fromGroup, from); |
| Compositor::iterator toIt = model->m_compositor.find(toGroup, to); |
| |
| if (!fromIt->isUnresolved()) { |
| qmlWarning(this) << tr("resolve: from is not an unresolved item"); |
| return; |
| } |
| if (!toIt->list) { |
| qmlWarning(this) << tr("resolve: to is not a model item"); |
| return; |
| } |
| |
| const int unresolvedFlags = fromIt->flags; |
| const int resolvedFlags = toIt->flags; |
| const int resolvedIndex = toIt.modelIndex(); |
| void * const resolvedList = toIt->list; |
| |
| QQmlDelegateModelItem *cacheItem = model->m_cache.at(fromIt.cacheIndex); |
| cacheItem->groups &= ~Compositor::UnresolvedFlag; |
| |
| if (toIt.cacheIndex > fromIt.cacheIndex) |
| toIt.decrementIndexes(1, unresolvedFlags); |
| if (!toIt->inGroup(fromGroup) || toIt.index[fromGroup] > from) |
| from += 1; |
| |
| model->itemsMoved( |
| QVector<Compositor::Remove>(1, Compositor::Remove(fromIt, 1, unresolvedFlags, 0)), |
| QVector<Compositor::Insert>(1, Compositor::Insert(toIt, 1, unresolvedFlags, 0))); |
| model->itemsInserted( |
| QVector<Compositor::Insert>(1, Compositor::Insert(toIt, 1, (resolvedFlags & ~unresolvedFlags) | Compositor::CacheFlag))); |
| toIt.incrementIndexes(1, resolvedFlags | unresolvedFlags); |
| model->itemsRemoved(QVector<Compositor::Remove>(1, Compositor::Remove(toIt, 1, resolvedFlags))); |
| |
| model->m_compositor.setFlags(toGroup, to, 1, unresolvedFlags & ~Compositor::UnresolvedFlag); |
| model->m_compositor.clearFlags(fromGroup, from, 1, unresolvedFlags); |
| |
| if (resolvedFlags & Compositor::CacheFlag) |
| model->m_compositor.insert(Compositor::Cache, toIt.cacheIndex, resolvedList, resolvedIndex, 1, Compositor::CacheFlag); |
| |
| Q_ASSERT(model->m_cache.count() == model->m_compositor.count(Compositor::Cache)); |
| |
| if (!cacheItem->isReferenced()) { |
| Q_ASSERT(toIt.cacheIndex == model->m_cache.indexOf(cacheItem)); |
| model->m_cache.removeAt(toIt.cacheIndex); |
| model->m_compositor.clearFlags(Compositor::Cache, toIt.cacheIndex, 1, Compositor::CacheFlag); |
| delete cacheItem; |
| Q_ASSERT(model->m_cache.count() == model->m_compositor.count(Compositor::Cache)); |
| } else { |
| cacheItem->resolveIndex(model->m_adaptorModel, resolvedIndex); |
| if (cacheItem->attached) |
| cacheItem->attached->emitUnresolvedChanged(); |
| } |
| |
| model->emitChanges(); |
| } |
| |
| /*! |
| \qmlmethod QtQml.Models::DelegateModelGroup::remove(int index, int count) |
| |
| Removes \a count items starting at \a index from the group. |
| */ |
| |
| void QQmlDelegateModelGroup::remove(QQmlV4Function *args) |
| { |
| Q_D(QQmlDelegateModelGroup); |
| if (!d->model) |
| return; |
| Compositor::Group group = d->group; |
| int index = -1; |
| int count = 1; |
| |
| if (args->length() == 0) |
| return; |
| |
| int i = 0; |
| QV4::Scope scope(args->v4engine()); |
| QV4::ScopedValue v(scope, (*args)[0]); |
| if (!d->parseIndex(v, &index, &group)) { |
| qmlWarning(this) << tr("remove: invalid index"); |
| return; |
| } |
| |
| if (++i < args->length()) { |
| v = (*args)[i]; |
| if (v->isNumber()) |
| count = v->toInt32(); |
| } |
| |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); |
| if (index < 0 || index >= model->m_compositor.count(group)) { |
| qmlWarning(this) << tr("remove: index out of range"); |
| } else if (count != 0) { |
| Compositor::iterator it = model->m_compositor.find(group, index); |
| if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) { |
| qmlWarning(this) << tr("remove: invalid count"); |
| } else { |
| model->removeGroups(it, count, d->group, 1 << d->group); |
| } |
| } |
| } |
| |
| bool QQmlDelegateModelGroupPrivate::parseGroupArgs( |
| QQmlV4Function *args, Compositor::Group *group, int *index, int *count, int *groups) const |
| { |
| if (!model || !QQmlDelegateModelPrivate::get(model)->m_cacheMetaType) |
| return false; |
| |
| if (args->length() < 2) |
| return false; |
| |
| int i = 0; |
| QV4::Scope scope(args->v4engine()); |
| QV4::ScopedValue v(scope, (*args)[i]); |
| if (!parseIndex(v, index, group)) |
| return false; |
| |
| v = (*args)[++i]; |
| if (v->isNumber()) { |
| *count = v->toInt32(); |
| |
| if (++i == args->length()) |
| return false; |
| v = (*args)[i]; |
| } |
| |
| *groups = QQmlDelegateModelPrivate::get(model)->m_cacheMetaType->parseGroups(v); |
| |
| return true; |
| } |
| |
| /*! |
| \qmlmethod QtQml.Models::DelegateModelGroup::addGroups(int index, int count, stringlist groups) |
| |
| Adds \a count items starting at \a index to \a groups. |
| */ |
| |
| void QQmlDelegateModelGroup::addGroups(QQmlV4Function *args) |
| { |
| Q_D(QQmlDelegateModelGroup); |
| Compositor::Group group = d->group; |
| int index = -1; |
| int count = 1; |
| int groups = 0; |
| |
| if (!d->parseGroupArgs(args, &group, &index, &count, &groups)) |
| return; |
| |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); |
| if (index < 0 || index >= model->m_compositor.count(group)) { |
| qmlWarning(this) << tr("addGroups: index out of range"); |
| } else if (count != 0) { |
| Compositor::iterator it = model->m_compositor.find(group, index); |
| if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) { |
| qmlWarning(this) << tr("addGroups: invalid count"); |
| } else { |
| model->addGroups(it, count, d->group, groups); |
| } |
| } |
| } |
| |
| /*! |
| \qmlmethod QtQml.Models::DelegateModelGroup::removeGroups(int index, int count, stringlist groups) |
| |
| Removes \a count items starting at \a index from \a groups. |
| */ |
| |
| void QQmlDelegateModelGroup::removeGroups(QQmlV4Function *args) |
| { |
| Q_D(QQmlDelegateModelGroup); |
| Compositor::Group group = d->group; |
| int index = -1; |
| int count = 1; |
| int groups = 0; |
| |
| if (!d->parseGroupArgs(args, &group, &index, &count, &groups)) |
| return; |
| |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); |
| if (index < 0 || index >= model->m_compositor.count(group)) { |
| qmlWarning(this) << tr("removeGroups: index out of range"); |
| } else if (count != 0) { |
| Compositor::iterator it = model->m_compositor.find(group, index); |
| if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) { |
| qmlWarning(this) << tr("removeGroups: invalid count"); |
| } else { |
| model->removeGroups(it, count, d->group, groups); |
| } |
| } |
| } |
| |
| /*! |
| \qmlmethod QtQml.Models::DelegateModelGroup::setGroups(int index, int count, stringlist groups) |
| |
| Sets the \a groups \a count items starting at \a index belong to. |
| */ |
| |
| void QQmlDelegateModelGroup::setGroups(QQmlV4Function *args) |
| { |
| Q_D(QQmlDelegateModelGroup); |
| Compositor::Group group = d->group; |
| int index = -1; |
| int count = 1; |
| int groups = 0; |
| |
| if (!d->parseGroupArgs(args, &group, &index, &count, &groups)) |
| return; |
| |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); |
| if (index < 0 || index >= model->m_compositor.count(group)) { |
| qmlWarning(this) << tr("setGroups: index out of range"); |
| } else if (count != 0) { |
| Compositor::iterator it = model->m_compositor.find(group, index); |
| if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) { |
| qmlWarning(this) << tr("setGroups: invalid count"); |
| } else { |
| model->setGroups(it, count, d->group, groups); |
| } |
| } |
| } |
| |
| /*! |
| \qmlmethod QtQml.Models::DelegateModelGroup::setGroups(int index, int count, stringlist groups) |
| |
| Sets the \a groups \a count items starting at \a index belong to. |
| */ |
| |
| /*! |
| \qmlmethod QtQml.Models::DelegateModelGroup::move(var from, var to, int count) |
| |
| Moves \a count at \a from in a group \a to a new position. |
| |
| \note The DelegateModel acts as a proxy model: it holds the delegates in a |
| different order than the \l{dm-model-property}{underlying model} has them. |
| Any subsequent changes to the underlying model will not undo whatever |
| reordering you have done via this function. |
| */ |
| |
| void QQmlDelegateModelGroup::move(QQmlV4Function *args) |
| { |
| Q_D(QQmlDelegateModelGroup); |
| |
| if (args->length() < 2) |
| return; |
| |
| Compositor::Group fromGroup = d->group; |
| Compositor::Group toGroup = d->group; |
| int from = -1; |
| int to = -1; |
| int count = 1; |
| |
| QV4::Scope scope(args->v4engine()); |
| QV4::ScopedValue v(scope, (*args)[0]); |
| if (!d->parseIndex(v, &from, &fromGroup)) { |
| qmlWarning(this) << tr("move: invalid from index"); |
| return; |
| } |
| |
| v = (*args)[1]; |
| if (!d->parseIndex(v, &to, &toGroup)) { |
| qmlWarning(this) << tr("move: invalid to index"); |
| return; |
| } |
| |
| if (args->length() > 2) { |
| v = (*args)[2]; |
| if (v->isNumber()) |
| count = v->toInt32(); |
| } |
| |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); |
| |
| if (count < 0) { |
| qmlWarning(this) << tr("move: invalid count"); |
| } else if (from < 0 || from + count > model->m_compositor.count(fromGroup)) { |
| qmlWarning(this) << tr("move: from index out of range"); |
| } else if (!model->m_compositor.verifyMoveTo(fromGroup, from, toGroup, to, count, d->group)) { |
| qmlWarning(this) << tr("move: to index out of range"); |
| } else if (count > 0) { |
| QVector<Compositor::Remove> removes; |
| QVector<Compositor::Insert> inserts; |
| |
| model->m_compositor.move(fromGroup, from, toGroup, to, count, d->group, &removes, &inserts); |
| model->itemsMoved(removes, inserts); |
| model->emitChanges(); |
| } |
| |
| } |
| |
| /*! |
| \qmlsignal QtQml.Models::DelegateModelGroup::changed(array removed, array inserted) |
| |
| This signal is emitted when items have been removed from or inserted into the group. |
| |
| Each object in the \a removed and \a inserted arrays has two values; the \e index of the first |
| item inserted or removed and a \e count of the number of consecutive items inserted or removed. |
| |
| Each index is adjusted for previous changes with all removed items preceding any inserted |
| items. |
| */ |
| |
| //============================================================================ |
| |
| QQmlPartsModel::QQmlPartsModel(QQmlDelegateModel *model, const QString &part, QObject *parent) |
| : QQmlInstanceModel(*new QObjectPrivate, parent) |
| , m_model(model) |
| , m_part(part) |
| , m_compositorGroup(Compositor::Cache) |
| , m_inheritGroup(true) |
| { |
| QQmlDelegateModelPrivate *d = QQmlDelegateModelPrivate::get(m_model); |
| if (d->m_cacheMetaType) { |
| QQmlDelegateModelGroupPrivate::get(d->m_groups[1])->emitters.insert(this); |
| m_compositorGroup = Compositor::Default; |
| } else { |
| d->m_pendingParts.insert(this); |
| } |
| } |
| |
| QQmlPartsModel::~QQmlPartsModel() |
| { |
| } |
| |
| QString QQmlPartsModel::filterGroup() const |
| { |
| if (m_inheritGroup) |
| return m_model->filterGroup(); |
| return m_filterGroup; |
| } |
| |
| void QQmlPartsModel::setFilterGroup(const QString &group) |
| { |
| if (QQmlDelegateModelPrivate::get(m_model)->m_transaction) { |
| qmlWarning(this) << tr("The group of a DelegateModel cannot be changed within onChanged"); |
| return; |
| } |
| |
| if (m_filterGroup != group || m_inheritGroup) { |
| m_filterGroup = group; |
| m_inheritGroup = false; |
| updateFilterGroup(); |
| |
| emit filterGroupChanged(); |
| } |
| } |
| |
| void QQmlPartsModel::resetFilterGroup() |
| { |
| if (!m_inheritGroup) { |
| m_inheritGroup = true; |
| updateFilterGroup(); |
| emit filterGroupChanged(); |
| } |
| } |
| |
| void QQmlPartsModel::updateFilterGroup() |
| { |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); |
| if (!model->m_cacheMetaType) |
| return; |
| |
| if (m_inheritGroup) { |
| if (m_filterGroup == model->m_filterGroup) |
| return; |
| m_filterGroup = model->m_filterGroup; |
| } |
| |
| QQmlListCompositor::Group previousGroup = m_compositorGroup; |
| m_compositorGroup = Compositor::Default; |
| QQmlDelegateModelGroupPrivate::get(model->m_groups[Compositor::Default])->emitters.insert(this); |
| for (int i = 1; i < model->m_groupCount; ++i) { |
| if (m_filterGroup == model->m_cacheMetaType->groupNames.at(i - 1)) { |
| m_compositorGroup = Compositor::Group(i); |
| break; |
| } |
| } |
| |
| QQmlDelegateModelGroupPrivate::get(model->m_groups[m_compositorGroup])->emitters.insert(this); |
| if (m_compositorGroup != previousGroup) { |
| QVector<QQmlChangeSet::Change> removes; |
| QVector<QQmlChangeSet::Change> inserts; |
| model->m_compositor.transition(previousGroup, m_compositorGroup, &removes, &inserts); |
| |
| QQmlChangeSet changeSet; |
| changeSet.move(removes, inserts); |
| if (!changeSet.isEmpty()) |
| emit modelUpdated(changeSet, false); |
| |
| if (changeSet.difference() != 0) |
| emit countChanged(); |
| } |
| } |
| |
| void QQmlPartsModel::updateFilterGroup( |
| Compositor::Group group, const QQmlChangeSet &changeSet) |
| { |
| if (!m_inheritGroup) |
| return; |
| |
| m_compositorGroup = group; |
| QQmlDelegateModelGroupPrivate::get(QQmlDelegateModelPrivate::get(m_model)->m_groups[m_compositorGroup])->emitters.insert(this); |
| |
| if (!changeSet.isEmpty()) |
| emit modelUpdated(changeSet, false); |
| |
| if (changeSet.difference() != 0) |
| emit countChanged(); |
| |
| emit filterGroupChanged(); |
| } |
| |
| int QQmlPartsModel::count() const |
| { |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); |
| return model->m_delegate |
| ? model->m_compositor.count(m_compositorGroup) |
| : 0; |
| } |
| |
| bool QQmlPartsModel::isValid() const |
| { |
| return m_model->isValid(); |
| } |
| |
| QObject *QQmlPartsModel::object(int index, QQmlIncubator::IncubationMode incubationMode) |
| { |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); |
| |
| if (!model->m_delegate || index < 0 || index >= model->m_compositor.count(m_compositorGroup)) { |
| qWarning() << "DelegateModel::item: index out range" << index << model->m_compositor.count(m_compositorGroup); |
| return nullptr; |
| } |
| |
| QObject *object = model->object(m_compositorGroup, index, incubationMode); |
| |
| if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(object)) { |
| QObject *part = package->part(m_part); |
| if (!part) |
| return nullptr; |
| m_packaged.insert(part, package); |
| return part; |
| } |
| |
| model->release(object); |
| if (!model->m_delegateValidated) { |
| if (object) |
| qmlWarning(model->m_delegate) << tr("Delegate component must be Package type."); |
| model->m_delegateValidated = true; |
| } |
| |
| return nullptr; |
| } |
| |
| QQmlInstanceModel::ReleaseFlags QQmlPartsModel::release(QObject *item, ReusableFlag) |
| { |
| QQmlInstanceModel::ReleaseFlags flags; |
| |
| auto it = m_packaged.find(item); |
| if (it != m_packaged.end()) { |
| QQuickPackage *package = *it; |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); |
| flags = model->release(package); |
| m_packaged.erase(it); |
| if (!m_packaged.contains(item)) |
| flags &= ~Referenced; |
| if (flags & Destroyed) |
| QQmlDelegateModelPrivate::get(m_model)->emitDestroyingPackage(package); |
| } |
| return flags; |
| } |
| |
| QVariant QQmlPartsModel::variantValue(int index, const QString &role) |
| { |
| return QQmlDelegateModelPrivate::get(m_model)->variantValue(m_compositorGroup, index, role); |
| } |
| |
| void QQmlPartsModel::setWatchedRoles(const QList<QByteArray> &roles) |
| { |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); |
| model->m_adaptorModel.replaceWatchedRoles(m_watchedRoles, roles); |
| m_watchedRoles = roles; |
| } |
| |
| QQmlIncubator::Status QQmlPartsModel::incubationStatus(int index) |
| { |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); |
| Compositor::iterator it = model->m_compositor.find(model->m_compositorGroup, index); |
| if (!it->inCache()) |
| return QQmlIncubator::Null; |
| |
| if (auto incubationTask = model->m_cache.at(it.cacheIndex)->incubationTask) |
| return incubationTask->status(); |
| |
| return QQmlIncubator::Ready; |
| } |
| |
| int QQmlPartsModel::indexOf(QObject *item, QObject *) const |
| { |
| auto it = m_packaged.find(item); |
| if (it != m_packaged.end()) { |
| if (QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(*it)) |
| return cacheItem->groupIndex(m_compositorGroup); |
| } |
| return -1; |
| } |
| |
| void QQmlPartsModel::createdPackage(int index, QQuickPackage *package) |
| { |
| emit createdItem(index, package->part(m_part)); |
| } |
| |
| void QQmlPartsModel::initPackage(int index, QQuickPackage *package) |
| { |
| if (m_modelUpdatePending) |
| m_pendingPackageInitializations << index; |
| else |
| emit initItem(index, package->part(m_part)); |
| } |
| |
| void QQmlPartsModel::destroyingPackage(QQuickPackage *package) |
| { |
| QObject *item = package->part(m_part); |
| Q_ASSERT(!m_packaged.contains(item)); |
| emit destroyingItem(item); |
| } |
| |
| void QQmlPartsModel::emitModelUpdated(const QQmlChangeSet &changeSet, bool reset) |
| { |
| m_modelUpdatePending = false; |
| emit modelUpdated(changeSet, reset); |
| if (changeSet.difference() != 0) |
| emit countChanged(); |
| |
| QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); |
| QVector<int> pendingPackageInitializations; |
| qSwap(pendingPackageInitializations, m_pendingPackageInitializations); |
| for (int index : pendingPackageInitializations) { |
| if (!model->m_delegate || index < 0 || index >= model->m_compositor.count(m_compositorGroup)) |
| continue; |
| QObject *object = model->object(m_compositorGroup, index, QQmlIncubator::Asynchronous); |
| if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(object)) |
| emit initItem(index, package->part(m_part)); |
| model->release(object); |
| } |
| } |
| |
| void QQmlReusableDelegateModelItemsPool::insertItem(QQmlDelegateModelItem *modelItem) |
| { |
| // Currently, the only way for a view to reuse items is to call release() |
| // in the model class with the second argument explicitly set to |
| // QQmlReuseableDelegateModelItemsPool::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 properties and attached properties. |
| |
| // When a view is recycling items, it should call drain() 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. Between these times, 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 drain() 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 reduce resource consumption. |
| |
| // 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 dimensions 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->object); |
| Q_ASSERT(modelItem->delegate); |
| |
| modelItem->poolTime = 0; |
| m_reusableItemsPool.append(modelItem); |
| |
| qCDebug(lcItemViewDelegateRecycling) |
| << "item:" << modelItem |
| << "delegate:" << modelItem->delegate |
| << "index:" << modelItem->modelIndex() |
| << "row:" << modelItem->modelRow() |
| << "column:" << modelItem->modelColumn() |
| << "pool size:" << m_reusableItemsPool.size(); |
| } |
| |
| QQmlDelegateModelItem *QQmlReusableDelegateModelItemsPool::takeItem(const QQmlComponent *delegate, int newIndexHint) |
| { |
| // 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. |
| for (auto it = m_reusableItemsPool.begin(); it != m_reusableItemsPool.end(); ++it) { |
| if ((*it)->delegate != delegate) |
| continue; |
| auto modelItem = *it; |
| m_reusableItemsPool.erase(it); |
| |
| qCDebug(lcItemViewDelegateRecycling) |
| << "item:" << modelItem |
| << "delegate:" << delegate |
| << "old index:" << modelItem->modelIndex() |
| << "old row:" << modelItem->modelRow() |
| << "old column:" << modelItem->modelColumn() |
| << "new index:" << newIndexHint |
| << "pool size:" << m_reusableItemsPool.size(); |
| |
| return modelItem; |
| } |
| |
| qCDebug(lcItemViewDelegateRecycling) |
| << "no available item for delegate:" << delegate |
| << "new index:" << newIndexHint |
| << "pool size:" << m_reusableItemsPool.size(); |
| |
| return nullptr; |
| } |
| |
| void QQmlReusableDelegateModelItemsPool::drain(int maxPoolTime, std::function<void(QQmlDelegateModelItem *cacheItem)> releaseItem) |
| { |
| // 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. |
| qCDebug(lcItemViewDelegateRecycling) << "pool size before drain:" << m_reusableItemsPool.size(); |
| |
| 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); |
| releaseItem(modelItem); |
| } |
| } |
| |
| qCDebug(lcItemViewDelegateRecycling) << "pool size after drain:" << m_reusableItemsPool.size(); |
| } |
| |
| //============================================================================ |
| |
| struct QQmlDelegateModelGroupChange : QV4::Object |
| { |
| V4_OBJECT2(QQmlDelegateModelGroupChange, QV4::Object) |
| |
| static QV4::Heap::QQmlDelegateModelGroupChange *create(QV4::ExecutionEngine *e) { |
| return e->memoryManager->allocate<QQmlDelegateModelGroupChange>(); |
| } |
| |
| static QV4::ReturnedValue method_get_index(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) { |
| QV4::Scope scope(b); |
| QV4::Scoped<QQmlDelegateModelGroupChange> that(scope, thisObject->as<QQmlDelegateModelGroupChange>()); |
| if (!that) |
| THROW_TYPE_ERROR(); |
| return QV4::Encode(that->d()->change.index); |
| } |
| static QV4::ReturnedValue method_get_count(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) { |
| QV4::Scope scope(b); |
| QV4::Scoped<QQmlDelegateModelGroupChange> that(scope, thisObject->as<QQmlDelegateModelGroupChange>()); |
| if (!that) |
| THROW_TYPE_ERROR(); |
| return QV4::Encode(that->d()->change.count); |
| } |
| static QV4::ReturnedValue method_get_moveId(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) { |
| QV4::Scope scope(b); |
| QV4::Scoped<QQmlDelegateModelGroupChange> that(scope, thisObject->as<QQmlDelegateModelGroupChange>()); |
| if (!that) |
| THROW_TYPE_ERROR(); |
| if (that->d()->change.moveId < 0) |
| RETURN_UNDEFINED(); |
| return QV4::Encode(that->d()->change.moveId); |
| } |
| }; |
| |
| DEFINE_OBJECT_VTABLE(QQmlDelegateModelGroupChange); |
| |
| struct QQmlDelegateModelGroupChangeArray : public QV4::Object |
| { |
| V4_OBJECT2(QQmlDelegateModelGroupChangeArray, QV4::Object) |
| V4_NEEDS_DESTROY |
| public: |
| static QV4::Heap::QQmlDelegateModelGroupChangeArray *create(QV4::ExecutionEngine *engine, const QVector<QQmlChangeSet::Change> &changes) |
| { |
| return engine->memoryManager->allocate<QQmlDelegateModelGroupChangeArray>(changes); |
| } |
| |
| quint32 count() const { return d()->changes->count(); } |
| const QQmlChangeSet::Change &at(int index) const { return d()->changes->at(index); } |
| |
| static QV4::ReturnedValue virtualGet(const QV4::Managed *m, QV4::PropertyKey id, const QV4::Value *receiver, bool *hasProperty) |
| { |
| if (id.isArrayIndex()) { |
| uint index = id.asArrayIndex(); |
| Q_ASSERT(m->as<QQmlDelegateModelGroupChangeArray>()); |
| QV4::ExecutionEngine *v4 = static_cast<const QQmlDelegateModelGroupChangeArray *>(m)->engine(); |
| QV4::Scope scope(v4); |
| QV4::Scoped<QQmlDelegateModelGroupChangeArray> array(scope, static_cast<const QQmlDelegateModelGroupChangeArray *>(m)); |
| |
| if (index >= array->count()) { |
| if (hasProperty) |
| *hasProperty = false; |
| return QV4::Value::undefinedValue().asReturnedValue(); |
| } |
| |
| const QQmlChangeSet::Change &change = array->at(index); |
| |
| QV4::ScopedObject changeProto(scope, engineData(v4)->changeProto.value()); |
| QV4::Scoped<QQmlDelegateModelGroupChange> object(scope, QQmlDelegateModelGroupChange::create(v4)); |
| object->setPrototypeOf(changeProto); |
| object->d()->change = change; |
| |
| if (hasProperty) |
| *hasProperty = true; |
| return object.asReturnedValue(); |
| } |
| |
| Q_ASSERT(m->as<QQmlDelegateModelGroupChangeArray>()); |
| const QQmlDelegateModelGroupChangeArray *array = static_cast<const QQmlDelegateModelGroupChangeArray *>(m); |
| |
| if (id == array->engine()->id_length()->propertyKey()) { |
| if (hasProperty) |
| *hasProperty = true; |
| return QV4::Encode(array->count()); |
| } |
| |
| return Object::virtualGet(m, id, receiver, hasProperty); |
| } |
| }; |
| |
| void QV4::Heap::QQmlDelegateModelGroupChangeArray::init(const QVector<QQmlChangeSet::Change> &changes) |
| { |
| Object::init(); |
| this->changes = new QVector<QQmlChangeSet::Change>(changes); |
| QV4::Scope scope(internalClass->engine); |
| QV4::ScopedObject o(scope, this); |
| o->setArrayType(QV4::Heap::ArrayData::Custom); |
| } |
| |
| DEFINE_OBJECT_VTABLE(QQmlDelegateModelGroupChangeArray); |
| |
| QQmlDelegateModelEngineData::QQmlDelegateModelEngineData(QV4::ExecutionEngine *v4) |
| { |
| QV4::Scope scope(v4); |
| |
| QV4::ScopedObject proto(scope, v4->newObject()); |
| proto->defineAccessorProperty(QStringLiteral("index"), QQmlDelegateModelGroupChange::method_get_index, nullptr); |
| proto->defineAccessorProperty(QStringLiteral("count"), QQmlDelegateModelGroupChange::method_get_count, nullptr); |
| proto->defineAccessorProperty(QStringLiteral("moveId"), QQmlDelegateModelGroupChange::method_get_moveId, nullptr); |
| changeProto.set(v4, proto); |
| } |
| |
| QQmlDelegateModelEngineData::~QQmlDelegateModelEngineData() |
| { |
| } |
| |
| QV4::ReturnedValue QQmlDelegateModelEngineData::array(QV4::ExecutionEngine *v4, |
| const QVector<QQmlChangeSet::Change> &changes) |
| { |
| QV4::Scope scope(v4); |
| QV4::ScopedObject o(scope, QQmlDelegateModelGroupChangeArray::create(v4, changes)); |
| return o.asReturnedValue(); |
| } |
| |
| QT_END_NAMESPACE |
| |
| #include "moc_qqmldelegatemodel_p.cpp" |