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

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