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 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | QQuickRepeaterPrivate::QQuickRepeaterPrivate() |
16 | : model(nullptr) |
17 | , ownModel(false) |
18 | , dataSourceIsObject(false) |
19 | , delegateValidated(false) |
20 | , itemCount(0) |
21 | { |
22 | setTransparentForPositioner(true); |
23 | } |
24 | |
25 | QQuickRepeaterPrivate::~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 | */ |
118 | QQuickRepeater::QQuickRepeater(QQuickItem *parent) |
119 | : QQuickItem(*(new QQuickRepeaterPrivate), parent) |
120 | { |
121 | } |
122 | |
123 | QQuickRepeater::~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 | */ |
145 | QVariant 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 | |
157 | void 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 | */ |
243 | QQmlComponent *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 | |
254 | void 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 | */ |
283 | int 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 | */ |
297 | QQuickItem *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 | |
305 | void 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 | |
316 | void QQuickRepeater::itemChange(ItemChange change, const ItemChangeData &value) |
317 | { |
318 | QQuickItem::itemChange(change, value); |
319 | if (change == ItemParentHasChanged) { |
320 | regenerate(); |
321 | } |
322 | } |
323 | |
324 | void 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 | |
348 | void 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 | |
364 | void 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 | |
373 | void 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 | |
381 | void 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 | |
427 | void 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 | |
497 | QT_END_NAMESPACE |
498 | |
499 | #include "moc_qquickrepeater_p.cpp" |
500 | |