diff options
Diffstat (limited to 'src/qmlmodels/qqmlinstantiator.cpp')
-rw-r--r-- | src/qmlmodels/qqmlinstantiator.cpp | 509 |
1 files changed, 509 insertions, 0 deletions
diff --git a/src/qmlmodels/qqmlinstantiator.cpp b/src/qmlmodels/qqmlinstantiator.cpp new file mode 100644 index 0000000000..af1b526e1d --- /dev/null +++ b/src/qmlmodels/qqmlinstantiator.cpp @@ -0,0 +1,509 @@ +/**************************************************************************** +** +** 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 + \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. + +*/ +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. + + The corresponding handler is \c onObjectAdded. +*/ + +/*! + \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. + + The corresponding handler is \c onObjectRemoved. +*/ +/*! + \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" |