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