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 | |
56 | QT_BEGIN_NAMESPACE |
57 | |
58 | namespace Qt3DCore { |
59 | namespace Quick { |
60 | |
61 | class Quick3DNodeInstantiatorPrivate : public QNodePrivate |
62 | { |
63 | Q_DECLARE_PUBLIC(Quick3DNodeInstantiator) |
64 | |
65 | public: |
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 | */ |
93 | Quick3DNodeInstantiatorPrivate::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 | |
108 | Quick3DNodeInstantiatorPrivate::~Quick3DNodeInstantiatorPrivate() |
109 | { |
110 | #if QT_CONFIG(qml_delegate_model) |
111 | if (m_ownModel) |
112 | delete m_instanceModel; |
113 | #endif |
114 | } |
115 | |
116 | void 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 | |
132 | void 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 | |
159 | void 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 | |
171 | void 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) |
230 | void 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 | */ |
260 | Quick3DNodeInstantiator::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 | */ |
297 | bool Quick3DNodeInstantiator::isActive() const |
298 | { |
299 | Q_D(const Quick3DNodeInstantiator); |
300 | return d->m_active; |
301 | } |
302 | |
303 | void 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 | */ |
324 | bool Quick3DNodeInstantiator::isAsync() const |
325 | { |
326 | Q_D(const Quick3DNodeInstantiator); |
327 | return d->m_async; |
328 | } |
329 | |
330 | void 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 | |
347 | int 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 | */ |
366 | QQmlComponent *Quick3DNodeInstantiator::delegate() |
367 | { |
368 | Q_D(Quick3DNodeInstantiator); |
369 | return d->m_delegate; |
370 | } |
371 | |
372 | void 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 | |
411 | QVariant Quick3DNodeInstantiator::model() const |
412 | { |
413 | Q_D(const Quick3DNodeInstantiator); |
414 | return d->m_model; |
415 | } |
416 | |
417 | void 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 | */ |
479 | QObject *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 | */ |
492 | QObject *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 | */ |
503 | void Quick3DNodeInstantiator::classBegin() |
504 | { |
505 | Q_D(Quick3DNodeInstantiator); |
506 | d->m_componentComplete = false; |
507 | } |
508 | |
509 | /*! |
510 | \internal |
511 | */ |
512 | void 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 | */ |
533 | void 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 | |
561 | QT_END_NAMESPACE |
562 | |
563 | #include "moc_quick3dnodeinstantiator_p.cpp" |
564 | |