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

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