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
10#include <QtQml/QQmlInfo>
11
12QT_BEGIN_NAMESPACE
13
14
15/*!
16 \qmltype Repeater3D
17 \inqmlmodule QtQuick3D
18 \inherits Node
19 \brief Instantiates a number of Node-based components using a provided model.
20
21 The Repeater3D type is used to create a large number of
22 similar items. Like other view types, a Repeater3D has a \l model and a \l delegate:
23 for each entry in the model, the delegate is instantiated
24 in a context seeded with data from the model.
25
26 A Repeater's \l model can be any of the supported \l {qml-data-models}{data models}.
27 Additionally, like delegates for other views, a Repeater delegate can access
28 its index within the repeater, as well as the model data relevant to the
29 delegate. See the \l delegate property documentation for details.
30
31 \note A Repeater3D item owns all items it instantiates. Removing or dynamically destroying
32 an item created by a Repeater3D results in unpredictable behavior.
33
34 \note Repeater3D is \l {Node}-based, and can only repeat \l {Node}-derived objects.
35 */
36
37/*!
38 \qmlsignal QtQuick3D::Repeater3D::objectAdded(int index, Object3D object)
39
40 This signal is emitted when an object is added to the repeater. The \a index
41 parameter holds the index at which object has been inserted within the
42 repeater, and the \a object parameter holds the \l Object3D that has been added.
43
44 The corresponding handler is \c onObjectAdded.
45*/
46
47/*!
48 \qmlsignal QtQuick3D::Repeater3D::objectRemoved(int index, Object3D object)
49
50 This signal is emitted when an object is removed from the repeater. The \a index
51 parameter holds the index at which the item was removed from the repeater,
52 and the \a object parameter holds the \l Object3D that was removed.
53
54 Do not keep a reference to \a object if it was created by this repeater, as
55 in these cases it will be deleted shortly after the signal is handled.
56
57 The corresponding handler is \c onObjectRemoved.
58*/
59
60QQuick3DRepeater::QQuick3DRepeater(QQuick3DNode *parent)
61 : QQuick3DNode(parent)
62 , m_model(nullptr)
63 , m_itemCount(0)
64 , m_ownModel(false)
65 , m_dataSourceIsObject(false)
66 , m_delegateValidated(false)
67 , m_explicitDelegate(false)
68 , m_explicitDelegateModelAccess(false)
69{
70}
71
72QQuick3DRepeater::~QQuick3DRepeater()
73{
74 if (m_ownModel) {
75 delete m_model;
76 } else {
77 QQmlDelegateModelPointer model(m_model);
78 disconnectModel(model: &model);
79 }
80}
81
82void QQuick3DRepeater::connectModel(QQmlDelegateModelPointer *model)
83{
84 QQmlInstanceModel *instanceModel = model->instanceModel();
85 if (!instanceModel)
86 return;
87
88 connect(sender: instanceModel, signal: &QQmlInstanceModel::modelUpdated,
89 context: this, slot: &QQuick3DRepeater::modelUpdated);
90 connect(sender: instanceModel, signal: &QQmlInstanceModel::createdItem,
91 context: this, slot: &QQuick3DRepeater::createdObject);
92 connect(sender: instanceModel, signal: &QQmlInstanceModel::initItem,
93 context: this, slot: &QQuick3DRepeater::initObject);
94 if (QQmlDelegateModel *dataModel = model->delegateModel()) {
95 QObject::connect(
96 sender: dataModel, signal: &QQmlDelegateModel::delegateChanged,
97 context: this, slot: &QQuick3DRepeater::applyDelegateChange);
98 }
99 regenerate();
100}
101
102void QQuick3DRepeater::disconnectModel(QQmlDelegateModelPointer *model)
103{
104 QQmlInstanceModel *instanceModel = model->instanceModel();
105 if (!instanceModel)
106 return;
107
108 disconnect(sender: instanceModel, signal: &QQmlInstanceModel::modelUpdated,
109 receiver: this, slot: &QQuick3DRepeater::modelUpdated);
110 disconnect(sender: instanceModel, signal: &QQmlInstanceModel::createdItem,
111 receiver: this, slot: &QQuick3DRepeater::createdObject);
112 disconnect(sender: instanceModel, signal: &QQmlInstanceModel::initItem,
113 receiver: this, slot: &QQuick3DRepeater::initObject);
114 if (QQmlDelegateModel *delegateModel = model->delegateModel()) {
115 QObject::disconnect(
116 sender: delegateModel, signal: &QQmlDelegateModel::delegateChanged,
117 receiver: this, slot: &QQuick3DRepeater::applyDelegateChange);
118 }
119}
120
121/*!
122 \qmlproperty any QtQuick3D::Repeater3D::model
123
124 The model providing data for the repeater.
125
126 This property can be set to any of the supported \l {qml-data-models}{data models}:
127
128 \list
129 \li A number that indicates the number of delegates to be created by the repeater
130 \li A model (e.g. a ListModel item, or a QAbstractItemModel subclass)
131 \li A string list
132 \li An object list
133 \endlist
134
135 The type of model affects the properties that are exposed to the \l delegate.
136
137 \sa {qml-data-models}{Data Models}
138*/
139
140QVariant QQuick3DRepeater::model() const
141{
142 if (m_dataSourceIsObject) {
143 QObject *o = m_dataSourceAsObject;
144 return QVariant::fromValue(value: o);
145 }
146
147 return m_dataSource;
148
149}
150
151void QQuick3DRepeater::applyDelegateChange()
152{
153 if (m_explicitDelegate) {
154 qmlWarning(me: this) << "Explicitly set delegate is externally overridden";
155 m_explicitDelegate = false;
156 }
157
158 emit delegateChanged();
159}
160
161QQmlDelegateModel *QQuick3DRepeater::createDelegateModel()
162{
163 Q_ASSERT(m_model.isNull());
164 QQmlDelegateModel *delegateModel = new QQmlDelegateModel(qmlContext(this), this);
165 m_model = delegateModel;
166 m_ownModel = true;
167 if (isComponentComplete())
168 delegateModel->componentComplete();
169 return delegateModel;
170}
171
172void QQuick3DRepeater::setModel(const QVariant &m)
173{
174 QVariant model = m;
175 if (model.userType() == qMetaTypeId<QJSValue>())
176 model = model.value<QJSValue>().toVariant();
177
178 if (m_dataSource == model)
179 return;
180
181 clear();
182
183 QQmlDelegateModelPointer oldModel(m_model);
184 disconnectModel(model: &oldModel);
185
186 m_model = nullptr;
187 m_dataSource = model;
188
189 QObject *object = qvariant_cast<QObject *>(v: model);
190 m_dataSourceAsObject = object;
191 m_dataSourceIsObject = object != nullptr;
192
193 QQmlDelegateModelPointer newModel(qobject_cast<QQmlInstanceModel *>(object));
194 if (newModel) {
195 if (m_explicitDelegate) {
196 QQmlComponent *delegate = nullptr;
197 if (QQmlDelegateModel *old = oldModel.delegateModel())
198 delegate = old->delegate();
199 if (QQmlDelegateModel *delegateModel = newModel.delegateModel()) {
200 delegateModel->setDelegate(delegate);
201 } else if (delegate) {
202 qmlWarning(me: this) << "Cannot retain explicitly set delegate on non-DelegateModel";
203 m_explicitDelegate = false;
204 }
205 }
206 if (m_ownModel) {
207 delete oldModel.instanceModel();
208 m_ownModel = false;
209 }
210 m_model = newModel.instanceModel();
211 } else if (m_ownModel) {
212 newModel = oldModel;
213 m_model = newModel.instanceModel();
214 if (QQmlDelegateModel *delegateModel = newModel.delegateModel())
215 delegateModel->setModel(model);
216 } else {
217 newModel = createDelegateModel();
218 if (m_explicitDelegate) {
219 QQmlComponent *delegate = nullptr;
220 if (QQmlDelegateModel *old = oldModel.delegateModel())
221 delegate = old->delegate();
222 newModel.delegateModel()->setDelegate(delegate);
223 }
224
225 newModel.delegateModel()->setModel(model);
226 }
227
228 connectModel(model: &newModel);
229
230 emit modelChanged();
231 emit countChanged();
232}
233
234/*!
235 \qmlproperty Component QtQuick3D::Repeater3D::delegate
236 \qmldefault
237
238 The delegate provides a template defining each object instantiated by the repeater.
239
240 Delegates are exposed to a read-only \c index property that indicates the index
241 of the delegate within the repeater.
242
243 If the \l model is a model object (such as a \l ListModel) the delegate
244 can access all model roles as named properties, in the same way that delegates
245 do for view classes like ListView.
246
247 \sa {QML Data Models}
248 */
249
250QQmlComponent *QQuick3DRepeater::delegate() const
251{
252 if (m_model) {
253 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(object: m_model))
254 return dataModel->delegate();
255 }
256
257 return nullptr;
258}
259
260void QQuick3DRepeater::setDelegate(QQmlComponent *delegate)
261{
262 const auto setExplicitDelegate = [&](QQmlDelegateModel *delegateModel) {
263 if (delegateModel->delegate() == delegate) {
264 m_explicitDelegate = true;
265 return;
266 }
267
268 const int oldCount = delegateModel->count();
269 delegateModel->setDelegate(delegate);
270 regenerate();
271 if (oldCount != delegateModel->count())
272 emit countChanged();
273 m_explicitDelegate = true;
274 m_delegateValidated = false;
275 };
276
277 if (!m_model) {
278 if (!delegate) {
279 // Explicitly set a null delegate. We can do this without model.
280 m_explicitDelegate = true;
281 return;
282 }
283
284 setExplicitDelegate(createDelegateModel());
285 // The new model is not connected to applyDelegateChange, yet. We only do this once
286 // there is actual data, via an explicit setModel(). So we have to manually emit the
287 // delegateChanged() here.
288 emit delegateChanged();
289 return;
290 }
291
292 if (QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel *>(object: m_model)) {
293 // Disable the warning in applyDelegateChange since the new delegate is also explicit.
294 m_explicitDelegate = false;
295 setExplicitDelegate(delegateModel);
296 return;
297 }
298
299 if (delegate)
300 qmlWarning(me: this) << "Cannot set a delegate on an explicitly provided non-DelegateModel";
301 else
302 m_explicitDelegate = true; // Explicitly set null delegate always works
303}
304
305/*!
306 \qmlproperty int QtQuick3D::Repeater3D::count
307 \readonly
308
309 This property holds the number of items in the model.
310
311 \note The number of items in the model as reported by count may differ from
312 the number of created delegates if the Repeater3D is in the process of
313 instantiating delegates or is incorrectly set up.
314*/
315
316int QQuick3DRepeater::count() const
317{
318 if (m_model)
319 return m_model->count();
320 return 0;
321}
322
323/*!
324 \qmlmethod Object3D QtQuick3D::Repeater3D::objectAt(index)
325
326 Returns the \l Object3D that has been created at the given \a index, or \c null
327 if no item exists at \a index.
328*/
329
330QQuick3DObject *QQuick3DRepeater::objectAt(int index) const
331{
332 if (index >= 0 && index < m_deletables.size())
333 return m_deletables[index];
334 return nullptr;
335}
336
337/*!
338 \qmlproperty enumeration QtQuick3D::Repeater3D::delegateModelAccess
339 \since 6.10
340
341 This property determines how delegates can access the model.
342
343 \value DelegateModel.ReadOnly
344 Prohibit delegates from writing the model via either context properties,
345 the \c model object, or required properties.
346
347 \value DelegateModel.ReadWrite
348 Allow delegates to write the model via either context properties,
349 the \c model object, or required properties.
350
351 \value DelegateModel.Qt5ReadWrite
352 Allow delegates to write the model via the \c model object and context
353 properties but \e not via required properties.
354
355 The default is \c DelegateModel.Qt5ReadWrite.
356
357 \sa {Models and Views in Qt Quick#Changing Model Data}
358*/
359QQmlDelegateModel::DelegateModelAccess QQuick3DRepeater::delegateModelAccess() const
360{
361 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel *>(object: m_model))
362 return dataModel->delegateModelAccess();
363 return QQmlDelegateModel::Qt5ReadWrite;
364}
365
366void QQuick3DRepeater::setDelegateModelAccess(QQmlDelegateModel::DelegateModelAccess delegateModelAccess)
367{
368 const auto setExplicitDelegateModelAccess = [&](QQmlDelegateModel *delegateModel) {
369 delegateModel->setDelegateModelAccess(delegateModelAccess);
370 m_explicitDelegateModelAccess = true;
371 };
372
373 if (!m_model) {
374 if (delegateModelAccess == QQmlDelegateModel::Qt5ReadWrite) {
375 // Explicitly set delegateModelAccess to Legacy. We can do this without model.
376 m_explicitDelegateModelAccess = true;
377 return;
378 }
379
380 QQmlDelegateModel *delegateModel = new QQmlDelegateModel(qmlContext(this), this);
381 m_model = delegateModel;
382 m_ownModel = true;
383 if (isComponentComplete())
384 delegateModel->componentComplete();
385
386 setExplicitDelegateModelAccess(delegateModel);
387
388 // The new model is not connected to applyDelegateModelAccessChange, yet. We only do this
389 // once there is actual data, via an explicit setModel(). So we have to manually emit the
390 // delegateModelAccessChanged() here.
391 emit delegateModelAccessChanged();
392 return;
393 }
394
395 if (QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel *>(object: m_model)) {
396 // Disable the warning in applyDelegateModelAccessChange since the new delegate model
397 // access is also explicit.
398 m_explicitDelegateModelAccess = false;
399 setExplicitDelegateModelAccess(delegateModel);
400 return;
401 }
402
403 if (delegateModelAccess == QQmlDelegateModel::Qt5ReadWrite) {
404 m_explicitDelegateModelAccess = true; // Explicitly set null delegate always works
405 } else {
406 qmlWarning(me: this) << "Cannot set a delegateModelAccess on an explicitly provided "
407 "non-DelegateModel";
408 }
409}
410
411void QQuick3DRepeater::clear()
412{
413 bool complete = isComponentComplete();
414
415 if (m_model) {
416 // We remove in reverse order deliberately; so that signals are emitted
417 // with sensible indices.
418 for (int i = m_deletables.size() - 1; i >= 0; --i) {
419 if (QQuick3DObject *item = m_deletables.at(i)) {
420 if (complete)
421 emit objectRemoved(index: i, object: item);
422 m_model->release(object: item);
423 }
424 }
425 for (QQuick3DObject *item : std::as_const(t&: m_deletables)) {
426 if (item)
427 item->setParentItem(nullptr);
428 }
429 }
430 m_deletables.clear();
431 m_itemCount = 0;
432}
433
434void QQuick3DRepeater::regenerate()
435{
436 if (!isComponentComplete())
437 return;
438
439 clear();
440
441 if (!m_model || !m_model->count() || !m_model->isValid() || !parentItem() || !isComponentComplete())
442 return;
443
444 m_itemCount = count();
445 m_deletables.resize(size: m_itemCount);
446 requestItems();
447}
448
449void QQuick3DRepeater::componentComplete()
450{
451 if (m_model && m_ownModel)
452 static_cast<QQmlDelegateModel *>(m_model.data())->componentComplete();
453 QQuick3DNode::componentComplete();
454 regenerate();
455 if (m_model && m_model->count())
456 emit countChanged();
457}
458
459void QQuick3DRepeater::itemChange(QQuick3DObject::ItemChange change, const QQuick3DObject::ItemChangeData &value)
460{
461 QQuick3DObject::itemChange(change, value);
462 if (change == ItemParentHasChanged) {
463 regenerate();
464 }
465}
466
467void QQuick3DRepeater::createdObject(int index, QObject *)
468{
469 QObject *object = m_model->object(index, incubationMode: QQmlIncubator::AsynchronousIfNested);
470 QQuick3DObject *item = qmlobject_cast<QQuick3DObject*>(object);
471 emit objectAdded(index, object: item);
472}
473
474void QQuick3DRepeater::initObject(int index, QObject *object)
475{
476 QQuick3DNode *item = qmlobject_cast<QQuick3DNode*>(object);
477
478 if (!m_deletables.at(i: index)) {
479 if (!item) {
480 if (object) {
481 m_model->release(object);
482 if (!m_delegateValidated) {
483 m_delegateValidated = true;
484 QObject* delegate = this->delegate();
485 qmlWarning(me: delegate ? delegate : this) << QQuick3DRepeater::tr(s: "Delegate must be of Node type");
486 }
487 }
488 return;
489 }
490 m_deletables[index] = item;
491 item->setParent(this);
492 item->setParentItem(static_cast<QQuick3DNode*>(this));
493 initDelegate(index, item);
494 }
495}
496
497void QQuick3DRepeater::modelUpdated(const QQmlChangeSet &changeSet, bool reset)
498{
499 if (!isComponentComplete())
500 return;
501
502 if (reset) {
503 regenerate();
504 if (changeSet.difference() != 0)
505 emit countChanged();
506 return;
507 }
508
509 int difference = 0;
510 QHash<int, QVector<QPointer<QQuick3DNode> > > moved;
511 for (const QQmlChangeSet::Change &remove : changeSet.removes()) {
512 int index = qMin(a: remove.index, b: m_deletables.size());
513 int count = qMin(a: remove.index + remove.count, b: m_deletables.size()) - index;
514 if (remove.isMove()) {
515 moved.insert(key: remove.moveId, value: m_deletables.mid(pos: index, len: count));
516 m_deletables.erase(
517 begin: m_deletables.begin() + index,
518 end: m_deletables.begin() + index + count);
519 } else while (count--) {
520 QQuick3DNode *item = m_deletables.at(i: index);
521 m_deletables.remove(i: index);
522 emit objectRemoved(index, object: item);
523 if (item) {
524 m_model->release(object: item);
525 item->setParentItem(nullptr);
526 }
527 --m_itemCount;
528 }
529
530 difference -= remove.count;
531 }
532
533 for (const QQmlChangeSet::Change &insert : changeSet.inserts()) {
534 int index = qMin(a: insert.index, b: m_deletables.size());
535 if (insert.isMove()) {
536 QVector<QPointer<QQuick3DNode> > items = moved.value(key: insert.moveId);
537 m_deletables = m_deletables.mid(pos: 0, len: index) + items + m_deletables.mid(pos: index);
538 } else for (int i = 0; i < insert.count; ++i) {
539 int modelIndex = index + i;
540 ++m_itemCount;
541 m_deletables.insert(i: modelIndex, t: nullptr);
542 QObject *object = m_model->object(index: modelIndex, incubationMode: QQmlIncubator::AsynchronousIfNested);
543 if (object)
544 m_model->release(object);
545 }
546 difference += insert.count;
547 }
548
549 if (difference != 0)
550 emit countChanged();
551}
552
553void QQuick3DRepeater::requestItems()
554{
555 for (int i = 0; i < m_itemCount; i++) {
556 QObject *object = m_model->object(index: i, incubationMode: QQmlIncubator::AsynchronousIfNested);
557 if (object)
558 m_model->release(object);
559 }
560}
561
562QT_END_NAMESPACE
563
564#include "moc_qquick3drepeater_p.cpp"
565

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