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/qml/qqmlengine.cpp | 2 + src/qml/types/qqmlinstantiator.cpp | 459 +++++++++++++++++++++++++++++++++++ src/qml/types/qqmlinstantiator_p.h | 109 +++++++++ src/qml/types/qqmlinstantiator_p_p.h | 90 +++++++ src/qml/types/types.pri | 8 +- 5 files changed, 665 insertions(+), 3 deletions(-) create mode 100644 src/qml/types/qqmlinstantiator.cpp create mode 100644 src/qml/types/qqmlinstantiator_p.h create mode 100644 src/qml/types/qqmlinstantiator_p_p.h (limited to 'src/qml') diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index 96d01920d9..25ef080cb5 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -96,6 +96,7 @@ #include #include #include +#include #ifdef Q_OS_WIN // for %APPDATA% #include @@ -183,6 +184,7 @@ void QQmlEnginePrivate::registerBaseTypes(const char *uri, int versionMajor, int qmlRegisterType(uri, versionMajor, versionMinor,"Binding"); qmlRegisterType(uri, versionMajor, versionMinor,"Connections"); qmlRegisterType(uri, versionMajor, versionMinor,"Timer"); + qmlRegisterType(uri, versionMajor, (versionMinor < 1 ? 1 : versionMinor), "Instantiator"); //Only available in >=2.1 qmlRegisterCustomType(uri, versionMajor, versionMinor,"Connections", new QQmlConnectionsParser); qmlRegisterType(); } 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" diff --git a/src/qml/types/qqmlinstantiator_p.h b/src/qml/types/qqmlinstantiator_p.h new file mode 100644 index 0000000000..d4eb793357 --- /dev/null +++ b/src/qml/types/qqmlinstantiator_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QQMLINSTANTIATOR_P_H +#define QQMLINSTANTIATOR_P_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QQmlInstantiatorPrivate; +class Q_AUTOTEST_EXPORT QQmlInstantiator : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + + Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged) + Q_PROPERTY(bool asynchronous READ isAsync WRITE setAsync NOTIFY asynchronousChanged) + Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged) + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) + Q_PROPERTY(QObject *object READ object NOTIFY objectChanged) + Q_CLASSINFO("DefaultProperty", "delegate") + +public: + QQmlInstantiator(QObject *parent = 0); + virtual ~QQmlInstantiator(); + + bool isActive() const; + void setActive(bool newVal); + + bool isAsync() const; + void setAsync(bool newVal); + + int count() const; + + QQmlComponent* delegate(); + void setDelegate(QQmlComponent* c); + + QVariant model() const; + void setModel(const QVariant &v); + + QObject *object() const; + + Q_INVOKABLE QObject *objectAt(int index) const; + + void classBegin(); + void componentComplete(); + +Q_SIGNALS: + void modelChanged(); + void delegateChanged(); + void countChanged(); + void objectChanged(); + void activeChanged(); + void asynchronousChanged(); + + void objectAdded(int index, QObject* object); + void objectRemoved(int index, QObject* object); + +private: + Q_DISABLE_COPY(QQmlInstantiator) + Q_DECLARE_PRIVATE(QQmlInstantiator) + Q_PRIVATE_SLOT(d_func(), void _q_createdItem(int, QObject *)) + Q_PRIVATE_SLOT(d_func(), void _q_modelUpdated(const QQmlChangeSet &, bool)) +}; + +QT_END_NAMESPACE + +#endif // QQMLCREATOR_P_H diff --git a/src/qml/types/qqmlinstantiator_p_p.h b/src/qml/types/qqmlinstantiator_p_p.h new file mode 100644 index 0000000000..79459299dc --- /dev/null +++ b/src/qml/types/qqmlinstantiator_p_p.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QQMLINSTANTIATOR_P_P_H +#define QQMLINSTANTIATOR_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qqmlinstantiator_p.h" +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QQmlInstantiatorPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQmlInstantiator) + +public: + QQmlInstantiatorPrivate(); + ~QQmlInstantiatorPrivate(); + + void clear(); + void regenerate(); + void makeModel(); + void _q_createdItem(int, QObject *); + void _q_modelUpdated(const QQmlChangeSet &, bool); + + bool componentComplete; + bool active; + bool async; + bool ownModel; + QVariant model; + QQmlInstanceModel *instanceModel; + QQmlComponent *delegate; + QVector > objects; +}; + +QT_END_NAMESPACE + +#endif // QQMLCREATOR_P_P_H diff --git a/src/qml/types/types.pri b/src/qml/types/types.pri index 22e62ea8de..3e6153759d 100644 --- a/src/qml/types/types.pri +++ b/src/qml/types/types.pri @@ -8,7 +8,8 @@ SOURCES += \ $$PWD/qqmlobjectmodel.cpp \ $$PWD/qqmltimer.cpp \ $$PWD/qquickpackage.cpp \ - $$PWD/qquickworkerscript.cpp + $$PWD/qquickworkerscript.cpp \ + $$PWD/qqmlinstantiator.cpp HEADERS += \ $$PWD/qqmlbind_p.h \ @@ -22,5 +23,6 @@ HEADERS += \ $$PWD/qqmlobjectmodel_p.h \ $$PWD/qqmltimer_p.h \ $$PWD/qquickpackage_p.h \ - $$PWD/qquickworkerscript_p.h - + $$PWD/qquickworkerscript_p.h \ + $$PWD/qqmlinstantiator_p.h \ + $$PWD/qqmlinstantiator_p_p.h -- cgit v1.2.3