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