1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquickrepeater_p.h"
5#include "qquickrepeater_p_p.h"
6
7#include <private/qqmlglobal_p.h>
8#include <private/qqmlchangeset_p.h>
9#include <private/qqmldelegatemodel_p.h>
10
11#include <QtQml/QQmlInfo>
12#include <QtQml/qqmlcomponent.h>
13
14QT_BEGIN_NAMESPACE
15
16QQuickRepeaterPrivate::QQuickRepeaterPrivate()
17 : model(nullptr)
18 , ownModel(false)
19 , dataSourceIsObject(false)
20 , delegateValidated(false)
21 , explicitDelegate(false)
22 , explicitDelegateModelAccess(false)
23 , itemCount(0)
24{
25 setTransparentForPositioner(true);
26}
27
28QQuickRepeaterPrivate::~QQuickRepeaterPrivate()
29{
30 if (ownModel)
31 delete model;
32}
33
34/*!
35 \qmltype Repeater
36 \nativetype QQuickRepeater
37 \inqmlmodule QtQuick
38 \ingroup qtquick-models
39 \ingroup qtquick-positioning
40 \inherits Item
41 \brief Instantiates a number of Item-based components using a provided model.
42
43 The Repeater type is used to create a large number of
44 similar items. Like other view types, a Repeater has a \l model and a \l delegate:
45 for each entry in the model, the delegate is instantiated
46 in a context seeded with data from the model. A Repeater item is usually
47 enclosed in a positioner type such as \l Row or \l Column to visually
48 position the multiple delegate items created by the Repeater.
49
50 The following Repeater creates three instances of a \l Rectangle item within
51 a \l Row:
52
53 \snippet qml/repeaters/repeater.qml import
54 \codeline
55 \snippet qml/repeaters/repeater.qml simple
56
57 \image repeater-simple.png
58
59 A Repeater's \l model can be any of the supported \l {qml-data-models}{data models}.
60 Additionally, like delegates for other views, a Repeater delegate can access
61 its index within the repeater, as well as the model data relevant to the
62 delegate. See the \l delegate property documentation for details.
63
64 Items instantiated by the Repeater are inserted, in order, as
65 children of the Repeater's parent. The insertion starts immediately after
66 the repeater's position in its parent stacking list. This allows
67 a Repeater to be used inside a layout. For example, the following Repeater's
68 items are stacked between a red rectangle and a blue rectangle:
69
70 \snippet qml/repeaters/repeater.qml layout
71
72 \image repeater.png
73
74
75 \note A Repeater item owns all items it instantiates. Removing or dynamically destroying
76 an item created by a Repeater results in unpredictable behavior.
77
78
79 \section2 Considerations when using Repeater
80
81 The Repeater type creates all of its delegate items when the repeater is first
82 created. This can be inefficient if there are a large number of delegate items and
83 not all of the items are required to be visible at the same time. If this is the case,
84 consider using other view types like ListView (which only creates delegate items
85 when they are scrolled into view) or use the \l {Dynamic Object Creation} methods to
86 create items as they are required.
87
88 Also, note that Repeater is \l {Item}-based, and can only repeat \l {Item}-derived objects.
89 For example, it cannot be used to repeat QtObjects:
90
91 \qml
92 // bad code:
93 Item {
94 // Can't repeat QtObject as it doesn't derive from Item.
95 Repeater {
96 model: 10
97 QtObject {}
98 }
99 }
100 \endqml
101 */
102
103/*!
104 \qmlsignal QtQuick::Repeater::itemAdded(int index, Item item)
105
106 This signal is emitted when an item is added to the repeater. The \a index
107 parameter holds the index at which the item has been inserted within the
108 repeater, and the \a item parameter holds the \l Item that has been added.
109*/
110
111/*!
112 \qmlsignal QtQuick::Repeater::itemRemoved(int index, Item item)
113
114 This signal is emitted when an item is removed from the repeater. The \a index
115 parameter holds the index at which the item was removed from the repeater,
116 and the \a item parameter holds the \l Item that was removed.
117
118 Do not keep a reference to \a item if it was created by this repeater, as
119 in these cases it will be deleted shortly after the signal is handled.
120*/
121QQuickRepeater::QQuickRepeater(QQuickItem *parent)
122 : QQuickItem(*(new QQuickRepeaterPrivate), parent)
123{
124}
125
126QQuickRepeater::~QQuickRepeater()
127{
128 Q_D(QQuickRepeater);
129 QQmlDelegateModelPointer model(d->model);
130 d->disconnectModel(q: this, model: &model);
131}
132
133/*!
134 \qmlproperty var QtQuick::Repeater::model
135
136 The model providing data for the repeater.
137
138 This property can be set to any of the supported \l {qml-data-models}{data models}:
139
140 \list
141 \li A number that indicates the number of delegates to be created by the repeater
142 \li A model (e.g. a ListModel item, or a QAbstractItemModel subclass)
143 \li A string list
144 \li An object list
145 \endlist
146
147 The type of model affects the properties that are exposed to the \l delegate.
148
149 \sa {qml-data-models}{Data Models}
150*/
151QVariant QQuickRepeater::model() const
152{
153 Q_D(const QQuickRepeater);
154
155 if (d->dataSourceIsObject) {
156 QObject *o = d->dataSourceAsObject;
157 return QVariant::fromValue(value: o);
158 }
159
160 return d->dataSource;
161}
162
163void QQuickRepeater::setModel(const QVariant &m)
164{
165 Q_D(QQuickRepeater);
166 QVariant model = m;
167 if (model.userType() == qMetaTypeId<QJSValue>())
168 model = model.value<QJSValue>().toVariant();
169
170 if (d->dataSource == model)
171 return;
172
173 clear();
174
175 QQmlDelegateModelPointer oldModel(d->model);
176 d->disconnectModel(q: this, model: &oldModel);
177
178 d->model = nullptr;
179 d->dataSource = model;
180
181 QObject *object = qvariant_cast<QObject *>(v: model);
182 d->dataSourceAsObject = object;
183 d->dataSourceIsObject = object != nullptr;
184
185 QQmlDelegateModelPointer newModel(qobject_cast<QQmlInstanceModel *>(object));
186 if (newModel) {
187 if (d->explicitDelegate) {
188 QQmlComponent *delegate = nullptr;
189 if (QQmlDelegateModel *old = oldModel.delegateModel())
190 delegate = old->delegate();
191
192 if (QQmlDelegateModel *delegateModel = newModel.delegateModel()) {
193 delegateModel->setDelegate(delegate);
194 } else if (delegate) {
195 qmlWarning(me: this) << "Cannot retain explicitly set delegate on non-DelegateModel";
196 d->explicitDelegate = false;
197 }
198 }
199
200 if (d->explicitDelegateModelAccess) {
201 QQmlDelegateModel::DelegateModelAccess access = QQmlDelegateModel::Qt5ReadWrite;
202 if (QQmlDelegateModel *old = oldModel.delegateModel())
203 access = old->delegateModelAccess();
204
205 if (QQmlDelegateModel *delegateModel = newModel.delegateModel()) {
206 delegateModel->setDelegateModelAccess(access);
207 } else if (access != QQmlDelegateModel::Qt5ReadWrite) {
208 qmlWarning(me: this) << "Cannot retain explicitly set delegate model access "
209 "on non-DelegateModel";
210 d->explicitDelegateModelAccess = false;
211 }
212 }
213
214 if (d->ownModel) {
215 delete oldModel.instanceModel();
216 d->ownModel = false;
217 }
218 d->model = newModel.instanceModel();
219 } else if (d->ownModel) {
220 newModel = oldModel;
221 d->model = newModel.instanceModel();
222 if (QQmlDelegateModel *delegateModel = newModel.delegateModel())
223 delegateModel->setModel(model);
224 } else {
225 newModel = QQmlDelegateModel::createForView(q: this, d);
226 if (d->explicitDelegate) {
227 QQmlComponent *delegate = nullptr;
228 if (QQmlDelegateModel *old = oldModel.delegateModel())
229 delegate = old->delegate();
230 newModel.delegateModel()->setDelegate(delegate);
231 }
232
233 if (d->explicitDelegateModelAccess) {
234 QQmlDelegateModel::DelegateModelAccess access = QQmlDelegateModel::Qt5ReadWrite;
235 if (QQmlDelegateModel *old = oldModel.delegateModel())
236 access = old->delegateModelAccess();
237 newModel.delegateModel()->setDelegateModelAccess(access);
238 }
239
240 newModel.delegateModel()->setModel(model);
241 }
242
243 d->connectModel(q: this, model: &newModel);
244 emit modelChanged();
245 emit countChanged();
246}
247
248/*!
249 \qmlproperty Component QtQuick::Repeater::delegate
250 \qmldefault
251
252 The delegate provides a template defining each item instantiated by the repeater.
253
254 Delegates are exposed to a read-only \c index property that indicates the index
255 of the delegate within the repeater. For example, the following \l Text delegate
256 displays the index of each repeated item:
257
258 \table
259 \row
260 \li \snippet qml/repeaters/repeater.qml index
261 \li \image repeater-index.png
262 \endtable
263
264 If the \l model is a \l{QStringList-based model}{string list} or
265 \l{QObjectList-based model}{object list}, the delegate is also exposed to
266 a read-only \c modelData property that holds the string or object data. For
267 example:
268
269 \table
270 \row
271 \li \snippet qml/repeaters/repeater.qml modeldata
272 \li \image repeater-modeldata.png
273 \endtable
274
275 If the \l model is a model object (such as a \l ListModel) the delegate
276 can access all model roles as named properties, in the same way that delegates
277 do for view classes like ListView.
278
279 \sa {QML Data Models}
280 */
281QQmlComponent *QQuickRepeater::delegate() const
282{
283 Q_D(const QQuickRepeater);
284 if (d->model) {
285 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(object: d->model))
286 return dataModel->delegate();
287 }
288
289 return nullptr;
290}
291
292void QQuickRepeater::setDelegate(QQmlComponent *delegate)
293{
294 Q_D(QQuickRepeater);
295 const auto setExplicitDelegate = [&](QQmlDelegateModel *delegateModel) {
296 if (delegateModel->delegate() == delegate) {
297 d->explicitDelegate = true;
298 return;
299 }
300
301 const int oldCount = delegateModel->count();
302 delegateModel->setDelegate(delegate);
303 regenerate();
304 if (oldCount != delegateModel->count())
305 emit countChanged();
306 d->explicitDelegate = true;
307 d->delegateValidated = false;
308 };
309
310 if (!d->model) {
311 if (!delegate) {
312 // Explicitly set a null delegate. We can do this without model.
313 d->explicitDelegate = true;
314 return;
315 }
316
317 setExplicitDelegate(QQmlDelegateModel::createForView(q: this, d));
318 // The new model is not connected to applyDelegateChange, yet. We only do this once
319 // there is actual data, via an explicit setModel(). So we have to manually emit the
320 // delegateChanged() here.
321 emit delegateChanged();
322 return;
323 }
324
325 if (QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel *>(object: d->model)) {
326 // Disable the warning in applyDelegateChange since the new delegate is also explicit.
327 d->explicitDelegate = false;
328 setExplicitDelegate(delegateModel);
329 return;
330 }
331
332 if (delegate)
333 qmlWarning(me: this) << "Cannot set a delegate on an explicitly provided non-DelegateModel";
334 else
335 d->explicitDelegate = true; // Explicitly set null delegate always works
336}
337
338/*!
339 \qmlproperty int QtQuick::Repeater::count
340
341 This property holds the number of items in the \l model.
342
343 The value of \c count does not always match the number of instantiated
344 \l {delegate}{delegates}; use \l itemAt() to check if a delegate at a given
345 index exists. It returns \c null if the delegate is not instantiated.
346
347 \list
348 \li While the Repeater is in the process of instantiating delegates (at
349 startup, or because of \c model changes), the \l itemAdded signal is
350 emitted for each delegate created, and the \c count property changes
351 afterwards.
352 \li If the Repeater is not part of a completed
353 \l {Concepts - Visual Parent in Qt Quick}{visual hierarchy},
354 \c count reflects the model size, but no delegates are created.
355 \li If the Repeater destroys delegates because of \c model changes,
356 the \l itemRemoved() signal is emitted for each, and the \c count
357 property changes afterwards.
358 \li If the Repeater is taken out of the visual hierarchy (for example by
359 setting \c {parent = null}), delegates are destroyed, the \l itemRemoved()
360 signal is emitted for each, but \c count does not change.
361 \endlist
362
363 \sa itemAt(), itemAdded(), itemRemoved()
364*/
365int QQuickRepeater::count() const
366{
367 Q_D(const QQuickRepeater);
368 if (d->model)
369 return d->model->count();
370 return 0;
371}
372
373/*!
374 \qmlmethod Item QtQuick::Repeater::itemAt(index)
375
376 Returns the \l Item that has been created at the given \a index, or \c null
377 if no item exists at \a index.
378*/
379QQuickItem *QQuickRepeater::itemAt(int index) const
380{
381 Q_D(const QQuickRepeater);
382 if (index >= 0 && index < d->deletables.size())
383 return d->deletables[index];
384 return nullptr;
385}
386
387void QQuickRepeater::componentComplete()
388{
389 Q_D(QQuickRepeater);
390 if (d->model && d->ownModel)
391 static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete();
392 QQuickItem::componentComplete();
393 regenerate();
394 if (d->model && d->model->count())
395 emit countChanged();
396}
397
398void QQuickRepeater::itemChange(ItemChange change, const ItemChangeData &value)
399{
400 QQuickItem::itemChange(change, value);
401 if (change == ItemParentHasChanged) {
402 regenerate();
403 }
404}
405
406void QQuickRepeater::clear()
407{
408 Q_D(QQuickRepeater);
409 bool complete = isComponentComplete();
410
411 if (d->model) {
412 // We remove in reverse order deliberately; so that signals are emitted
413 // with sensible indices.
414 for (int i = d->deletables.size() - 1; i >= 0; --i) {
415 if (QQuickItem *item = d->deletables.at(i)) {
416 if (complete)
417 emit itemRemoved(index: i, item);
418 d->model->release(object: item);
419 }
420 }
421 for (QQuickItem *item : std::as_const(t&: d->deletables)) {
422 if (item)
423 item->setParentItem(nullptr);
424 }
425 }
426 d->deletables.clear();
427 d->itemCount = 0;
428}
429
430void QQuickRepeater::regenerate()
431{
432 Q_D(QQuickRepeater);
433 if (!isComponentComplete())
434 return;
435
436 clear();
437
438 if (!d->model || !d->model->count() || !d->model->isValid() || !parentItem() || !isComponentComplete())
439 return;
440
441 d->itemCount = count();
442 d->deletables.resize(size: d->itemCount);
443 d->requestItems();
444}
445
446void QQuickRepeaterPrivate::requestItems()
447{
448 for (int i = 0; i < itemCount; i++) {
449 QObject *object = model->object(index: i, incubationMode: QQmlIncubator::AsynchronousIfNested);
450 if (object)
451 model->release(object);
452 }
453}
454
455void QQuickRepeaterPrivate::connectModel(QQuickRepeater *q, QQmlDelegateModelPointer *model)
456{
457 QQmlInstanceModel *instanceModel = model->instanceModel();
458 if (!instanceModel)
459 return;
460
461 QObject::connect(sender: instanceModel, signal: &QQmlInstanceModel::modelUpdated,
462 context: q, slot: &QQuickRepeater::modelUpdated);
463 QObject::connect(sender: instanceModel, signal: &QQmlInstanceModel::createdItem,
464 context: q, slot: &QQuickRepeater::createdItem);
465 QObject::connect(sender: instanceModel, signal: &QQmlInstanceModel::initItem,
466 context: q, slot: &QQuickRepeater::initItem);
467 if (QQmlDelegateModel *dataModel = model->delegateModel()) {
468 QObjectPrivate::connect(
469 sender: dataModel, signal: &QQmlDelegateModel::delegateChanged,
470 receiverPrivate: this, slot: &QQuickRepeaterPrivate::applyDelegateChange);
471 QObjectPrivate::connect(
472 sender: dataModel, signal: &QQmlDelegateModel::delegateModelAccessChanged,
473 receiverPrivate: this, slot: &QQuickRepeaterPrivate::applyDelegateModelAccessChange);
474 }
475 q->regenerate();
476}
477
478void QQuickRepeaterPrivate::disconnectModel(QQuickRepeater *q, QQmlDelegateModelPointer *model)
479{
480 QQmlInstanceModel *instanceModel = model->instanceModel();
481 if (!instanceModel)
482 return;
483
484 QObject::disconnect(sender: instanceModel, signal: &QQmlInstanceModel::modelUpdated,
485 receiver: q, slot: &QQuickRepeater::modelUpdated);
486 QObject::disconnect(sender: instanceModel, signal: &QQmlInstanceModel::createdItem,
487 receiver: q, slot: &QQuickRepeater::createdItem);
488 QObject::disconnect(sender: instanceModel, signal: &QQmlInstanceModel::initItem,
489 receiver: q, slot: &QQuickRepeater::initItem);
490 if (QQmlDelegateModel *delegateModel = model->delegateModel()) {
491 QObjectPrivate::disconnect(
492 sender: delegateModel, signal: &QQmlDelegateModel::delegateChanged,
493 receiverPrivate: this, slot: &QQuickRepeaterPrivate::applyDelegateChange);
494 QObjectPrivate::disconnect(
495 sender: delegateModel, signal: &QQmlDelegateModel::delegateModelAccessChanged,
496 receiverPrivate: this, slot: &QQuickRepeaterPrivate::applyDelegateModelAccessChange);
497 }
498}
499
500void QQuickRepeater::createdItem(int index, QObject *)
501{
502 Q_D(QQuickRepeater);
503 QObject *object = d->model->object(index, incubationMode: QQmlIncubator::AsynchronousIfNested);
504 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
505 emit itemAdded(index, item);
506}
507
508void QQuickRepeater::initItem(int index, QObject *object)
509{
510 Q_D(QQuickRepeater);
511 if (index >= d->deletables.size()) {
512 // this can happen when Package is used
513 // calling regenerate does too much work, all we need is to call resize
514 // so that d->deletables[index] = item below works
515 d->deletables.resize(size: d->model->count() + 1);
516 }
517 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
518
519 if (!d->deletables.at(i: index)) {
520 if (!item) {
521 if (object) {
522 d->model->release(object);
523 if (!d->delegateValidated) {
524 d->delegateValidated = true;
525 QObject* delegate = this->delegate();
526 qmlWarning(me: delegate ? delegate : this) << QQuickRepeater::tr(s: "Delegate must be of Item type");
527 }
528 }
529 return;
530 }
531 d->deletables[index] = item;
532 item->setParentItem(parentItem());
533
534 // If the item comes from an ObjectModel, it might be used as
535 // ComboBox/Menu/TabBar's contentItem. These types unconditionally cull items
536 // that are inserted, so account for that here.
537 if (d->dataSourceIsObject)
538 QQuickItemPrivate::get(item)->setCulled(false);
539 if (index > 0 && d->deletables.at(i: index-1)) {
540 item->stackAfter(d->deletables.at(i: index-1));
541 } else {
542 QQuickItem *after = this;
543 for (int si = index+1; si < d->itemCount; ++si) {
544 if (d->deletables.at(i: si)) {
545 after = d->deletables.at(i: si);
546 break;
547 }
548 }
549 item->stackBefore(after);
550 }
551 }
552}
553
554void QQuickRepeater::modelUpdated(const QQmlChangeSet &changeSet, bool reset)
555{
556 Q_D(QQuickRepeater);
557
558 if (!isComponentComplete())
559 return;
560
561 if (reset) {
562 regenerate();
563 if (changeSet.difference() != 0)
564 emit countChanged();
565 return;
566 }
567
568 int difference = 0;
569 QHash<int, QVector<QPointer<QQuickItem> > > moved;
570 for (const QQmlChangeSet::Change &remove : changeSet.removes()) {
571 int index = qMin(a: remove.index, b: d->deletables.size());
572 int count = qMin(a: remove.index + remove.count, b: d->deletables.size()) - index;
573 if (remove.isMove()) {
574 moved.insert(key: remove.moveId, value: d->deletables.mid(pos: index, len: count));
575 d->deletables.erase(
576 begin: d->deletables.begin() + index,
577 end: d->deletables.begin() + index + count);
578 } else while (count--) {
579 QQuickItem *item = d->deletables.at(i: index);
580 d->deletables.remove(i: index);
581 emit itemRemoved(index, item);
582 if (item) {
583 d->model->release(object: item);
584 item->setParentItem(nullptr);
585 }
586 --d->itemCount;
587 }
588
589 difference -= remove.count;
590 }
591
592 for (const QQmlChangeSet::Change &insert : changeSet.inserts()) {
593 int index = qMin(a: insert.index, b: d->deletables.size());
594 if (insert.isMove()) {
595 QVector<QPointer<QQuickItem> > items = moved.value(key: insert.moveId);
596 d->deletables = d->deletables.mid(pos: 0, len: index) + items + d->deletables.mid(pos: index);
597 QQuickItem *stackBefore = index + items.size() < d->deletables.size()
598 ? d->deletables.at(i: index + items.size())
599 : this;
600 if (stackBefore) {
601 for (int i = index; i < index + items.size(); ++i) {
602 if (i < d->deletables.size()) {
603 QPointer<QQuickItem> item = d->deletables.at(i);
604 if (item)
605 item->stackBefore(stackBefore);
606 }
607 }
608 }
609 } else for (int i = 0; i < insert.count; ++i) {
610 int modelIndex = index + i;
611 ++d->itemCount;
612 d->deletables.insert(i: modelIndex, t: nullptr);
613 QObject *object = d->model->object(index: modelIndex, incubationMode: QQmlIncubator::AsynchronousIfNested);
614 if (object)
615 d->model->release(object);
616 }
617 difference += insert.count;
618 }
619
620 if (difference != 0)
621 emit countChanged();
622}
623
624/*!
625 \qmlproperty enumeration QtQuick::Repeater::delegateModelAccess
626
627 \include delegatemodelaccess.qdocinc
628*/
629QQmlDelegateModel::DelegateModelAccess QQuickRepeater::delegateModelAccess() const
630{
631 Q_D(const QQuickRepeater);
632 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel *>(object: d->model))
633 return dataModel->delegateModelAccess();
634 return QQmlDelegateModel::Qt5ReadWrite;
635}
636
637void QQuickRepeater::setDelegateModelAccess(
638 QQmlDelegateModel::DelegateModelAccess delegateModelAccess)
639{
640 Q_D(QQuickRepeater);
641 const auto setExplicitDelegateModelAccess = [&](QQmlDelegateModel *delegateModel) {
642 delegateModel->setDelegateModelAccess(delegateModelAccess);
643 d->explicitDelegateModelAccess = true;
644 };
645
646 if (!d->model) {
647 if (delegateModelAccess == QQmlDelegateModel::Qt5ReadWrite) {
648 // Explicitly set delegateModelAccess to Legacy. We can do this without model.
649 d->explicitDelegateModelAccess = true;
650 return;
651 }
652
653 setExplicitDelegateModelAccess(QQmlDelegateModel::createForView(q: this, d));
654
655 // The new model is not connected to applyDelegateModelAccessChange, yet. We only do this
656 // once there is actual data, via an explicit setModel(). So we have to manually emit the
657 // delegateModelAccessChanged() here.
658 emit delegateModelAccessChanged();
659 return;
660 }
661
662 if (QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel *>(object: d->model)) {
663 // Disable the warning in applyDelegateModelAccessChange since the new delegate model
664 // access is also explicit.
665 d->explicitDelegateModelAccess = false;
666 setExplicitDelegateModelAccess(delegateModel);
667 return;
668 }
669
670 if (delegateModelAccess == QQmlDelegateModel::Qt5ReadWrite) {
671 d->explicitDelegateModelAccess = true; // Explicitly set null delegate always works
672 } else {
673 qmlWarning(me: this) << "Cannot set a delegateModelAccess on an explicitly provided "
674 "non-DelegateModel";
675 }
676}
677
678QT_END_NAMESPACE
679
680#include "moc_qquickrepeater_p.cpp"
681

source code of qtdeclarative/src/quick/items/qquickrepeater.cpp