1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3drepeater_p.h"
5
6#include <private/qqmlglobal_p.h>
7#include <private/qqmllistaccessor_p.h>
8#include <private/qqmlchangeset_p.h>
9#include <private/qqmldelegatemodel_p.h>
10
11#include <QtQml/QQmlInfo>
12
13QT_BEGIN_NAMESPACE
14
15
16/*!
17 \qmltype Repeater3D
18 \inqmlmodule QtQuick3D
19 \inherits Node
20 \brief Instantiates a number of Node-based components using a provided model.
21
22 The Repeater3D type is used to create a large number of
23 similar items. Like other view types, a Repeater3D has a \l model and a \l delegate:
24 for each entry in the model, the delegate is instantiated
25 in a context seeded with data from the model.
26
27 A Repeater's \l model can be any of the supported \l {qml-data-models}{data models}.
28 Additionally, like delegates for other views, a Repeater delegate can access
29 its index within the repeater, as well as the model data relevant to the
30 delegate. See the \l delegate property documentation for details.
31
32 \note A Repeater3D item owns all items it instantiates. Removing or dynamically destroying
33 an item created by a Repeater3D results in unpredictable behavior.
34
35 \note Repeater3D is \l {Node}-based, and can only repeat \l {Node}-derived objects.
36 */
37
38/*!
39 \qmlsignal QtQuick3D::Repeater3D::objectAdded(int index, Object3D object)
40
41 This signal is emitted when an object is added to the repeater. The \a index
42 parameter holds the index at which object has been inserted within the
43 repeater, and the \a object parameter holds the \l Object3D that has been added.
44
45 The corresponding handler is \c onObjectAdded.
46*/
47
48/*!
49 \qmlsignal QtQuick3D::Repeater3D::objectRemoved(int index, Object3D object)
50
51 This signal is emitted when an object is removed from the repeater. The \a index
52 parameter holds the index at which the item was removed from the repeater,
53 and the \a object parameter holds the \l Object3D that was removed.
54
55 Do not keep a reference to \a object if it was created by this repeater, as
56 in these cases it will be deleted shortly after the signal is handled.
57
58 The corresponding handler is \c onObjectRemoved.
59*/
60
61QQuick3DRepeater::QQuick3DRepeater(QQuick3DNode *parent)
62 : QQuick3DNode(parent)
63 , m_model(nullptr)
64 , m_itemCount(0)
65 , m_ownModel(false)
66 , m_dataSourceIsObject(false)
67 , m_delegateValidated(false)
68{
69}
70
71QQuick3DRepeater::~QQuick3DRepeater()
72{
73 if (m_ownModel)
74 delete m_model;
75}
76
77/*!
78 \qmlproperty any QtQuick3D::Repeater3D::model
79
80 The model providing data for the repeater.
81
82 This property can be set to any of the supported \l {qml-data-models}{data models}:
83
84 \list
85 \li A number that indicates the number of delegates to be created by the repeater
86 \li A model (e.g. a ListModel item, or a QAbstractItemModel subclass)
87 \li A string list
88 \li An object list
89 \endlist
90
91 The type of model affects the properties that are exposed to the \l delegate.
92
93 \sa {qml-data-models}{Data Models}
94*/
95
96QVariant QQuick3DRepeater::model() const
97{
98 if (m_dataSourceIsObject) {
99 QObject *o = m_dataSourceAsObject;
100 return QVariant::fromValue(value: o);
101 }
102
103 return m_dataSource;
104
105}
106
107void QQuick3DRepeater::setModel(const QVariant &m)
108{
109 QVariant model = m;
110 if (model.userType() == qMetaTypeId<QJSValue>())
111 model = model.value<QJSValue>().toVariant();
112
113 if (m_dataSource == model)
114 return;
115
116 clear();
117 if (m_model) {
118 qmlobject_disconnect(m_model, QQmlInstanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
119 this, QQuick3DRepeater, SLOT(modelUpdated(QQmlChangeSet,bool)));
120 qmlobject_disconnect(m_model, QQmlInstanceModel, SIGNAL(createdItem(int,QObject*)),
121 this, QQuick3DRepeater, SLOT(createdObject(int,QObject*)));
122 qmlobject_disconnect(m_model, QQmlInstanceModel, SIGNAL(initItem(int,QObject*)),
123 this, QQuick3DRepeater, SLOT(initObject(int,QObject*)));
124 }
125 m_dataSource = model;
126 QObject *object = qvariant_cast<QObject*>(v: model);
127 m_dataSourceAsObject = object;
128 m_dataSourceIsObject = object != nullptr;
129 QQmlInstanceModel *vim = nullptr;
130 if (object && (vim = qobject_cast<QQmlInstanceModel *>(object))) {
131 if (m_ownModel) {
132 delete m_model;
133 m_ownModel = false;
134 }
135 m_model = vim;
136 } else {
137 if (!m_ownModel) {
138 m_model = new QQmlDelegateModel(qmlContext(this));
139 m_ownModel = true;
140 if (isComponentComplete())
141 static_cast<QQmlDelegateModel *>(m_model.data())->componentComplete();
142 }
143 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(object: m_model))
144 dataModel->setModel(model);
145 }
146 if (m_model) {
147 qmlobject_connect(m_model, QQmlInstanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
148 this, QQuick3DRepeater, SLOT(modelUpdated(QQmlChangeSet,bool)));
149 qmlobject_connect(m_model, QQmlInstanceModel, SIGNAL(createdItem(int,QObject*)),
150 this, QQuick3DRepeater, SLOT(createdObject(int,QObject*)));
151 qmlobject_connect(m_model, QQmlInstanceModel, SIGNAL(initItem(int,QObject*)),
152 this, QQuick3DRepeater, SLOT(initObject(int,QObject*)));
153 regenerate();
154 }
155 emit modelChanged();
156 emit countChanged();
157}
158
159/*!
160 \qmlproperty Component QtQuick3D::Repeater3D::delegate
161 \qmldefault
162
163 The delegate provides a template defining each object instantiated by the repeater.
164
165 Delegates are exposed to a read-only \c index property that indicates the index
166 of the delegate within the repeater.
167
168 If the \l model is a model object (such as a \l ListModel) the delegate
169 can access all model roles as named properties, in the same way that delegates
170 do for view classes like ListView.
171
172 \sa {QML Data Models}
173 */
174
175QQmlComponent *QQuick3DRepeater::delegate() const
176{
177 if (m_model) {
178 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(object: m_model))
179 return dataModel->delegate();
180 }
181
182 return nullptr;
183}
184
185void QQuick3DRepeater::setDelegate(QQmlComponent *delegate)
186{
187 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(object: m_model))
188 if (delegate == dataModel->delegate())
189 return;
190
191 if (!m_ownModel) {
192 m_model = new QQmlDelegateModel(qmlContext(this));
193 m_ownModel = true;
194 if (isComponentComplete())
195 static_cast<QQmlDelegateModel *>(m_model.data())->componentComplete();
196 }
197
198 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(object: m_model)) {
199 dataModel->setDelegate(delegate);
200 regenerate();
201 emit delegateChanged();
202 m_delegateValidated = false;
203 }
204}
205
206/*!
207 \qmlproperty int QtQuick3D::Repeater3D::count
208 \readonly
209
210 This property holds the number of items in the model.
211
212 \note The number of items in the model as reported by count may differ from
213 the number of created delegates if the Repeater3D is in the process of
214 instantiating delegates or is incorrectly set up.
215*/
216
217int QQuick3DRepeater::count() const
218{
219 if (m_model)
220 return m_model->count();
221 return 0;
222}
223
224/*!
225 \qmlmethod Object3D QtQuick3D::Repeater3D::objectAt(index)
226
227 Returns the \l Object3D that has been created at the given \a index, or \c null
228 if no item exists at \a index.
229*/
230
231QQuick3DObject *QQuick3DRepeater::objectAt(int index) const
232{
233 if (index >= 0 && index < m_deletables.size())
234 return m_deletables[index];
235 return nullptr;
236}
237
238void QQuick3DRepeater::clear()
239{
240 bool complete = isComponentComplete();
241
242 if (m_model) {
243 // We remove in reverse order deliberately; so that signals are emitted
244 // with sensible indices.
245 for (int i = m_deletables.size() - 1; i >= 0; --i) {
246 if (QQuick3DObject *item = m_deletables.at(i)) {
247 if (complete)
248 emit objectRemoved(index: i, object: item);
249 m_model->release(object: item);
250 }
251 }
252 for (QQuick3DObject *item : std::as_const(t&: m_deletables)) {
253 if (item)
254 item->setParentItem(nullptr);
255 }
256 }
257 m_deletables.clear();
258 m_itemCount = 0;
259}
260
261void QQuick3DRepeater::regenerate()
262{
263 if (!isComponentComplete())
264 return;
265
266 clear();
267
268 if (!m_model || !m_model->count() || !m_model->isValid() || !parentItem() || !isComponentComplete())
269 return;
270
271 m_itemCount = count();
272 m_deletables.resize(size: m_itemCount);
273 requestItems();
274}
275
276void QQuick3DRepeater::componentComplete()
277{
278 if (m_model && m_ownModel)
279 static_cast<QQmlDelegateModel *>(m_model.data())->componentComplete();
280 QQuick3DNode::componentComplete();
281 regenerate();
282 if (m_model && m_model->count())
283 emit countChanged();
284}
285
286void QQuick3DRepeater::itemChange(QQuick3DObject::ItemChange change, const QQuick3DObject::ItemChangeData &value)
287{
288 QQuick3DObject::itemChange(change, value);
289 if (change == ItemParentHasChanged) {
290 regenerate();
291 }
292}
293
294void QQuick3DRepeater::createdObject(int index, QObject *)
295{
296 QObject *object = m_model->object(index, incubationMode: QQmlIncubator::AsynchronousIfNested);
297 QQuick3DObject *item = qmlobject_cast<QQuick3DObject*>(object);
298 emit objectAdded(index, object: item);
299}
300
301void QQuick3DRepeater::initObject(int index, QObject *object)
302{
303 QQuick3DNode *item = qmlobject_cast<QQuick3DNode*>(object);
304
305 if (!m_deletables.at(i: index)) {
306 if (!item) {
307 if (object) {
308 m_model->release(object);
309 if (!m_delegateValidated) {
310 m_delegateValidated = true;
311 QObject* delegate = this->delegate();
312 qmlWarning(me: delegate ? delegate : this) << QQuick3DRepeater::tr(s: "Delegate must be of Node type");
313 }
314 }
315 return;
316 }
317 m_deletables[index] = item;
318 item->setParent(this);
319 item->setParentItem(static_cast<QQuick3DNode*>(this));
320 initDelegate(index, item);
321 }
322}
323
324void QQuick3DRepeater::modelUpdated(const QQmlChangeSet &changeSet, bool reset)
325{
326 if (!isComponentComplete())
327 return;
328
329 if (reset) {
330 regenerate();
331 if (changeSet.difference() != 0)
332 emit countChanged();
333 return;
334 }
335
336 int difference = 0;
337 QHash<int, QVector<QPointer<QQuick3DNode> > > moved;
338 for (const QQmlChangeSet::Change &remove : changeSet.removes()) {
339 int index = qMin(a: remove.index, b: m_deletables.size());
340 int count = qMin(a: remove.index + remove.count, b: m_deletables.size()) - index;
341 if (remove.isMove()) {
342 moved.insert(key: remove.moveId, value: m_deletables.mid(pos: index, len: count));
343 m_deletables.erase(
344 begin: m_deletables.begin() + index,
345 end: m_deletables.begin() + index + count);
346 } else while (count--) {
347 QQuick3DNode *item = m_deletables.at(i: index);
348 m_deletables.remove(i: index);
349 emit objectRemoved(index, object: item);
350 if (item) {
351 m_model->release(object: item);
352 item->setParentItem(nullptr);
353 }
354 --m_itemCount;
355 }
356
357 difference -= remove.count;
358 }
359
360 for (const QQmlChangeSet::Change &insert : changeSet.inserts()) {
361 int index = qMin(a: insert.index, b: m_deletables.size());
362 if (insert.isMove()) {
363 QVector<QPointer<QQuick3DNode> > items = moved.value(key: insert.moveId);
364 m_deletables = m_deletables.mid(pos: 0, len: index) + items + m_deletables.mid(pos: index);
365 } else for (int i = 0; i < insert.count; ++i) {
366 int modelIndex = index + i;
367 ++m_itemCount;
368 m_deletables.insert(i: modelIndex, t: nullptr);
369 QObject *object = m_model->object(index: modelIndex, incubationMode: QQmlIncubator::AsynchronousIfNested);
370 if (object)
371 m_model->release(object);
372 }
373 difference += insert.count;
374 }
375
376 if (difference != 0)
377 emit countChanged();
378}
379
380void QQuick3DRepeater::requestItems()
381{
382 for (int i = 0; i < m_itemCount; i++) {
383 QObject *object = m_model->object(index: i, incubationMode: QQmlIncubator::AsynchronousIfNested);
384 if (object)
385 m_model->release(object);
386 }
387}
388
389QT_END_NAMESPACE
390
391#include "moc_qquick3drepeater_p.cpp"
392

source code of qtquick3d/src/quick3d/qquick3drepeater.cpp