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