Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickrepeater.cpp
Go to the documentation of this file.
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// Qt-Security score:significant reason:default
4
7
8#include <private/qqmlglobal_p.h>
9#include <private/qqmlchangeset_p.h>
10#include <private/qqmldelegatemodel_p.h>
11
12#include <QtQml/QQmlInfo>
13#include <QtQml/qqmlcomponent.h>
14
16
17QQuickRepeaterPrivate::QQuickRepeaterPrivate()
18 : model(nullptr)
19 , ownModel(false)
20 , delegateValidated(false)
21 , explicitDelegate(false)
22 , explicitDelegateModelAccess(false)
23 , itemCount(0)
24{
25 setTransparentForPositioner(true);
26}
27
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(this, &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->ownModel)
156 return static_cast<QQmlDelegateModel *>(d->model.data())->model();
157 if (d->model)
158 return QVariant::fromValue(d->model.data());
159 return QVariant();
160}
161
162void QQuickRepeater::setModel(const QVariant &m)
163{
164 Q_D(QQuickRepeater);
165 QVariant model = m;
166 if (model.userType() == qMetaTypeId<QJSValue>())
167 model = model.value<QJSValue>().toVariant();
168
169 QQmlDelegateModelPointer oldModel(d->model);
170 if (d->ownModel) {
171 if (oldModel.delegateModel()->model() == model)
172 return;
173 } else if (QVariant::fromValue(d->model) == model) {
174 return;
175 }
176
177 clear();
178
179 d->disconnectModel(this, &oldModel);
180 d->model = nullptr;
181
182 QObject *object = qvariant_cast<QObject *>(model);
183
184 QQmlDelegateModelPointer newModel(qobject_cast<QQmlInstanceModel *>(object));
185 if (newModel) {
186 if (d->explicitDelegate) {
187 QQmlComponent *delegate = nullptr;
188 if (QQmlDelegateModel *old = oldModel.delegateModel())
189 delegate = old->delegate();
190
191 if (QQmlDelegateModel *delegateModel = newModel.delegateModel()) {
192 delegateModel->setDelegate(delegate);
193 } else if (delegate) {
194 qmlWarning(this) << "Cannot retain explicitly set delegate on non-DelegateModel";
195 d->explicitDelegate = false;
196 }
197 }
198
199 if (d->explicitDelegateModelAccess) {
200 QQmlDelegateModel::DelegateModelAccess access = QQmlDelegateModel::Qt5ReadWrite;
201 if (QQmlDelegateModel *old = oldModel.delegateModel())
202 access = old->delegateModelAccess();
203
204 if (QQmlDelegateModel *delegateModel = newModel.delegateModel()) {
205 delegateModel->setDelegateModelAccess(access);
206 } else if (access != QQmlDelegateModel::Qt5ReadWrite) {
207 qmlWarning(this) << "Cannot retain explicitly set delegate model access "
208 "on non-DelegateModel";
209 d->explicitDelegateModelAccess = false;
210 }
211 }
212
213 if (d->ownModel) {
214 delete oldModel.instanceModel();
215 d->ownModel = false;
216 }
217 d->model = newModel.instanceModel();
218 } else if (d->ownModel) {
219 // d->ownModel can only be set if the old model is a QQmlDelegateModel.
220 Q_ASSERT(oldModel.delegateModel());
221 newModel = oldModel;
222 d->model = newModel.instanceModel();
223 newModel.delegateModel()->setModel(model);
224 } else {
225 newModel = QQmlDelegateModel::createForView(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(this, &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*>(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(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 *>(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(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(i, item);
418 d->model->release(item);
419 }
420 }
421 for (QQuickItem *item : std::as_const(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(d->itemCount);
443 d->requestItems();
444}
445
446void QQuickRepeaterPrivate::requestItems()
447{
448 for (int i = 0; i < itemCount; i++) {
449 QObject *object = model->object(i, 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(instanceModel, &QQmlInstanceModel::modelUpdated,
462 q, &QQuickRepeater::modelUpdated);
463 QObject::connect(instanceModel, &QQmlInstanceModel::createdItem,
464 q, &QQuickRepeater::createdItem);
465 QObject::connect(instanceModel, &QQmlInstanceModel::initItem,
466 q, &QQuickRepeater::initItem);
467 if (QQmlDelegateModel *dataModel = model->delegateModel()) {
468 QObjectPrivate::connect(
469 dataModel, &QQmlDelegateModel::delegateChanged,
470 this, &QQuickRepeaterPrivate::applyDelegateChange);
471 QObjectPrivate::connect(
472 dataModel, &QQmlDelegateModel::delegateModelAccessChanged,
473 this, &QQuickRepeaterPrivate::applyDelegateModelAccessChange);
474 if (ownModel) {
475 QObject::connect(dataModel, &QQmlDelegateModel::modelChanged,
476 q, &QQuickRepeater::modelChanged);
477 }
478 }
479 q->regenerate();
480}
481
482void QQuickRepeaterPrivate::disconnectModel(QQuickRepeater *q, QQmlDelegateModelPointer *model)
483{
484 QQmlInstanceModel *instanceModel = model->instanceModel();
485 if (!instanceModel)
486 return;
487
488 QObject::disconnect(instanceModel, &QQmlInstanceModel::modelUpdated,
489 q, &QQuickRepeater::modelUpdated);
490 QObject::disconnect(instanceModel, &QQmlInstanceModel::createdItem,
491 q, &QQuickRepeater::createdItem);
492 QObject::disconnect(instanceModel, &QQmlInstanceModel::initItem,
493 q, &QQuickRepeater::initItem);
494 if (QQmlDelegateModel *delegateModel = model->delegateModel()) {
495 QObjectPrivate::disconnect(
496 delegateModel, &QQmlDelegateModel::delegateChanged,
497 this, &QQuickRepeaterPrivate::applyDelegateChange);
498 QObjectPrivate::disconnect(
499 delegateModel, &QQmlDelegateModel::delegateModelAccessChanged,
500 this, &QQuickRepeaterPrivate::applyDelegateModelAccessChange);
501 if (ownModel) {
502 QObject::disconnect(delegateModel, &QQmlDelegateModel::modelChanged,
503 q, &QQuickRepeater::modelChanged);
504 }
505 }
506}
507
508void QQuickRepeater::createdItem(int index, QObject *)
509{
510 Q_D(QQuickRepeater);
511 QObject *object = d->model->object(index, QQmlIncubator::AsynchronousIfNested);
512 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
513 emit itemAdded(index, item);
514}
515
516void QQuickRepeater::initItem(int index, QObject *object)
517{
518 Q_D(QQuickRepeater);
519 if (index >= d->deletables.size()) {
520 // this can happen when Package is used
521 // calling regenerate does too much work, all we need is to call resize
522 // so that d->deletables[index] = item below works
523 d->deletables.resize(d->model->count() + 1);
524 }
525 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
526
527 if (!d->deletables.at(index)) {
528 if (!item) {
529 if (object) {
530 d->model->release(object);
531 if (!d->delegateValidated) {
532 d->delegateValidated = true;
533 QObject* delegate = this->delegate();
534 qmlWarning(delegate ? delegate : this) << QQuickRepeater::tr("Delegate must be of Item type");
535 }
536 }
537 return;
538 }
539 d->deletables[index] = item;
540 item->setParentItem(parentItem());
541
542 // If the item comes from an ObjectModel, it might be used as
543 // ComboBox/Menu/TabBar's contentItem. These types unconditionally cull items
544 // that are inserted, so account for that here.
545 if (d->model && !d->ownModel)
546 QQuickItemPrivate::get(item)->setCulled(false);
547 if (index > 0 && d->deletables.at(index-1)) {
548 item->stackAfter(d->deletables.at(index-1));
549 } else {
550 QQuickItem *after = this;
551 for (int si = index+1; si < d->itemCount; ++si) {
552 if (d->deletables.at(si)) {
553 after = d->deletables.at(si);
554 break;
555 }
556 }
557 item->stackBefore(after);
558 }
559 }
560}
561
562void QQuickRepeater::modelUpdated(const QQmlChangeSet &changeSet, bool reset)
563{
564 Q_D(QQuickRepeater);
565
566 if (!isComponentComplete())
567 return;
568
569 if (reset) {
570 regenerate();
571 if (changeSet.difference() != 0)
572 emit countChanged();
573 return;
574 }
575
576 int difference = 0;
577 QHash<int, QVector<QPointer<QQuickItem> > > moved;
578 for (const QQmlChangeSet::Change &remove : changeSet.removes()) {
579 int index = qMin(remove.index, d->deletables.size());
580 int count = qMin(remove.index + remove.count, d->deletables.size()) - index;
581 if (remove.isMove()) {
582 moved.insert(remove.moveId, d->deletables.mid(index, count));
583 d->deletables.erase(
584 d->deletables.begin() + index,
585 d->deletables.begin() + index + count);
586 } else while (count--) {
587 QQuickItem *item = d->deletables.at(index);
588 d->deletables.remove(index);
589 emit itemRemoved(index, item);
590 if (item) {
591 d->model->release(item);
592 item->setParentItem(nullptr);
593 }
594 --d->itemCount;
595 }
596
597 difference -= remove.count;
598 }
599
600 for (const QQmlChangeSet::Change &insert : changeSet.inserts()) {
601 int index = qMin(insert.index, d->deletables.size());
602 if (insert.isMove()) {
603 QVector<QPointer<QQuickItem> > items = moved.value(insert.moveId);
604 d->deletables = d->deletables.mid(0, index) + items + d->deletables.mid(index);
605 QQuickItem *stackBefore = index + items.size() < d->deletables.size()
606 ? d->deletables.at(index + items.size())
607 : this;
608 if (stackBefore) {
609 for (int i = index; i < index + items.size(); ++i) {
610 if (i < d->deletables.size()) {
611 QPointer<QQuickItem> item = d->deletables.at(i);
612 if (item)
613 item->stackBefore(stackBefore);
614 }
615 }
616 }
617 } else for (int i = 0; i < insert.count; ++i) {
618 int modelIndex = index + i;
619 ++d->itemCount;
620 d->deletables.insert(modelIndex, nullptr);
621 QObject *object = d->model->object(modelIndex, QQmlIncubator::AsynchronousIfNested);
622 if (object)
623 d->model->release(object);
624 }
625 difference += insert.count;
626 }
627
628 if (difference != 0)
629 emit countChanged();
630}
631
632/*!
633 \qmlproperty enumeration QtQuick::Repeater::delegateModelAccess
634
635 \include delegatemodelaccess.qdocinc
636*/
637QQmlDelegateModel::DelegateModelAccess QQuickRepeater::delegateModelAccess() const
638{
639 Q_D(const QQuickRepeater);
640 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel *>(d->model))
641 return dataModel->delegateModelAccess();
642 return QQmlDelegateModel::Qt5ReadWrite;
643}
644
645void QQuickRepeater::setDelegateModelAccess(
646 QQmlDelegateModel::DelegateModelAccess delegateModelAccess)
647{
648 Q_D(QQuickRepeater);
649 const auto setExplicitDelegateModelAccess = [&](QQmlDelegateModel *delegateModel) {
650 delegateModel->setDelegateModelAccess(delegateModelAccess);
651 d->explicitDelegateModelAccess = true;
652 };
653
654 if (!d->model) {
655 if (delegateModelAccess == QQmlDelegateModel::Qt5ReadWrite) {
656 // Explicitly set delegateModelAccess to Legacy. We can do this without model.
657 d->explicitDelegateModelAccess = true;
658 return;
659 }
660
661 setExplicitDelegateModelAccess(QQmlDelegateModel::createForView(this, d));
662
663 // The new model is not connected to applyDelegateModelAccessChange, yet. We only do this
664 // once there is actual data, via an explicit setModel(). So we have to manually emit the
665 // delegateModelAccessChanged() here.
666 emit delegateModelAccessChanged();
667 return;
668 }
669
670 if (QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel *>(d->model)) {
671 // Disable the warning in applyDelegateModelAccessChange since the new delegate model
672 // access is also explicit.
673 d->explicitDelegateModelAccess = false;
674 setExplicitDelegateModelAccess(delegateModel);
675 return;
676 }
677
678 if (delegateModelAccess == QQmlDelegateModel::Qt5ReadWrite) {
679 d->explicitDelegateModelAccess = true; // Explicitly set null delegate always works
680 } else {
681 qmlWarning(this) << "Cannot set a delegateModelAccess on an explicitly provided "
682 "non-DelegateModel";
683 }
684}
685
686QT_END_NAMESPACE
687
688#include "moc_qquickrepeater_p.cpp"