1// Copyright (C) 2016 Research In Motion.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qqmlinstantiator_p.h"
5#include "qqmlinstantiator_p_p.h"
6#include <QtQml/QQmlContext>
7#include <QtQml/QQmlComponent>
8#include <QtQml/QQmlInfo>
9#include <QtQml/QQmlError>
10#include <QtQmlModels/private/qqmlobjectmodel_p.h>
11#if QT_CONFIG(qml_delegate_model)
12#include <QtQmlModels/private/qqmldelegatemodel_p.h>
13#endif
14
15QT_BEGIN_NAMESPACE
16
17QQmlInstantiatorPrivate::QQmlInstantiatorPrivate()
18 : componentComplete(true)
19 , effectiveReset(false)
20 , active(true)
21 , async(false)
22#if QT_CONFIG(qml_delegate_model)
23 , ownModel(false)
24#endif
25 , requestedIndex(-1)
26 , model(QVariant(1))
27 , instanceModel(nullptr)
28 , delegate(nullptr)
29{
30}
31
32void QQmlInstantiatorPrivate::clear()
33{
34 Q_Q(QQmlInstantiator);
35 if (!instanceModel)
36 return;
37
38 if (objects.isEmpty())
39 return;
40
41 for (int i=0; i < objects.size(); i++) {
42 QObject *object = objects[i];
43 emit q->objectRemoved(index: i, object);
44 instanceModel->release(object);
45 if (object && object->parent() == q)
46 object->setParent(nullptr);
47 }
48
49 objects.clear();
50 emit q->objectChanged();
51}
52
53QObject *QQmlInstantiatorPrivate::modelObject(int index, bool async)
54{
55 requestedIndex = index;
56 QObject *o = instanceModel->object(index, incubationMode: async ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested);
57 requestedIndex = -1;
58 return o;
59}
60
61
62void QQmlInstantiatorPrivate::regenerate()
63{
64 Q_Q(QQmlInstantiator);
65 if (!componentComplete)
66 return;
67
68 int prevCount = q->count();
69
70 clear();
71
72 if (!active || !instanceModel || !instanceModel->count() || !instanceModel->isValid()) {
73 if (prevCount)
74 q->countChanged();
75 return;
76 }
77
78 for (int i = 0; i < instanceModel->count(); i++) {
79 QObject *object = modelObject(index: i, async);
80 // If the item was already created we won't get a createdItem
81 if (object)
82 _q_createdItem(i, object);
83 }
84 if (q->count() != prevCount)
85 q->countChanged();
86}
87
88void QQmlInstantiatorPrivate::_q_createdItem(int idx, QObject* item)
89{
90 Q_Q(QQmlInstantiator);
91 if (objects.contains(t: item)) //Case when it was created synchronously in regenerate
92 return;
93 if (requestedIndex != idx) // Asynchronous creation, reference the object
94 (void)instanceModel->object(index: idx);
95 if (!item->parent())
96 item->setParent(q);
97 if (objects.size() < idx + 1) {
98 int modelCount = instanceModel->count();
99 if (objects.capacity() < modelCount)
100 objects.reserve(size: modelCount);
101 objects.resize(size: idx + 1);
102 }
103 if (QObject *o = objects.at(i: idx))
104 instanceModel->release(object: o);
105 objects.replace(i: idx, t: item);
106 if (objects.size() == 1)
107 q->objectChanged();
108 q->objectAdded(index: idx, object: item);
109}
110
111void QQmlInstantiatorPrivate::_q_modelUpdated(const QQmlChangeSet &changeSet, bool reset)
112{
113 Q_Q(QQmlInstantiator);
114
115 if (!componentComplete || effectiveReset || !active)
116 return;
117
118 if (reset) {
119 regenerate();
120 if (changeSet.difference() != 0)
121 q->countChanged();
122 return;
123 }
124
125 int difference = 0;
126 QHash<int, QVector<QPointer<QObject> > > moved;
127 const QVector<QQmlChangeSet::Change> &removes = changeSet.removes();
128 for (const QQmlChangeSet::Change &remove : removes) {
129 int index = qMin(a: remove.index, b: objects.size());
130 int count = qMin(a: remove.index + remove.count, b: objects.size()) - index;
131 if (remove.isMove()) {
132 moved.insert(key: remove.moveId, value: objects.mid(pos: index, len: count));
133 objects.erase(
134 begin: objects.begin() + index,
135 end: objects.begin() + index + count);
136 } else while (count--) {
137 QObject *obj = objects.at(i: index);
138 objects.remove(i: index);
139 q->objectRemoved(index, object: obj);
140 if (obj)
141 instanceModel->release(object: obj);
142 }
143
144 difference -= remove.count;
145 }
146
147 const QVector<QQmlChangeSet::Change> &inserts = changeSet.inserts();
148 for (const QQmlChangeSet::Change &insert : inserts) {
149 int index = qMin(a: insert.index, b: objects.size());
150 if (insert.isMove()) {
151 QVector<QPointer<QObject> > movedObjects = moved.value(key: insert.moveId);
152 objects = objects.mid(pos: 0, len: index) + movedObjects + objects.mid(pos: index);
153 } else {
154 if (insert.index <= objects.size())
155 objects.insert(i: insert.index, n: insert.count, t: nullptr);
156 for (int i = 0; i < insert.count; ++i) {
157 int modelIndex = index + i;
158 QObject* obj = modelObject(index: modelIndex, async);
159 if (obj)
160 _q_createdItem(idx: modelIndex, item: obj);
161 }
162 }
163 difference += insert.count;
164 }
165
166 if (difference != 0)
167 q->countChanged();
168}
169
170#if QT_CONFIG(qml_delegate_model)
171void QQmlInstantiatorPrivate::makeModel()
172{
173 Q_Q(QQmlInstantiator);
174 QQmlDelegateModel* delegateModel = new QQmlDelegateModel(qmlContext(q), q);
175 instanceModel = delegateModel;
176 ownModel = true;
177 delegateModel->setDelegate(delegate);
178 delegateModel->classBegin(); //Pretend it was made in QML
179 if (componentComplete)
180 delegateModel->componentComplete();
181}
182#endif
183
184
185/*!
186 \qmltype Instantiator
187 \instantiates QQmlInstantiator
188 \inqmlmodule QtQml.Models
189 \ingroup qtquick-models
190 \brief Dynamically creates objects.
191
192 A Instantiator can be used to control the dynamic creation of objects, or to dynamically
193 create multiple objects from a template.
194
195 The Instantiator element will manage the objects it creates. Those objects are parented to the
196 Instantiator and can also be deleted by the Instantiator if the Instantiator's properties change. Objects
197 can also be destroyed dynamically through other means, and the Instantiator will not recreate
198 them unless the properties of the Instantiator change.
199
200 \note Instantiator is part of QtQml.Models since version 2.14 and part of QtQml since
201 version 2.1. Importing Instantiator via QtQml is deprecated since Qt 5.14.
202*/
203QQmlInstantiator::QQmlInstantiator(QObject *parent)
204 : QObject(*(new QQmlInstantiatorPrivate), parent)
205{
206}
207
208QQmlInstantiator::~QQmlInstantiator()
209{
210 Q_D(QQmlInstantiator);
211 d->clear();
212}
213
214/*!
215 \qmlsignal QtQml.Models::Instantiator::objectAdded(int index, QtObject object)
216
217 This signal is emitted when an object is added to the Instantiator. The \a index
218 parameter holds the index which the object has been given, and the \a object
219 parameter holds the \l QtObject that has been added.
220*/
221
222/*!
223 \qmlsignal QtQml.Models::Instantiator::objectRemoved(int index, QtObject object)
224
225 This signal is emitted when an object is removed from the Instantiator. The \a index
226 parameter holds the index which the object had been given, and the \a object
227 parameter holds the \l QtObject that has been removed.
228
229 Do not keep a reference to \a object if it was created by this Instantiator, as
230 in these cases it will be deleted shortly after the signal is handled.
231*/
232/*!
233 \qmlproperty bool QtQml.Models::Instantiator::active
234
235 When active is true, and the delegate component is ready, the Instantiator will
236 create objects according to the model. When active is false, no objects
237 will be created and any previously created objects will be destroyed.
238
239 Default is true.
240*/
241bool QQmlInstantiator::isActive() const
242{
243 Q_D(const QQmlInstantiator);
244 return d->active;
245}
246
247void QQmlInstantiator::setActive(bool newVal)
248{
249 Q_D(QQmlInstantiator);
250 if (newVal == d->active)
251 return;
252 d->active = newVal;
253 emit activeChanged();
254 d->regenerate();
255}
256
257/*!
258 \qmlproperty bool QtQml.Models::Instantiator::asynchronous
259
260 When asynchronous is true the Instantiator will attempt to create objects
261 asynchronously. This means that objects may not be available immediately,
262 even if active is set to true.
263
264 You can use the objectAdded signal to respond to items being created.
265
266 Default is false.
267*/
268bool QQmlInstantiator::isAsync() const
269{
270 Q_D(const QQmlInstantiator);
271 return d->async;
272}
273
274void QQmlInstantiator::setAsync(bool newVal)
275{
276 Q_D(QQmlInstantiator);
277 if (newVal == d->async)
278 return;
279 d->async = newVal;
280 emit asynchronousChanged();
281}
282
283
284/*!
285 \qmlproperty int QtQml.Models::Instantiator::count
286
287 The number of objects the Instantiator is currently managing.
288*/
289
290int QQmlInstantiator::count() const
291{
292 Q_D(const QQmlInstantiator);
293 return d->objects.size();
294}
295
296/*!
297 \qmlproperty QtQml::Component QtQml.Models::Instantiator::delegate
298 \qmldefault
299
300 The component used to create all objects.
301
302 Note that an extra variable, index, will be available inside instances of the
303 delegate. This variable refers to the index of the instance inside the Instantiator,
304 and can be used to obtain the object through the objectAt method of the Instantiator.
305
306 If this property is changed, all instances using the old delegate will be destroyed
307 and new instances will be created using the new delegate.
308*/
309QQmlComponent* QQmlInstantiator::delegate()
310{
311 Q_D(QQmlInstantiator);
312 return d->delegate;
313}
314
315void QQmlInstantiator::setDelegate(QQmlComponent* c)
316{
317 Q_D(QQmlInstantiator);
318 if (c == d->delegate)
319 return;
320
321 d->delegate = c;
322 emit delegateChanged();
323
324#if QT_CONFIG(qml_delegate_model)
325 if (!d->ownModel)
326 return;
327
328 if (QQmlDelegateModel *dModel = qobject_cast<QQmlDelegateModel*>(object: d->instanceModel))
329 dModel->setDelegate(c);
330 if (d->componentComplete)
331 d->regenerate();
332#endif
333}
334
335/*!
336 \qmlproperty variant QtQml.Models::Instantiator::model
337
338 This property can be set to any of the supported \l {qml-data-models}{data models}:
339
340 \list
341 \li A number that indicates the number of delegates to be created by the repeater
342 \li A model (e.g. a ListModel item, or a QAbstractItemModel subclass)
343 \li A string list
344 \li An object list
345 \endlist
346
347 The type of model affects the properties that are exposed to the \l delegate.
348
349 Default value is 1, which creates a single delegate instance.
350
351 \sa {qml-data-models}{Data Models}
352*/
353
354QVariant QQmlInstantiator::model() const
355{
356 Q_D(const QQmlInstantiator);
357 return d->model;
358}
359
360void QQmlInstantiator::setModel(const QVariant &v)
361{
362 Q_D(QQmlInstantiator);
363 if (d->model == v)
364 return;
365
366 d->model = v;
367 //Don't actually set model until componentComplete in case it wants to create its delegates immediately
368 if (!d->componentComplete)
369 return;
370
371 QQmlInstanceModel *prevModel = d->instanceModel;
372 QObject *object = qvariant_cast<QObject*>(v);
373 QQmlInstanceModel *vim = nullptr;
374 if (object && (vim = qobject_cast<QQmlInstanceModel *>(object))) {
375#if QT_CONFIG(qml_delegate_model)
376 if (d->ownModel) {
377 delete d->instanceModel;
378 prevModel = nullptr;
379 d->ownModel = false;
380 }
381#endif
382 d->instanceModel = vim;
383#if QT_CONFIG(qml_delegate_model)
384 } else if (v != QVariant(0)){
385 if (!d->ownModel)
386 d->makeModel();
387
388 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel *>(object: d->instanceModel)) {
389 d->effectiveReset = true;
390 dataModel->setModel(v);
391 d->effectiveReset = false;
392 }
393#endif
394 }
395
396 if (d->instanceModel != prevModel) {
397 if (prevModel) {
398 disconnect(sender: prevModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
399 receiver: this, SLOT(_q_modelUpdated(QQmlChangeSet,bool)));
400 disconnect(sender: prevModel, SIGNAL(createdItem(int,QObject*)), receiver: this, SLOT(_q_createdItem(int,QObject*)));
401 //disconnect(prevModel, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*)));
402 }
403
404 if (d->instanceModel) {
405 connect(sender: d->instanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
406 receiver: this, SLOT(_q_modelUpdated(QQmlChangeSet,bool)));
407 connect(sender: d->instanceModel, SIGNAL(createdItem(int,QObject*)), receiver: this, SLOT(_q_createdItem(int,QObject*)));
408 //connect(d->instanceModel, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*)));
409 }
410 }
411
412 d->regenerate();
413 emit modelChanged();
414}
415
416/*!
417 \qmlproperty QtObject QtQml.Models::Instantiator::object
418
419 This is a reference to the first created object, intended as a convenience
420 for the case where only one object has been created.
421*/
422QObject *QQmlInstantiator::object() const
423{
424 Q_D(const QQmlInstantiator);
425 if (d->objects.size())
426 return d->objects[0];
427 return nullptr;
428}
429
430/*!
431 \qmlmethod QtObject QtQml.Models::Instantiator::objectAt(int index)
432
433 Returns a reference to the object with the given \a index.
434*/
435QObject *QQmlInstantiator::objectAt(int index) const
436{
437 Q_D(const QQmlInstantiator);
438 if (index >= 0 && index < d->objects.size())
439 return d->objects[index];
440 return nullptr;
441}
442
443/*!
444 \internal
445*/
446void QQmlInstantiator::classBegin()
447{
448 Q_D(QQmlInstantiator);
449 d->componentComplete = false;
450}
451
452/*!
453 \internal
454*/
455void QQmlInstantiator::componentComplete()
456{
457 Q_D(QQmlInstantiator);
458 d->componentComplete = true;
459#if QT_CONFIG(qml_delegate_model)
460 if (d->ownModel) {
461 static_cast<QQmlDelegateModel*>(d->instanceModel)->componentComplete();
462 d->regenerate();
463 } else
464#endif
465 {
466 QVariant realModel = d->model;
467 d->model = QVariant(0);
468 setModel(realModel); //If realModel == d->model this won't do anything, but that's fine since the model's 0
469 //setModel calls regenerate
470 }
471}
472
473QT_END_NAMESPACE
474
475#include "moc_qqmlinstantiator_p.cpp"
476

source code of qtdeclarative/src/qmlmodels/qqmlinstantiator.cpp