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 
49 QT_BEGIN_NAMESPACE
50 
QQuickRepeaterPrivate()51 QQuickRepeaterPrivate::QQuickRepeaterPrivate()
52     : model(nullptr)
53     , ownModel(false)
54     , dataSourceIsObject(false)
55     , delegateValidated(false)
56     , itemCount(0)
57 {
58     setTransparentForPositioner(true);
59 }
60 
~QQuickRepeaterPrivate()61 QQuickRepeaterPrivate::~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 */
QQuickRepeater(QQuickItem * parent)154 QQuickRepeater::QQuickRepeater(QQuickItem *parent)
155   : QQuickItem(*(new QQuickRepeaterPrivate), parent)
156 {
157 }
158 
~QQuickRepeater()159 QQuickRepeater::~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 */
model() const181 QVariant QQuickRepeater::model() const
182 {
183     Q_D(const QQuickRepeater);
184 
185     if (d->dataSourceIsObject) {
186         QObject *o = d->dataSourceAsObject;
187         return QVariant::fromValue(o);
188     }
189 
190     return d->dataSource;
191 }
192 
setModel(const QVariant & m)193 void 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*>(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*>(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  */
delegate() const279 QQmlComponent *QQuickRepeater::delegate() const
280 {
281     Q_D(const QQuickRepeater);
282     if (d->model) {
283         if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(d->model))
284             return dataModel->delegate();
285     }
286 
287     return nullptr;
288 }
289 
setDelegate(QQmlComponent * delegate)290 void QQuickRepeater::setDelegate(QQmlComponent *delegate)
291 {
292     Q_D(QQuickRepeater);
293     if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(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*>(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 */
count() const319 int 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 */
itemAt(int index) const333 QQuickItem *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 
componentComplete()341 void 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 
itemChange(ItemChange change,const ItemChangeData & value)352 void QQuickRepeater::itemChange(ItemChange change, const ItemChangeData &value)
353 {
354     QQuickItem::itemChange(change, value);
355     if (change == ItemParentHasChanged) {
356         regenerate();
357     }
358 }
359 
clear()360 void 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(i, item);
372                 d->model->release(item);
373             }
374         }
375         for (QQuickItem *item : qAsConst(d->deletables)) {
376             if (item)
377                 item->setParentItem(nullptr);
378         }
379     }
380     d->deletables.clear();
381     d->itemCount = 0;
382 }
383 
regenerate()384 void 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(d->itemCount);
397     d->requestItems();
398 }
399 
requestItems()400 void QQuickRepeaterPrivate::requestItems()
401 {
402     for (int i = 0; i < itemCount; i++) {
403         QObject *object = model->object(i, QQmlIncubator::AsynchronousIfNested);
404         if (object)
405             model->release(object);
406     }
407 }
408 
createdItem(int index,QObject *)409 void QQuickRepeater::createdItem(int index, QObject *)
410 {
411     Q_D(QQuickRepeater);
412     QObject *object = d->model->object(index, QQmlIncubator::AsynchronousIfNested);
413     QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
414     emit itemAdded(index, item);
415 }
416 
initItem(int index,QObject * object)417 void 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(d->model->count() + 1);
425     }
426     QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
427 
428     if (!d->deletables.at(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(delegate ? delegate : this) << QQuickRepeater::tr("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(index-1)) {
443             item->stackAfter(d->deletables.at(index-1));
444         } else {
445             QQuickItem *after = this;
446             for (int si = index+1; si < d->itemCount; ++si) {
447                 if (d->deletables.at(si)) {
448                     after = d->deletables.at(si);
449                     break;
450                 }
451             }
452             item->stackBefore(after);
453         }
454     }
455 }
456 
modelUpdated(const QQmlChangeSet & changeSet,bool reset)457 void 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(remove.index, d->deletables.count());
475         int count = qMin(remove.index + remove.count, d->deletables.count()) - index;
476         if (remove.isMove()) {
477             moved.insert(remove.moveId, d->deletables.mid(index, count));
478             d->deletables.erase(
479                     d->deletables.begin() + index,
480                     d->deletables.begin() + index + count);
481         } else while (count--) {
482             QQuickItem *item = d->deletables.at(index);
483             d->deletables.remove(index);
484             emit itemRemoved(index, item);
485             if (item) {
486                 d->model->release(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(insert.index, d->deletables.count());
497         if (insert.isMove()) {
498             QVector<QPointer<QQuickItem> > items = moved.value(insert.moveId);
499             d->deletables = d->deletables.mid(0, index) + items + d->deletables.mid(index);
500             QQuickItem *stackBefore = index + items.count() < d->deletables.count()
501                     ? d->deletables.at(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(modelIndex, nullptr);
516             QObject *object = d->model->object(modelIndex, 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 
527 QT_END_NAMESPACE
528 
529 #include "moc_qquickrepeater_p.cpp"
530