1 /****************************************************************************
2 **
3 ** Copyright (C) 2020 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qqmldelegatemodel_p_p.h"
41 
42 #include <QtQml/qqmlinfo.h>
43 
44 #include <private/qqmlabstractdelegatecomponent_p.h>
45 #include <private/qquickpackage_p.h>
46 #include <private/qmetaobjectbuilder_p.h>
47 #include <private/qqmladaptormodel_p.h>
48 #include <private/qqmlchangeset_p.h>
49 #include <private/qqmlengine_p.h>
50 #include <private/qqmlcomponent_p.h>
51 
52 #include <private/qv4value_p.h>
53 #include <private/qv4functionobject_p.h>
54 #include <private/qv4objectiterator_p.h>
55 
56 QT_BEGIN_NAMESPACE
57 
58 Q_LOGGING_CATEGORY(lcItemViewDelegateRecycling, "qt.quick.itemview.delegaterecycling")
59 
60 class QQmlDelegateModelItem;
61 
62 namespace QV4 {
63 
64 namespace Heap {
65 
66 struct DelegateModelGroupFunction : FunctionObject {
67     void init(QV4::ExecutionContext *scope, uint flag, QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg));
68 
69     QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg);
70     uint flag;
71 };
72 
73 struct QQmlDelegateModelGroupChange : Object {
initQV4::Heap::QQmlDelegateModelGroupChange74     void init() { Object::init(); }
75 
76     QQmlChangeSet::ChangeData change;
77 };
78 
79 struct QQmlDelegateModelGroupChangeArray : Object {
80     void init(const QVector<QQmlChangeSet::Change> &changes);
destroyQV4::Heap::QQmlDelegateModelGroupChangeArray81     void destroy() {
82         delete changes;
83         Object::destroy();
84     }
85 
86     QVector<QQmlChangeSet::Change> *changes;
87 };
88 
89 
90 }
91 
92 struct DelegateModelGroupFunction : QV4::FunctionObject
93 {
V4_OBJECT2QV4::DelegateModelGroupFunction94     V4_OBJECT2(DelegateModelGroupFunction, FunctionObject)
95 
96     static Heap::DelegateModelGroupFunction *create(QV4::ExecutionContext *scope, uint flag, QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg))
97     {
98         return scope->engine()->memoryManager->allocate<DelegateModelGroupFunction>(scope, flag, code);
99     }
100 
virtualCallQV4::DelegateModelGroupFunction101     static ReturnedValue virtualCall(const QV4::FunctionObject *that, const Value *thisObject, const Value *argv, int argc)
102     {
103         QV4::Scope scope(that->engine());
104         QV4::Scoped<DelegateModelGroupFunction> f(scope, static_cast<const DelegateModelGroupFunction *>(that));
105         QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject);
106         if (!o)
107             return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object"));
108 
109         QV4::ScopedValue v(scope, argc ? argv[0] : Value::undefinedValue());
110         return f->d()->code(o->d()->item, f->d()->flag, v);
111     }
112 };
113 
init(QV4::ExecutionContext * scope,uint flag,QV4::ReturnedValue (* code)(QQmlDelegateModelItem * item,uint flag,const QV4::Value & arg))114 void Heap::DelegateModelGroupFunction::init(QV4::ExecutionContext *scope, uint flag, QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg))
115 {
116     QV4::Heap::FunctionObject::init(scope, QStringLiteral("DelegateModelGroupFunction"));
117     this->flag = flag;
118     this->code = code;
119 }
120 
121 }
122 
123 DEFINE_OBJECT_VTABLE(QV4::DelegateModelGroupFunction);
124 
125 
126 
127 class QQmlDelegateModelEngineData : public QV4::ExecutionEngine::Deletable
128 {
129 public:
130     QQmlDelegateModelEngineData(QV4::ExecutionEngine *v4);
131     ~QQmlDelegateModelEngineData();
132 
133     QV4::ReturnedValue array(QV4::ExecutionEngine *engine,
134                              const QVector<QQmlChangeSet::Change> &changes);
135 
136     QV4::PersistentValue changeProto;
137 };
138 
V4_DEFINE_EXTENSION(QQmlDelegateModelEngineData,engineData)139 V4_DEFINE_EXTENSION(QQmlDelegateModelEngineData, engineData)
140 
141 
142 void QQmlDelegateModelPartsMetaObject::propertyCreated(int, QMetaPropertyBuilder &prop)
143 {
144     prop.setWritable(false);
145 }
146 
initialValue(int id)147 QVariant QQmlDelegateModelPartsMetaObject::initialValue(int id)
148 {
149     QQmlDelegateModelParts *parts = static_cast<QQmlDelegateModelParts *>(object());
150     QQmlPartsModel *m = new QQmlPartsModel(
151             parts->model, QString::fromUtf8(name(id)), parts);
152     parts->models.append(m);
153     return QVariant::fromValue(static_cast<QObject *>(m));
154 }
155 
QQmlDelegateModelParts(QQmlDelegateModel * parent)156 QQmlDelegateModelParts::QQmlDelegateModelParts(QQmlDelegateModel *parent)
157 : QObject(parent), model(parent)
158 {
159     new QQmlDelegateModelPartsMetaObject(this);
160 }
161 
162 //---------------------------------------------------------------------------
163 
164 /*!
165     \qmltype DelegateModel
166     \instantiates QQmlDelegateModel
167     \inqmlmodule QtQml.Models
168     \brief Encapsulates a model and delegate.
169 
170     The DelegateModel type encapsulates a model and the delegate that will
171     be instantiated for items in the model.
172 
173     It is usually not necessary to create a DelegateModel.
174     However, it can be useful for manipulating and accessing the \l modelIndex
175     when a QAbstractItemModel subclass is used as the
176     model. Also, DelegateModel is used together with \l Package to
177     provide delegates to multiple views, and with DelegateModelGroup to sort and filter
178     delegate items.
179 
180     The example below illustrates using a DelegateModel with a ListView.
181 
182     \snippet delegatemodel/delegatemodel.qml 0
183 */
184 
QQmlDelegateModelPrivate(QQmlContext * ctxt)185 QQmlDelegateModelPrivate::QQmlDelegateModelPrivate(QQmlContext *ctxt)
186     : m_delegateChooser(nullptr)
187     , m_cacheMetaType(nullptr)
188     , m_context(ctxt)
189     , m_parts(nullptr)
190     , m_filterGroup(QStringLiteral("items"))
191     , m_count(0)
192     , m_groupCount(Compositor::MinimumGroupCount)
193     , m_compositorGroup(Compositor::Cache)
194     , m_complete(false)
195     , m_delegateValidated(false)
196     , m_reset(false)
197     , m_transaction(false)
198     , m_incubatorCleanupScheduled(false)
199     , m_waitingToFetchMore(false)
200     , m_cacheItems(nullptr)
201     , m_items(nullptr)
202     , m_persistedItems(nullptr)
203 {
204 }
205 
~QQmlDelegateModelPrivate()206 QQmlDelegateModelPrivate::~QQmlDelegateModelPrivate()
207 {
208     qDeleteAll(m_finishedIncubating);
209 
210     // Free up all items in the pool
211     drainReusableItemsPool(0);
212 
213     if (m_cacheMetaType)
214         m_cacheMetaType->release();
215 }
216 
adaptorModelCount() const217 int QQmlDelegateModelPrivate::adaptorModelCount() const
218 {
219     // QQmlDelegateModel currently only support list models.
220     // So even if a model is a table model, only the first
221     // column will be used.
222     return m_adaptorModel.rowCount();
223 }
224 
requestMoreIfNecessary()225 void QQmlDelegateModelPrivate::requestMoreIfNecessary()
226 {
227     Q_Q(QQmlDelegateModel);
228     if (!m_waitingToFetchMore && m_adaptorModel.canFetchMore()) {
229         m_waitingToFetchMore = true;
230         QCoreApplication::postEvent(q, new QEvent(QEvent::UpdateRequest));
231     }
232 }
233 
init()234 void QQmlDelegateModelPrivate::init()
235 {
236     Q_Q(QQmlDelegateModel);
237     m_compositor.setRemoveGroups(Compositor::GroupMask & ~Compositor::PersistedFlag);
238 
239     m_items = new QQmlDelegateModelGroup(QStringLiteral("items"), q, Compositor::Default, q);
240     m_items->setDefaultInclude(true);
241     m_persistedItems = new QQmlDelegateModelGroup(QStringLiteral("persistedItems"), q, Compositor::Persisted, q);
242     QQmlDelegateModelGroupPrivate::get(m_items)->emitters.insert(this);
243 }
244 
QQmlDelegateModel()245 QQmlDelegateModel::QQmlDelegateModel()
246     : QQmlDelegateModel(nullptr, nullptr)
247 {
248 }
249 
QQmlDelegateModel(QQmlContext * ctxt,QObject * parent)250 QQmlDelegateModel::QQmlDelegateModel(QQmlContext *ctxt, QObject *parent)
251 : QQmlInstanceModel(*(new QQmlDelegateModelPrivate(ctxt)), parent)
252 {
253     Q_D(QQmlDelegateModel);
254     d->init();
255 }
256 
~QQmlDelegateModel()257 QQmlDelegateModel::~QQmlDelegateModel()
258 {
259     Q_D(QQmlDelegateModel);
260     d->disconnectFromAbstractItemModel();
261     d->m_adaptorModel.setObject(nullptr, this);
262 
263     for (QQmlDelegateModelItem *cacheItem : qAsConst(d->m_cache)) {
264         if (cacheItem->object) {
265             delete cacheItem->object;
266 
267             cacheItem->object = nullptr;
268             cacheItem->contextData->invalidate();
269             Q_ASSERT(cacheItem->contextData->refCount == 1);
270             cacheItem->contextData = nullptr;
271             cacheItem->scriptRef -= 1;
272         } else if (cacheItem->incubationTask) {
273             // Both the incubationTask and the object may hold a scriptRef,
274             // but if both are present, only one scriptRef is held in total.
275             cacheItem->scriptRef -= 1;
276         }
277 
278         cacheItem->groups &= ~Compositor::UnresolvedFlag;
279         cacheItem->objectRef = 0;
280 
281         if (cacheItem->incubationTask) {
282             d->releaseIncubator(cacheItem->incubationTask);
283             cacheItem->incubationTask->vdm = nullptr;
284             cacheItem->incubationTask = nullptr;
285         }
286 
287         if (!cacheItem->isReferenced())
288             delete cacheItem;
289     }
290 }
291 
292 
classBegin()293 void QQmlDelegateModel::classBegin()
294 {
295     Q_D(QQmlDelegateModel);
296     if (!d->m_context)
297         d->m_context = qmlContext(this);
298 }
299 
componentComplete()300 void QQmlDelegateModel::componentComplete()
301 {
302     Q_D(QQmlDelegateModel);
303     d->m_complete = true;
304 
305     int defaultGroups = 0;
306     QStringList groupNames;
307     groupNames.append(QStringLiteral("items"));
308     groupNames.append(QStringLiteral("persistedItems"));
309     if (QQmlDelegateModelGroupPrivate::get(d->m_items)->defaultInclude)
310         defaultGroups |= Compositor::DefaultFlag;
311     if (QQmlDelegateModelGroupPrivate::get(d->m_persistedItems)->defaultInclude)
312         defaultGroups |= Compositor::PersistedFlag;
313     for (int i = Compositor::MinimumGroupCount; i < d->m_groupCount; ++i) {
314         QString name = d->m_groups[i]->name();
315         if (name.isEmpty()) {
316             d->m_groups[i] = d->m_groups[d->m_groupCount - 1];
317             --d->m_groupCount;
318             --i;
319         } else if (name.at(0).isUpper()) {
320             qmlWarning(d->m_groups[i]) << QQmlDelegateModelGroup::tr("Group names must start with a lower case letter");
321             d->m_groups[i] = d->m_groups[d->m_groupCount - 1];
322             --d->m_groupCount;
323             --i;
324         } else {
325             groupNames.append(name);
326 
327             QQmlDelegateModelGroupPrivate *group = QQmlDelegateModelGroupPrivate::get(d->m_groups[i]);
328             group->setModel(this, Compositor::Group(i));
329             if (group->defaultInclude)
330                 defaultGroups |= (1 << i);
331         }
332     }
333 
334     d->m_cacheMetaType = new QQmlDelegateModelItemMetaType(
335                 d->m_context->engine()->handle(), this, groupNames);
336 
337     d->m_compositor.setGroupCount(d->m_groupCount);
338     d->m_compositor.setDefaultGroups(defaultGroups);
339     d->updateFilterGroup();
340 
341     while (!d->m_pendingParts.isEmpty())
342         static_cast<QQmlPartsModel *>(d->m_pendingParts.first())->updateFilterGroup();
343 
344     QVector<Compositor::Insert> inserts;
345     d->m_count = d->adaptorModelCount();
346     d->m_compositor.append(
347             &d->m_adaptorModel,
348             0,
349             d->m_count,
350             defaultGroups | Compositor::AppendFlag | Compositor::PrependFlag,
351             &inserts);
352     d->itemsInserted(inserts);
353     d->emitChanges();
354     d->requestMoreIfNecessary();
355 }
356 
357 /*!
358     \qmlproperty model QtQml.Models::DelegateModel::model
359     This property holds the model providing data for the DelegateModel.
360 
361     The model provides a set of data that is used to create the items
362     for a view.  For large or dynamic datasets the model is usually
363     provided by a C++ model object.  The C++ model object must be a \l
364     {QAbstractItemModel} subclass or a simple list.
365 
366     Models can also be created directly in QML, using a \l{ListModel} or
367     \l{QtQuick.XmlListModel::XmlListModel}{XmlListModel}.
368 
369     \sa {qml-data-models}{Data Models}
370     \keyword dm-model-property
371 */
model() const372 QVariant QQmlDelegateModel::model() const
373 {
374     Q_D(const QQmlDelegateModel);
375     return d->m_adaptorModel.model();
376 }
377 
connectToAbstractItemModel()378 void QQmlDelegateModelPrivate::connectToAbstractItemModel()
379 {
380     Q_Q(QQmlDelegateModel);
381     if (!m_adaptorModel.adaptsAim())
382         return;
383 
384     auto aim = m_adaptorModel.aim();
385 
386     qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
387                       q, QQmlDelegateModel, SLOT(_q_rowsInserted(QModelIndex,int,int)));
388     qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
389                       q,  QQmlDelegateModel, SLOT(_q_rowsRemoved(QModelIndex,int,int)));
390     qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
391                       q,  QQmlDelegateModel, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int)));
392     qmlobject_connect(aim, QAbstractItemModel, SIGNAL(columnsInserted(QModelIndex,int,int)),
393                       q, QQmlDelegateModel, SLOT(_q_columnsInserted(QModelIndex,int,int)));
394     qmlobject_connect(aim, QAbstractItemModel, SIGNAL(columnsRemoved(QModelIndex,int,int)),
395                       q, QQmlDelegateModel, SLOT(_q_columnsRemoved(QModelIndex,int,int)));
396     qmlobject_connect(aim, QAbstractItemModel, SIGNAL(columnsMoved(QModelIndex,int,int,QModelIndex,int)),
397                       q, QQmlDelegateModel, SLOT(_q_columnsMoved(QModelIndex,int,int,QModelIndex,int)));
398     qmlobject_connect(aim, QAbstractItemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)),
399                       q, QQmlDelegateModel, SLOT(_q_dataChanged(QModelIndex,QModelIndex,QVector<int>)));
400     qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
401                       q, QQmlDelegateModel, SLOT(_q_rowsMoved(QModelIndex,int,int,QModelIndex,int)));
402     qmlobject_connect(aim, QAbstractItemModel, SIGNAL(modelReset()),
403                       q, QQmlDelegateModel, SLOT(_q_modelReset()));
404     qmlobject_connect(aim, QAbstractItemModel, SIGNAL(layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)),
405                       q, QQmlDelegateModel, SLOT(_q_layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)));
406 }
407 
disconnectFromAbstractItemModel()408 void QQmlDelegateModelPrivate::disconnectFromAbstractItemModel()
409 {
410     Q_Q(QQmlDelegateModel);
411     if (!m_adaptorModel.adaptsAim())
412         return;
413 
414     auto aim = m_adaptorModel.aim();
415 
416     QObject::disconnect(aim, SIGNAL(rowsInserted(QModelIndex,int,int)),
417                         q, SLOT(_q_rowsInserted(QModelIndex,int,int)));
418     QObject::disconnect(aim, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
419                         q, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int)));
420     QObject::disconnect(aim, SIGNAL(rowsRemoved(QModelIndex,int,int)),
421                         q, SLOT(_q_rowsRemoved(QModelIndex,int,int)));
422     QObject::disconnect(aim, SIGNAL(columnsInserted(QModelIndex,int,int)), q,
423                         SLOT(_q_columnsInserted(QModelIndex,int,int)));
424     QObject::disconnect(aim, SIGNAL(columnsRemoved(QModelIndex,int,int)), q,
425                         SLOT(_q_columnsRemoved(QModelIndex,int,int)));
426     QObject::disconnect(aim, SIGNAL(columnsMoved(QModelIndex,int,int,QModelIndex,int)), q,
427                         SLOT(_q_columnsMoved(QModelIndex,int,int,QModelIndex,int)));
428     QObject::disconnect(aim, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)),
429                         q, SLOT(_q_dataChanged(QModelIndex,QModelIndex,QVector<int>)));
430     QObject::disconnect(aim, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
431                         q, SLOT(_q_rowsMoved(QModelIndex,int,int,QModelIndex,int)));
432     QObject::disconnect(aim, SIGNAL(modelReset()),
433                         q, SLOT(_q_modelReset()));
434     QObject::disconnect(aim, SIGNAL(layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)),
435                         q, SLOT(_q_layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)));
436 }
437 
setModel(const QVariant & model)438 void QQmlDelegateModel::setModel(const QVariant &model)
439 {
440     Q_D(QQmlDelegateModel);
441 
442     if (d->m_complete)
443         _q_itemsRemoved(0, d->m_count);
444 
445     d->disconnectFromAbstractItemModel();
446     d->m_adaptorModel.setModel(model, this, d->m_context->engine());
447     d->connectToAbstractItemModel();
448 
449     d->m_adaptorModel.replaceWatchedRoles(QList<QByteArray>(), d->m_watchedRoles);
450     for (int i = 0; d->m_parts && i < d->m_parts->models.count(); ++i) {
451         d->m_adaptorModel.replaceWatchedRoles(
452                 QList<QByteArray>(), d->m_parts->models.at(i)->watchedRoles());
453     }
454 
455     if (d->m_complete) {
456         _q_itemsInserted(0, d->adaptorModelCount());
457         d->requestMoreIfNecessary();
458     }
459 }
460 
461 /*!
462     \qmlproperty Component QtQml.Models::DelegateModel::delegate
463 
464     The delegate provides a template defining each item instantiated by a view.
465     The index is exposed as an accessible \c index property.  Properties of the
466     model are also available depending upon the type of \l {qml-data-models}{Data Model}.
467 */
delegate() const468 QQmlComponent *QQmlDelegateModel::delegate() const
469 {
470     Q_D(const QQmlDelegateModel);
471     return d->m_delegate;
472 }
473 
setDelegate(QQmlComponent * delegate)474 void QQmlDelegateModel::setDelegate(QQmlComponent *delegate)
475 {
476     Q_D(QQmlDelegateModel);
477     if (d->m_transaction) {
478         qmlWarning(this) << tr("The delegate of a DelegateModel cannot be changed within onUpdated.");
479         return;
480     }
481     if (d->m_delegate == delegate)
482         return;
483     if (d->m_complete)
484         _q_itemsRemoved(0, d->m_count);
485     d->m_delegate.setObject(delegate, this);
486     d->m_delegateValidated = false;
487     if (d->m_delegateChooser)
488         QObject::disconnect(d->m_delegateChooserChanged);
489 
490     d->m_delegateChooser = nullptr;
491     if (delegate) {
492         QQmlAbstractDelegateComponent *adc =
493                 qobject_cast<QQmlAbstractDelegateComponent *>(delegate);
494         if (adc) {
495             d->m_delegateChooser = adc;
496             d->m_delegateChooserChanged = connect(adc, &QQmlAbstractDelegateComponent::delegateChanged,
497                                                [d](){ d->delegateChanged(); });
498         }
499     }
500     if (d->m_complete) {
501         _q_itemsInserted(0, d->adaptorModelCount());
502         d->requestMoreIfNecessary();
503     }
504     emit delegateChanged();
505 }
506 
507 /*!
508     \qmlproperty QModelIndex QtQml.Models::DelegateModel::rootIndex
509 
510     QAbstractItemModel provides a hierarchical tree of data, whereas
511     QML only operates on list data.  \c rootIndex allows the children of
512     any node in a QAbstractItemModel to be provided by this model.
513 
514     This property only affects models of type QAbstractItemModel that
515     are hierarchical (e.g, a tree model).
516 
517     For example, here is a simple interactive file system browser.
518     When a directory name is clicked, the view's \c rootIndex is set to the
519     QModelIndex node of the clicked directory, thus updating the view to show
520     the new directory's contents.
521 
522     \c main.cpp:
523     \snippet delegatemodel/delegatemodel_rootindex/main.cpp 0
524 
525     \c view.qml:
526     \snippet delegatemodel/delegatemodel_rootindex/view.qml 0
527 
528     If the \l {dm-model-property}{model} is a QAbstractItemModel subclass,
529     the delegate can also reference a \c hasModelChildren property (optionally
530     qualified by a \e model. prefix) that indicates whether the delegate's
531     model item has any child nodes.
532 
533     \sa modelIndex(), parentModelIndex()
534 */
rootIndex() const535 QVariant QQmlDelegateModel::rootIndex() const
536 {
537     Q_D(const QQmlDelegateModel);
538     return QVariant::fromValue(QModelIndex(d->m_adaptorModel.rootIndex));
539 }
540 
setRootIndex(const QVariant & root)541 void QQmlDelegateModel::setRootIndex(const QVariant &root)
542 {
543     Q_D(QQmlDelegateModel);
544 
545     QModelIndex modelIndex = qvariant_cast<QModelIndex>(root);
546     const bool changed = d->m_adaptorModel.rootIndex != modelIndex;
547     if (changed || !d->m_adaptorModel.isValid()) {
548         const int oldCount = d->m_count;
549         d->m_adaptorModel.rootIndex = modelIndex;
550         if (!d->m_adaptorModel.isValid() && d->m_adaptorModel.aim()) {
551             // The previous root index was invalidated, so we need to reconnect the model.
552             d->disconnectFromAbstractItemModel();
553             d->m_adaptorModel.setModel(d->m_adaptorModel.list.list(), this, d->m_context->engine());
554             d->connectToAbstractItemModel();
555         }
556         if (d->m_adaptorModel.canFetchMore())
557             d->m_adaptorModel.fetchMore();
558         if (d->m_complete) {
559             const int newCount = d->adaptorModelCount();
560             if (oldCount)
561                 _q_itemsRemoved(0, oldCount);
562             if (newCount)
563                 _q_itemsInserted(0, newCount);
564         }
565         if (changed)
566             emit rootIndexChanged();
567     }
568 }
569 
570 /*!
571     \qmlmethod QModelIndex QtQml.Models::DelegateModel::modelIndex(int index)
572 
573     QAbstractItemModel provides a hierarchical tree of data, whereas
574     QML only operates on list data. This function assists in using
575     tree models in QML.
576 
577     Returns a QModelIndex for the specified \a index.
578     This value can be assigned to rootIndex.
579 
580     \sa rootIndex
581 */
modelIndex(int idx) const582 QVariant QQmlDelegateModel::modelIndex(int idx) const
583 {
584     Q_D(const QQmlDelegateModel);
585     return d->m_adaptorModel.modelIndex(idx);
586 }
587 
588 /*!
589     \qmlmethod QModelIndex QtQml.Models::DelegateModel::parentModelIndex()
590 
591     QAbstractItemModel provides a hierarchical tree of data, whereas
592     QML only operates on list data.  This function assists in using
593     tree models in QML.
594 
595     Returns a QModelIndex for the parent of the current rootIndex.
596     This value can be assigned to rootIndex.
597 
598     \sa rootIndex
599 */
parentModelIndex() const600 QVariant QQmlDelegateModel::parentModelIndex() const
601 {
602     Q_D(const QQmlDelegateModel);
603     return d->m_adaptorModel.parentModelIndex();
604 }
605 
606 /*!
607     \qmlproperty int QtQml.Models::DelegateModel::count
608 */
609 
count() const610 int QQmlDelegateModel::count() const
611 {
612     Q_D(const QQmlDelegateModel);
613     if (!d->m_delegate)
614         return 0;
615     return d->m_compositor.count(d->m_compositorGroup);
616 }
617 
release(QObject * object,QQmlInstanceModel::ReusableFlag reusableFlag)618 QQmlDelegateModel::ReleaseFlags QQmlDelegateModelPrivate::release(QObject *object, QQmlInstanceModel::ReusableFlag reusableFlag)
619 {
620     if (!object)
621         return QQmlDelegateModel::ReleaseFlags{};
622 
623     QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(object);
624     if (!cacheItem)
625         return QQmlDelegateModel::ReleaseFlags{};
626 
627     if (!cacheItem->releaseObject())
628         return QQmlDelegateModel::Referenced;
629 
630     if (reusableFlag == QQmlInstanceModel::Reusable) {
631         removeCacheItem(cacheItem);
632         m_reusableItemsPool.insertItem(cacheItem);
633         emit q_func()->itemPooled(cacheItem->index, cacheItem->object);
634         return QQmlInstanceModel::Pooled;
635     }
636 
637     destroyCacheItem(cacheItem);
638     return QQmlInstanceModel::Destroyed;
639 }
640 
destroyCacheItem(QQmlDelegateModelItem * cacheItem)641 void QQmlDelegateModelPrivate::destroyCacheItem(QQmlDelegateModelItem *cacheItem)
642 {
643     emitDestroyingItem(cacheItem->object);
644     cacheItem->destroyObject();
645     if (cacheItem->incubationTask) {
646         releaseIncubator(cacheItem->incubationTask);
647         cacheItem->incubationTask = nullptr;
648     }
649     cacheItem->Dispose();
650 }
651 
652 /*
653   Returns ReleaseStatus flags.
654 */
release(QObject * item,QQmlInstanceModel::ReusableFlag reusableFlag)655 QQmlDelegateModel::ReleaseFlags QQmlDelegateModel::release(QObject *item, QQmlInstanceModel::ReusableFlag reusableFlag)
656 {
657     Q_D(QQmlDelegateModel);
658     QQmlInstanceModel::ReleaseFlags stat = d->release(item, reusableFlag);
659     return stat;
660 }
661 
662 // Cancel a requested async item
cancel(int index)663 void QQmlDelegateModel::cancel(int index)
664 {
665     Q_D(QQmlDelegateModel);
666     if (index < 0 || index >= d->m_compositor.count(d->m_compositorGroup)) {
667         qWarning() << "DelegateModel::cancel: index out range" << index << d->m_compositor.count(d->m_compositorGroup);
668         return;
669     }
670 
671     Compositor::iterator it = d->m_compositor.find(d->m_compositorGroup, index);
672     QQmlDelegateModelItem *cacheItem = it->inCache() ? d->m_cache.at(it.cacheIndex) : 0;
673     if (cacheItem) {
674         if (cacheItem->incubationTask && !cacheItem->isObjectReferenced()) {
675             d->releaseIncubator(cacheItem->incubationTask);
676             cacheItem->incubationTask = nullptr;
677 
678             if (cacheItem->object) {
679                 QObject *object = cacheItem->object;
680                 cacheItem->destroyObject();
681                 if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(object))
682                     d->emitDestroyingPackage(package);
683                 else
684                     d->emitDestroyingItem(object);
685             }
686 
687             cacheItem->scriptRef -= 1;
688         }
689         if (!cacheItem->isReferenced()) {
690             d->m_compositor.clearFlags(Compositor::Cache, it.cacheIndex, 1, Compositor::CacheFlag);
691             d->m_cache.removeAt(it.cacheIndex);
692             delete cacheItem;
693             Q_ASSERT(d->m_cache.count() == d->m_compositor.count(Compositor::Cache));
694         }
695     }
696 }
697 
group_append(QQmlListProperty<QQmlDelegateModelGroup> * property,QQmlDelegateModelGroup * group)698 void QQmlDelegateModelPrivate::group_append(
699         QQmlListProperty<QQmlDelegateModelGroup> *property, QQmlDelegateModelGroup *group)
700 {
701     QQmlDelegateModelPrivate *d = static_cast<QQmlDelegateModelPrivate *>(property->data);
702     if (d->m_complete)
703         return;
704     if (d->m_groupCount == Compositor::MaximumGroupCount) {
705         qmlWarning(d->q_func()) << QQmlDelegateModel::tr("The maximum number of supported DelegateModelGroups is 8");
706         return;
707     }
708     d->m_groups[d->m_groupCount] = group;
709     d->m_groupCount += 1;
710 }
711 
group_count(QQmlListProperty<QQmlDelegateModelGroup> * property)712 int QQmlDelegateModelPrivate::group_count(
713         QQmlListProperty<QQmlDelegateModelGroup> *property)
714 {
715     QQmlDelegateModelPrivate *d = static_cast<QQmlDelegateModelPrivate *>(property->data);
716     return d->m_groupCount - 1;
717 }
718 
group_at(QQmlListProperty<QQmlDelegateModelGroup> * property,int index)719 QQmlDelegateModelGroup *QQmlDelegateModelPrivate::group_at(
720         QQmlListProperty<QQmlDelegateModelGroup> *property, int index)
721 {
722     QQmlDelegateModelPrivate *d = static_cast<QQmlDelegateModelPrivate *>(property->data);
723     return index >= 0 && index < d->m_groupCount - 1
724             ? d->m_groups[index + 1]
725             : nullptr;
726 }
727 
728 /*!
729     \qmlproperty list<DelegateModelGroup> QtQml.Models::DelegateModel::groups
730 
731     This property holds a delegate model's group definitions.
732 
733     Groups define a sub-set of the items in a delegate model and can be used to filter
734     a model.
735 
736     For every group defined in a DelegateModel two attached properties are added to each
737     delegate item.  The first of the form DelegateModel.in\e{GroupName} holds whether the
738     item belongs to the group and the second DelegateModel.\e{groupName}Index holds the
739     index of the item in that group.
740 
741     The following example illustrates using groups to select items in a model.
742 
743     \snippet delegatemodel/delegatemodelgroup.qml 0
744     \keyword dm-groups-property
745 */
746 
groups()747 QQmlListProperty<QQmlDelegateModelGroup> QQmlDelegateModel::groups()
748 {
749     Q_D(QQmlDelegateModel);
750     return QQmlListProperty<QQmlDelegateModelGroup>(
751             this,
752             d,
753             QQmlDelegateModelPrivate::group_append,
754             QQmlDelegateModelPrivate::group_count,
755             QQmlDelegateModelPrivate::group_at,
756             nullptr, nullptr, nullptr);
757 }
758 
759 /*!
760     \qmlproperty DelegateModelGroup QtQml.Models::DelegateModel::items
761 
762     This property holds default group to which all new items are added.
763 */
764 
items()765 QQmlDelegateModelGroup *QQmlDelegateModel::items()
766 {
767     Q_D(QQmlDelegateModel);
768     return d->m_items;
769 }
770 
771 /*!
772     \qmlproperty DelegateModelGroup QtQml.Models::DelegateModel::persistedItems
773 
774     This property holds delegate model's persisted items group.
775 
776     Items in this group are not destroyed when released by a view, instead they are persisted
777     until removed from the group.
778 
779     An item can be removed from the persistedItems group by setting the
780     DelegateModel.inPersistedItems property to false.  If the item is not referenced by a view
781     at that time it will be destroyed.  Adding an item to this group will not create a new
782     instance.
783 
784     Items returned by the \l QtQml.Models::DelegateModelGroup::create() function are automatically added
785     to this group.
786 */
787 
persistedItems()788 QQmlDelegateModelGroup *QQmlDelegateModel::persistedItems()
789 {
790     Q_D(QQmlDelegateModel);
791     return d->m_persistedItems;
792 }
793 
794 /*!
795     \qmlproperty string QtQml.Models::DelegateModel::filterOnGroup
796 
797     This property holds name of the group that is used to filter the delegate model.
798 
799     Only items that belong to this group are visible to a view.
800 
801     By default this is the \l items group.
802 */
803 
filterGroup() const804 QString QQmlDelegateModel::filterGroup() const
805 {
806     Q_D(const QQmlDelegateModel);
807     return d->m_filterGroup;
808 }
809 
setFilterGroup(const QString & group)810 void QQmlDelegateModel::setFilterGroup(const QString &group)
811 {
812     Q_D(QQmlDelegateModel);
813 
814     if (d->m_transaction) {
815         qmlWarning(this) << tr("The group of a DelegateModel cannot be changed within onChanged");
816         return;
817     }
818 
819     if (d->m_filterGroup != group) {
820         d->m_filterGroup = group;
821         d->updateFilterGroup();
822         emit filterGroupChanged();
823     }
824 }
825 
resetFilterGroup()826 void QQmlDelegateModel::resetFilterGroup()
827 {
828     setFilterGroup(QStringLiteral("items"));
829 }
830 
updateFilterGroup()831 void QQmlDelegateModelPrivate::updateFilterGroup()
832 {
833     Q_Q(QQmlDelegateModel);
834     if (!m_cacheMetaType)
835         return;
836 
837     QQmlListCompositor::Group previousGroup = m_compositorGroup;
838     m_compositorGroup = Compositor::Default;
839     for (int i = 1; i < m_groupCount; ++i) {
840         if (m_filterGroup == m_cacheMetaType->groupNames.at(i - 1)) {
841             m_compositorGroup = Compositor::Group(i);
842             break;
843         }
844     }
845 
846     QQmlDelegateModelGroupPrivate::get(m_groups[m_compositorGroup])->emitters.insert(this);
847     if (m_compositorGroup != previousGroup) {
848         QVector<QQmlChangeSet::Change> removes;
849         QVector<QQmlChangeSet::Change> inserts;
850         m_compositor.transition(previousGroup, m_compositorGroup, &removes, &inserts);
851 
852         QQmlChangeSet changeSet;
853         changeSet.move(removes, inserts);
854         emit q->modelUpdated(changeSet, false);
855 
856         if (changeSet.difference() != 0)
857             emit q->countChanged();
858 
859         if (m_parts) {
860             auto partsCopy = m_parts->models; // deliberate; this may alter m_parts
861             for (QQmlPartsModel *model : qAsConst(partsCopy))
862                 model->updateFilterGroup(m_compositorGroup, changeSet);
863         }
864     }
865 }
866 
867 /*!
868     \qmlproperty object QtQml.Models::DelegateModel::parts
869 
870     The \a parts property selects a DelegateModel which creates
871     delegates from the part named.  This is used in conjunction with
872     the \l Package type.
873 
874     For example, the code below selects a model which creates
875     delegates named \e list from a \l Package:
876 
877     \code
878     DelegateModel {
879         id: visualModel
880         delegate: Package {
881             Item { Package.name: "list" }
882         }
883         model: myModel
884     }
885 
886     ListView {
887         width: 200; height:200
888         model: visualModel.parts.list
889     }
890     \endcode
891 
892     \sa Package
893 */
894 
parts()895 QObject *QQmlDelegateModel::parts()
896 {
897     Q_D(QQmlDelegateModel);
898     if (!d->m_parts)
899         d->m_parts = new QQmlDelegateModelParts(this);
900     return d->m_parts;
901 }
902 
abstractItemModel() const903 const QAbstractItemModel *QQmlDelegateModel::abstractItemModel() const
904 {
905     Q_D(const QQmlDelegateModel);
906     return d->m_adaptorModel.adaptsAim() ? d->m_adaptorModel.aim() : nullptr;
907 }
908 
emitCreatedPackage(QQDMIncubationTask * incubationTask,QQuickPackage * package)909 void QQmlDelegateModelPrivate::emitCreatedPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package)
910 {
911     for (int i = 1; i < m_groupCount; ++i)
912         QQmlDelegateModelGroupPrivate::get(m_groups[i])->createdPackage(incubationTask->index[i], package);
913 }
914 
emitInitPackage(QQDMIncubationTask * incubationTask,QQuickPackage * package)915 void QQmlDelegateModelPrivate::emitInitPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package)
916 {
917     for (int i = 1; i < m_groupCount; ++i)
918         QQmlDelegateModelGroupPrivate::get(m_groups[i])->initPackage(incubationTask->index[i], package);
919 }
920 
emitDestroyingPackage(QQuickPackage * package)921 void QQmlDelegateModelPrivate::emitDestroyingPackage(QQuickPackage *package)
922 {
923     for (int i = 1; i < m_groupCount; ++i)
924         QQmlDelegateModelGroupPrivate::get(m_groups[i])->destroyingPackage(package);
925 }
926 
isDoneIncubating(QQmlIncubator::Status status)927 static bool isDoneIncubating(QQmlIncubator::Status status)
928 {
929      return status == QQmlIncubator::Ready || status == QQmlIncubator::Error;
930 }
931 
PropertyUpdater(QObject * parent)932 PropertyUpdater::PropertyUpdater(QObject *parent) :
933       QObject(parent) {}
934 
doUpdate()935 void PropertyUpdater::doUpdate()
936 {
937     auto sender = QObject::sender();
938     auto mo = sender->metaObject();
939     auto signalIndex = QObject::senderSignalIndex();
940     ++updateCount;
941     auto property = mo->property(changeSignalIndexToPropertyIndex[signalIndex]);
942     // we synchronize between required properties and model rolenames by name
943     // that's why the QQmlProperty and the metaobject property must have the same name
944     QQmlProperty qmlProp(parent(), QString::fromLatin1(property.name()));
945     qmlProp.write(property.read(QObject::sender()));
946 }
947 
breakBinding()948 void PropertyUpdater::breakBinding()
949 {
950     auto it = senderToConnection.find(QObject::senderSignalIndex());
951     if (it == senderToConnection.end())
952         return;
953     if (updateCount == 0) {
954         QObject::disconnect(*it);
955         senderToConnection.erase(it);
956         QQmlError warning;
957         if (auto context = qmlContext(QObject::sender()))
958             warning.setUrl(context->baseUrl());
959         else
960             return;
961         auto signalName = QString::fromLatin1(QObject::sender()->metaObject()->method(QObject::senderSignalIndex()).name());
962         signalName.chop(sizeof("changed")-1);
963         QString propName = signalName;
964         propName[0] = propName[0].toLower();
965         warning.setDescription(QString::fromUtf8("Writing to \"%1\" broke the binding to the underlying model").arg(propName));
966         qmlWarning(this, warning);
967     } else {
968         --updateCount;
969     }
970 }
971 
initializeRequiredProperties(QQmlDelegateModelItem * modelItemToIncubate,QObject * object)972 void QQDMIncubationTask::initializeRequiredProperties(QQmlDelegateModelItem *modelItemToIncubate, QObject *object)
973 {
974     auto incubatorPriv = QQmlIncubatorPrivate::get(this);
975     if (incubatorPriv->hadRequiredProperties()) {
976         QQmlData *d = QQmlData::get(object);
977         auto contextData = d ? d->context : nullptr;
978         if (contextData) {
979             contextData->hasExtraObject = true;
980             contextData->extraObject = modelItemToIncubate;
981         }
982 
983         if (incubatorPriv->requiredProperties().empty())
984             return;
985         RequiredProperties &requiredProperties = incubatorPriv->requiredProperties();
986 
987         auto qmlMetaObject = modelItemToIncubate->metaObject();
988         // if a required property was not in the model, it might still be a static property of the
989         // QQmlDelegateModelItem or one of its derived classes this is the case for index, row,
990         // column, model and more
991         // the most derived subclass of QQmlDelegateModelItem is QQmlDMAbstractModelData at depth 2,
992         // so 4 should be plenty
993         QVarLengthArray<QPair<const QMetaObject *, QObject *>, 4> mos;
994         // we first check the dynamic meta object for properties originating from the model
995         // contains abstractitemmodelproperties
996         mos.push_back(qMakePair(qmlMetaObject, modelItemToIncubate));
997         auto delegateModelItemSubclassMO = qmlMetaObject->superClass();
998         mos.push_back(qMakePair(delegateModelItemSubclassMO, modelItemToIncubate));
999 
1000         while (strcmp(delegateModelItemSubclassMO->className(),
1001                       modelItemToIncubate->staticMetaObject.className())) {
1002             delegateModelItemSubclassMO = delegateModelItemSubclassMO->superClass();
1003             mos.push_back(qMakePair(delegateModelItemSubclassMO, modelItemToIncubate));
1004         }
1005         if (proxiedObject)
1006             mos.push_back(qMakePair(proxiedObject->metaObject(), proxiedObject));
1007 
1008         auto updater = new PropertyUpdater(object);
1009         for (const auto &metaObjectAndObject : mos) {
1010             const QMetaObject *mo = metaObjectAndObject.first;
1011             QObject *itemOrProxy = metaObjectAndObject.second;
1012             for (int i = mo->propertyOffset(); i < mo->propertyCount() + mo->propertyOffset(); ++i) {
1013                 auto prop = mo->property(i);
1014                 if (!prop.name())
1015                     continue;
1016                 auto propName = QString::fromUtf8(prop.name());
1017                 bool wasInRequired = false;
1018                 QQmlProperty componentProp = QQmlComponentPrivate::removePropertyFromRequired(
1019                         object, propName, requiredProperties, &wasInRequired);
1020                 // only write to property if it was actually requested by the component
1021                 if (wasInRequired && prop.hasNotifySignal()) {
1022                     QMetaMethod changeSignal = prop.notifySignal();
1023                     static QMetaMethod updateSlot = PropertyUpdater::staticMetaObject.method(
1024                                 PropertyUpdater::staticMetaObject.indexOfSlot("doUpdate()"));
1025 
1026                     QMetaObject::Connection conn = QObject::connect(itemOrProxy, changeSignal,
1027                                                                     updater, updateSlot);
1028                     updater->changeSignalIndexToPropertyIndex[changeSignal.methodIndex()] = i;
1029                     auto propIdx = object->metaObject()->indexOfProperty(propName.toUtf8());
1030                     QMetaMethod writeToPropSignal
1031                             = object->metaObject()->property(propIdx).notifySignal();
1032                     updater->senderToConnection[writeToPropSignal.methodIndex()] = conn;
1033                     static QMetaMethod breakBinding = PropertyUpdater::staticMetaObject.method(
1034                                 PropertyUpdater::staticMetaObject.indexOfSlot("breakBinding()"));
1035                     componentProp.write(prop.read(itemOrProxy));
1036                     // the connection needs to established after the write,
1037                     // else the signal gets triggered by it and breakBinding will remove the connection
1038                     QObject::connect(object, writeToPropSignal, updater, breakBinding);
1039                 }
1040                 else if (wasInRequired) // we still have to write, even if there is no change signal
1041                     componentProp.write(prop.read(itemOrProxy));
1042             }
1043         }
1044     } else {
1045         modelItemToIncubate->contextData->contextObject = modelItemToIncubate;
1046         if (proxiedObject)
1047             proxyContext->contextObject = proxiedObject;
1048     }
1049 }
1050 
statusChanged(Status status)1051 void QQDMIncubationTask::statusChanged(Status status)
1052 {
1053     if (vdm) {
1054         vdm->incubatorStatusChanged(this, status);
1055     } else if (isDoneIncubating(status)) {
1056         Q_ASSERT(incubating);
1057         // The model was deleted from under our feet, cleanup ourselves
1058         delete incubating->object;
1059         incubating->object = nullptr;
1060         if (incubating->contextData) {
1061             incubating->contextData->invalidate();
1062             Q_ASSERT(incubating->contextData->refCount == 1);
1063             incubating->contextData = nullptr;
1064         }
1065         incubating->scriptRef = 0;
1066         incubating->deleteLater();
1067     }
1068 }
1069 
releaseIncubator(QQDMIncubationTask * incubationTask)1070 void QQmlDelegateModelPrivate::releaseIncubator(QQDMIncubationTask *incubationTask)
1071 {
1072     Q_Q(QQmlDelegateModel);
1073     if (!incubationTask->isError())
1074         incubationTask->clear();
1075     m_finishedIncubating.append(incubationTask);
1076     if (!m_incubatorCleanupScheduled) {
1077         m_incubatorCleanupScheduled = true;
1078         QCoreApplication::postEvent(q, new QEvent(QEvent::User));
1079     }
1080 }
1081 
reuseItem(QQmlDelegateModelItem * item,int newModelIndex,int newGroups)1082 void QQmlDelegateModelPrivate::reuseItem(QQmlDelegateModelItem *item, int newModelIndex, int newGroups)
1083 {
1084     Q_ASSERT(item->object);
1085 
1086     // Update/reset which groups the item belongs to
1087     item->groups = newGroups;
1088 
1089     // Update context property index (including row and column) on the delegate
1090     // item, and inform the application about it. For a list, the row is the same
1091     // as the index, and the column is always 0. We set alwaysEmit to true, to
1092     // force all bindings to be reevaluated, even if the index didn't change.
1093     const bool alwaysEmit = true;
1094     item->setModelIndex(newModelIndex, newModelIndex, 0, alwaysEmit);
1095 
1096     // Notify the application that all 'dynamic'/role-based context data has
1097     // changed as well (their getter function will use the updated index).
1098     auto const itemAsList = QList<QQmlDelegateModelItem *>() << item;
1099     auto const updateAllRoles = QVector<int>();
1100     m_adaptorModel.notify(itemAsList, newModelIndex, 1, updateAllRoles);
1101 
1102     if (QQmlDelegateModelAttached *att = static_cast<QQmlDelegateModelAttached *>(
1103                 qmlAttachedPropertiesObject<QQmlDelegateModel>(item->object, false))) {
1104         // Update currentIndex of the attached DelegateModel object
1105         // to the index the item has in the cache.
1106         att->resetCurrentIndex();
1107         // emitChanges will emit both group-, and index changes to the application
1108         att->emitChanges();
1109     }
1110 
1111     // Inform the view that the item is recycled. This will typically result
1112     // in the view updating its own attached delegate item properties.
1113     emit q_func()->itemReused(newModelIndex, item->object);
1114 }
1115 
drainReusableItemsPool(int maxPoolTime)1116 void QQmlDelegateModelPrivate::drainReusableItemsPool(int maxPoolTime)
1117 {
1118     m_reusableItemsPool.drain(maxPoolTime, [=](QQmlDelegateModelItem *cacheItem){ destroyCacheItem(cacheItem); });
1119 }
1120 
drainReusableItemsPool(int maxPoolTime)1121 void QQmlDelegateModel::drainReusableItemsPool(int maxPoolTime)
1122 {
1123     d_func()->drainReusableItemsPool(maxPoolTime);
1124 }
1125 
poolSize()1126 int QQmlDelegateModel::poolSize()
1127 {
1128     return d_func()->m_reusableItemsPool.size();
1129 }
1130 
resolveDelegate(int index)1131 QQmlComponent *QQmlDelegateModelPrivate::resolveDelegate(int index)
1132 {
1133     if (!m_delegateChooser)
1134         return m_delegate;
1135 
1136     QQmlComponent *delegate = nullptr;
1137     QQmlAbstractDelegateComponent *chooser = m_delegateChooser;
1138 
1139     do {
1140         delegate = chooser->delegate(&m_adaptorModel, index);
1141         chooser = qobject_cast<QQmlAbstractDelegateComponent *>(delegate);
1142     } while (chooser);
1143 
1144     return delegate;
1145 }
1146 
addCacheItem(QQmlDelegateModelItem * item,Compositor::iterator it)1147 void QQmlDelegateModelPrivate::addCacheItem(QQmlDelegateModelItem *item, Compositor::iterator it)
1148 {
1149     m_cache.insert(it.cacheIndex, item);
1150     m_compositor.setFlags(it, 1, Compositor::CacheFlag);
1151     Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache));
1152 }
1153 
removeCacheItem(QQmlDelegateModelItem * cacheItem)1154 void QQmlDelegateModelPrivate::removeCacheItem(QQmlDelegateModelItem *cacheItem)
1155 {
1156     int cidx = m_cache.lastIndexOf(cacheItem);
1157     if (cidx >= 0) {
1158         m_compositor.clearFlags(Compositor::Cache, cidx, 1, Compositor::CacheFlag);
1159         m_cache.removeAt(cidx);
1160     }
1161     Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache));
1162 }
1163 
incubatorStatusChanged(QQDMIncubationTask * incubationTask,QQmlIncubator::Status status)1164 void QQmlDelegateModelPrivate::incubatorStatusChanged(QQDMIncubationTask *incubationTask, QQmlIncubator::Status status)
1165 {
1166     if (!isDoneIncubating(status))
1167         return;
1168 
1169     const QList<QQmlError> incubationTaskErrors = incubationTask->errors();
1170 
1171     QQmlDelegateModelItem *cacheItem = incubationTask->incubating;
1172     cacheItem->incubationTask = nullptr;
1173     incubationTask->incubating = nullptr;
1174     releaseIncubator(incubationTask);
1175 
1176     if (status == QQmlIncubator::Ready) {
1177         cacheItem->referenceObject();
1178         if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(cacheItem->object))
1179             emitCreatedPackage(incubationTask, package);
1180         else
1181             emitCreatedItem(incubationTask, cacheItem->object);
1182         cacheItem->releaseObject();
1183     } else if (status == QQmlIncubator::Error) {
1184         qmlInfo(m_delegate, incubationTaskErrors + m_delegate->errors()) << "Cannot create delegate";
1185     }
1186 
1187     if (!cacheItem->isObjectReferenced()) {
1188         if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(cacheItem->object))
1189             emitDestroyingPackage(package);
1190         else
1191             emitDestroyingItem(cacheItem->object);
1192         delete cacheItem->object;
1193         cacheItem->object = nullptr;
1194         cacheItem->scriptRef -= 1;
1195         if (cacheItem->contextData) {
1196             cacheItem->contextData->invalidate();
1197             Q_ASSERT(cacheItem->contextData->refCount == 1);
1198         }
1199         cacheItem->contextData = nullptr;
1200 
1201         if (!cacheItem->isReferenced()) {
1202             removeCacheItem(cacheItem);
1203             delete cacheItem;
1204         }
1205     }
1206 }
1207 
setInitialState(QObject * o)1208 void QQDMIncubationTask::setInitialState(QObject *o)
1209 {
1210     vdm->setInitialState(this, o);
1211 }
1212 
setInitialState(QQDMIncubationTask * incubationTask,QObject * o)1213 void QQmlDelegateModelPrivate::setInitialState(QQDMIncubationTask *incubationTask, QObject *o)
1214 {
1215     QQmlDelegateModelItem *cacheItem = incubationTask->incubating;
1216     incubationTask->initializeRequiredProperties(incubationTask->incubating, o);
1217     cacheItem->object = o;
1218 
1219     if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(cacheItem->object))
1220         emitInitPackage(incubationTask, package);
1221     else
1222         emitInitItem(incubationTask, cacheItem->object);
1223 }
1224 
object(Compositor::Group group,int index,QQmlIncubator::IncubationMode incubationMode)1225 QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQmlIncubator::IncubationMode incubationMode)
1226 {
1227     if (!m_delegate || index < 0 || index >= m_compositor.count(group)) {
1228         qWarning() << "DelegateModel::item: index out range" << index << m_compositor.count(group);
1229         return nullptr;
1230     } else if (!m_context || !m_context->isValid()) {
1231         return nullptr;
1232     }
1233 
1234     Compositor::iterator it = m_compositor.find(group, index);
1235     const auto flags = it->flags;
1236     const auto modelIndex = it.modelIndex();
1237 
1238     QQmlDelegateModelItem *cacheItem = it->inCache() ? m_cache.at(it.cacheIndex) : 0;
1239 
1240     if (!cacheItem || !cacheItem->delegate) {
1241         QQmlComponent *delegate = resolveDelegate(modelIndex);
1242         if (!delegate)
1243             return nullptr;
1244 
1245         if (!cacheItem) {
1246             cacheItem = m_reusableItemsPool.takeItem(delegate, index);
1247             if (cacheItem) {
1248                 // Move the pooled item back into the cache, update
1249                 // all related properties, and return the object (which
1250                 // has already been incubated, otherwise it wouldn't be in the pool).
1251                 addCacheItem(cacheItem, it);
1252                 reuseItem(cacheItem, index, flags);
1253                 cacheItem->referenceObject();
1254                 return cacheItem->object;
1255             }
1256 
1257             // Since we could't find an available item in the pool, we create a new one
1258             cacheItem = m_adaptorModel.createItem(m_cacheMetaType, modelIndex);
1259             if (!cacheItem)
1260                 return nullptr;
1261 
1262             cacheItem->groups = flags;
1263             addCacheItem(cacheItem, it);
1264         }
1265 
1266         cacheItem->delegate = delegate;
1267     }
1268 
1269     // Bump the reference counts temporarily so neither the content data or the delegate object
1270     // are deleted if incubatorStatusChanged() is called synchronously.
1271     cacheItem->scriptRef += 1;
1272     cacheItem->referenceObject();
1273 
1274     if (cacheItem->incubationTask) {
1275         bool sync = (incubationMode == QQmlIncubator::Synchronous || incubationMode == QQmlIncubator::AsynchronousIfNested);
1276         if (sync && cacheItem->incubationTask->incubationMode() == QQmlIncubator::Asynchronous) {
1277             // previously requested async - now needed immediately
1278             cacheItem->incubationTask->forceCompletion();
1279         }
1280     } else if (!cacheItem->object) {
1281         QQmlContext *creationContext = cacheItem->delegate->creationContext();
1282 
1283         cacheItem->scriptRef += 1;
1284 
1285         cacheItem->incubationTask = new QQDMIncubationTask(this, incubationMode);
1286         cacheItem->incubationTask->incubating = cacheItem;
1287         cacheItem->incubationTask->clear();
1288 
1289         for (int i = 1; i < m_groupCount; ++i)
1290             cacheItem->incubationTask->index[i] = it.index[i];
1291 
1292         QQmlContextData *ctxt = new QQmlContextData;
1293         ctxt->setParent(QQmlContextData::get(creationContext  ? creationContext : m_context.data()));
1294         cacheItem->contextData = ctxt;
1295 
1296         if (m_adaptorModel.hasProxyObject()) {
1297             if (QQmlAdaptorModelProxyInterface *proxy
1298                     = qobject_cast<QQmlAdaptorModelProxyInterface *>(cacheItem)) {
1299                 ctxt = new QQmlContextData;
1300                 ctxt->setParent(cacheItem->contextData, /*stronglyReferencedByParent*/true);
1301                 QObject *proxied = proxy->proxiedObject();
1302                 cacheItem->incubationTask->proxiedObject = proxied;
1303                 cacheItem->incubationTask->proxyContext = ctxt;
1304                 // We don't own the proxied object. We need to clear it if it goes away.
1305                 QObject::connect(proxied, &QObject::destroyed,
1306                                  cacheItem, &QQmlDelegateModelItem::childContextObjectDestroyed);
1307             }
1308         }
1309 
1310         QQmlComponentPrivate *cp = QQmlComponentPrivate::get(cacheItem->delegate);
1311         cp->incubateObject(
1312                     cacheItem->incubationTask,
1313                     cacheItem->delegate,
1314                     m_context->engine(),
1315                     ctxt,
1316                     QQmlContextData::get(m_context));
1317     }
1318 
1319     if (index == m_compositor.count(group) - 1)
1320         requestMoreIfNecessary();
1321 
1322     // Remove the temporary reference count.
1323     cacheItem->scriptRef -= 1;
1324     if (cacheItem->object && (!cacheItem->incubationTask || isDoneIncubating(cacheItem->incubationTask->status())))
1325         return cacheItem->object;
1326 
1327     cacheItem->releaseObject();
1328     if (!cacheItem->isReferenced()) {
1329         removeCacheItem(cacheItem);
1330         delete cacheItem;
1331     }
1332 
1333     return nullptr;
1334 }
1335 
1336 /*
1337   If asynchronous is true or the component is being loaded asynchronously due
1338   to an ancestor being loaded asynchronously, object() may return 0.  In this
1339   case createdItem() will be emitted when the object is available.  The object
1340   at this stage does not have any references, so object() must be called again
1341   to ensure a reference is held.  Any call to object() which returns a valid object
1342   must be matched by a call to release() in order to destroy the object.
1343 */
object(int index,QQmlIncubator::IncubationMode incubationMode)1344 QObject *QQmlDelegateModel::object(int index, QQmlIncubator::IncubationMode incubationMode)
1345 {
1346     Q_D(QQmlDelegateModel);
1347     if (!d->m_delegate || index < 0 || index >= d->m_compositor.count(d->m_compositorGroup)) {
1348         qWarning() << "DelegateModel::item: index out range" << index << d->m_compositor.count(d->m_compositorGroup);
1349         return nullptr;
1350     }
1351 
1352     return d->object(d->m_compositorGroup, index, incubationMode);
1353 }
1354 
incubationStatus(int index)1355 QQmlIncubator::Status QQmlDelegateModel::incubationStatus(int index)
1356 {
1357     Q_D(QQmlDelegateModel);
1358     Compositor::iterator it = d->m_compositor.find(d->m_compositorGroup, index);
1359     if (!it->inCache())
1360         return QQmlIncubator::Null;
1361 
1362     if (auto incubationTask = d->m_cache.at(it.cacheIndex)->incubationTask)
1363         return incubationTask->status();
1364 
1365     return QQmlIncubator::Ready;
1366 }
1367 
variantValue(QQmlListCompositor::Group group,int index,const QString & name)1368 QVariant QQmlDelegateModelPrivate::variantValue(QQmlListCompositor::Group group, int index, const QString &name)
1369 {
1370     Compositor::iterator it = m_compositor.find(group, index);
1371     if (QQmlAdaptorModel *model = it.list<QQmlAdaptorModel>()) {
1372         QString role = name;
1373         int dot = name.indexOf(QLatin1Char('.'));
1374         if (dot > 0)
1375             role = name.left(dot);
1376         QVariant value = model->value(it.modelIndex(), role);
1377         while (dot > 0) {
1378             QObject *obj = qvariant_cast<QObject*>(value);
1379             if (!obj)
1380                 return QVariant();
1381             const int from = dot + 1;
1382             dot = name.indexOf(QLatin1Char('.'), from);
1383             value = obj->property(name.midRef(from, dot - from).toUtf8());
1384         }
1385         return value;
1386     }
1387     return QVariant();
1388 }
1389 
variantValue(int index,const QString & role)1390 QVariant QQmlDelegateModel::variantValue(int index, const QString &role)
1391 {
1392     Q_D(QQmlDelegateModel);
1393     return d->variantValue(d->m_compositorGroup, index, role);
1394 }
1395 
indexOf(QObject * item,QObject *) const1396 int QQmlDelegateModel::indexOf(QObject *item, QObject *) const
1397 {
1398     Q_D(const QQmlDelegateModel);
1399     if (QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(item))
1400         return cacheItem->groupIndex(d->m_compositorGroup);
1401     return -1;
1402 }
1403 
setWatchedRoles(const QList<QByteArray> & roles)1404 void QQmlDelegateModel::setWatchedRoles(const QList<QByteArray> &roles)
1405 {
1406     Q_D(QQmlDelegateModel);
1407     d->m_adaptorModel.replaceWatchedRoles(d->m_watchedRoles, roles);
1408     d->m_watchedRoles = roles;
1409 }
1410 
addGroups(Compositor::iterator from,int count,Compositor::Group group,int groupFlags)1411 void QQmlDelegateModelPrivate::addGroups(
1412         Compositor::iterator from, int count, Compositor::Group group, int groupFlags)
1413 {
1414     QVector<Compositor::Insert> inserts;
1415     m_compositor.setFlags(from, count, group, groupFlags, &inserts);
1416     itemsInserted(inserts);
1417     emitChanges();
1418 }
1419 
removeGroups(Compositor::iterator from,int count,Compositor::Group group,int groupFlags)1420 void QQmlDelegateModelPrivate::removeGroups(
1421         Compositor::iterator from, int count, Compositor::Group group, int groupFlags)
1422 {
1423     QVector<Compositor::Remove> removes;
1424     m_compositor.clearFlags(from, count, group, groupFlags, &removes);
1425     itemsRemoved(removes);
1426     emitChanges();
1427 }
1428 
setGroups(Compositor::iterator from,int count,Compositor::Group group,int groupFlags)1429 void QQmlDelegateModelPrivate::setGroups(
1430         Compositor::iterator from, int count, Compositor::Group group, int groupFlags)
1431 {
1432     QVector<Compositor::Remove> removes;
1433     QVector<Compositor::Insert> inserts;
1434 
1435     m_compositor.setFlags(from, count, group, groupFlags, &inserts);
1436     itemsInserted(inserts);
1437     const int removeFlags = ~groupFlags & Compositor::GroupMask;
1438 
1439     from = m_compositor.find(from.group, from.index[from.group]);
1440     m_compositor.clearFlags(from, count, group, removeFlags, &removes);
1441     itemsRemoved(removes);
1442     emitChanges();
1443 }
1444 
event(QEvent * e)1445 bool QQmlDelegateModel::event(QEvent *e)
1446 {
1447     Q_D(QQmlDelegateModel);
1448     if (e->type() == QEvent::UpdateRequest) {
1449         d->m_waitingToFetchMore = false;
1450         d->m_adaptorModel.fetchMore();
1451     } else if (e->type() == QEvent::User) {
1452         d->m_incubatorCleanupScheduled = false;
1453         qDeleteAll(d->m_finishedIncubating);
1454         d->m_finishedIncubating.clear();
1455     }
1456     return QQmlInstanceModel::event(e);
1457 }
1458 
itemsChanged(const QVector<Compositor::Change> & changes)1459 void QQmlDelegateModelPrivate::itemsChanged(const QVector<Compositor::Change> &changes)
1460 {
1461     if (!m_delegate)
1462         return;
1463 
1464     QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> translatedChanges(m_groupCount);
1465 
1466     for (const Compositor::Change &change : changes) {
1467         for (int i = 1; i < m_groupCount; ++i) {
1468             if (change.inGroup(i)) {
1469                 translatedChanges[i].append(QQmlChangeSet::Change(change.index[i], change.count));
1470             }
1471         }
1472     }
1473 
1474     for (int i = 1; i < m_groupCount; ++i)
1475         QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.change(translatedChanges.at(i));
1476 }
1477 
_q_itemsChanged(int index,int count,const QVector<int> & roles)1478 void QQmlDelegateModel::_q_itemsChanged(int index, int count, const QVector<int> &roles)
1479 {
1480     Q_D(QQmlDelegateModel);
1481     if (count <= 0 || !d->m_complete)
1482         return;
1483 
1484     if (d->m_adaptorModel.notify(d->m_cache, index, count, roles)) {
1485         QVector<Compositor::Change> changes;
1486         d->m_compositor.listItemsChanged(&d->m_adaptorModel, index, count, &changes);
1487         d->itemsChanged(changes);
1488         d->emitChanges();
1489     }
1490 }
1491 
incrementIndexes(QQmlDelegateModelItem * cacheItem,int count,const int * deltas)1492 static void incrementIndexes(QQmlDelegateModelItem *cacheItem, int count, const int *deltas)
1493 {
1494     if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) {
1495         for (int i = 1; i < count; ++i)
1496             incubationTask->index[i] += deltas[i];
1497     }
1498     if (QQmlDelegateModelAttached *attached = cacheItem->attached) {
1499         for (int i = 1; i < qMin<int>(count, Compositor::MaximumGroupCount); ++i)
1500             attached->m_currentIndex[i] += deltas[i];
1501     }
1502 }
1503 
itemsInserted(const QVector<Compositor::Insert> & inserts,QVarLengthArray<QVector<QQmlChangeSet::Change>,Compositor::MaximumGroupCount> * translatedInserts,QHash<int,QList<QQmlDelegateModelItem * >> * movedItems)1504 void QQmlDelegateModelPrivate::itemsInserted(
1505         const QVector<Compositor::Insert> &inserts,
1506         QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> *translatedInserts,
1507         QHash<int, QList<QQmlDelegateModelItem *> > *movedItems)
1508 {
1509     int cacheIndex = 0;
1510 
1511     int inserted[Compositor::MaximumGroupCount];
1512     for (int i = 1; i < m_groupCount; ++i)
1513         inserted[i] = 0;
1514 
1515     for (const Compositor::Insert &insert : inserts) {
1516         for (; cacheIndex < insert.cacheIndex; ++cacheIndex)
1517             incrementIndexes(m_cache.at(cacheIndex), m_groupCount, inserted);
1518 
1519         for (int i = 1; i < m_groupCount; ++i) {
1520             if (insert.inGroup(i)) {
1521                 (*translatedInserts)[i].append(
1522                         QQmlChangeSet::Change(insert.index[i], insert.count, insert.moveId));
1523                 inserted[i] += insert.count;
1524             }
1525         }
1526 
1527         if (!insert.inCache())
1528             continue;
1529 
1530         if (movedItems && insert.isMove()) {
1531             QList<QQmlDelegateModelItem *> items = movedItems->take(insert.moveId);
1532             Q_ASSERT(items.count() == insert.count);
1533             m_cache = m_cache.mid(0, insert.cacheIndex) + items + m_cache.mid(insert.cacheIndex);
1534         }
1535         if (insert.inGroup()) {
1536             for (int offset = 0; cacheIndex < insert.cacheIndex + insert.count; ++cacheIndex, ++offset) {
1537                 QQmlDelegateModelItem *cacheItem = m_cache.at(cacheIndex);
1538                 cacheItem->groups |= insert.flags & Compositor::GroupMask;
1539 
1540                 if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) {
1541                     for (int i = 1; i < m_groupCount; ++i)
1542                         incubationTask->index[i] = cacheItem->groups & (1 << i)
1543                                 ? insert.index[i] + offset
1544                                 : insert.index[i];
1545                 }
1546                 if (QQmlDelegateModelAttached *attached = cacheItem->attached) {
1547                     for (int i = 1; i < m_groupCount; ++i)
1548                         attached->m_currentIndex[i] = cacheItem->groups & (1 << i)
1549                                 ? insert.index[i] + offset
1550                                 : insert.index[i];
1551                 }
1552             }
1553         } else {
1554             cacheIndex = insert.cacheIndex + insert.count;
1555         }
1556     }
1557     for (const QList<QQmlDelegateModelItem *> cache = m_cache; cacheIndex < cache.count(); ++cacheIndex)
1558         incrementIndexes(cache.at(cacheIndex), m_groupCount, inserted);
1559 }
1560 
itemsInserted(const QVector<Compositor::Insert> & inserts)1561 void QQmlDelegateModelPrivate::itemsInserted(const QVector<Compositor::Insert> &inserts)
1562 {
1563     QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> translatedInserts(m_groupCount);
1564     itemsInserted(inserts, &translatedInserts);
1565     Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache));
1566     if (!m_delegate)
1567         return;
1568 
1569     for (int i = 1; i < m_groupCount; ++i)
1570         QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.insert(translatedInserts.at(i));
1571 }
1572 
_q_itemsInserted(int index,int count)1573 void QQmlDelegateModel::_q_itemsInserted(int index, int count)
1574 {
1575 
1576     Q_D(QQmlDelegateModel);
1577     if (count <= 0 || !d->m_complete)
1578         return;
1579 
1580     d->m_count += count;
1581 
1582     const QList<QQmlDelegateModelItem *> cache = d->m_cache;
1583     for (int i = 0, c = cache.count();  i < c; ++i) {
1584         QQmlDelegateModelItem *item = cache.at(i);
1585         // layout change triggered by changing the modelIndex might have
1586         // already invalidated this item in d->m_cache and deleted it.
1587         if (!d->m_cache.isSharedWith(cache) && !d->m_cache.contains(item))
1588             continue;
1589 
1590         if (item->modelIndex() >= index) {
1591             const int newIndex = item->modelIndex() + count;
1592             const int row = newIndex;
1593             const int column = 0;
1594             item->setModelIndex(newIndex, row, column);
1595         }
1596     }
1597 
1598     QVector<Compositor::Insert> inserts;
1599     d->m_compositor.listItemsInserted(&d->m_adaptorModel, index, count, &inserts);
1600     d->itemsInserted(inserts);
1601     d->emitChanges();
1602 }
1603 
1604 //### This method should be split in two. It will remove delegates, and it will re-render the list.
1605 // When e.g. QQmlListModel::remove is called, the removal of the delegates should be done on
1606 // QAbstractItemModel::rowsAboutToBeRemoved, and the re-rendering on
1607 // QAbstractItemModel::rowsRemoved. Currently both are done on the latter signal. The problem is
1608 // that the destruction of an item will emit a changed signal that ends up at the delegate, which
1609 // in turn will try to load the data from the model (which should have already freed it), resulting
1610 // in a use-after-free. See QTBUG-59256.
itemsRemoved(const QVector<Compositor::Remove> & removes,QVarLengthArray<QVector<QQmlChangeSet::Change>,Compositor::MaximumGroupCount> * translatedRemoves,QHash<int,QList<QQmlDelegateModelItem * >> * movedItems)1611 void QQmlDelegateModelPrivate::itemsRemoved(
1612         const QVector<Compositor::Remove> &removes,
1613         QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> *translatedRemoves,
1614         QHash<int, QList<QQmlDelegateModelItem *> > *movedItems)
1615 {
1616     int cacheIndex = 0;
1617     int removedCache = 0;
1618 
1619     int removed[Compositor::MaximumGroupCount];
1620     for (int i = 1; i < m_groupCount; ++i)
1621         removed[i] = 0;
1622 
1623     for (const Compositor::Remove &remove : removes) {
1624         for (; cacheIndex < remove.cacheIndex && cacheIndex < m_cache.size(); ++cacheIndex)
1625             incrementIndexes(m_cache.at(cacheIndex), m_groupCount, removed);
1626 
1627         for (int i = 1; i < m_groupCount; ++i) {
1628             if (remove.inGroup(i)) {
1629                 (*translatedRemoves)[i].append(
1630                         QQmlChangeSet::Change(remove.index[i], remove.count, remove.moveId));
1631                 removed[i] -= remove.count;
1632             }
1633         }
1634 
1635         if (!remove.inCache())
1636             continue;
1637 
1638         if (movedItems && remove.isMove()) {
1639             movedItems->insert(remove.moveId, m_cache.mid(remove.cacheIndex, remove.count));
1640             QList<QQmlDelegateModelItem *>::iterator begin = m_cache.begin() + remove.cacheIndex;
1641             QList<QQmlDelegateModelItem *>::iterator end = begin + remove.count;
1642             m_cache.erase(begin, end);
1643         } else {
1644             for (; cacheIndex < remove.cacheIndex + remove.count - removedCache; ++cacheIndex) {
1645                 QQmlDelegateModelItem *cacheItem = m_cache.at(cacheIndex);
1646                 if (remove.inGroup(Compositor::Persisted) && cacheItem->objectRef == 0 && cacheItem->object) {
1647                     QObject *object = cacheItem->object;
1648                     cacheItem->destroyObject();
1649                     if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(object))
1650                         emitDestroyingPackage(package);
1651                     else
1652                         emitDestroyingItem(object);
1653                     cacheItem->scriptRef -= 1;
1654                 }
1655                 if (!cacheItem->isReferenced()) {
1656                     m_compositor.clearFlags(Compositor::Cache, cacheIndex, 1, Compositor::CacheFlag);
1657                     m_cache.removeAt(cacheIndex);
1658                     delete cacheItem;
1659                     --cacheIndex;
1660                     ++removedCache;
1661                     Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache));
1662                 } else if (remove.groups() == cacheItem->groups) {
1663                     cacheItem->groups = 0;
1664                     if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) {
1665                         for (int i = 1; i < m_groupCount; ++i)
1666                             incubationTask->index[i] = -1;
1667                     }
1668                     if (QQmlDelegateModelAttached *attached = cacheItem->attached) {
1669                         for (int i = 1; i < m_groupCount; ++i)
1670                             attached->m_currentIndex[i] = -1;
1671                     }
1672                 } else {
1673                     if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) {
1674                         if (!cacheItem->isObjectReferenced()) {
1675                             releaseIncubator(cacheItem->incubationTask);
1676                             cacheItem->incubationTask = nullptr;
1677                             if (cacheItem->object) {
1678                                 QObject *object = cacheItem->object;
1679                                 cacheItem->destroyObject();
1680                                 if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(object))
1681                                     emitDestroyingPackage(package);
1682                                 else
1683                                     emitDestroyingItem(object);
1684                             }
1685                             cacheItem->scriptRef -= 1;
1686                         } else {
1687                             for (int i = 1; i < m_groupCount; ++i) {
1688                                 if (remove.inGroup(i))
1689                                     incubationTask->index[i] = remove.index[i];
1690                             }
1691                         }
1692                     }
1693                     if (QQmlDelegateModelAttached *attached = cacheItem->attached) {
1694                         for (int i = 1; i < m_groupCount; ++i) {
1695                             if (remove.inGroup(i))
1696                                 attached->m_currentIndex[i] = remove.index[i];
1697                         }
1698                     }
1699                     cacheItem->groups &= ~remove.flags;
1700                 }
1701             }
1702         }
1703     }
1704 
1705     for (const QList<QQmlDelegateModelItem *> cache = m_cache; cacheIndex < cache.count(); ++cacheIndex)
1706         incrementIndexes(cache.at(cacheIndex), m_groupCount, removed);
1707 }
1708 
itemsRemoved(const QVector<Compositor::Remove> & removes)1709 void QQmlDelegateModelPrivate::itemsRemoved(const QVector<Compositor::Remove> &removes)
1710 {
1711     QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> translatedRemoves(m_groupCount);
1712     itemsRemoved(removes, &translatedRemoves);
1713     Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache));
1714     if (!m_delegate)
1715         return;
1716 
1717     for (int i = 1; i < m_groupCount; ++i)
1718        QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.remove(translatedRemoves.at(i));
1719 }
1720 
_q_itemsRemoved(int index,int count)1721 void QQmlDelegateModel::_q_itemsRemoved(int index, int count)
1722 {
1723     Q_D(QQmlDelegateModel);
1724     if (count <= 0|| !d->m_complete)
1725         return;
1726 
1727     d->m_count -= count;
1728     const QList<QQmlDelegateModelItem *> cache = d->m_cache;
1729     //Prevents items being deleted in remove loop
1730     for (QQmlDelegateModelItem *item : cache)
1731         item->referenceObject();
1732 
1733     for (int i = 0, c = cache.count();  i < c; ++i) {
1734         QQmlDelegateModelItem *item = cache.at(i);
1735         // layout change triggered by removal of a previous item might have
1736         // already invalidated this item in d->m_cache and deleted it
1737         if (!d->m_cache.isSharedWith(cache) && !d->m_cache.contains(item))
1738             continue;
1739 
1740         if (item->modelIndex() >= index + count) {
1741             const int newIndex = item->modelIndex() - count;
1742             const int row = newIndex;
1743             const int column = 0;
1744             item->setModelIndex(newIndex, row, column);
1745         } else if (item->modelIndex() >= index) {
1746             item->setModelIndex(-1, -1, -1);
1747         }
1748     }
1749     //Release items which are referenced before the loop
1750     for (QQmlDelegateModelItem *item : cache)
1751         item->releaseObject();
1752 
1753     QVector<Compositor::Remove> removes;
1754     d->m_compositor.listItemsRemoved(&d->m_adaptorModel, index, count, &removes);
1755     d->itemsRemoved(removes);
1756 
1757     d->emitChanges();
1758 }
1759 
itemsMoved(const QVector<Compositor::Remove> & removes,const QVector<Compositor::Insert> & inserts)1760 void QQmlDelegateModelPrivate::itemsMoved(
1761         const QVector<Compositor::Remove> &removes, const QVector<Compositor::Insert> &inserts)
1762 {
1763     QHash<int, QList<QQmlDelegateModelItem *> > movedItems;
1764 
1765     QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> translatedRemoves(m_groupCount);
1766     itemsRemoved(removes, &translatedRemoves, &movedItems);
1767 
1768     QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> translatedInserts(m_groupCount);
1769     itemsInserted(inserts, &translatedInserts, &movedItems);
1770     Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache));
1771     Q_ASSERT(movedItems.isEmpty());
1772     if (!m_delegate)
1773         return;
1774 
1775     for (int i = 1; i < m_groupCount; ++i) {
1776         QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.move(
1777                     translatedRemoves.at(i),
1778                     translatedInserts.at(i));
1779     }
1780 }
1781 
_q_itemsMoved(int from,int to,int count)1782 void QQmlDelegateModel::_q_itemsMoved(int from, int to, int count)
1783 {
1784     Q_D(QQmlDelegateModel);
1785     if (count <= 0 || !d->m_complete)
1786         return;
1787 
1788     const int minimum = qMin(from, to);
1789     const int maximum = qMax(from, to) + count;
1790     const int difference = from > to ? count : -count;
1791 
1792     const QList<QQmlDelegateModelItem *> cache = d->m_cache;
1793     for (int i = 0, c = cache.count();  i < c; ++i) {
1794         QQmlDelegateModelItem *item = cache.at(i);
1795         // layout change triggered by changing the modelIndex might have
1796         // already invalidated this item in d->m_cache and deleted it.
1797         if (!d->m_cache.isSharedWith(cache) && !d->m_cache.contains(item))
1798             continue;
1799 
1800         if (item->modelIndex() >= from && item->modelIndex() < from + count) {
1801             const int newIndex = item->modelIndex() - from + to;
1802             const int row = newIndex;
1803             const int column = 0;
1804             item->setModelIndex(newIndex, row, column);
1805         } else if (item->modelIndex() >= minimum && item->modelIndex() < maximum) {
1806             const int newIndex = item->modelIndex() + difference;
1807             const int row = newIndex;
1808             const int column = 0;
1809             item->setModelIndex(newIndex, row, column);
1810         }
1811     }
1812 
1813     QVector<Compositor::Remove> removes;
1814     QVector<Compositor::Insert> inserts;
1815     d->m_compositor.listItemsMoved(&d->m_adaptorModel, from, to, count, &removes, &inserts);
1816     d->itemsMoved(removes, inserts);
1817     d->emitChanges();
1818 }
1819 
emitModelUpdated(const QQmlChangeSet & changeSet,bool reset)1820 void QQmlDelegateModelPrivate::emitModelUpdated(const QQmlChangeSet &changeSet, bool reset)
1821 {
1822     Q_Q(QQmlDelegateModel);
1823     emit q->modelUpdated(changeSet, reset);
1824     if (changeSet.difference() != 0)
1825         emit q->countChanged();
1826 }
1827 
delegateChanged(bool add,bool remove)1828 void QQmlDelegateModelPrivate::delegateChanged(bool add, bool remove)
1829 {
1830     Q_Q(QQmlDelegateModel);
1831     if (!m_complete)
1832         return;
1833 
1834     if (m_transaction) {
1835         qmlWarning(q) << QQmlDelegateModel::tr("The delegates of a DelegateModel cannot be changed within onUpdated.");
1836         return;
1837     }
1838 
1839     if (remove) {
1840         for (int i = 1; i < m_groupCount; ++i) {
1841             QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.remove(
1842                     0, m_compositor.count(Compositor::Group(i)));
1843         }
1844     }
1845     if (add) {
1846         for (int i = 1; i < m_groupCount; ++i) {
1847             QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.insert(
1848                     0, m_compositor.count(Compositor::Group(i)));
1849         }
1850     }
1851     emitChanges();
1852 }
1853 
emitChanges()1854 void QQmlDelegateModelPrivate::emitChanges()
1855 {
1856     if (m_transaction || !m_complete || !m_context || !m_context->isValid())
1857         return;
1858 
1859     m_transaction = true;
1860     QV4::ExecutionEngine *engine = m_context->engine()->handle();
1861     for (int i = 1; i < m_groupCount; ++i)
1862         QQmlDelegateModelGroupPrivate::get(m_groups[i])->emitChanges(engine);
1863     m_transaction = false;
1864 
1865     const bool reset = m_reset;
1866     m_reset = false;
1867     for (int i = 1; i < m_groupCount; ++i)
1868         QQmlDelegateModelGroupPrivate::get(m_groups[i])->emitModelUpdated(reset);
1869 
1870     auto cacheCopy = m_cache; // deliberate; emitChanges may alter m_cache
1871     for (QQmlDelegateModelItem *cacheItem : qAsConst(cacheCopy)) {
1872         if (cacheItem->attached)
1873             cacheItem->attached->emitChanges();
1874     }
1875 }
1876 
_q_modelReset()1877 void QQmlDelegateModel::_q_modelReset()
1878 {
1879     Q_D(QQmlDelegateModel);
1880     if (!d->m_delegate)
1881         return;
1882 
1883     int oldCount = d->m_count;
1884     d->m_adaptorModel.rootIndex = QModelIndex();
1885 
1886     if (d->m_complete) {
1887         d->m_count = d->adaptorModelCount();
1888 
1889         const QList<QQmlDelegateModelItem *> cache = d->m_cache;
1890         for (int i = 0, c = cache.count();  i < c; ++i) {
1891             QQmlDelegateModelItem *item = cache.at(i);
1892             // layout change triggered by changing the modelIndex might have
1893             // already invalidated this item in d->m_cache and deleted it.
1894             if (!d->m_cache.isSharedWith(cache) && !d->m_cache.contains(item))
1895                 continue;
1896 
1897             if (item->modelIndex() != -1)
1898                 item->setModelIndex(-1, -1, -1);
1899         }
1900 
1901         QVector<Compositor::Remove> removes;
1902         QVector<Compositor::Insert> inserts;
1903         if (oldCount)
1904             d->m_compositor.listItemsRemoved(&d->m_adaptorModel, 0, oldCount, &removes);
1905         if (d->m_count)
1906             d->m_compositor.listItemsInserted(&d->m_adaptorModel, 0, d->m_count, &inserts);
1907         d->itemsMoved(removes, inserts);
1908         d->m_reset = true;
1909 
1910         if (d->m_adaptorModel.canFetchMore())
1911             d->m_adaptorModel.fetchMore();
1912 
1913         d->emitChanges();
1914     }
1915     emit rootIndexChanged();
1916 }
1917 
_q_rowsInserted(const QModelIndex & parent,int begin,int end)1918 void QQmlDelegateModel::_q_rowsInserted(const QModelIndex &parent, int begin, int end)
1919 {
1920     Q_D(QQmlDelegateModel);
1921     if (parent == d->m_adaptorModel.rootIndex)
1922         _q_itemsInserted(begin, end - begin + 1);
1923 }
1924 
_q_rowsAboutToBeRemoved(const QModelIndex & parent,int begin,int end)1925 void QQmlDelegateModel::_q_rowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end)
1926 {
1927     Q_D(QQmlDelegateModel);
1928     if (!d->m_adaptorModel.rootIndex.isValid())
1929         return;
1930     const QModelIndex index = d->m_adaptorModel.rootIndex;
1931     if (index.parent() == parent && index.row() >= begin && index.row() <= end) {
1932         const int oldCount = d->m_count;
1933         d->m_count = 0;
1934         d->disconnectFromAbstractItemModel();
1935         d->m_adaptorModel.invalidateModel();
1936 
1937         if (d->m_complete && oldCount > 0) {
1938             QVector<Compositor::Remove> removes;
1939             d->m_compositor.listItemsRemoved(&d->m_adaptorModel, 0, oldCount, &removes);
1940             d->itemsRemoved(removes);
1941             d->emitChanges();
1942         }
1943     }
1944 }
1945 
_q_rowsRemoved(const QModelIndex & parent,int begin,int end)1946 void QQmlDelegateModel::_q_rowsRemoved(const QModelIndex &parent, int begin, int end)
1947 {
1948     Q_D(QQmlDelegateModel);
1949     if (parent == d->m_adaptorModel.rootIndex)
1950         _q_itemsRemoved(begin, end - begin + 1);
1951 }
1952 
_q_rowsMoved(const QModelIndex & sourceParent,int sourceStart,int sourceEnd,const QModelIndex & destinationParent,int destinationRow)1953 void QQmlDelegateModel::_q_rowsMoved(
1954         const QModelIndex &sourceParent, int sourceStart, int sourceEnd,
1955         const QModelIndex &destinationParent, int destinationRow)
1956 {
1957    Q_D(QQmlDelegateModel);
1958     const int count = sourceEnd - sourceStart + 1;
1959     if (destinationParent == d->m_adaptorModel.rootIndex && sourceParent == d->m_adaptorModel.rootIndex) {
1960         _q_itemsMoved(sourceStart, sourceStart > destinationRow ? destinationRow : destinationRow - count, count);
1961     } else if (sourceParent == d->m_adaptorModel.rootIndex) {
1962         _q_itemsRemoved(sourceStart, count);
1963     } else if (destinationParent == d->m_adaptorModel.rootIndex) {
1964         _q_itemsInserted(destinationRow, count);
1965     }
1966 }
1967 
_q_columnsInserted(const QModelIndex & parent,int begin,int end)1968 void QQmlDelegateModel::_q_columnsInserted(const QModelIndex &parent, int begin, int end)
1969 {
1970     Q_D(QQmlDelegateModel);
1971     Q_UNUSED(end);
1972     if (parent == d->m_adaptorModel.rootIndex && begin == 0) {
1973         // mark all items as changed
1974         _q_itemsChanged(0, d->m_count, QVector<int>());
1975     }
1976 }
1977 
_q_columnsRemoved(const QModelIndex & parent,int begin,int end)1978 void QQmlDelegateModel::_q_columnsRemoved(const QModelIndex &parent, int begin, int end)
1979 {
1980     Q_D(QQmlDelegateModel);
1981     Q_UNUSED(end);
1982     if (parent == d->m_adaptorModel.rootIndex && begin == 0) {
1983         // mark all items as changed
1984         _q_itemsChanged(0, d->m_count, QVector<int>());
1985     }
1986 }
1987 
_q_columnsMoved(const QModelIndex & parent,int start,int end,const QModelIndex & destination,int column)1988 void QQmlDelegateModel::_q_columnsMoved(const QModelIndex &parent, int start, int end,
1989                                         const QModelIndex &destination, int column)
1990 {
1991     Q_D(QQmlDelegateModel);
1992     Q_UNUSED(end);
1993     if ((parent == d->m_adaptorModel.rootIndex && start == 0)
1994         || (destination == d->m_adaptorModel.rootIndex && column == 0)) {
1995         // mark all items as changed
1996         _q_itemsChanged(0, d->m_count, QVector<int>());
1997     }
1998 }
1999 
_q_dataChanged(const QModelIndex & begin,const QModelIndex & end,const QVector<int> & roles)2000 void QQmlDelegateModel::_q_dataChanged(const QModelIndex &begin, const QModelIndex &end, const QVector<int> &roles)
2001 {
2002     Q_D(QQmlDelegateModel);
2003     if (begin.parent() == d->m_adaptorModel.rootIndex)
2004         _q_itemsChanged(begin.row(), end.row() - begin.row() + 1, roles);
2005 }
2006 
isDescendantOf(const QPersistentModelIndex & desc,const QList<QPersistentModelIndex> & parents) const2007 bool QQmlDelegateModel::isDescendantOf(const QPersistentModelIndex& desc, const QList< QPersistentModelIndex >& parents) const
2008 {
2009     for (int i = 0, c = parents.count(); i < c; ++i) {
2010         for (QPersistentModelIndex parent = desc; parent.isValid(); parent = parent.parent()) {
2011             if (parent == parents[i])
2012                 return true;
2013         }
2014     }
2015 
2016     return false;
2017 }
2018 
_q_layoutChanged(const QList<QPersistentModelIndex> & parents,QAbstractItemModel::LayoutChangeHint hint)2019 void QQmlDelegateModel::_q_layoutChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint)
2020 {
2021     Q_D(QQmlDelegateModel);
2022     if (!d->m_complete)
2023         return;
2024 
2025     if (hint == QAbstractItemModel::VerticalSortHint) {
2026         if (!parents.isEmpty() && d->m_adaptorModel.rootIndex.isValid() && !isDescendantOf(d->m_adaptorModel.rootIndex, parents)) {
2027             return;
2028         }
2029 
2030         // mark all items as changed
2031         _q_itemsChanged(0, d->m_count, QVector<int>());
2032 
2033     } else if (hint == QAbstractItemModel::HorizontalSortHint) {
2034         // Ignored
2035     } else {
2036         // We don't know what's going on, so reset the model
2037         _q_modelReset();
2038     }
2039 }
2040 
qmlAttachedProperties(QObject * obj)2041 QQmlDelegateModelAttached *QQmlDelegateModel::qmlAttachedProperties(QObject *obj)
2042 {
2043     if (QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(obj)) {
2044         if (cacheItem->object == obj) { // Don't create attached item for child objects.
2045             cacheItem->attached = new QQmlDelegateModelAttached(cacheItem, obj);
2046             return cacheItem->attached;
2047         }
2048     }
2049     return new QQmlDelegateModelAttached(obj);
2050 }
2051 
insert(Compositor::insert_iterator & before,const QV4::Value & object,int groups)2052 bool QQmlDelegateModelPrivate::insert(Compositor::insert_iterator &before, const QV4::Value &object, int groups)
2053 {
2054     if (!m_context || !m_context->isValid())
2055         return false;
2056 
2057     QQmlDelegateModelItem *cacheItem = m_adaptorModel.createItem(m_cacheMetaType, -1);
2058     if (!cacheItem)
2059         return false;
2060     if (!object.isObject())
2061         return false;
2062 
2063     QV4::ExecutionEngine *v4 = object.as<QV4::Object>()->engine();
2064     QV4::Scope scope(v4);
2065     QV4::ScopedObject o(scope, object);
2066     if (!o)
2067         return false;
2068 
2069     QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly);
2070     QV4::ScopedValue propertyName(scope);
2071     QV4::ScopedValue v(scope);
2072     while (1) {
2073         propertyName = it.nextPropertyNameAsString(v);
2074         if (propertyName->isNull())
2075             break;
2076         cacheItem->setValue(propertyName->toQStringNoThrow(), scope.engine->toVariant(v, QMetaType::UnknownType));
2077     }
2078 
2079     cacheItem->groups = groups | Compositor::UnresolvedFlag | Compositor::CacheFlag;
2080 
2081     // Must be before the new object is inserted into the cache or its indexes will be adjusted too.
2082     itemsInserted(QVector<Compositor::Insert>(1, Compositor::Insert(before, 1, cacheItem->groups & ~Compositor::CacheFlag)));
2083 
2084     before = m_compositor.insert(before, nullptr, 0, 1, cacheItem->groups);
2085     m_cache.insert(before.cacheIndex, cacheItem);
2086 
2087     return true;
2088 }
2089 
2090 //============================================================================
2091 
QQmlDelegateModelItemMetaType(QV4::ExecutionEngine * engine,QQmlDelegateModel * model,const QStringList & groupNames)2092 QQmlDelegateModelItemMetaType::QQmlDelegateModelItemMetaType(
2093         QV4::ExecutionEngine *engine, QQmlDelegateModel *model, const QStringList &groupNames)
2094     : model(model)
2095     , groupCount(groupNames.count() + 1)
2096     , v4Engine(engine)
2097     , metaObject(nullptr)
2098     , groupNames(groupNames)
2099 {
2100 }
2101 
~QQmlDelegateModelItemMetaType()2102 QQmlDelegateModelItemMetaType::~QQmlDelegateModelItemMetaType()
2103 {
2104     if (metaObject)
2105         metaObject->release();
2106 }
2107 
initializeMetaObject()2108 void QQmlDelegateModelItemMetaType::initializeMetaObject()
2109 {
2110     QMetaObjectBuilder builder;
2111     builder.setFlags(QMetaObjectBuilder::DynamicMetaObject);
2112     builder.setClassName(QQmlDelegateModelAttached::staticMetaObject.className());
2113     builder.setSuperClass(&QQmlDelegateModelAttached::staticMetaObject);
2114 
2115     int notifierId = 0;
2116     for (int i = 0; i < groupNames.count(); ++i, ++notifierId) {
2117         QString propertyName = QLatin1String("in") + groupNames.at(i);
2118         propertyName.replace(2, 1, propertyName.at(2).toUpper());
2119         builder.addSignal("__" + propertyName.toUtf8() + "Changed()");
2120         QMetaPropertyBuilder propertyBuilder = builder.addProperty(
2121                 propertyName.toUtf8(), "bool", notifierId);
2122         propertyBuilder.setWritable(true);
2123     }
2124     for (int i = 0; i < groupNames.count(); ++i, ++notifierId) {
2125         const QString propertyName = groupNames.at(i) + QLatin1String("Index");
2126         builder.addSignal("__" + propertyName.toUtf8() + "Changed()");
2127         QMetaPropertyBuilder propertyBuilder = builder.addProperty(
2128                 propertyName.toUtf8(), "int", notifierId);
2129         propertyBuilder.setWritable(true);
2130     }
2131 
2132     metaObject = new QQmlDelegateModelAttachedMetaObject(this, builder.toMetaObject());
2133 }
2134 
initializePrototype()2135 void QQmlDelegateModelItemMetaType::initializePrototype()
2136 {
2137     QV4::Scope scope(v4Engine);
2138 
2139     QV4::ScopedObject proto(scope, v4Engine->newObject());
2140     proto->defineAccessorProperty(QStringLiteral("model"), QQmlDelegateModelItem::get_model, nullptr);
2141     proto->defineAccessorProperty(QStringLiteral("groups"), QQmlDelegateModelItem::get_groups, QQmlDelegateModelItem::set_groups);
2142     QV4::ScopedString s(scope);
2143     QV4::ScopedProperty p(scope);
2144 
2145     s = v4Engine->newString(QStringLiteral("isUnresolved"));
2146     QV4::ScopedFunctionObject f(scope);
2147     QV4::ExecutionContext *global = scope.engine->rootContext();
2148     p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, 30, QQmlDelegateModelItem::get_member)));
2149     p->setSetter(nullptr);
2150     proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
2151 
2152     s = v4Engine->newString(QStringLiteral("inItems"));
2153     p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Default, QQmlDelegateModelItem::get_member)));
2154     p->setSetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Default, QQmlDelegateModelItem::set_member)));
2155     proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
2156 
2157     s = v4Engine->newString(QStringLiteral("inPersistedItems"));
2158     p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Persisted, QQmlDelegateModelItem::get_member)));
2159     p->setSetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Persisted, QQmlDelegateModelItem::set_member)));
2160     proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
2161 
2162     s = v4Engine->newString(QStringLiteral("itemsIndex"));
2163     p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Default, QQmlDelegateModelItem::get_index)));
2164     proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
2165 
2166     s = v4Engine->newString(QStringLiteral("persistedItemsIndex"));
2167     p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Persisted, QQmlDelegateModelItem::get_index)));
2168     p->setSetter(nullptr);
2169     proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
2170 
2171     for (int i = 2; i < groupNames.count(); ++i) {
2172         QString propertyName = QLatin1String("in") + groupNames.at(i);
2173         propertyName.replace(2, 1, propertyName.at(2).toUpper());
2174         s = v4Engine->newString(propertyName);
2175         p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, i + 1, QQmlDelegateModelItem::get_member)));
2176         p->setSetter((f = QV4::DelegateModelGroupFunction::create(global, i + 1, QQmlDelegateModelItem::set_member)));
2177         proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
2178     }
2179     for (int i = 2; i < groupNames.count(); ++i) {
2180         const QString propertyName = groupNames.at(i) + QLatin1String("Index");
2181         s = v4Engine->newString(propertyName);
2182         p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, i + 1, QQmlDelegateModelItem::get_index)));
2183         p->setSetter(nullptr);
2184         proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
2185     }
2186     modelItemProto.set(v4Engine, proto);
2187 }
2188 
parseGroups(const QStringList & groups) const2189 int QQmlDelegateModelItemMetaType::parseGroups(const QStringList &groups) const
2190 {
2191     int groupFlags = 0;
2192     for (const QString &groupName : groups) {
2193         int index = groupNames.indexOf(groupName);
2194         if (index != -1)
2195             groupFlags |= 2 << index;
2196     }
2197     return groupFlags;
2198 }
2199 
parseGroups(const QV4::Value & groups) const2200 int QQmlDelegateModelItemMetaType::parseGroups(const QV4::Value &groups) const
2201 {
2202     int groupFlags = 0;
2203     QV4::Scope scope(v4Engine);
2204 
2205     QV4::ScopedString s(scope, groups);
2206     if (s) {
2207         const QString groupName = s->toQString();
2208         int index = groupNames.indexOf(groupName);
2209         if (index != -1)
2210             groupFlags |= 2 << index;
2211         return groupFlags;
2212     }
2213 
2214     QV4::ScopedArrayObject array(scope, groups);
2215     if (array) {
2216         QV4::ScopedValue v(scope);
2217         uint arrayLength = array->getLength();
2218         for (uint i = 0; i < arrayLength; ++i) {
2219             v = array->get(i);
2220             const QString groupName = v->toQString();
2221             int index = groupNames.indexOf(groupName);
2222             if (index != -1)
2223                 groupFlags |= 2 << index;
2224         }
2225     }
2226     return groupFlags;
2227 }
2228 
get_model(const QV4::FunctionObject * b,const QV4::Value * thisObject,const QV4::Value *,int)2229 QV4::ReturnedValue QQmlDelegateModelItem::get_model(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
2230 {
2231     QV4::Scope scope(b);
2232     QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>());
2233     if (!o)
2234         return b->engine()->throwTypeError(QStringLiteral("Not a valid DelegateModel object"));
2235     if (!o->d()->item->metaType->model)
2236         RETURN_UNDEFINED();
2237 
2238     return o->d()->item->get();
2239 }
2240 
get_groups(const QV4::FunctionObject * b,const QV4::Value * thisObject,const QV4::Value *,int)2241 QV4::ReturnedValue QQmlDelegateModelItem::get_groups(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
2242 {
2243     QV4::Scope scope(b);
2244     QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>());
2245     if (!o)
2246         return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object"));
2247 
2248     QStringList groups;
2249     for (int i = 1; i < o->d()->item->metaType->groupCount; ++i) {
2250         if (o->d()->item->groups & (1 << i))
2251             groups.append(o->d()->item->metaType->groupNames.at(i - 1));
2252     }
2253 
2254     return scope.engine->fromVariant(groups);
2255 }
2256 
set_groups(const QV4::FunctionObject * b,const QV4::Value * thisObject,const QV4::Value * argv,int argc)2257 QV4::ReturnedValue QQmlDelegateModelItem::set_groups(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
2258 {
2259     QV4::Scope scope(b);
2260     QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>());
2261     if (!o)
2262         return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object"));
2263 
2264     if (!argc)
2265         THROW_TYPE_ERROR();
2266 
2267     if (!o->d()->item->metaType->model)
2268         RETURN_UNDEFINED();
2269     QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(o->d()->item->metaType->model);
2270 
2271     const int groupFlags = model->m_cacheMetaType->parseGroups(argv[0]);
2272     const int cacheIndex = model->m_cache.indexOf(o->d()->item);
2273     Compositor::iterator it = model->m_compositor.find(Compositor::Cache, cacheIndex);
2274     model->setGroups(it, 1, Compositor::Cache, groupFlags);
2275     return QV4::Encode::undefined();
2276 }
2277 
get_member(QQmlDelegateModelItem * thisItem,uint flag,const QV4::Value &)2278 QV4::ReturnedValue QQmlDelegateModelItem::get_member(QQmlDelegateModelItem *thisItem, uint flag, const QV4::Value &)
2279 {
2280     return QV4::Encode(bool(thisItem->groups & (1 << flag)));
2281 }
2282 
set_member(QQmlDelegateModelItem * cacheItem,uint flag,const QV4::Value & arg)2283 QV4::ReturnedValue QQmlDelegateModelItem::set_member(QQmlDelegateModelItem *cacheItem, uint flag, const QV4::Value &arg)
2284 {
2285     if (!cacheItem->metaType->model)
2286         return QV4::Encode::undefined();
2287 
2288     QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(cacheItem->metaType->model);
2289 
2290     bool member = arg.toBoolean();
2291     uint groupFlag = (1 << flag);
2292     if (member == ((cacheItem->groups & groupFlag) != 0))
2293         return QV4::Encode::undefined();
2294 
2295     const int cacheIndex = model->m_cache.indexOf(cacheItem);
2296     Compositor::iterator it = model->m_compositor.find(Compositor::Cache, cacheIndex);
2297     if (member)
2298         model->addGroups(it, 1, Compositor::Cache, groupFlag);
2299     else
2300         model->removeGroups(it, 1, Compositor::Cache, groupFlag);
2301     return QV4::Encode::undefined();
2302 }
2303 
get_index(QQmlDelegateModelItem * thisItem,uint flag,const QV4::Value &)2304 QV4::ReturnedValue QQmlDelegateModelItem::get_index(QQmlDelegateModelItem *thisItem, uint flag, const QV4::Value &)
2305 {
2306     return QV4::Encode((int)thisItem->groupIndex(Compositor::Group(flag)));
2307 }
2308 
childContextObjectDestroyed(QObject * childContextObject)2309 void QQmlDelegateModelItem::childContextObjectDestroyed(QObject *childContextObject)
2310 {
2311     if (!contextData)
2312         return;
2313 
2314     for (QQmlContextData *ctxt = contextData->childContexts; ctxt; ctxt = ctxt->nextChild) {
2315         if (ctxt->contextObject == childContextObject)
2316             ctxt->contextObject = nullptr;
2317     }
2318 }
2319 
2320 
2321 //---------------------------------------------------------------------------
2322 
2323 DEFINE_OBJECT_VTABLE(QQmlDelegateModelItemObject);
2324 
destroy()2325 void QV4::Heap::QQmlDelegateModelItemObject::destroy()
2326 {
2327     item->Dispose();
2328     Object::destroy();
2329 }
2330 
2331 
QQmlDelegateModelItem(const QQmlRefPointer<QQmlDelegateModelItemMetaType> & metaType,QQmlAdaptorModel::Accessors * accessor,int modelIndex,int row,int column)2332 QQmlDelegateModelItem::QQmlDelegateModelItem(
2333         const QQmlRefPointer<QQmlDelegateModelItemMetaType> &metaType,
2334         QQmlAdaptorModel::Accessors *accessor,
2335         int modelIndex, int row, int column)
2336     : v4(metaType->v4Engine)
2337     , metaType(metaType)
2338     , contextData(nullptr)
2339     , object(nullptr)
2340     , attached(nullptr)
2341     , incubationTask(nullptr)
2342     , delegate(nullptr)
2343     , poolTime(0)
2344     , objectRef(0)
2345     , scriptRef(0)
2346     , groups(0)
2347     , index(modelIndex)
2348     , row(row)
2349     , column(column)
2350 {
2351     if (accessor->propertyCache) {
2352         // The property cache in the accessor is common for all the model
2353         // items in the model it wraps. It describes available model roles,
2354         // together with revisioned properties like row, column and index, all
2355         // which should be available in the delegate. We assign this cache to the
2356         // model item so that the QML engine can use the revision information
2357         // when resolving the properties (rather than falling back to just
2358         // inspecting the QObject in the model item directly).
2359         QQmlData *qmldata = QQmlData::get(this, true);
2360         if (qmldata->propertyCache)
2361             qmldata->propertyCache->release();
2362         qmldata->propertyCache = accessor->propertyCache.data();
2363         qmldata->propertyCache->addref();
2364     }
2365 }
2366 
~QQmlDelegateModelItem()2367 QQmlDelegateModelItem::~QQmlDelegateModelItem()
2368 {
2369     Q_ASSERT(scriptRef == 0);
2370     Q_ASSERT(objectRef == 0);
2371     Q_ASSERT(!object);
2372 
2373     if (incubationTask) {
2374         if (metaType->model)
2375             QQmlDelegateModelPrivate::get(metaType->model)->releaseIncubator(incubationTask);
2376         else
2377             delete incubationTask;
2378     }
2379 }
2380 
Dispose()2381 void QQmlDelegateModelItem::Dispose()
2382 {
2383     --scriptRef;
2384     if (isReferenced())
2385         return;
2386 
2387     if (metaType->model) {
2388         QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(metaType->model);
2389         model->removeCacheItem(this);
2390     }
2391     delete this;
2392 }
2393 
setModelIndex(int idx,int newRow,int newColumn,bool alwaysEmit)2394 void QQmlDelegateModelItem::setModelIndex(int idx, int newRow, int newColumn, bool alwaysEmit)
2395 {
2396     const int prevIndex = index;
2397     const int prevRow = row;
2398     const int prevColumn = column;
2399 
2400     index = idx;
2401     row = newRow;
2402     column = newColumn;
2403 
2404     if (idx != prevIndex || alwaysEmit)
2405         emit modelIndexChanged();
2406     if (row != prevRow || alwaysEmit)
2407         emit rowChanged();
2408     if (column != prevColumn || alwaysEmit)
2409         emit columnChanged();
2410 }
2411 
destroyObject()2412 void QQmlDelegateModelItem::destroyObject()
2413 {
2414     Q_ASSERT(object);
2415     Q_ASSERT(contextData);
2416 
2417     QQmlData *data = QQmlData::get(object);
2418     Q_ASSERT(data);
2419     if (data->ownContext) {
2420         data->ownContext->clearContext();
2421         if (data->ownContext->contextObject == object)
2422             data->ownContext->contextObject = nullptr;
2423         data->ownContext = nullptr;
2424         data->context = nullptr;
2425     }
2426     /* QTBUG-87228: when destroying object at the application exit, the deferred
2427      * parent by setting it to QCoreApplication instance if it's nullptr, so
2428      * deletion won't work. Not to leak memory, make sure our object has a that
2429      * the parent claims the object at the end of the lifetime. When not at the
2430      * application exit, normal event loop will handle the deferred deletion
2431      * earlier.
2432      */
2433     if (object->parent() == nullptr)
2434         object->setParent(QCoreApplication::instance());
2435     object->deleteLater();
2436 
2437     if (attached) {
2438         attached->m_cacheItem = nullptr;
2439         attached = nullptr;
2440     }
2441 
2442     contextData->invalidate();
2443     contextData = nullptr;
2444     object = nullptr;
2445 }
2446 
dataForObject(QObject * object)2447 QQmlDelegateModelItem *QQmlDelegateModelItem::dataForObject(QObject *object)
2448 {
2449     QQmlData *d = QQmlData::get(object);
2450     QQmlContextData *context = d ? d->context : nullptr;
2451     if (context && context->hasExtraObject)
2452         return qobject_cast<QQmlDelegateModelItem *>(context->extraObject);
2453     for (context = context ? context->parent : nullptr; context; context = context->parent) {
2454         if (QQmlDelegateModelItem *cacheItem = qobject_cast<QQmlDelegateModelItem *>(
2455                 context->contextObject)) {
2456             return cacheItem;
2457         }
2458     }
2459     return nullptr;
2460 }
2461 
groupIndex(Compositor::Group group)2462 int QQmlDelegateModelItem::groupIndex(Compositor::Group group)
2463 {
2464     if (QQmlDelegateModelPrivate * const model = metaType->model
2465             ? QQmlDelegateModelPrivate::get(metaType->model)
2466             : nullptr) {
2467         return model->m_compositor.find(Compositor::Cache, model->m_cache.indexOf(this)).index[group];
2468     }
2469     return -1;
2470 }
2471 
2472 //---------------------------------------------------------------------------
2473 
QQmlDelegateModelAttachedMetaObject(QQmlDelegateModelItemMetaType * metaType,QMetaObject * metaObject)2474 QQmlDelegateModelAttachedMetaObject::QQmlDelegateModelAttachedMetaObject(
2475         QQmlDelegateModelItemMetaType *metaType, QMetaObject *metaObject)
2476     : metaType(metaType)
2477     , metaObject(metaObject)
2478     , memberPropertyOffset(QQmlDelegateModelAttached::staticMetaObject.propertyCount())
2479     , indexPropertyOffset(QQmlDelegateModelAttached::staticMetaObject.propertyCount() + metaType->groupNames.count())
2480 {
2481     // Don't reference count the meta-type here as that would create a circular reference.
2482     // Instead we rely the fact that the meta-type's reference count can't reach 0 without first
2483     // destroying all delegates with attached objects.
2484     *static_cast<QMetaObject *>(this) = *metaObject;
2485 }
2486 
~QQmlDelegateModelAttachedMetaObject()2487 QQmlDelegateModelAttachedMetaObject::~QQmlDelegateModelAttachedMetaObject()
2488 {
2489     ::free(metaObject);
2490 }
2491 
objectDestroyed(QObject *)2492 void QQmlDelegateModelAttachedMetaObject::objectDestroyed(QObject *)
2493 {
2494     release();
2495 }
2496 
metaCall(QObject * object,QMetaObject::Call call,int _id,void ** arguments)2497 int QQmlDelegateModelAttachedMetaObject::metaCall(QObject *object, QMetaObject::Call call, int _id, void **arguments)
2498 {
2499     QQmlDelegateModelAttached *attached = static_cast<QQmlDelegateModelAttached *>(object);
2500     if (call == QMetaObject::ReadProperty) {
2501         if (_id >= indexPropertyOffset) {
2502             Compositor::Group group = Compositor::Group(_id - indexPropertyOffset + 1);
2503             *static_cast<int *>(arguments[0]) = attached->m_currentIndex[group];
2504             return -1;
2505         } else if (_id >= memberPropertyOffset) {
2506             Compositor::Group group = Compositor::Group(_id - memberPropertyOffset + 1);
2507             *static_cast<bool *>(arguments[0]) = attached->m_cacheItem->groups & (1 << group);
2508             return -1;
2509         }
2510     } else if (call == QMetaObject::WriteProperty) {
2511         if (_id >= memberPropertyOffset) {
2512             if (!metaType->model)
2513                 return -1;
2514             QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(metaType->model);
2515             Compositor::Group group = Compositor::Group(_id - memberPropertyOffset + 1);
2516             const int groupFlag = 1 << group;
2517             const bool member = attached->m_cacheItem->groups & groupFlag;
2518             if (member && !*static_cast<bool *>(arguments[0])) {
2519                 Compositor::iterator it = model->m_compositor.find(
2520                         group, attached->m_currentIndex[group]);
2521                 model->removeGroups(it, 1, group, groupFlag);
2522             } else if (!member && *static_cast<bool *>(arguments[0])) {
2523                 for (int i = 1; i < metaType->groupCount; ++i) {
2524                     if (attached->m_cacheItem->groups & (1 << i)) {
2525                         Compositor::iterator it = model->m_compositor.find(
2526                                 Compositor::Group(i), attached->m_currentIndex[i]);
2527                         model->addGroups(it, 1, Compositor::Group(i), groupFlag);
2528                         break;
2529                     }
2530                 }
2531             }
2532             return -1;
2533         }
2534     }
2535     return attached->qt_metacall(call, _id, arguments);
2536 }
2537 
QQmlDelegateModelAttached(QObject * parent)2538 QQmlDelegateModelAttached::QQmlDelegateModelAttached(QObject *parent)
2539     : m_cacheItem(nullptr)
2540     , m_previousGroups(0)
2541 {
2542     QQml_setParent_noEvent(this, parent);
2543 }
2544 
QQmlDelegateModelAttached(QQmlDelegateModelItem * cacheItem,QObject * parent)2545 QQmlDelegateModelAttached::QQmlDelegateModelAttached(
2546         QQmlDelegateModelItem *cacheItem, QObject *parent)
2547     : m_cacheItem(cacheItem)
2548     , m_previousGroups(cacheItem->groups)
2549 {
2550     QQml_setParent_noEvent(this, parent);
2551     resetCurrentIndex();
2552     // Let m_previousIndex be equal to m_currentIndex
2553     std::copy(std::begin(m_currentIndex), std::end(m_currentIndex), std::begin(m_previousIndex));
2554 
2555     if (!cacheItem->metaType->metaObject)
2556         cacheItem->metaType->initializeMetaObject();
2557 
2558     QObjectPrivate::get(this)->metaObject = cacheItem->metaType->metaObject;
2559     cacheItem->metaType->metaObject->addref();
2560 }
2561 
resetCurrentIndex()2562 void QQmlDelegateModelAttached::resetCurrentIndex()
2563 {
2564     if (QQDMIncubationTask *incubationTask = m_cacheItem->incubationTask) {
2565         for (int i = 1; i < qMin<int>(m_cacheItem->metaType->groupCount, Compositor::MaximumGroupCount); ++i)
2566             m_currentIndex[i] = incubationTask->index[i];
2567     } else {
2568         QQmlDelegateModelPrivate * const model = QQmlDelegateModelPrivate::get(m_cacheItem->metaType->model);
2569         Compositor::iterator it = model->m_compositor.find(
2570                 Compositor::Cache, model->m_cache.indexOf(m_cacheItem));
2571         for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i)
2572             m_currentIndex[i] = it.index[i];
2573     }
2574 }
2575 
2576 /*!
2577     \qmlattachedproperty model QtQml.Models::DelegateModel::model
2578 
2579     This attached property holds the data model this delegate instance belongs to.
2580 
2581     It is attached to each instance of the delegate.
2582 */
2583 
model() const2584 QQmlDelegateModel *QQmlDelegateModelAttached::model() const
2585 {
2586     return m_cacheItem ? m_cacheItem->metaType->model : nullptr;
2587 }
2588 
2589 /*!
2590     \qmlattachedproperty stringlist QtQml.Models::DelegateModel::groups
2591 
2592     This attached property holds the name of DelegateModelGroups the item belongs to.
2593 
2594     It is attached to each instance of the delegate.
2595 */
2596 
groups() const2597 QStringList QQmlDelegateModelAttached::groups() const
2598 {
2599     QStringList groups;
2600 
2601     if (!m_cacheItem)
2602         return groups;
2603     for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i) {
2604         if (m_cacheItem->groups & (1 << i))
2605             groups.append(m_cacheItem->metaType->groupNames.at(i - 1));
2606     }
2607     return groups;
2608 }
2609 
setGroups(const QStringList & groups)2610 void QQmlDelegateModelAttached::setGroups(const QStringList &groups)
2611 {
2612     if (!m_cacheItem)
2613         return;
2614 
2615     QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_cacheItem->metaType->model);
2616 
2617     const int groupFlags = model->m_cacheMetaType->parseGroups(groups);
2618     const int cacheIndex = model->m_cache.indexOf(m_cacheItem);
2619     Compositor::iterator it = model->m_compositor.find(Compositor::Cache, cacheIndex);
2620     model->setGroups(it, 1, Compositor::Cache, groupFlags);
2621 }
2622 
2623 /*!
2624     \qmlattachedproperty bool QtQml.Models::DelegateModel::isUnresolved
2625 
2626     This attached property indicates whether the visual item is bound to a data model index.
2627     Returns true if the item is not bound to the model, and false if it is.
2628 
2629     An unresolved item can be bound to the data model using the DelegateModelGroup::resolve()
2630     function.
2631 
2632     It is attached to each instance of the delegate.
2633 */
2634 
isUnresolved() const2635 bool QQmlDelegateModelAttached::isUnresolved() const
2636 {
2637     if (!m_cacheItem)
2638         return false;
2639 
2640     return m_cacheItem->groups & Compositor::UnresolvedFlag;
2641 }
2642 
2643 /*!
2644     \qmlattachedproperty bool QtQml.Models::DelegateModel::inItems
2645 
2646     This attached property holds whether the item belongs to the default \l items
2647     DelegateModelGroup.
2648 
2649     Changing this property will add or remove the item from the items group.
2650 
2651     It is attached to each instance of the delegate.
2652 */
2653 
2654 /*!
2655     \qmlattachedproperty int QtQml.Models::DelegateModel::itemsIndex
2656 
2657     This attached property holds the index of the item in the default \l items DelegateModelGroup.
2658 
2659     It is attached to each instance of the delegate.
2660 */
2661 
2662 /*!
2663     \qmlattachedproperty bool QtQml.Models::DelegateModel::inPersistedItems
2664 
2665     This attached property holds whether the item belongs to the \l persistedItems
2666     DelegateModelGroup.
2667 
2668     Changing this property will add or remove the item from the items group.  Change with caution
2669     as removing an item from the persistedItems group will destroy the current instance if it is
2670     not referenced by a model.
2671 
2672     It is attached to each instance of the delegate.
2673 */
2674 
2675 /*!
2676     \qmlattachedproperty int QtQml.Models::DelegateModel::persistedItemsIndex
2677 
2678     This attached property holds the index of the item in the \l persistedItems DelegateModelGroup.
2679 
2680     It is attached to each instance of the delegate.
2681 */
2682 
emitChanges()2683 void QQmlDelegateModelAttached::emitChanges()
2684 {
2685     const int groupChanges = m_previousGroups ^ m_cacheItem->groups;
2686     m_previousGroups = m_cacheItem->groups;
2687 
2688     int indexChanges = 0;
2689     for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i) {
2690         if (m_previousIndex[i] != m_currentIndex[i]) {
2691             m_previousIndex[i] = m_currentIndex[i];
2692             indexChanges |= (1 << i);
2693         }
2694     }
2695 
2696     int notifierId = 0;
2697     const QMetaObject *meta = metaObject();
2698     for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i, ++notifierId) {
2699         if (groupChanges & (1 << i))
2700             QMetaObject::activate(this, meta, notifierId, nullptr);
2701     }
2702     for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i, ++notifierId) {
2703         if (indexChanges & (1 << i))
2704             QMetaObject::activate(this, meta, notifierId, nullptr);
2705     }
2706 
2707     if (groupChanges)
2708         emit groupsChanged();
2709 }
2710 
2711 //============================================================================
2712 
setModel(QQmlDelegateModel * m,Compositor::Group g)2713 void QQmlDelegateModelGroupPrivate::setModel(QQmlDelegateModel *m, Compositor::Group g)
2714 {
2715     Q_ASSERT(!model);
2716     model = m;
2717     group = g;
2718 }
2719 
isChangedConnected()2720 bool QQmlDelegateModelGroupPrivate::isChangedConnected()
2721 {
2722     Q_Q(QQmlDelegateModelGroup);
2723     IS_SIGNAL_CONNECTED(q, QQmlDelegateModelGroup, changed, (const QJSValue &,const QJSValue &));
2724 }
2725 
emitChanges(QV4::ExecutionEngine * v4)2726 void QQmlDelegateModelGroupPrivate::emitChanges(QV4::ExecutionEngine *v4)
2727 {
2728     Q_Q(QQmlDelegateModelGroup);
2729     if (isChangedConnected() && !changeSet.isEmpty()) {
2730         emit q->changed(QJSValue(v4, engineData(v4)->array(v4, changeSet.removes())),
2731                         QJSValue(v4, engineData(v4)->array(v4, changeSet.inserts())));
2732     }
2733     if (changeSet.difference() != 0)
2734         emit q->countChanged();
2735 }
2736 
emitModelUpdated(bool reset)2737 void QQmlDelegateModelGroupPrivate::emitModelUpdated(bool reset)
2738 {
2739     for (QQmlDelegateModelGroupEmitterList::iterator it = emitters.begin(); it != emitters.end(); ++it)
2740         it->emitModelUpdated(changeSet, reset);
2741     changeSet.clear();
2742 }
2743 
2744 typedef QQmlDelegateModelGroupEmitterList::iterator GroupEmitterListIt;
2745 
createdPackage(int index,QQuickPackage * package)2746 void QQmlDelegateModelGroupPrivate::createdPackage(int index, QQuickPackage *package)
2747 {
2748     for (GroupEmitterListIt it = emitters.begin(), end = emitters.end(); it != end; ++it)
2749         it->createdPackage(index, package);
2750 }
2751 
initPackage(int index,QQuickPackage * package)2752 void QQmlDelegateModelGroupPrivate::initPackage(int index, QQuickPackage *package)
2753 {
2754     for (GroupEmitterListIt it = emitters.begin(), end = emitters.end(); it != end; ++it)
2755         it->initPackage(index, package);
2756 }
2757 
destroyingPackage(QQuickPackage * package)2758 void QQmlDelegateModelGroupPrivate::destroyingPackage(QQuickPackage *package)
2759 {
2760     for (GroupEmitterListIt it = emitters.begin(), end = emitters.end(); it != end; ++it)
2761         it->destroyingPackage(package);
2762 }
2763 
2764 /*!
2765     \qmltype DelegateModelGroup
2766     \instantiates QQmlDelegateModelGroup
2767     \inqmlmodule QtQml.Models
2768     \ingroup qtquick-models
2769     \brief Encapsulates a filtered set of visual data items.
2770 
2771     The DelegateModelGroup type provides a means to address the model data of a
2772     DelegateModel's delegate items, as well as sort and filter these delegate
2773     items.
2774 
2775     The initial set of instantiable delegate items in a DelegateModel is represented
2776     by its \l {QtQml.Models::DelegateModel::items}{items} group, which normally directly reflects
2777     the contents of the model assigned to DelegateModel::model.  This set can be changed to
2778     the contents of any other member of DelegateModel::groups by assigning the  \l name of that
2779     DelegateModelGroup to the DelegateModel::filterOnGroup property.
2780 
2781     The data of an item in a DelegateModelGroup can be accessed using the get() function, which returns
2782     information about group membership and indexes as well as model data.  In combination
2783     with the move() function this can be used to implement view sorting, with remove() to filter
2784     items out of a view, or with setGroups() and \l Package delegates to categorize items into
2785     different views. Different groups can only be sorted independently if they are disjunct. Moving
2786     an item in one group will also move it in all other groups it is a part of.
2787 
2788     Data from models can be supplemented by inserting data directly into a DelegateModelGroup
2789     with the insert() function.  This can be used to introduce mock items into a view, or
2790     placeholder items that are later \l {resolve()}{resolved} to real model data when it becomes
2791     available.
2792 
2793     Delegate items can also be instantiated directly from a DelegateModelGroup using the
2794     create() function, making it possible to use DelegateModel without an accompanying view
2795     type or to cherry-pick specific items that should be instantiated irregardless of whether
2796     they're currently within a view's visible area.
2797 
2798     \sa {QML Dynamic View Ordering Tutorial}
2799 */
QQmlDelegateModelGroup(QObject * parent)2800 QQmlDelegateModelGroup::QQmlDelegateModelGroup(QObject *parent)
2801     : QObject(*new QQmlDelegateModelGroupPrivate, parent)
2802 {
2803 }
2804 
QQmlDelegateModelGroup(const QString & name,QQmlDelegateModel * model,int index,QObject * parent)2805 QQmlDelegateModelGroup::QQmlDelegateModelGroup(
2806         const QString &name, QQmlDelegateModel *model, int index, QObject *parent)
2807     : QQmlDelegateModelGroup(parent)
2808 {
2809     Q_D(QQmlDelegateModelGroup);
2810     d->name = name;
2811     d->setModel(model, Compositor::Group(index));
2812 }
2813 
~QQmlDelegateModelGroup()2814 QQmlDelegateModelGroup::~QQmlDelegateModelGroup()
2815 {
2816 }
2817 
2818 /*!
2819     \qmlproperty string QtQml.Models::DelegateModelGroup::name
2820 
2821     This property holds the name of the group.
2822 
2823     Each group in a model must have a unique name starting with a lower case letter.
2824 */
2825 
name() const2826 QString QQmlDelegateModelGroup::name() const
2827 {
2828     Q_D(const QQmlDelegateModelGroup);
2829     return d->name;
2830 }
2831 
setName(const QString & name)2832 void QQmlDelegateModelGroup::setName(const QString &name)
2833 {
2834     Q_D(QQmlDelegateModelGroup);
2835     if (d->model)
2836         return;
2837     if (d->name != name) {
2838         d->name = name;
2839         emit nameChanged();
2840     }
2841 }
2842 
2843 /*!
2844     \qmlproperty int QtQml.Models::DelegateModelGroup::count
2845 
2846     This property holds the number of items in the group.
2847 */
2848 
count() const2849 int QQmlDelegateModelGroup::count() const
2850 {
2851     Q_D(const QQmlDelegateModelGroup);
2852     if (!d->model)
2853         return 0;
2854     return QQmlDelegateModelPrivate::get(d->model)->m_compositor.count(d->group);
2855 }
2856 
2857 /*!
2858     \qmlproperty bool QtQml.Models::DelegateModelGroup::includeByDefault
2859 
2860     This property holds whether new items are assigned to this group by default.
2861 */
2862 
defaultInclude() const2863 bool QQmlDelegateModelGroup::defaultInclude() const
2864 {
2865     Q_D(const QQmlDelegateModelGroup);
2866     return d->defaultInclude;
2867 }
2868 
setDefaultInclude(bool include)2869 void QQmlDelegateModelGroup::setDefaultInclude(bool include)
2870 {
2871     Q_D(QQmlDelegateModelGroup);
2872     if (d->defaultInclude != include) {
2873         d->defaultInclude = include;
2874 
2875         if (d->model) {
2876             if (include)
2877                 QQmlDelegateModelPrivate::get(d->model)->m_compositor.setDefaultGroup(d->group);
2878             else
2879                 QQmlDelegateModelPrivate::get(d->model)->m_compositor.clearDefaultGroup(d->group);
2880         }
2881         emit defaultIncludeChanged();
2882     }
2883 }
2884 
2885 /*!
2886     \qmlmethod object QtQml.Models::DelegateModelGroup::get(int index)
2887 
2888     Returns a javascript object describing the item at \a index in the group.
2889 
2890     The returned object contains the same information that is available to a delegate from the
2891     DelegateModel attached as well as the model for that item.  It has the properties:
2892 
2893     \list
2894     \li \b model The model data of the item.  This is the same as the model context property in
2895     a delegate
2896     \li \b groups A list the of names of groups the item is a member of.  This property can be
2897     written to change the item's membership.
2898     \li \b inItems Whether the item belongs to the \l {QtQml.Models::DelegateModel::items}{items} group.
2899     Writing to this property will add or remove the item from the group.
2900     \li \b itemsIndex The index of the item within the \l {QtQml.Models::DelegateModel::items}{items} group.
2901     \li \b {in<GroupName>} Whether the item belongs to the dynamic group \e groupName.  Writing to
2902     this property will add or remove the item from the group.
2903     \li \b {<groupName>Index} The index of the item within the dynamic group \e groupName.
2904     \li \b isUnresolved Whether the item is bound to an index in the model assigned to
2905     DelegateModel::model.  Returns true if the item is not bound to the model, and false if it is.
2906     \endlist
2907 */
2908 
get(int index)2909 QJSValue QQmlDelegateModelGroup::get(int index)
2910 {
2911     Q_D(QQmlDelegateModelGroup);
2912     if (!d->model)
2913         return QJSValue();
2914 
2915     QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model);
2916     if (!model->m_context || !model->m_context->isValid()) {
2917         return QJSValue();
2918     } else if (index < 0 || index >= model->m_compositor.count(d->group)) {
2919         qmlWarning(this) << tr("get: index out of range");
2920         return QJSValue();
2921     }
2922 
2923     Compositor::iterator it = model->m_compositor.find(d->group, index);
2924     QQmlDelegateModelItem *cacheItem = it->inCache()
2925             ? model->m_cache.at(it.cacheIndex)
2926             : 0;
2927 
2928     if (!cacheItem) {
2929         cacheItem = model->m_adaptorModel.createItem(
2930                 model->m_cacheMetaType, it.modelIndex());
2931         if (!cacheItem)
2932             return QJSValue();
2933         cacheItem->groups = it->flags;
2934 
2935         model->m_cache.insert(it.cacheIndex, cacheItem);
2936         model->m_compositor.setFlags(it, 1, Compositor::CacheFlag);
2937     }
2938 
2939     if (model->m_cacheMetaType->modelItemProto.isUndefined())
2940         model->m_cacheMetaType->initializePrototype();
2941     QV4::ExecutionEngine *v4 = model->m_cacheMetaType->v4Engine;
2942     QV4::Scope scope(v4);
2943     ++cacheItem->scriptRef;
2944     QV4::ScopedObject o(scope, v4->memoryManager->allocate<QQmlDelegateModelItemObject>(cacheItem));
2945     QV4::ScopedObject p(scope, model->m_cacheMetaType->modelItemProto.value());
2946     o->setPrototypeOf(p);
2947 
2948     return QJSValue(v4, o->asReturnedValue());
2949 }
2950 
parseIndex(const QV4::Value & value,int * index,Compositor::Group * group) const2951 bool QQmlDelegateModelGroupPrivate::parseIndex(const QV4::Value &value, int *index, Compositor::Group *group) const
2952 {
2953     if (value.isNumber()) {
2954         *index = value.toInt32();
2955         return true;
2956     }
2957 
2958     if (!value.isObject())
2959         return false;
2960 
2961     QV4::ExecutionEngine *v4 = value.as<QV4::Object>()->engine();
2962     QV4::Scope scope(v4);
2963     QV4::Scoped<QQmlDelegateModelItemObject> object(scope, value);
2964 
2965     if (object) {
2966         QQmlDelegateModelItem * const cacheItem = object->d()->item;
2967         if (QQmlDelegateModelPrivate *model = cacheItem->metaType->model
2968                 ? QQmlDelegateModelPrivate::get(cacheItem->metaType->model)
2969                 : nullptr) {
2970             *index = model->m_cache.indexOf(cacheItem);
2971             *group = Compositor::Cache;
2972             return true;
2973         }
2974     }
2975     return false;
2976 }
2977 
2978 /*!
2979     \qmlmethod QtQml.Models::DelegateModelGroup::insert(int index, jsdict data, array groups = undefined)
2980     \qmlmethod QtQml.Models::DelegateModelGroup::insert(jsdict data, var groups = undefined)
2981 
2982     Creates a new entry at \a index in a DelegateModel with the values from \a data that
2983     correspond to roles in the model assigned to DelegateModel::model.
2984 
2985     If no index is supplied the data is appended to the model.
2986 
2987     The optional \a groups parameter identifies the groups the new entry should belong to,
2988     if unspecified this is equal to the group insert was called on.
2989 
2990     Data inserted into a DelegateModel can later be merged with an existing entry in
2991     DelegateModel::model using the \l resolve() function.  This can be used to create placeholder
2992     items that are later replaced by actual data.
2993 */
2994 
insert(QQmlV4Function * args)2995 void QQmlDelegateModelGroup::insert(QQmlV4Function *args)
2996 {
2997     Q_D(QQmlDelegateModelGroup);
2998     QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model);
2999 
3000     int index = model->m_compositor.count(d->group);
3001     Compositor::Group group = d->group;
3002 
3003     if (args->length() == 0)
3004         return;
3005 
3006     int  i = 0;
3007     QV4::Scope scope(args->v4engine());
3008     QV4::ScopedValue v(scope, (*args)[i]);
3009     if (d->parseIndex(v, &index, &group)) {
3010         if (index < 0 || index > model->m_compositor.count(group)) {
3011             qmlWarning(this) << tr("insert: index out of range");
3012             return;
3013         }
3014         if (++i == args->length())
3015             return;
3016         v = (*args)[i];
3017     }
3018 
3019     Compositor::insert_iterator before = index < model->m_compositor.count(group)
3020             ? model->m_compositor.findInsertPosition(group, index)
3021             : model->m_compositor.end();
3022 
3023     int groups = 1 << d->group;
3024     if (++i < args->length()) {
3025         QV4::ScopedValue val(scope, (*args)[i]);
3026         groups |= model->m_cacheMetaType->parseGroups(val);
3027     }
3028 
3029     if (v->as<QV4::ArrayObject>()) {
3030         return;
3031     } else if (v->as<QV4::Object>()) {
3032         model->insert(before, v, groups);
3033         model->emitChanges();
3034     }
3035 }
3036 
3037 /*!
3038     \qmlmethod QtQml.Models::DelegateModelGroup::create(int index)
3039     \qmlmethod QtQml.Models::DelegateModelGroup::create(int index, jsdict data, array groups = undefined)
3040     \qmlmethod QtQml.Models::DelegateModelGroup::create(jsdict data, array groups = undefined)
3041 
3042     Returns a reference to the instantiated item at \a index in the group.
3043 
3044     If a \a data object is provided it will be \l {insert}{inserted} at \a index and an item
3045     referencing this new entry will be returned.  The optional \a groups parameter identifies
3046     the groups the new entry should belong to, if unspecified this is equal to the group create()
3047     was called on.
3048 
3049     All items returned by create are added to the
3050     \l {QtQml.Models::DelegateModel::persistedItems}{persistedItems} group.  Items in this
3051     group remain instantiated when not referenced by any view.
3052 */
3053 
create(QQmlV4Function * args)3054 void QQmlDelegateModelGroup::create(QQmlV4Function *args)
3055 {
3056     Q_D(QQmlDelegateModelGroup);
3057     if (!d->model)
3058         return;
3059 
3060     if (args->length() == 0)
3061         return;
3062 
3063     QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model);
3064 
3065     int index = model->m_compositor.count(d->group);
3066     Compositor::Group group = d->group;
3067 
3068     int  i = 0;
3069     QV4::Scope scope(args->v4engine());
3070     QV4::ScopedValue v(scope, (*args)[i]);
3071     if (d->parseIndex(v, &index, &group))
3072         ++i;
3073 
3074     if (i < args->length() && index >= 0 && index <= model->m_compositor.count(group)) {
3075         v = (*args)[i];
3076         if (v->as<QV4::Object>()) {
3077             int groups = 1 << d->group;
3078             if (++i < args->length()) {
3079                 QV4::ScopedValue val(scope, (*args)[i]);
3080                 groups |= model->m_cacheMetaType->parseGroups(val);
3081             }
3082 
3083             Compositor::insert_iterator before = index < model->m_compositor.count(group)
3084                     ? model->m_compositor.findInsertPosition(group, index)
3085                     : model->m_compositor.end();
3086 
3087             index = before.index[d->group];
3088             group = d->group;
3089 
3090             if (!model->insert(before, v, groups)) {
3091                 return;
3092             }
3093         }
3094     }
3095     if (index < 0 || index >= model->m_compositor.count(group)) {
3096         qmlWarning(this) << tr("create: index out of range");
3097         return;
3098     }
3099 
3100     QObject *object = model->object(group, index, QQmlIncubator::AsynchronousIfNested);
3101     if (object) {
3102         QVector<Compositor::Insert> inserts;
3103         Compositor::iterator it = model->m_compositor.find(group, index);
3104         model->m_compositor.setFlags(it, 1, d->group, Compositor::PersistedFlag, &inserts);
3105         model->itemsInserted(inserts);
3106         model->m_cache.at(it.cacheIndex)->releaseObject();
3107     }
3108 
3109     args->setReturnValue(QV4::QObjectWrapper::wrap(args->v4engine(), object));
3110     model->emitChanges();
3111 }
3112 
3113 /*!
3114     \qmlmethod QtQml.Models::DelegateModelGroup::resolve(int from, int to)
3115 
3116     Binds an unresolved item at \a from to an item in DelegateModel::model at index \a to.
3117 
3118     Unresolved items are entries whose data has been \l {insert()}{inserted} into a DelegateModelGroup
3119     instead of being derived from a DelegateModel::model index.  Resolving an item will replace
3120     the item at the target index with the unresolved item. A resolved an item will reflect the data
3121     of the source model at its bound index and will move when that index moves like any other item.
3122 
3123     If a new item is replaced in the DelegateModelGroup onChanged() handler its insertion and
3124     replacement will be communicated to views as an atomic operation, creating the appearance
3125     that the model contents have not changed, or if the unresolved and model item are not adjacent
3126     that the previously unresolved item has simply moved.
3127 
3128 */
resolve(QQmlV4Function * args)3129 void QQmlDelegateModelGroup::resolve(QQmlV4Function *args)
3130 {
3131     Q_D(QQmlDelegateModelGroup);
3132     if (!d->model)
3133         return;
3134 
3135     QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model);
3136 
3137     if (args->length() < 2)
3138         return;
3139 
3140     int from = -1;
3141     int to = -1;
3142     Compositor::Group fromGroup = d->group;
3143     Compositor::Group toGroup = d->group;
3144 
3145     QV4::Scope scope(args->v4engine());
3146     QV4::ScopedValue v(scope, (*args)[0]);
3147     if (d->parseIndex(v, &from, &fromGroup)) {
3148         if (from < 0 || from >= model->m_compositor.count(fromGroup)) {
3149             qmlWarning(this) << tr("resolve: from index out of range");
3150             return;
3151         }
3152     } else {
3153         qmlWarning(this) << tr("resolve: from index invalid");
3154         return;
3155     }
3156 
3157     v = (*args)[1];
3158     if (d->parseIndex(v, &to, &toGroup)) {
3159         if (to < 0 || to >= model->m_compositor.count(toGroup)) {
3160             qmlWarning(this) << tr("resolve: to index out of range");
3161             return;
3162         }
3163     } else {
3164         qmlWarning(this) << tr("resolve: to index invalid");
3165         return;
3166     }
3167 
3168     Compositor::iterator fromIt = model->m_compositor.find(fromGroup, from);
3169     Compositor::iterator toIt = model->m_compositor.find(toGroup, to);
3170 
3171     if (!fromIt->isUnresolved()) {
3172         qmlWarning(this) << tr("resolve: from is not an unresolved item");
3173         return;
3174     }
3175     if (!toIt->list) {
3176         qmlWarning(this) << tr("resolve: to is not a model item");
3177         return;
3178     }
3179 
3180     const int unresolvedFlags = fromIt->flags;
3181     const int resolvedFlags = toIt->flags;
3182     const int resolvedIndex = toIt.modelIndex();
3183     void * const resolvedList = toIt->list;
3184 
3185     QQmlDelegateModelItem *cacheItem = model->m_cache.at(fromIt.cacheIndex);
3186     cacheItem->groups &= ~Compositor::UnresolvedFlag;
3187 
3188     if (toIt.cacheIndex > fromIt.cacheIndex)
3189         toIt.decrementIndexes(1, unresolvedFlags);
3190     if (!toIt->inGroup(fromGroup) || toIt.index[fromGroup] > from)
3191         from += 1;
3192 
3193     model->itemsMoved(
3194             QVector<Compositor::Remove>(1, Compositor::Remove(fromIt, 1, unresolvedFlags, 0)),
3195             QVector<Compositor::Insert>(1, Compositor::Insert(toIt, 1, unresolvedFlags, 0)));
3196     model->itemsInserted(
3197             QVector<Compositor::Insert>(1, Compositor::Insert(toIt, 1, (resolvedFlags & ~unresolvedFlags) | Compositor::CacheFlag)));
3198     toIt.incrementIndexes(1, resolvedFlags | unresolvedFlags);
3199     model->itemsRemoved(QVector<Compositor::Remove>(1, Compositor::Remove(toIt, 1, resolvedFlags)));
3200 
3201     model->m_compositor.setFlags(toGroup, to, 1, unresolvedFlags & ~Compositor::UnresolvedFlag);
3202     model->m_compositor.clearFlags(fromGroup, from, 1, unresolvedFlags);
3203 
3204     if (resolvedFlags & Compositor::CacheFlag)
3205         model->m_compositor.insert(Compositor::Cache, toIt.cacheIndex, resolvedList, resolvedIndex, 1, Compositor::CacheFlag);
3206 
3207     Q_ASSERT(model->m_cache.count() == model->m_compositor.count(Compositor::Cache));
3208 
3209     if (!cacheItem->isReferenced()) {
3210         Q_ASSERT(toIt.cacheIndex == model->m_cache.indexOf(cacheItem));
3211         model->m_cache.removeAt(toIt.cacheIndex);
3212         model->m_compositor.clearFlags(Compositor::Cache, toIt.cacheIndex, 1, Compositor::CacheFlag);
3213         delete cacheItem;
3214         Q_ASSERT(model->m_cache.count() == model->m_compositor.count(Compositor::Cache));
3215     } else {
3216         cacheItem->resolveIndex(model->m_adaptorModel, resolvedIndex);
3217         if (cacheItem->attached)
3218             cacheItem->attached->emitUnresolvedChanged();
3219     }
3220 
3221     model->emitChanges();
3222 }
3223 
3224 /*!
3225     \qmlmethod QtQml.Models::DelegateModelGroup::remove(int index, int count)
3226 
3227     Removes \a count items starting at \a index from the group.
3228 */
3229 
remove(QQmlV4Function * args)3230 void QQmlDelegateModelGroup::remove(QQmlV4Function *args)
3231 {
3232     Q_D(QQmlDelegateModelGroup);
3233     if (!d->model)
3234         return;
3235     Compositor::Group group = d->group;
3236     int index = -1;
3237     int count = 1;
3238 
3239     if (args->length() == 0)
3240         return;
3241 
3242     int i = 0;
3243     QV4::Scope scope(args->v4engine());
3244     QV4::ScopedValue v(scope, (*args)[0]);
3245     if (!d->parseIndex(v, &index, &group)) {
3246         qmlWarning(this) << tr("remove: invalid index");
3247         return;
3248     }
3249 
3250     if (++i < args->length()) {
3251         v = (*args)[i];
3252         if (v->isNumber())
3253             count = v->toInt32();
3254     }
3255 
3256     QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model);
3257     if (index < 0 || index >= model->m_compositor.count(group)) {
3258         qmlWarning(this) << tr("remove: index out of range");
3259     } else if (count != 0) {
3260         Compositor::iterator it = model->m_compositor.find(group, index);
3261         if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) {
3262             qmlWarning(this) << tr("remove: invalid count");
3263         } else {
3264             model->removeGroups(it, count, d->group, 1 << d->group);
3265         }
3266     }
3267 }
3268 
parseGroupArgs(QQmlV4Function * args,Compositor::Group * group,int * index,int * count,int * groups) const3269 bool QQmlDelegateModelGroupPrivate::parseGroupArgs(
3270         QQmlV4Function *args, Compositor::Group *group, int *index, int *count, int *groups) const
3271 {
3272     if (!model || !QQmlDelegateModelPrivate::get(model)->m_cacheMetaType)
3273         return false;
3274 
3275     if (args->length() < 2)
3276         return false;
3277 
3278     int i = 0;
3279     QV4::Scope scope(args->v4engine());
3280     QV4::ScopedValue v(scope, (*args)[i]);
3281     if (!parseIndex(v, index, group))
3282         return false;
3283 
3284     v = (*args)[++i];
3285     if (v->isNumber()) {
3286         *count = v->toInt32();
3287 
3288         if (++i == args->length())
3289             return false;
3290         v = (*args)[i];
3291     }
3292 
3293     *groups = QQmlDelegateModelPrivate::get(model)->m_cacheMetaType->parseGroups(v);
3294 
3295     return true;
3296 }
3297 
3298 /*!
3299     \qmlmethod QtQml.Models::DelegateModelGroup::addGroups(int index, int count, stringlist groups)
3300 
3301     Adds \a count items starting at \a index to \a groups.
3302 */
3303 
addGroups(QQmlV4Function * args)3304 void QQmlDelegateModelGroup::addGroups(QQmlV4Function *args)
3305 {
3306     Q_D(QQmlDelegateModelGroup);
3307     Compositor::Group group = d->group;
3308     int index = -1;
3309     int count = 1;
3310     int groups = 0;
3311 
3312     if (!d->parseGroupArgs(args, &group, &index, &count, &groups))
3313         return;
3314 
3315     QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model);
3316     if (index < 0 || index >= model->m_compositor.count(group)) {
3317         qmlWarning(this) << tr("addGroups: index out of range");
3318     } else if (count != 0) {
3319         Compositor::iterator it = model->m_compositor.find(group, index);
3320         if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) {
3321             qmlWarning(this) << tr("addGroups: invalid count");
3322         } else {
3323             model->addGroups(it, count, d->group, groups);
3324         }
3325     }
3326 }
3327 
3328 /*!
3329     \qmlmethod QtQml.Models::DelegateModelGroup::removeGroups(int index, int count, stringlist groups)
3330 
3331     Removes \a count items starting at \a index from \a groups.
3332 */
3333 
removeGroups(QQmlV4Function * args)3334 void QQmlDelegateModelGroup::removeGroups(QQmlV4Function *args)
3335 {
3336     Q_D(QQmlDelegateModelGroup);
3337     Compositor::Group group = d->group;
3338     int index = -1;
3339     int count = 1;
3340     int groups = 0;
3341 
3342     if (!d->parseGroupArgs(args, &group, &index, &count, &groups))
3343         return;
3344 
3345     QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model);
3346     if (index < 0 || index >= model->m_compositor.count(group)) {
3347         qmlWarning(this) << tr("removeGroups: index out of range");
3348     } else if (count != 0) {
3349         Compositor::iterator it = model->m_compositor.find(group, index);
3350         if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) {
3351             qmlWarning(this) << tr("removeGroups: invalid count");
3352         } else {
3353             model->removeGroups(it, count, d->group, groups);
3354         }
3355     }
3356 }
3357 
3358 /*!
3359     \qmlmethod QtQml.Models::DelegateModelGroup::setGroups(int index, int count, stringlist groups)
3360 
3361     Changes the group membership of \a count items starting at \a index. The items are removed from
3362     their existing groups and added to \a groups.
3363 */
3364 
setGroups(QQmlV4Function * args)3365 void QQmlDelegateModelGroup::setGroups(QQmlV4Function *args)
3366 {
3367     Q_D(QQmlDelegateModelGroup);
3368     Compositor::Group group = d->group;
3369     int index = -1;
3370     int count = 1;
3371     int groups = 0;
3372 
3373     if (!d->parseGroupArgs(args, &group, &index, &count, &groups))
3374         return;
3375 
3376     QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model);
3377     if (index < 0 || index >= model->m_compositor.count(group)) {
3378         qmlWarning(this) << tr("setGroups: index out of range");
3379     } else if (count != 0) {
3380         Compositor::iterator it = model->m_compositor.find(group, index);
3381         if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) {
3382             qmlWarning(this) << tr("setGroups: invalid count");
3383         } else {
3384             model->setGroups(it, count, d->group, groups);
3385         }
3386     }
3387 }
3388 
3389 /*!
3390     \qmlmethod QtQml.Models::DelegateModelGroup::move(var from, var to, int count)
3391 
3392     Moves \a count at \a from in a group \a to a new position.
3393 
3394     \note The DelegateModel acts as a proxy model: it holds the delegates in a
3395     different order than the \l{dm-model-property}{underlying model} has them.
3396     Any subsequent changes to the underlying model will not undo whatever
3397     reordering you have done via this function.
3398 */
3399 
move(QQmlV4Function * args)3400 void QQmlDelegateModelGroup::move(QQmlV4Function *args)
3401 {
3402     Q_D(QQmlDelegateModelGroup);
3403 
3404     if (args->length() < 2)
3405         return;
3406 
3407     Compositor::Group fromGroup = d->group;
3408     Compositor::Group toGroup = d->group;
3409     int from = -1;
3410     int to = -1;
3411     int count = 1;
3412 
3413     QV4::Scope scope(args->v4engine());
3414     QV4::ScopedValue v(scope, (*args)[0]);
3415     if (!d->parseIndex(v, &from, &fromGroup)) {
3416         qmlWarning(this) << tr("move: invalid from index");
3417         return;
3418     }
3419 
3420     v = (*args)[1];
3421     if (!d->parseIndex(v, &to, &toGroup)) {
3422         qmlWarning(this) << tr("move: invalid to index");
3423         return;
3424     }
3425 
3426     if (args->length() > 2) {
3427         v = (*args)[2];
3428         if (v->isNumber())
3429             count = v->toInt32();
3430     }
3431 
3432     QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model);
3433 
3434     if (count < 0) {
3435         qmlWarning(this) << tr("move: invalid count");
3436     } else if (from < 0 || from + count > model->m_compositor.count(fromGroup)) {
3437         qmlWarning(this) << tr("move: from index out of range");
3438     } else if (!model->m_compositor.verifyMoveTo(fromGroup, from, toGroup, to, count, d->group)) {
3439         qmlWarning(this) << tr("move: to index out of range");
3440     } else if (count > 0) {
3441         QVector<Compositor::Remove> removes;
3442         QVector<Compositor::Insert> inserts;
3443 
3444         model->m_compositor.move(fromGroup, from, toGroup, to, count, d->group, &removes, &inserts);
3445         model->itemsMoved(removes, inserts);
3446         model->emitChanges();
3447     }
3448 
3449 }
3450 
3451 /*!
3452     \qmlsignal QtQml.Models::DelegateModelGroup::changed(array removed, array inserted)
3453 
3454     This signal is emitted when items have been removed from or inserted into the group.
3455 
3456     Each object in the \a removed and \a inserted arrays has two values; the \e index of the first
3457     item inserted or removed and a \e count of the number of consecutive items inserted or removed.
3458 
3459     Each index is adjusted for previous changes with all removed items preceding any inserted
3460     items.
3461 */
3462 
3463 //============================================================================
3464 
QQmlPartsModel(QQmlDelegateModel * model,const QString & part,QObject * parent)3465 QQmlPartsModel::QQmlPartsModel(QQmlDelegateModel *model, const QString &part, QObject *parent)
3466     : QQmlInstanceModel(*new QObjectPrivate, parent)
3467     , m_model(model)
3468     , m_part(part)
3469     , m_compositorGroup(Compositor::Cache)
3470     , m_inheritGroup(true)
3471 {
3472     QQmlDelegateModelPrivate *d = QQmlDelegateModelPrivate::get(m_model);
3473     if (d->m_cacheMetaType) {
3474         QQmlDelegateModelGroupPrivate::get(d->m_groups[1])->emitters.insert(this);
3475         m_compositorGroup = Compositor::Default;
3476     } else {
3477         d->m_pendingParts.insert(this);
3478     }
3479 }
3480 
~QQmlPartsModel()3481 QQmlPartsModel::~QQmlPartsModel()
3482 {
3483 }
3484 
filterGroup() const3485 QString QQmlPartsModel::filterGroup() const
3486 {
3487     if (m_inheritGroup)
3488         return m_model->filterGroup();
3489     return m_filterGroup;
3490 }
3491 
setFilterGroup(const QString & group)3492 void QQmlPartsModel::setFilterGroup(const QString &group)
3493 {
3494     if (QQmlDelegateModelPrivate::get(m_model)->m_transaction) {
3495         qmlWarning(this) << tr("The group of a DelegateModel cannot be changed within onChanged");
3496         return;
3497     }
3498 
3499     if (m_filterGroup != group || m_inheritGroup) {
3500         m_filterGroup = group;
3501         m_inheritGroup = false;
3502         updateFilterGroup();
3503 
3504         emit filterGroupChanged();
3505     }
3506 }
3507 
resetFilterGroup()3508 void QQmlPartsModel::resetFilterGroup()
3509 {
3510     if (!m_inheritGroup) {
3511         m_inheritGroup = true;
3512         updateFilterGroup();
3513         emit filterGroupChanged();
3514     }
3515 }
3516 
updateFilterGroup()3517 void QQmlPartsModel::updateFilterGroup()
3518 {
3519     QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model);
3520     if (!model->m_cacheMetaType)
3521         return;
3522 
3523     if (m_inheritGroup) {
3524         if (m_filterGroup == model->m_filterGroup)
3525             return;
3526         m_filterGroup = model->m_filterGroup;
3527     }
3528 
3529     QQmlListCompositor::Group previousGroup = m_compositorGroup;
3530     m_compositorGroup = Compositor::Default;
3531     QQmlDelegateModelGroupPrivate::get(model->m_groups[Compositor::Default])->emitters.insert(this);
3532     for (int i = 1; i < model->m_groupCount; ++i) {
3533         if (m_filterGroup == model->m_cacheMetaType->groupNames.at(i - 1)) {
3534             m_compositorGroup = Compositor::Group(i);
3535             break;
3536         }
3537     }
3538 
3539     QQmlDelegateModelGroupPrivate::get(model->m_groups[m_compositorGroup])->emitters.insert(this);
3540     if (m_compositorGroup != previousGroup) {
3541         QVector<QQmlChangeSet::Change> removes;
3542         QVector<QQmlChangeSet::Change> inserts;
3543         model->m_compositor.transition(previousGroup, m_compositorGroup, &removes, &inserts);
3544 
3545         QQmlChangeSet changeSet;
3546         changeSet.move(removes, inserts);
3547         if (!changeSet.isEmpty())
3548             emit modelUpdated(changeSet, false);
3549 
3550         if (changeSet.difference() != 0)
3551             emit countChanged();
3552     }
3553 }
3554 
updateFilterGroup(Compositor::Group group,const QQmlChangeSet & changeSet)3555 void QQmlPartsModel::updateFilterGroup(
3556         Compositor::Group group, const QQmlChangeSet &changeSet)
3557 {
3558     if (!m_inheritGroup)
3559         return;
3560 
3561     m_compositorGroup = group;
3562     QQmlDelegateModelGroupPrivate::get(QQmlDelegateModelPrivate::get(m_model)->m_groups[m_compositorGroup])->emitters.insert(this);
3563 
3564     if (!changeSet.isEmpty())
3565         emit modelUpdated(changeSet, false);
3566 
3567     if (changeSet.difference() != 0)
3568         emit countChanged();
3569 
3570     emit filterGroupChanged();
3571 }
3572 
count() const3573 int QQmlPartsModel::count() const
3574 {
3575     QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model);
3576     return model->m_delegate
3577             ? model->m_compositor.count(m_compositorGroup)
3578             : 0;
3579 }
3580 
isValid() const3581 bool QQmlPartsModel::isValid() const
3582 {
3583     return m_model->isValid();
3584 }
3585 
object(int index,QQmlIncubator::IncubationMode incubationMode)3586 QObject *QQmlPartsModel::object(int index, QQmlIncubator::IncubationMode incubationMode)
3587 {
3588     QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model);
3589 
3590     if (!model->m_delegate || index < 0 || index >= model->m_compositor.count(m_compositorGroup)) {
3591         qWarning() << "DelegateModel::item: index out range" << index << model->m_compositor.count(m_compositorGroup);
3592         return nullptr;
3593     }
3594 
3595     QObject *object = model->object(m_compositorGroup, index, incubationMode);
3596 
3597     if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(object)) {
3598         QObject *part = package->part(m_part);
3599         if (!part)
3600             return nullptr;
3601         m_packaged.insert(part, package);
3602         return part;
3603     }
3604 
3605     model->release(object);
3606     if (!model->m_delegateValidated) {
3607         if (object)
3608             qmlWarning(model->m_delegate) << tr("Delegate component must be Package type.");
3609         model->m_delegateValidated = true;
3610     }
3611 
3612     return nullptr;
3613 }
3614 
release(QObject * item,ReusableFlag)3615 QQmlInstanceModel::ReleaseFlags QQmlPartsModel::release(QObject *item, ReusableFlag)
3616 {
3617     QQmlInstanceModel::ReleaseFlags flags;
3618 
3619     auto it = m_packaged.find(item);
3620     if (it != m_packaged.end()) {
3621         QQuickPackage *package = *it;
3622         QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model);
3623         flags = model->release(package);
3624         m_packaged.erase(it);
3625         if (!m_packaged.contains(item))
3626             flags &= ~Referenced;
3627         if (flags & Destroyed)
3628             QQmlDelegateModelPrivate::get(m_model)->emitDestroyingPackage(package);
3629     }
3630     return flags;
3631 }
3632 
variantValue(int index,const QString & role)3633 QVariant QQmlPartsModel::variantValue(int index, const QString &role)
3634 {
3635     return QQmlDelegateModelPrivate::get(m_model)->variantValue(m_compositorGroup, index, role);
3636 }
3637 
setWatchedRoles(const QList<QByteArray> & roles)3638 void QQmlPartsModel::setWatchedRoles(const QList<QByteArray> &roles)
3639 {
3640     QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model);
3641     model->m_adaptorModel.replaceWatchedRoles(m_watchedRoles, roles);
3642     m_watchedRoles = roles;
3643 }
3644 
incubationStatus(int index)3645 QQmlIncubator::Status QQmlPartsModel::incubationStatus(int index)
3646 {
3647     QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model);
3648     Compositor::iterator it = model->m_compositor.find(model->m_compositorGroup, index);
3649     if (!it->inCache())
3650         return QQmlIncubator::Null;
3651 
3652     if (auto incubationTask = model->m_cache.at(it.cacheIndex)->incubationTask)
3653         return incubationTask->status();
3654 
3655     return QQmlIncubator::Ready;
3656 }
3657 
indexOf(QObject * item,QObject *) const3658 int QQmlPartsModel::indexOf(QObject *item, QObject *) const
3659 {
3660     auto it = m_packaged.find(item);
3661     if (it != m_packaged.end()) {
3662         if (QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(*it))
3663             return cacheItem->groupIndex(m_compositorGroup);
3664     }
3665     return -1;
3666 }
3667 
createdPackage(int index,QQuickPackage * package)3668 void QQmlPartsModel::createdPackage(int index, QQuickPackage *package)
3669 {
3670     emit createdItem(index, package->part(m_part));
3671 }
3672 
initPackage(int index,QQuickPackage * package)3673 void QQmlPartsModel::initPackage(int index, QQuickPackage *package)
3674 {
3675     if (m_modelUpdatePending)
3676         m_pendingPackageInitializations << index;
3677     else
3678         emit initItem(index, package->part(m_part));
3679 }
3680 
destroyingPackage(QQuickPackage * package)3681 void QQmlPartsModel::destroyingPackage(QQuickPackage *package)
3682 {
3683     QObject *item = package->part(m_part);
3684     Q_ASSERT(!m_packaged.contains(item));
3685     emit destroyingItem(item);
3686 }
3687 
emitModelUpdated(const QQmlChangeSet & changeSet,bool reset)3688 void QQmlPartsModel::emitModelUpdated(const QQmlChangeSet &changeSet, bool reset)
3689 {
3690     m_modelUpdatePending = false;
3691     emit modelUpdated(changeSet, reset);
3692     if (changeSet.difference() != 0)
3693         emit countChanged();
3694 
3695     QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model);
3696     QVector<int> pendingPackageInitializations;
3697     qSwap(pendingPackageInitializations, m_pendingPackageInitializations);
3698     for (int index : pendingPackageInitializations) {
3699         if (!model->m_delegate || index < 0 || index >= model->m_compositor.count(m_compositorGroup))
3700             continue;
3701         QObject *object = model->object(m_compositorGroup, index, QQmlIncubator::Asynchronous);
3702         if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(object))
3703             emit initItem(index, package->part(m_part));
3704         model->release(object);
3705     }
3706 }
3707 
insertItem(QQmlDelegateModelItem * modelItem)3708 void QQmlReusableDelegateModelItemsPool::insertItem(QQmlDelegateModelItem *modelItem)
3709 {
3710     // Currently, the only way for a view to reuse items is to call release()
3711     // in the model class with the second argument explicitly set to
3712     // QQmlReuseableDelegateModelItemsPool::Reusable. If the released item is
3713     // no longer referenced, it will be added to the pool. Reusing of items can
3714     // be specified per item, in case certain items cannot be recycled. A
3715     // QQmlDelegateModelItem knows which delegate its object was created from.
3716     // So when we are about to create a new item, we first check if the pool
3717     // contains an item based on the same delegate from before. If so, we take
3718     // it out of the pool (instead of creating a new item), and update all its
3719     // context properties and attached properties.
3720 
3721     // When a view is recycling items, it should call drain() regularly. As
3722     // there is currently no logic to 'hibernate' items in the pool, they are
3723     // only meant to rest there for a short while, ideally only from the time
3724     // e.g a row is unloaded on one side of the view, and until a new row is
3725     // loaded on the opposite side. Between these times, the application will
3726     // see the item as fully functional and 'alive' (just not visible on
3727     // screen). Since this time is supposed to be short, we don't take any
3728     // action to notify the application about it, since we don't want to
3729     // trigger any bindings that can disturb performance.
3730 
3731     // A recommended time for calling drain() is each time a view has finished
3732     // loading e.g a new row or column. If there are more items in the pool
3733     // after that, it means that the view most likely doesn't need them anytime
3734     // soon. Those items should be destroyed to reduce resource consumption.
3735 
3736     // Depending on if a view is a list or a table, it can sometimes be
3737     // performant to keep items in the pool for a bit longer than one "row
3738     // out/row in" cycle. E.g for a table, if the number of visible rows in a
3739     // view is much larger than the number of visible columns. In that case, if
3740     // you flick out a row, and then flick in a column, you would throw away a
3741     // lot of items in the pool if completely draining it. The reason is that
3742     // unloading a row places more items in the pool than what ends up being
3743     // recycled when loading a new column. And then, when you next flick in a
3744     // new row, you would need to load all those drained items again from
3745     // scratch. For that reason, you can specify a maxPoolTime to the
3746     // drainReusableItemsPool() that allows you to keep items in the pool for a
3747     // bit longer, effectively keeping more items in circulation. A recommended
3748     // maxPoolTime would be equal to the number of dimensions in the view,
3749     // which means 1 for a list view and 2 for a table view. If you specify 0,
3750     // all items will be drained.
3751 
3752     Q_ASSERT(!modelItem->incubationTask);
3753     Q_ASSERT(!modelItem->isObjectReferenced());
3754     Q_ASSERT(modelItem->object);
3755     Q_ASSERT(modelItem->delegate);
3756 
3757     modelItem->poolTime = 0;
3758     m_reusableItemsPool.append(modelItem);
3759 
3760     qCDebug(lcItemViewDelegateRecycling)
3761             << "item:" << modelItem
3762             << "delegate:" << modelItem->delegate
3763             << "index:" << modelItem->modelIndex()
3764             << "row:" << modelItem->modelRow()
3765             << "column:" << modelItem->modelColumn()
3766             << "pool size:" << m_reusableItemsPool.size();
3767 }
3768 
takeItem(const QQmlComponent * delegate,int newIndexHint)3769 QQmlDelegateModelItem *QQmlReusableDelegateModelItemsPool::takeItem(const QQmlComponent *delegate, int newIndexHint)
3770 {
3771     // Find the oldest item in the pool that was made from the same delegate as
3772     // the given argument, remove it from the pool, and return it.
3773     for (auto it = m_reusableItemsPool.begin(); it != m_reusableItemsPool.end(); ++it) {
3774         if ((*it)->delegate != delegate)
3775             continue;
3776         auto modelItem = *it;
3777         m_reusableItemsPool.erase(it);
3778 
3779         qCDebug(lcItemViewDelegateRecycling)
3780                 << "item:" << modelItem
3781                 << "delegate:" << delegate
3782                 << "old index:" << modelItem->modelIndex()
3783                 << "old row:" << modelItem->modelRow()
3784                 << "old column:" << modelItem->modelColumn()
3785                 << "new index:" << newIndexHint
3786                 << "pool size:" << m_reusableItemsPool.size();
3787 
3788         return modelItem;
3789     }
3790 
3791     qCDebug(lcItemViewDelegateRecycling)
3792             << "no available item for delegate:" << delegate
3793             << "new index:" << newIndexHint
3794             << "pool size:" << m_reusableItemsPool.size();
3795 
3796     return nullptr;
3797 }
3798 
drain(int maxPoolTime,std::function<void (QQmlDelegateModelItem * cacheItem)> releaseItem)3799 void QQmlReusableDelegateModelItemsPool::drain(int maxPoolTime, std::function<void(QQmlDelegateModelItem *cacheItem)> releaseItem)
3800 {
3801     // Rather than releasing all pooled items upon a call to this function, each
3802     // item has a poolTime. The poolTime specifies for how many loading cycles an item
3803     // has been resting in the pool. And for each invocation of this function, poolTime
3804     // will increase. If poolTime is equal to, or exceeds, maxPoolTime, it will be removed
3805     // from the pool and released. This way, the view can tweak a bit for how long
3806     // items should stay in "circulation", even if they are not recycled right away.
3807     qCDebug(lcItemViewDelegateRecycling) << "pool size before drain:" << m_reusableItemsPool.size();
3808 
3809     for (auto it = m_reusableItemsPool.begin(); it != m_reusableItemsPool.end();) {
3810         auto modelItem = *it;
3811         modelItem->poolTime++;
3812         if (modelItem->poolTime <= maxPoolTime) {
3813             ++it;
3814         } else {
3815             it = m_reusableItemsPool.erase(it);
3816             releaseItem(modelItem);
3817         }
3818     }
3819 
3820     qCDebug(lcItemViewDelegateRecycling) << "pool size after drain:" << m_reusableItemsPool.size();
3821 }
3822 
3823 //============================================================================
3824 
3825 struct QQmlDelegateModelGroupChange : QV4::Object
3826 {
V4_OBJECT2QQmlDelegateModelGroupChange3827     V4_OBJECT2(QQmlDelegateModelGroupChange, QV4::Object)
3828 
3829     static QV4::Heap::QQmlDelegateModelGroupChange *create(QV4::ExecutionEngine *e) {
3830         return e->memoryManager->allocate<QQmlDelegateModelGroupChange>();
3831     }
3832 
method_get_indexQQmlDelegateModelGroupChange3833     static QV4::ReturnedValue method_get_index(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) {
3834         QV4::Scope scope(b);
3835         QV4::Scoped<QQmlDelegateModelGroupChange> that(scope, thisObject->as<QQmlDelegateModelGroupChange>());
3836         if (!that)
3837             THROW_TYPE_ERROR();
3838         return QV4::Encode(that->d()->change.index);
3839     }
method_get_countQQmlDelegateModelGroupChange3840     static QV4::ReturnedValue method_get_count(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) {
3841         QV4::Scope scope(b);
3842         QV4::Scoped<QQmlDelegateModelGroupChange> that(scope, thisObject->as<QQmlDelegateModelGroupChange>());
3843         if (!that)
3844             THROW_TYPE_ERROR();
3845         return QV4::Encode(that->d()->change.count);
3846     }
method_get_moveIdQQmlDelegateModelGroupChange3847     static QV4::ReturnedValue method_get_moveId(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) {
3848         QV4::Scope scope(b);
3849         QV4::Scoped<QQmlDelegateModelGroupChange> that(scope, thisObject->as<QQmlDelegateModelGroupChange>());
3850         if (!that)
3851             THROW_TYPE_ERROR();
3852         if (that->d()->change.moveId < 0)
3853             RETURN_UNDEFINED();
3854         return QV4::Encode(that->d()->change.moveId);
3855     }
3856 };
3857 
3858 DEFINE_OBJECT_VTABLE(QQmlDelegateModelGroupChange);
3859 
3860 struct QQmlDelegateModelGroupChangeArray : public QV4::Object
3861 {
3862     V4_OBJECT2(QQmlDelegateModelGroupChangeArray, QV4::Object)
3863     V4_NEEDS_DESTROY
3864 public:
createQQmlDelegateModelGroupChangeArray3865     static QV4::Heap::QQmlDelegateModelGroupChangeArray *create(QV4::ExecutionEngine *engine, const QVector<QQmlChangeSet::Change> &changes)
3866     {
3867         return engine->memoryManager->allocate<QQmlDelegateModelGroupChangeArray>(changes);
3868     }
3869 
countQQmlDelegateModelGroupChangeArray3870     quint32 count() const { return d()->changes->count(); }
atQQmlDelegateModelGroupChangeArray3871     const QQmlChangeSet::Change &at(int index) const { return d()->changes->at(index); }
3872 
virtualGetQQmlDelegateModelGroupChangeArray3873     static QV4::ReturnedValue virtualGet(const QV4::Managed *m, QV4::PropertyKey id, const QV4::Value *receiver, bool *hasProperty)
3874     {
3875         if (id.isArrayIndex()) {
3876             uint index = id.asArrayIndex();
3877             Q_ASSERT(m->as<QQmlDelegateModelGroupChangeArray>());
3878             QV4::ExecutionEngine *v4 = static_cast<const QQmlDelegateModelGroupChangeArray *>(m)->engine();
3879             QV4::Scope scope(v4);
3880             QV4::Scoped<QQmlDelegateModelGroupChangeArray> array(scope, static_cast<const QQmlDelegateModelGroupChangeArray *>(m));
3881 
3882             if (index >= array->count()) {
3883                 if (hasProperty)
3884                     *hasProperty = false;
3885                 return QV4::Value::undefinedValue().asReturnedValue();
3886             }
3887 
3888             const QQmlChangeSet::Change &change = array->at(index);
3889 
3890             QV4::ScopedObject changeProto(scope, engineData(v4)->changeProto.value());
3891             QV4::Scoped<QQmlDelegateModelGroupChange> object(scope, QQmlDelegateModelGroupChange::create(v4));
3892             object->setPrototypeOf(changeProto);
3893             object->d()->change = change;
3894 
3895             if (hasProperty)
3896                 *hasProperty = true;
3897             return object.asReturnedValue();
3898         }
3899 
3900         Q_ASSERT(m->as<QQmlDelegateModelGroupChangeArray>());
3901         const QQmlDelegateModelGroupChangeArray *array = static_cast<const QQmlDelegateModelGroupChangeArray *>(m);
3902 
3903         if (id == array->engine()->id_length()->propertyKey()) {
3904             if (hasProperty)
3905                 *hasProperty = true;
3906             return QV4::Encode(array->count());
3907         }
3908 
3909         return Object::virtualGet(m, id, receiver, hasProperty);
3910     }
3911 };
3912 
init(const QVector<QQmlChangeSet::Change> & changes)3913 void QV4::Heap::QQmlDelegateModelGroupChangeArray::init(const QVector<QQmlChangeSet::Change> &changes)
3914 {
3915     Object::init();
3916     this->changes = new QVector<QQmlChangeSet::Change>(changes);
3917     QV4::Scope scope(internalClass->engine);
3918     QV4::ScopedObject o(scope, this);
3919     o->setArrayType(QV4::Heap::ArrayData::Custom);
3920 }
3921 
3922 DEFINE_OBJECT_VTABLE(QQmlDelegateModelGroupChangeArray);
3923 
QQmlDelegateModelEngineData(QV4::ExecutionEngine * v4)3924 QQmlDelegateModelEngineData::QQmlDelegateModelEngineData(QV4::ExecutionEngine *v4)
3925 {
3926     QV4::Scope scope(v4);
3927 
3928     QV4::ScopedObject proto(scope, v4->newObject());
3929     proto->defineAccessorProperty(QStringLiteral("index"), QQmlDelegateModelGroupChange::method_get_index, nullptr);
3930     proto->defineAccessorProperty(QStringLiteral("count"), QQmlDelegateModelGroupChange::method_get_count, nullptr);
3931     proto->defineAccessorProperty(QStringLiteral("moveId"), QQmlDelegateModelGroupChange::method_get_moveId, nullptr);
3932     changeProto.set(v4, proto);
3933 }
3934 
~QQmlDelegateModelEngineData()3935 QQmlDelegateModelEngineData::~QQmlDelegateModelEngineData()
3936 {
3937 }
3938 
array(QV4::ExecutionEngine * v4,const QVector<QQmlChangeSet::Change> & changes)3939 QV4::ReturnedValue QQmlDelegateModelEngineData::array(QV4::ExecutionEngine *v4,
3940                                                       const QVector<QQmlChangeSet::Change> &changes)
3941 {
3942     QV4::Scope scope(v4);
3943     QV4::ScopedObject o(scope, QQmlDelegateModelGroupChangeArray::create(v4, changes));
3944     return o.asReturnedValue();
3945 }
3946 
3947 QT_END_NAMESPACE
3948 
3949 #include "moc_qqmldelegatemodel_p.cpp"
3950