1/****************************************************************************
2**
3** Copyright (C) 2013 Research In Motion.
4** Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB).
5** Contact: https://www.qt.io/licensing/
6**
7** This file is part of the Qt3D module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial License Usage
11** Licensees holding valid commercial Qt licenses may use this file in
12** accordance with the commercial license agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and The Qt Company. For licensing terms
15** and conditions see https://www.qt.io/terms-conditions. For further
16** information use the contact form at https://www.qt.io/contact-us.
17**
18** GNU Lesser General Public License Usage
19** Alternatively, this file may be used under the terms of the GNU Lesser
20** General Public License version 3 as published by the Free Software
21** Foundation and appearing in the file LICENSE.LGPL3 included in the
22** packaging of this file. Please review the following information to
23** ensure the GNU Lesser General Public License version 3 requirements
24** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25**
26** GNU General Public License Usage
27** Alternatively, this file may be used under the terms of the GNU
28** General Public License version 2.0 or (at your option) the GNU General
29** Public license version 3 or any later version approved by the KDE Free
30** Qt Foundation. The licenses are as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32** included in the packaging of this file. Please review the following
33** information to ensure the GNU General Public License requirements will
34** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35** https://www.gnu.org/licenses/gpl-3.0.html.
36**
37** $QT_END_LICENSE$
38**
39****************************************************************************/
40
41#include "quick3dnodeinstantiator_p.h"
42
43#include <QtQml/QQmlContext>
44#include <QtQml/QQmlComponent>
45#include <QtQml/QQmlError>
46#include <QtQml/QQmlInfo>
47#include <QQmlIncubator>
48
49#include <Qt3DCore/private/qnode_p.h>
50#include <private/qqmlchangeset_p.h>
51#if QT_CONFIG(qml_delegate_model)
52#include <private/qqmldelegatemodel_p.h>
53#endif
54#include <private/qqmlobjectmodel_p.h>
55
56QT_BEGIN_NAMESPACE
57
58namespace Qt3DCore {
59namespace Quick {
60
61class Quick3DNodeInstantiatorPrivate : public QNodePrivate
62{
63 Q_DECLARE_PUBLIC(Quick3DNodeInstantiator)
64
65public:
66 Quick3DNodeInstantiatorPrivate();
67 ~Quick3DNodeInstantiatorPrivate();
68
69 void clear();
70 void regenerate();
71#if QT_CONFIG(qml_delegate_model)
72 void makeModel();
73#endif
74 void _q_createdItem(int, QObject *);
75 void _q_modelUpdated(const QQmlChangeSet &, bool);
76
77 bool m_componentComplete:1;
78 bool m_effectiveReset:1;
79 bool m_active:1;
80 bool m_async:1;
81#if QT_CONFIG(qml_delegate_model)
82 bool m_ownModel:1;
83#endif
84 QVariant m_model;
85 QQmlInstanceModel *m_instanceModel;
86 QQmlComponent *m_delegate;
87 QVector<QPointer<QObject> > m_objects;
88};
89
90/*!
91 \internal
92*/
93Quick3DNodeInstantiatorPrivate::Quick3DNodeInstantiatorPrivate()
94 : QNodePrivate()
95 , m_componentComplete(true)
96 , m_effectiveReset(false)
97 , m_active(true)
98 , m_async(false)
99#if QT_CONFIG(qml_delegate_model)
100 , m_ownModel(false)
101#endif
102 , m_model(QVariant(1))
103 , m_instanceModel(0)
104 , m_delegate(0)
105{
106}
107
108Quick3DNodeInstantiatorPrivate::~Quick3DNodeInstantiatorPrivate()
109{
110#if QT_CONFIG(qml_delegate_model)
111 if (m_ownModel)
112 delete m_instanceModel;
113#endif
114}
115
116void Quick3DNodeInstantiatorPrivate::clear()
117{
118 Q_Q(Quick3DNodeInstantiator);
119 if (!m_instanceModel)
120 return;
121 if (!m_objects.count())
122 return;
123
124 for (int i = 0; i < m_objects.count(); i++) {
125 emit q->objectRemoved(index: i, object: m_objects[i]);
126 m_instanceModel->release(object: m_objects[i]);
127 }
128 m_objects.clear();
129 emit q->objectChanged();
130}
131
132void Quick3DNodeInstantiatorPrivate::regenerate()
133{
134 Q_Q(Quick3DNodeInstantiator);
135 if (!m_componentComplete)
136 return;
137
138 int prevCount = q->count();
139
140 clear();
141
142 if (!m_active || !m_instanceModel || !m_instanceModel->count() || !m_instanceModel->isValid()) {
143 if (prevCount)
144 emit q->countChanged();
145 return;
146 }
147
148 for (int i = 0; i < m_instanceModel->count(); i++) {
149 QObject *object = m_instanceModel->object(index: i, incubationMode: m_async ?
150 QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested);
151 // If the item was already created we won't get a createdItem
152 if (object)
153 _q_createdItem(i, object);
154 }
155 if (q->count() != prevCount)
156 emit q->countChanged();
157}
158
159void Quick3DNodeInstantiatorPrivate::_q_createdItem(int idx, QObject *item)
160{
161 Q_Q(Quick3DNodeInstantiator);
162 if (m_objects.contains(t: item)) //Case when it was created synchronously in regenerate
163 return;
164 static_cast<QNode *>(item)->setParent(q->parentNode());
165 m_objects.insert(i: idx, t: item);
166 if (m_objects.count() == 1)
167 emit q->objectChanged();
168 emit q->objectAdded(index: idx, object: item);
169}
170
171void Quick3DNodeInstantiatorPrivate::_q_modelUpdated(const QQmlChangeSet &changeSet, bool reset)
172{
173 Q_Q(Quick3DNodeInstantiator);
174
175 if (!m_componentComplete || m_effectiveReset)
176 return;
177
178 if (reset) {
179 regenerate();
180 if (changeSet.difference() != 0)
181 emit q->countChanged();
182 return;
183 }
184
185 int difference = 0;
186 QHash<int, QVector<QPointer<QObject> > > moved;
187 const auto removes = changeSet.removes();
188 for (const QQmlChangeSet::Change &remove : removes) {
189 int index = qMin(a: remove.index, b: m_objects.count());
190 int count = qMin(a: remove.index + remove.count, b: m_objects.count()) - index;
191 if (remove.isMove()) {
192 moved.insert(key: remove.moveId, value: m_objects.mid(pos: index, len: count));
193 m_objects.erase(
194 begin: m_objects.begin() + index,
195 end: m_objects.begin() + index + count);
196 } else {
197 while (count--) {
198 QObject *obj = m_objects.at(i: index);
199 m_objects.remove(i: index);
200 emit q->objectRemoved(index, object: obj);
201 if (obj)
202 m_instanceModel->release(object: obj);
203 }
204 }
205
206 difference -= remove.count;
207 }
208
209 const auto inserts = changeSet.inserts();
210 for (const QQmlChangeSet::Change &insert : inserts) {
211 int index = qMin(a: insert.index, b: m_objects.count());
212 if (insert.isMove()) {
213 QVector<QPointer<QObject> > movedObjects = moved.value(key: insert.moveId);
214 m_objects = m_objects.mid(pos: 0, len: index) + movedObjects + m_objects.mid(pos: index);
215 } else for (int i = 0; i < insert.count; ++i) {
216 int modelIndex = index + i;
217 QObject *obj = m_instanceModel->object(index: modelIndex, incubationMode: m_async ?
218 QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested);
219 if (obj)
220 _q_createdItem(idx: modelIndex, item: obj);
221 }
222 difference += insert.count;
223 }
224
225 if (difference != 0)
226 emit q->countChanged();
227}
228
229#if QT_CONFIG(qml_delegate_model)
230void Quick3DNodeInstantiatorPrivate::makeModel()
231{
232 Q_Q(Quick3DNodeInstantiator);
233 QQmlDelegateModel* delegateModel = new QQmlDelegateModel(qmlContext(q));
234 m_instanceModel = delegateModel;
235 m_ownModel = true;
236 delegateModel->setDelegate(m_delegate);
237 delegateModel->classBegin(); //Pretend it was made in QML
238 if (m_componentComplete)
239 delegateModel->componentComplete();
240}
241#endif
242
243/*!
244 \qmltype NodeInstantiator
245 \inqmlmodule Qt3D.Core
246 \brief Dynamically creates nodes.
247 \since 5.5
248
249 A NodeInstantiator can be used to control the dynamic creation of nodes,
250 or to dynamically create multiple objects from a template.
251
252 The NodeInstantiator element will manage the objects it creates. Those
253 objects are parented to the Instantiator and can also be deleted by the
254 NodeInstantiator if the NodeInstantiator's properties change. Nodes can
255 also be destroyed dynamically through other means, and the NodeInstantiator
256 will not recreate them unless the properties of the NodeInstantiator
257 change.
258
259*/
260Quick3DNodeInstantiator::Quick3DNodeInstantiator(QNode *parent)
261 : QNode(*new Quick3DNodeInstantiatorPrivate, parent)
262{
263 connect(sender: this, signal: &QNode::parentChanged, receiver: this, slot: &Quick3DNodeInstantiator::onParentChanged);
264}
265
266/*!
267 \qmlsignal Qt3D.Core::NodeInstantiator::objectAdded(int index, QtObject object)
268
269 This signal is emitted when a node is added to the NodeInstantiator. The \a index
270 parameter holds the index which the node has been given, and the \a object
271 parameter holds the \l Node that has been added.
272
273 The corresponding handler is \c onNodeAdded.
274*/
275
276/*!
277 \qmlsignal Qt3D.Core::NodeInstantiator::objectRemoved(int index, QtObject object)
278
279 This signal is emitted when an object is removed from the Instantiator. The \a index
280 parameter holds the index which the object had been given, and the \a object
281 parameter holds the \l [QML] {QtQml::}{QtObject} that has been removed.
282
283 Do not keep a reference to \a object if it was created by this Instantiator, as
284 in these cases it will be deleted shortly after the signal is handled.
285
286 The corresponding handler is \c onObjectRemoved.
287*/
288/*!
289 \qmlproperty bool Qt3D.Core::NodeInstantiator::active
290
291 When active is \c true, and the delegate component is ready, the Instantiator will
292 create objects according to the model. When active is \c false, no objects
293 will be created and any previously created objects will be destroyed.
294
295 Default is \c true.
296*/
297bool Quick3DNodeInstantiator::isActive() const
298{
299 Q_D(const Quick3DNodeInstantiator);
300 return d->m_active;
301}
302
303void Quick3DNodeInstantiator::setActive(bool newVal)
304{
305 Q_D(Quick3DNodeInstantiator);
306 if (newVal == d->m_active)
307 return;
308 d->m_active = newVal;
309 emit activeChanged();
310 d->regenerate();
311}
312
313/*!
314 \qmlproperty bool Qt3D.Core::NodeInstantiator::asynchronous
315
316 When asynchronous is true the Instantiator will attempt to create objects
317 asynchronously. This means that objects may not be available immediately,
318 even if active is set to true.
319
320 You can use the objectAdded signal to respond to items being created.
321
322 Default is \c false.
323*/
324bool Quick3DNodeInstantiator::isAsync() const
325{
326 Q_D(const Quick3DNodeInstantiator);
327 return d->m_async;
328}
329
330void Quick3DNodeInstantiator::setAsync(bool newVal)
331{
332 Q_D(Quick3DNodeInstantiator);
333 if (newVal == d->m_async)
334 return;
335 d->m_async = newVal;
336 emit asynchronousChanged();
337}
338
339
340/*!
341 \qmlproperty int Qt3D.Core::NodeInstantiator::count
342 \readonly
343
344 The number of objects the Instantiator is currently managing.
345*/
346
347int Quick3DNodeInstantiator::count() const
348{
349 Q_D(const Quick3DNodeInstantiator);
350 return d->m_objects.count();
351}
352
353/*!
354 \qmlproperty QtQml::Component Qt3D.Core::NodeInstantiator::delegate
355 \default
356
357 The component used to create all objects.
358
359 Note that an extra variable, index, will be available inside instances of the
360 delegate. This variable refers to the index of the instance inside the Instantiator,
361 and can be used to obtain the object through the itemAt method of the Instantiator.
362
363 If this property is changed, all instances using the old delegate will be destroyed
364 and new instances will be created using the new delegate.
365*/
366QQmlComponent *Quick3DNodeInstantiator::delegate()
367{
368 Q_D(Quick3DNodeInstantiator);
369 return d->m_delegate;
370}
371
372void Quick3DNodeInstantiator::setDelegate(QQmlComponent *c)
373{
374 Q_D(Quick3DNodeInstantiator);
375 if (c == d->m_delegate)
376 return;
377
378 d->m_delegate = c;
379 emit delegateChanged();
380
381#if QT_CONFIG(qml_delegate_model)
382 if (!d->m_ownModel)
383 return;
384
385 if (QQmlDelegateModel *dModel = qobject_cast<QQmlDelegateModel*>(object: d->m_instanceModel))
386 dModel->setDelegate(c);
387#endif
388 if (d->m_componentComplete)
389 d->regenerate();
390}
391
392/*!
393 \qmlproperty variant Qt3D.Core::NodeInstantiator::model
394
395 This property can be set to any of the supported \l {qml-data-models}{data models}:
396
397 \list
398 \li A number that indicates the number of delegates to be created by the repeater
399 \li A model (for example, a ListModel item or a QAbstractItemModel subclass)
400 \li A string list
401 \li An object list
402 \endlist
403
404 The type of model affects the properties that are exposed to the \l delegate.
405
406 Default value is 1, which creates a single delegate instance.
407
408 \sa {qml-data-models}{Data Models}
409*/
410
411QVariant Quick3DNodeInstantiator::model() const
412{
413 Q_D(const Quick3DNodeInstantiator);
414 return d->m_model;
415}
416
417void Quick3DNodeInstantiator::setModel(const QVariant &v)
418{
419 Q_D(Quick3DNodeInstantiator);
420 if (d->m_model == v)
421 return;
422
423 d->m_model = v;
424 //Don't actually set model until componentComplete in case it wants to create its delegates immediately
425 if (!d->m_componentComplete)
426 return;
427
428 QQmlInstanceModel *prevModel = d->m_instanceModel;
429 QObject *object = qvariant_cast<QObject*>(v);
430 QQmlInstanceModel *vim = 0;
431 if (object && (vim = qobject_cast<QQmlInstanceModel *>(object))) {
432#if QT_CONFIG(qml_delegate_model)
433 if (d->m_ownModel) {
434 delete d->m_instanceModel;
435 prevModel = 0;
436 d->m_ownModel = false;
437 }
438#endif
439 d->m_instanceModel = vim;
440 }
441#if QT_CONFIG(qml_delegate_model)
442 else if (v != QVariant(0)) {
443 if (!d->m_ownModel)
444 d->makeModel();
445
446 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel *>(object: d->m_instanceModel)) {
447 d->m_effectiveReset = true;
448 dataModel->setModel(v);
449 d->m_effectiveReset = false;
450 }
451 }
452#endif
453
454 if (d->m_instanceModel != prevModel) {
455 if (prevModel) {
456 disconnect(sender: prevModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
457 receiver: this, SLOT(_q_modelUpdated(QQmlChangeSet,bool)));
458 disconnect(sender: prevModel, SIGNAL(createdItem(int,QObject*)), receiver: this, SLOT(_q_createdItem(int,QObject*)));
459 //disconnect(prevModel, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*)));
460 }
461
462 connect(sender: d->m_instanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
463 receiver: this, SLOT(_q_modelUpdated(QQmlChangeSet,bool)));
464 connect(sender: d->m_instanceModel, SIGNAL(createdItem(int,QObject*)), receiver: this, SLOT(_q_createdItem(int,QObject*)));
465 //connect(d->m_instanceModel, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*)));
466 }
467
468 d->regenerate();
469 emit modelChanged();
470}
471
472/*!
473 \qmlproperty QtQml::QtObject Qt3D.Core::NodeInstantiator::object
474 \readonly
475
476 This is a reference to the first created object, intended as a convenience
477 for the case where only one object has been created.
478*/
479QObject *Quick3DNodeInstantiator::object() const
480{
481 Q_D(const Quick3DNodeInstantiator);
482 if (d->m_objects.count())
483 return d->m_objects[0];
484 return 0;
485}
486
487/*!
488 \qmlmethod QtQml::QtObject Qt3D.Core::NodeInstantiator::objectAt(int index)
489
490 Returns a reference to the object with the given \a index.
491*/
492QObject *Quick3DNodeInstantiator::objectAt(int index) const
493{
494 Q_D(const Quick3DNodeInstantiator);
495 if (index >= 0 && index < d->m_objects.count())
496 return d->m_objects[index];
497 return 0;
498}
499
500/*!
501 \internal
502*/
503void Quick3DNodeInstantiator::classBegin()
504{
505 Q_D(Quick3DNodeInstantiator);
506 d->m_componentComplete = false;
507}
508
509/*!
510 \internal
511*/
512void Quick3DNodeInstantiator::componentComplete()
513{
514 Q_D(Quick3DNodeInstantiator);
515 d->m_componentComplete = true;
516#if QT_CONFIG(qml_delegate_model)
517 if (d->m_ownModel) {
518 static_cast<QQmlDelegateModel *>(d->m_instanceModel)->componentComplete();
519 d->regenerate();
520 } else
521#endif
522 {
523 QVariant realModel = d->m_model;
524 d->m_model = QVariant(0);
525 setModel(realModel); //If realModel == d->m_model this won't do anything, but that's fine since the model's 0
526 //setModel calls regenerate
527 }
528}
529
530/*!
531 \internal
532*/
533void Quick3DNodeInstantiator::onParentChanged(QObject *parent)
534{
535 Q_D(const Quick3DNodeInstantiator);
536 auto parentNode = static_cast<QNode *>(parent);
537 for (auto obj : d->m_objects)
538 static_cast<QNode *>(obj.data())->setParent(parentNode);
539}
540
541// TODO: Avoid cloning here
542//void Quick3DNodeInstantiator::copy(const QNode *ref)
543//{
544// QNode::copy(ref);
545// const Quick3DNodeInstantiator *instantiator = static_cast<const Quick3DNodeInstantiator*>(ref);
546// // We only need to clone the children as the instantiator itself has no
547// // corresponding backend node type.
548// for (int i = 0; i < instantiator->d_func()->m_objects.size(); ++i) {
549// QNode *n = qobject_cast<QNode *>(instantiator->d_func()->m_objects.at(i));
550// if (!n)
551// continue;
552// QNode *clonedNode = QNode::clone(n);
553// clonedNode->setParent(this);
554// d_func()->m_objects.append(clonedNode);
555// }
556//}
557
558} // namespace Quick
559} // namespace Qt3DCore
560
561QT_END_NAMESPACE
562
563#include "moc_quick3dnodeinstantiator_p.cpp"
564

source code of qt3d/src/quick3d/quick3d/items/quick3dnodeinstantiator.cpp