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