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

source code of qtdeclarative/src/qmlmodels/qqmldelegatemodel.cpp