| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 Research In Motion. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtQml module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qqmlinstantiator_p.h" |
| #include "qqmlinstantiator_p_p.h" |
| #include <QtQml/QQmlContext> |
| #include <QtQml/QQmlComponent> |
| #include <QtQml/QQmlInfo> |
| #include <QtQml/QQmlError> |
| #include <QtQmlModels/private/qqmlobjectmodel_p.h> |
| #if QT_CONFIG(qml_delegate_model) |
| #include <QtQmlModels/private/qqmldelegatemodel_p.h> |
| #endif |
| |
| QT_BEGIN_NAMESPACE |
| |
| QQmlInstantiatorPrivate::QQmlInstantiatorPrivate() |
| : componentComplete(true) |
| , effectiveReset(false) |
| , active(true) |
| , async(false) |
| #if QT_CONFIG(qml_delegate_model) |
| , ownModel(false) |
| #endif |
| , requestedIndex(-1) |
| , model(QVariant(1)) |
| , instanceModel(nullptr) |
| , delegate(nullptr) |
| { |
| } |
| |
| QQmlInstantiatorPrivate::~QQmlInstantiatorPrivate() |
| { |
| qDeleteAll(objects); |
| } |
| |
| void QQmlInstantiatorPrivate::clear() |
| { |
| Q_Q(QQmlInstantiator); |
| if (!instanceModel) |
| return; |
| if (!objects.count()) |
| return; |
| |
| for (int i=0; i < objects.count(); i++) { |
| q->objectRemoved(i, objects[i]); |
| instanceModel->release(objects[i]); |
| } |
| objects.clear(); |
| q->objectChanged(); |
| } |
| |
| QObject *QQmlInstantiatorPrivate::modelObject(int index, bool async) |
| { |
| requestedIndex = index; |
| QObject *o = instanceModel->object(index, async ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested); |
| requestedIndex = -1; |
| return o; |
| } |
| |
| |
| void QQmlInstantiatorPrivate::regenerate() |
| { |
| Q_Q(QQmlInstantiator); |
| if (!componentComplete) |
| return; |
| |
| int prevCount = q->count(); |
| |
| clear(); |
| |
| if (!active || !instanceModel || !instanceModel->count() || !instanceModel->isValid()) { |
| if (prevCount) |
| q->countChanged(); |
| return; |
| } |
| |
| for (int i = 0; i < instanceModel->count(); i++) { |
| QObject *object = modelObject(i, async); |
| // If the item was already created we won't get a createdItem |
| if (object) |
| _q_createdItem(i, object); |
| } |
| if (q->count() != prevCount) |
| q->countChanged(); |
| } |
| |
| void QQmlInstantiatorPrivate::_q_createdItem(int idx, QObject* item) |
| { |
| Q_Q(QQmlInstantiator); |
| if (objects.contains(item)) //Case when it was created synchronously in regenerate |
| return; |
| if (requestedIndex != idx) // Asynchronous creation, reference the object |
| (void)instanceModel->object(idx); |
| item->setParent(q); |
| if (objects.size() < idx + 1) { |
| int modelCount = instanceModel->count(); |
| if (objects.capacity() < modelCount) |
| objects.reserve(modelCount); |
| objects.resize(idx + 1); |
| } |
| if (QObject *o = objects.at(idx)) |
| instanceModel->release(o); |
| objects.replace(idx, item); |
| if (objects.count() == 1) |
| q->objectChanged(); |
| q->objectAdded(idx, item); |
| } |
| |
| void QQmlInstantiatorPrivate::_q_modelUpdated(const QQmlChangeSet &changeSet, bool reset) |
| { |
| Q_Q(QQmlInstantiator); |
| |
| if (!componentComplete || effectiveReset) |
| return; |
| |
| if (reset) { |
| regenerate(); |
| if (changeSet.difference() != 0) |
| q->countChanged(); |
| return; |
| } |
| |
| int difference = 0; |
| QHash<int, QVector<QPointer<QObject> > > moved; |
| const QVector<QQmlChangeSet::Change> &removes = changeSet.removes(); |
| for (const QQmlChangeSet::Change &remove : removes) { |
| int index = qMin(remove.index, objects.count()); |
| int count = qMin(remove.index + remove.count, objects.count()) - index; |
| if (remove.isMove()) { |
| moved.insert(remove.moveId, objects.mid(index, count)); |
| objects.erase( |
| objects.begin() + index, |
| objects.begin() + index + count); |
| } else while (count--) { |
| QObject *obj = objects.at(index); |
| objects.remove(index); |
| q->objectRemoved(index, obj); |
| if (obj) |
| instanceModel->release(obj); |
| } |
| |
| difference -= remove.count; |
| } |
| |
| const QVector<QQmlChangeSet::Change> &inserts = changeSet.inserts(); |
| for (const QQmlChangeSet::Change &insert : inserts) { |
| int index = qMin(insert.index, objects.count()); |
| if (insert.isMove()) { |
| QVector<QPointer<QObject> > movedObjects = moved.value(insert.moveId); |
| objects = objects.mid(0, index) + movedObjects + objects.mid(index); |
| } else { |
| if (insert.index <= objects.size()) |
| objects.insert(insert.index, insert.count, nullptr); |
| for (int i = 0; i < insert.count; ++i) { |
| int modelIndex = index + i; |
| QObject* obj = modelObject(modelIndex, async); |
| if (obj) |
| _q_createdItem(modelIndex, obj); |
| } |
| } |
| difference += insert.count; |
| } |
| |
| if (difference != 0) |
| q->countChanged(); |
| } |
| |
| #if QT_CONFIG(qml_delegate_model) |
| void QQmlInstantiatorPrivate::makeModel() |
| { |
| Q_Q(QQmlInstantiator); |
| QQmlDelegateModel* delegateModel = new QQmlDelegateModel(qmlContext(q), q); |
| instanceModel = delegateModel; |
| ownModel = true; |
| delegateModel->setDelegate(delegate); |
| delegateModel->classBegin(); //Pretend it was made in QML |
| if (componentComplete) |
| delegateModel->componentComplete(); |
| } |
| #endif |
| |
| |
| /*! |
| \qmltype Instantiator |
| \instantiates QQmlInstantiator |
| \inqmlmodule QtQml.Models |
| \ingroup qtquick-models |
| \brief Dynamically creates objects. |
| |
| A Instantiator can be used to control the dynamic creation of objects, or to dynamically |
| create multiple objects from a template. |
| |
| The Instantiator element will manage the objects it creates. Those objects are parented to the |
| Instantiator and can also be deleted by the Instantiator if the Instantiator's properties change. Objects |
| can also be destroyed dynamically through other means, and the Instantiator will not recreate |
| them unless the properties of the Instantiator change. |
| |
| \note Instantiator is part of QtQml.Models since version 2.14 and part of QtQml since |
| version 2.1. Importing Instantiator via QtQml is deprecated since Qt 5.14. |
| */ |
| QQmlInstantiator::QQmlInstantiator(QObject *parent) |
| : QObject(*(new QQmlInstantiatorPrivate), parent) |
| { |
| } |
| |
| QQmlInstantiator::~QQmlInstantiator() |
| { |
| } |
| |
| /*! |
| \qmlsignal QtQml::Instantiator::objectAdded(int index, QtObject object) |
| |
| This signal is emitted when an object is added to the Instantiator. The \a index |
| parameter holds the index which the object has been given, and the \a object |
| parameter holds the \l QtObject that has been added. |
| */ |
| |
| /*! |
| \qmlsignal QtQml::Instantiator::objectRemoved(int index, QtObject object) |
| |
| This signal is emitted when an object is removed from the Instantiator. The \a index |
| parameter holds the index which the object had been given, and the \a object |
| parameter holds the \l QtObject that has been removed. |
| |
| Do not keep a reference to \a object if it was created by this Instantiator, as |
| in these cases it will be deleted shortly after the signal is handled. |
| */ |
| /*! |
| \qmlproperty bool QtQml::Instantiator::active |
| |
| When active is true, and the delegate component is ready, the Instantiator will |
| create objects according to the model. When active is false, no objects |
| will be created and any previously created objects will be destroyed. |
| |
| Default is true. |
| */ |
| bool QQmlInstantiator::isActive() const |
| { |
| Q_D(const QQmlInstantiator); |
| return d->active; |
| } |
| |
| void QQmlInstantiator::setActive(bool newVal) |
| { |
| Q_D(QQmlInstantiator); |
| if (newVal == d->active) |
| return; |
| d->active = newVal; |
| emit activeChanged(); |
| d->regenerate(); |
| } |
| |
| /*! |
| \qmlproperty bool QtQml::Instantiator::asynchronous |
| |
| When asynchronous is true the Instantiator will attempt to create objects |
| asynchronously. This means that objects may not be available immediately, |
| even if active is set to true. |
| |
| You can use the objectAdded signal to respond to items being created. |
| |
| Default is false. |
| */ |
| bool QQmlInstantiator::isAsync() const |
| { |
| Q_D(const QQmlInstantiator); |
| return d->async; |
| } |
| |
| void QQmlInstantiator::setAsync(bool newVal) |
| { |
| Q_D(QQmlInstantiator); |
| if (newVal == d->async) |
| return; |
| d->async = newVal; |
| emit asynchronousChanged(); |
| } |
| |
| |
| /*! |
| \qmlproperty int QtQml::Instantiator::count |
| |
| The number of objects the Instantiator is currently managing. |
| */ |
| |
| int QQmlInstantiator::count() const |
| { |
| Q_D(const QQmlInstantiator); |
| return d->objects.count(); |
| } |
| |
| /*! |
| \qmlproperty QtQml::Component QtQml::Instantiator::delegate |
| \default |
| |
| The component used to create all objects. |
| |
| Note that an extra variable, index, will be available inside instances of the |
| delegate. This variable refers to the index of the instance inside the Instantiator, |
| and can be used to obtain the object through the objectAt method of the Instantiator. |
| |
| If this property is changed, all instances using the old delegate will be destroyed |
| and new instances will be created using the new delegate. |
| */ |
| QQmlComponent* QQmlInstantiator::delegate() |
| { |
| Q_D(QQmlInstantiator); |
| return d->delegate; |
| } |
| |
| void QQmlInstantiator::setDelegate(QQmlComponent* c) |
| { |
| Q_D(QQmlInstantiator); |
| if (c == d->delegate) |
| return; |
| |
| d->delegate = c; |
| emit delegateChanged(); |
| |
| #if QT_CONFIG(qml_delegate_model) |
| if (!d->ownModel) |
| return; |
| |
| if (QQmlDelegateModel *dModel = qobject_cast<QQmlDelegateModel*>(d->instanceModel)) |
| dModel->setDelegate(c); |
| if (d->componentComplete) |
| d->regenerate(); |
| #endif |
| } |
| |
| /*! |
| \qmlproperty variant QtQml::Instantiator::model |
| |
| This property can be set to any of the supported \l {qml-data-models}{data models}: |
| |
| \list |
| \li A number that indicates the number of delegates to be created by the repeater |
| \li A model (e.g. a ListModel item, or a QAbstractItemModel subclass) |
| \li A string list |
| \li An object list |
| \endlist |
| |
| The type of model affects the properties that are exposed to the \l delegate. |
| |
| Default value is 1, which creates a single delegate instance. |
| |
| \sa {qml-data-models}{Data Models} |
| */ |
| |
| QVariant QQmlInstantiator::model() const |
| { |
| Q_D(const QQmlInstantiator); |
| return d->model; |
| } |
| |
| void QQmlInstantiator::setModel(const QVariant &v) |
| { |
| Q_D(QQmlInstantiator); |
| if (d->model == v) |
| return; |
| |
| d->model = v; |
| //Don't actually set model until componentComplete in case it wants to create its delegates immediately |
| if (!d->componentComplete) |
| return; |
| |
| QQmlInstanceModel *prevModel = d->instanceModel; |
| QObject *object = qvariant_cast<QObject*>(v); |
| QQmlInstanceModel *vim = nullptr; |
| if (object && (vim = qobject_cast<QQmlInstanceModel *>(object))) { |
| #if QT_CONFIG(qml_delegate_model) |
| if (d->ownModel) { |
| delete d->instanceModel; |
| prevModel = nullptr; |
| d->ownModel = false; |
| } |
| #endif |
| d->instanceModel = vim; |
| #if QT_CONFIG(qml_delegate_model) |
| } else if (v != QVariant(0)){ |
| if (!d->ownModel) |
| d->makeModel(); |
| |
| if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel *>(d->instanceModel)) { |
| d->effectiveReset = true; |
| dataModel->setModel(v); |
| d->effectiveReset = false; |
| } |
| #endif |
| } |
| |
| if (d->instanceModel != prevModel) { |
| if (prevModel) { |
| disconnect(prevModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), |
| this, SLOT(_q_modelUpdated(QQmlChangeSet,bool))); |
| disconnect(prevModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(_q_createdItem(int,QObject*))); |
| //disconnect(prevModel, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*))); |
| } |
| |
| if (d->instanceModel) { |
| connect(d->instanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), |
| this, SLOT(_q_modelUpdated(QQmlChangeSet,bool))); |
| connect(d->instanceModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(_q_createdItem(int,QObject*))); |
| //connect(d->instanceModel, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*))); |
| } |
| } |
| |
| d->regenerate(); |
| emit modelChanged(); |
| } |
| |
| /*! |
| \qmlproperty QtObject QtQml::Instantiator::object |
| |
| This is a reference to the first created object, intended as a convenience |
| for the case where only one object has been created. |
| */ |
| QObject *QQmlInstantiator::object() const |
| { |
| Q_D(const QQmlInstantiator); |
| if (d->objects.count()) |
| return d->objects[0]; |
| return nullptr; |
| } |
| |
| /*! |
| \qmlmethod QtObject QtQml::Instantiator::objectAt(int index) |
| |
| Returns a reference to the object with the given \a index. |
| */ |
| QObject *QQmlInstantiator::objectAt(int index) const |
| { |
| Q_D(const QQmlInstantiator); |
| if (index >= 0 && index < d->objects.count()) |
| return d->objects[index]; |
| return nullptr; |
| } |
| |
| /*! |
| \internal |
| */ |
| void QQmlInstantiator::classBegin() |
| { |
| Q_D(QQmlInstantiator); |
| d->componentComplete = false; |
| } |
| |
| /*! |
| \internal |
| */ |
| void QQmlInstantiator::componentComplete() |
| { |
| Q_D(QQmlInstantiator); |
| d->componentComplete = true; |
| #if QT_CONFIG(qml_delegate_model) |
| if (d->ownModel) { |
| static_cast<QQmlDelegateModel*>(d->instanceModel)->componentComplete(); |
| d->regenerate(); |
| } else |
| #endif |
| { |
| QVariant realModel = d->model; |
| d->model = QVariant(0); |
| setModel(realModel); //If realModel == d->model this won't do anything, but that's fine since the model's 0 |
| //setModel calls regenerate |
| } |
| } |
| |
| QT_END_NAMESPACE |
| |
| #include "moc_qqmlinstantiator_p.cpp" |