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
13QT_BEGIN_NAMESPACE
14
15QQuickRepeaterPrivate::QQuickRepeaterPrivate()
16 : model(nullptr)
17 , ownModel(false)
18 , dataSourceIsObject(false)
19 , delegateValidated(false)
20 , itemCount(0)
21{
22 setTransparentForPositioner(true);
23}
24
25QQuickRepeaterPrivate::~QQuickRepeaterPrivate()
26{
27 if (ownModel)
28 delete model;
29}
30
31/*!
32 \qmltype Repeater
33 \instantiates QQuickRepeater
34 \inqmlmodule QtQuick
35 \ingroup qtquick-models
36 \ingroup qtquick-positioning
37 \inherits Item
38 \brief Instantiates a number of Item-based components using a provided model.
39
40 The Repeater type is used to create a large number of
41 similar items. Like other view types, a Repeater has a \l model and a \l delegate:
42 for each entry in the model, the delegate is instantiated
43 in a context seeded with data from the model. A Repeater item is usually
44 enclosed in a positioner type such as \l Row or \l Column to visually
45 position the multiple delegate items created by the Repeater.
46
47 The following Repeater creates three instances of a \l Rectangle item within
48 a \l Row:
49
50 \snippet qml/repeaters/repeater.qml import
51 \codeline
52 \snippet qml/repeaters/repeater.qml simple
53
54 \image repeater-simple.png
55
56 A Repeater's \l model can be any of the supported \l {qml-data-models}{data models}.
57 Additionally, like delegates for other views, a Repeater delegate can access
58 its index within the repeater, as well as the model data relevant to the
59 delegate. See the \l delegate property documentation for details.
60
61 Items instantiated by the Repeater are inserted, in order, as
62 children of the Repeater's parent. The insertion starts immediately after
63 the repeater's position in its parent stacking list. This allows
64 a Repeater to be used inside a layout. For example, the following Repeater's
65 items are stacked between a red rectangle and a blue rectangle:
66
67 \snippet qml/repeaters/repeater.qml layout
68
69 \image repeater.png
70
71
72 \note A Repeater item owns all items it instantiates. Removing or dynamically destroying
73 an item created by a Repeater results in unpredictable behavior.
74
75
76 \section2 Considerations when using Repeater
77
78 The Repeater type creates all of its delegate items when the repeater is first
79 created. This can be inefficient if there are a large number of delegate items and
80 not all of the items are required to be visible at the same time. If this is the case,
81 consider using other view types like ListView (which only creates delegate items
82 when they are scrolled into view) or use the \l {Dynamic Object Creation} methods to
83 create items as they are required.
84
85 Also, note that Repeater is \l {Item}-based, and can only repeat \l {Item}-derived objects.
86 For example, it cannot be used to repeat QtObjects:
87
88 \qml
89 // bad code:
90 Item {
91 // Can't repeat QtObject as it doesn't derive from Item.
92 Repeater {
93 model: 10
94 QtObject {}
95 }
96 }
97 \endqml
98 */
99
100/*!
101 \qmlsignal QtQuick::Repeater::itemAdded(int index, Item item)
102
103 This signal is emitted when an item is added to the repeater. The \a index
104 parameter holds the index at which the item has been inserted within the
105 repeater, and the \a item parameter holds the \l Item that has been added.
106*/
107
108/*!
109 \qmlsignal QtQuick::Repeater::itemRemoved(int index, Item item)
110
111 This signal is emitted when an item is removed from the repeater. The \a index
112 parameter holds the index at which the item was removed from the repeater,
113 and the \a item parameter holds the \l Item that was removed.
114
115 Do not keep a reference to \a item if it was created by this repeater, as
116 in these cases it will be deleted shortly after the signal is handled.
117*/
118QQuickRepeater::QQuickRepeater(QQuickItem *parent)
119 : QQuickItem(*(new QQuickRepeaterPrivate), parent)
120{
121}
122
123QQuickRepeater::~QQuickRepeater()
124{
125}
126
127/*!
128 \qmlproperty var QtQuick::Repeater::model
129
130 The model providing data for the repeater.
131
132 This property can be set to any of the supported \l {qml-data-models}{data models}:
133
134 \list
135 \li A number that indicates the number of delegates to be created by the repeater
136 \li A model (e.g. a ListModel item, or a QAbstractItemModel subclass)
137 \li A string list
138 \li An object list
139 \endlist
140
141 The type of model affects the properties that are exposed to the \l delegate.
142
143 \sa {qml-data-models}{Data Models}
144*/
145QVariant QQuickRepeater::model() const
146{
147 Q_D(const QQuickRepeater);
148
149 if (d->dataSourceIsObject) {
150 QObject *o = d->dataSourceAsObject;
151 return QVariant::fromValue(value: o);
152 }
153
154 return d->dataSource;
155}
156
157void QQuickRepeater::setModel(const QVariant &m)
158{
159 Q_D(QQuickRepeater);
160 QVariant model = m;
161 if (model.userType() == qMetaTypeId<QJSValue>())
162 model = model.value<QJSValue>().toVariant();
163
164 if (d->dataSource == model)
165 return;
166
167 clear();
168 if (d->model) {
169 qmlobject_disconnect(d->model, QQmlInstanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
170 this, QQuickRepeater, SLOT(modelUpdated(QQmlChangeSet,bool)));
171 qmlobject_disconnect(d->model, QQmlInstanceModel, SIGNAL(createdItem(int,QObject*)),
172 this, QQuickRepeater, SLOT(createdItem(int,QObject*)));
173 qmlobject_disconnect(d->model, QQmlInstanceModel, SIGNAL(initItem(int,QObject*)),
174 this, QQuickRepeater, SLOT(initItem(int,QObject*)));
175 }
176 d->dataSource = model;
177 QObject *object = qvariant_cast<QObject*>(v: model);
178 d->dataSourceAsObject = object;
179 d->dataSourceIsObject = object != nullptr;
180 QQmlInstanceModel *vim = nullptr;
181 if (object && (vim = qobject_cast<QQmlInstanceModel *>(object))) {
182 if (d->ownModel) {
183 delete d->model;
184 d->ownModel = false;
185 }
186 d->model = vim;
187 } else {
188 if (!d->ownModel) {
189 d->model = new QQmlDelegateModel(qmlContext(this));
190 d->ownModel = true;
191 if (isComponentComplete())
192 static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete();
193 }
194 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(object: d->model))
195 dataModel->setModel(model);
196 }
197 if (d->model) {
198 qmlobject_connect(d->model, QQmlInstanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
199 this, QQuickRepeater, SLOT(modelUpdated(QQmlChangeSet,bool)));
200 qmlobject_connect(d->model, QQmlInstanceModel, SIGNAL(createdItem(int,QObject*)),
201 this, QQuickRepeater, SLOT(createdItem(int,QObject*)));
202 qmlobject_connect(d->model, QQmlInstanceModel, SIGNAL(initItem(int,QObject*)),
203 this, QQuickRepeater, SLOT(initItem(int,QObject*)));
204 regenerate();
205 }
206 emit modelChanged();
207 emit countChanged();
208}
209
210/*!
211 \qmlproperty Component QtQuick::Repeater::delegate
212 \qmldefault
213
214 The delegate provides a template defining each item instantiated by the repeater.
215
216 Delegates are exposed to a read-only \c index property that indicates the index
217 of the delegate within the repeater. For example, the following \l Text delegate
218 displays the index of each repeated item:
219
220 \table
221 \row
222 \li \snippet qml/repeaters/repeater.qml index
223 \li \image repeater-index.png
224 \endtable
225
226 If the \l model is a \l{QStringList-based model}{string list} or
227 \l{QObjectList-based model}{object list}, the delegate is also exposed to
228 a read-only \c modelData property that holds the string or object data. For
229 example:
230
231 \table
232 \row
233 \li \snippet qml/repeaters/repeater.qml modeldata
234 \li \image repeater-modeldata.png
235 \endtable
236
237 If the \l model is a model object (such as a \l ListModel) the delegate
238 can access all model roles as named properties, in the same way that delegates
239 do for view classes like ListView.
240
241 \sa {QML Data Models}
242 */
243QQmlComponent *QQuickRepeater::delegate() const
244{
245 Q_D(const QQuickRepeater);
246 if (d->model) {
247 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(object: d->model))
248 return dataModel->delegate();
249 }
250
251 return nullptr;
252}
253
254void QQuickRepeater::setDelegate(QQmlComponent *delegate)
255{
256 Q_D(QQuickRepeater);
257 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(object: d->model))
258 if (delegate == dataModel->delegate())
259 return;
260
261 if (!d->ownModel) {
262 d->model = new QQmlDelegateModel(qmlContext(this));
263 d->ownModel = true;
264 }
265
266 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(object: d->model)) {
267 dataModel->setDelegate(delegate);
268 regenerate();
269 emit delegateChanged();
270 d->delegateValidated = false;
271 }
272}
273
274/*!
275 \qmlproperty int QtQuick::Repeater::count
276
277 This property holds the number of items in the model.
278
279 \note The number of items in the model as reported by count may differ from
280 the number of created delegates if the Repeater is in the process of
281 instantiating delegates or is incorrectly set up.
282*/
283int QQuickRepeater::count() const
284{
285 Q_D(const QQuickRepeater);
286 if (d->model)
287 return d->model->count();
288 return 0;
289}
290
291/*!
292 \qmlmethod Item QtQuick::Repeater::itemAt(index)
293
294 Returns the \l Item that has been created at the given \a index, or \c null
295 if no item exists at \a index.
296*/
297QQuickItem *QQuickRepeater::itemAt(int index) const
298{
299 Q_D(const QQuickRepeater);
300 if (index >= 0 && index < d->deletables.size())
301 return d->deletables[index];
302 return nullptr;
303}
304
305void QQuickRepeater::componentComplete()
306{
307 Q_D(QQuickRepeater);
308 if (d->model && d->ownModel)
309 static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete();
310 QQuickItem::componentComplete();
311 regenerate();
312 if (d->model && d->model->count())
313 emit countChanged();
314}
315
316void QQuickRepeater::itemChange(ItemChange change, const ItemChangeData &value)
317{
318 QQuickItem::itemChange(change, value);
319 if (change == ItemParentHasChanged) {
320 regenerate();
321 }
322}
323
324void QQuickRepeater::clear()
325{
326 Q_D(QQuickRepeater);
327 bool complete = isComponentComplete();
328
329 if (d->model) {
330 // We remove in reverse order deliberately; so that signals are emitted
331 // with sensible indices.
332 for (int i = d->deletables.size() - 1; i >= 0; --i) {
333 if (QQuickItem *item = d->deletables.at(i)) {
334 if (complete)
335 emit itemRemoved(index: i, item);
336 d->model->release(object: item);
337 }
338 }
339 for (QQuickItem *item : std::as_const(t&: d->deletables)) {
340 if (item)
341 item->setParentItem(nullptr);
342 }
343 }
344 d->deletables.clear();
345 d->itemCount = 0;
346}
347
348void QQuickRepeater::regenerate()
349{
350 Q_D(QQuickRepeater);
351 if (!isComponentComplete())
352 return;
353
354 clear();
355
356 if (!d->model || !d->model->count() || !d->model->isValid() || !parentItem() || !isComponentComplete())
357 return;
358
359 d->itemCount = count();
360 d->deletables.resize(size: d->itemCount);
361 d->requestItems();
362}
363
364void QQuickRepeaterPrivate::requestItems()
365{
366 for (int i = 0; i < itemCount; i++) {
367 QObject *object = model->object(index: i, incubationMode: QQmlIncubator::AsynchronousIfNested);
368 if (object)
369 model->release(object);
370 }
371}
372
373void QQuickRepeater::createdItem(int index, QObject *)
374{
375 Q_D(QQuickRepeater);
376 QObject *object = d->model->object(index, incubationMode: QQmlIncubator::AsynchronousIfNested);
377 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
378 emit itemAdded(index, item);
379}
380
381void QQuickRepeater::initItem(int index, QObject *object)
382{
383 Q_D(QQuickRepeater);
384 if (index >= d->deletables.size()) {
385 // this can happen when Package is used
386 // calling regenerate does too much work, all we need is to call resize
387 // so that d->deletables[index] = item below works
388 d->deletables.resize(size: d->model->count() + 1);
389 }
390 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
391
392 if (!d->deletables.at(i: index)) {
393 if (!item) {
394 if (object) {
395 d->model->release(object);
396 if (!d->delegateValidated) {
397 d->delegateValidated = true;
398 QObject* delegate = this->delegate();
399 qmlWarning(me: delegate ? delegate : this) << QQuickRepeater::tr(s: "Delegate must be of Item type");
400 }
401 }
402 return;
403 }
404 d->deletables[index] = item;
405 item->setParentItem(parentItem());
406
407 // If the item comes from an ObjectModel, it might be used as
408 // ComboBox/Menu/TabBar's contentItem. These types unconditionally cull items
409 // that are inserted, so account for that here.
410 if (d->dataSourceIsObject)
411 QQuickItemPrivate::get(item)->setCulled(false);
412 if (index > 0 && d->deletables.at(i: index-1)) {
413 item->stackAfter(d->deletables.at(i: index-1));
414 } else {
415 QQuickItem *after = this;
416 for (int si = index+1; si < d->itemCount; ++si) {
417 if (d->deletables.at(i: si)) {
418 after = d->deletables.at(i: si);
419 break;
420 }
421 }
422 item->stackBefore(after);
423 }
424 }
425}
426
427void QQuickRepeater::modelUpdated(const QQmlChangeSet &changeSet, bool reset)
428{
429 Q_D(QQuickRepeater);
430
431 if (!isComponentComplete())
432 return;
433
434 if (reset) {
435 regenerate();
436 if (changeSet.difference() != 0)
437 emit countChanged();
438 return;
439 }
440
441 int difference = 0;
442 QHash<int, QVector<QPointer<QQuickItem> > > moved;
443 for (const QQmlChangeSet::Change &remove : changeSet.removes()) {
444 int index = qMin(a: remove.index, b: d->deletables.size());
445 int count = qMin(a: remove.index + remove.count, b: d->deletables.size()) - index;
446 if (remove.isMove()) {
447 moved.insert(key: remove.moveId, value: d->deletables.mid(pos: index, len: count));
448 d->deletables.erase(
449 abegin: d->deletables.begin() + index,
450 aend: d->deletables.begin() + index + count);
451 } else while (count--) {
452 QQuickItem *item = d->deletables.at(i: index);
453 d->deletables.remove(i: index);
454 emit itemRemoved(index, item);
455 if (item) {
456 d->model->release(object: item);
457 item->setParentItem(nullptr);
458 }
459 --d->itemCount;
460 }
461
462 difference -= remove.count;
463 }
464
465 for (const QQmlChangeSet::Change &insert : changeSet.inserts()) {
466 int index = qMin(a: insert.index, b: d->deletables.size());
467 if (insert.isMove()) {
468 QVector<QPointer<QQuickItem> > items = moved.value(key: insert.moveId);
469 d->deletables = d->deletables.mid(pos: 0, len: index) + items + d->deletables.mid(pos: index);
470 QQuickItem *stackBefore = index + items.size() < d->deletables.size()
471 ? d->deletables.at(i: index + items.size())
472 : this;
473 if (stackBefore) {
474 for (int i = index; i < index + items.size(); ++i) {
475 if (i < d->deletables.size()) {
476 QPointer<QQuickItem> item = d->deletables.at(i);
477 if (item)
478 item->stackBefore(stackBefore);
479 }
480 }
481 }
482 } else for (int i = 0; i < insert.count; ++i) {
483 int modelIndex = index + i;
484 ++d->itemCount;
485 d->deletables.insert(i: modelIndex, t: nullptr);
486 QObject *object = d->model->object(index: modelIndex, incubationMode: QQmlIncubator::AsynchronousIfNested);
487 if (object)
488 d->model->release(object);
489 }
490 difference += insert.count;
491 }
492
493 if (difference != 0)
494 emit countChanged();
495}
496
497QT_END_NAMESPACE
498
499#include "moc_qquickrepeater_p.cpp"
500

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