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