From 969dc839c3b0028e821d15015f99bd70d76fbc03 Mon Sep 17 00:00:00 2001 From: Alan Alpert Date: Sun, 23 Dec 2012 22:57:10 -0800 Subject: Add Instantiator type to QtQml Provides a dynamic instantiation type which is not tied to visual items. Change-Id: I42f7332b29b752dcc94979b6e0ec191fc76b96ef Reviewed-by: Alan Alpert Reviewed-by: Gabriel de Dietrich Reviewed-by: Lars Knoll --- src/qml/types/qqmlinstantiator.cpp | 459 +++++++++++++++++++++++++++++++++++++ 1 file changed, 459 insertions(+) create mode 100644 src/qml/types/qqmlinstantiator.cpp (limited to 'src/qml/types/qqmlinstantiator.cpp') diff --git a/src/qml/types/qqmlinstantiator.cpp b/src/qml/types/qqmlinstantiator.cpp new file mode 100644 index 0000000000..a2a1fa23ad --- /dev/null +++ b/src/qml/types/qqmlinstantiator.cpp @@ -0,0 +1,459 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Research In Motion. +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlinstantiator_p.h" +#include "qqmlinstantiator_p_p.h" +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QQmlInstantiatorPrivate::QQmlInstantiatorPrivate() + : componentComplete(true) + , active(true) + , async(false) + , ownModel(false) + , model(QVariant(1)) + , instanceModel(0) + , delegate(0) +{ +} + +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(); +} + +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 = instanceModel->object(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; + item->setParent(q); + objects << 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) + return; + + if (reset) { + regenerate(); + if (changeSet.difference() != 0) + q->countChanged(); + return; + } + + int difference = 0; + QHash > > moved; + foreach (const QQmlChangeSet::Remove &remove, changeSet.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; + } + + foreach (const QQmlChangeSet::Insert &insert, changeSet.inserts()) { + int index = qMin(insert.index, objects.count()); + if (insert.isMove()) { + QVector > movedObjects = moved.value(insert.moveId); + objects = objects.mid(0, index) + movedObjects + objects.mid(index); + } else for (int i = 0; i < insert.count; ++i) { + int modelIndex = index + i; + QObject* obj = instanceModel->object(i, async); + if (obj) + _q_createdItem(modelIndex, obj); + } + difference += insert.count; + } + + if (difference != 0) + q->countChanged(); +} + +void QQmlInstantiatorPrivate::makeModel() +{ + Q_Q(QQmlInstantiator); + QQmlDelegateModel* delegateModel = new QQmlDelegateModel(qmlContext(q)); + instanceModel = delegateModel; + ownModel = true; + delegateModel->setDelegate(delegate); + delegateModel->classBegin(); //Pretend it was made in QML + if (componentComplete) + delegateModel->componentComplete(); +} + + +/*! + \qmltype Instantiator + \instantiates QQmlInstantiator + \inqmlmodule QtQml 2 + \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 QtQml2::Instantiator::onObjectAdded(int index, QtObject object) + + This handler is called 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 QtQml2::Instantiator::onObjectRemoved(int index, QtObject object) + + This handler is called when an object is added to 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 handler is called. +*/ +/*! + \qmlproperty bool QtQml2::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 QtQml2::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 QtQml2::Instantiator::count + + The number of objects the Instantiator is currently managing. +*/ + +int QQmlInstantiator::count() const +{ + Q_D(const QQmlInstantiator); + return d->objects.count(); +} + +/*! + \qmlproperty QtQml2::Component QtQml2::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 itemAt 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 (!d->ownModel) + return; + + if (QQmlDelegateModel *dModel = qobject_cast(d->instanceModel)) + dModel->setDelegate(c); + if (d->componentComplete) + d->regenerate(); +} + +/*! + \qmlproperty variant QtQml2::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(v); + QQmlInstanceModel *vim = 0; + if (object && (vim = qobject_cast(object))) { + if (d->ownModel) { + delete d->instanceModel; + d->ownModel = false; + } + d->instanceModel = vim; + } else if (v != QVariant(0)){ + if (!d->ownModel) + d->makeModel(); + + if (QQmlDelegateModel *dataModel = qobject_cast(d->instanceModel)) + dataModel->setModel(v); + } + + 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*))); + } + + 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 QtQml2::QtObject QtQml2::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 0; +} + +/*! + \qmlmethod QtQml2::QtObject QtQml2::Instantiator::objectAt + + 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 0; +} + +/*! + \internal +*/ +void QQmlInstantiator::classBegin() +{ + Q_D(QQmlInstantiator); + d->componentComplete = false; +} + +/*! + \internal +*/ +void QQmlInstantiator::componentComplete() +{ + Q_D(QQmlInstantiator); + d->componentComplete = true; + if (d->ownModel) { + static_cast(d->instanceModel)->componentComplete(); + d->regenerate(); + } else { + 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" -- cgit v1.2.3