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

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