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

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