diff options
author | Sean Harmer <sean.harmer@kdab.com> | 2015-02-15 19:47:25 +0000 |
---|---|---|
committer | Sean Harmer <sean.harmer@kdab.com> | 2015-02-28 16:28:39 +0000 |
commit | 7ccaf0f9a917f49e1cde551c2a4f4dcff575406c (patch) | |
tree | 3dd47490fce2cf99f783b0bad608cf97d84fd6ab | |
parent | 66b26f86856ff57713ffab35fb689d087aa91ecc (diff) |
Added NodeInstantiator
Hopefully in the long term we can make the renderer smart enough
that it can automatically use instancing to render such things.
Change-Id: I9f5cfabeea11f5e6b925d5ad2466afa8e601e6f8
Reviewed-by: Sean Harmer <sean.harmer@kdab.com>
Reviewed-by: Paul Lemire <paul.lemire@kdab.com>
25 files changed, 1468 insertions, 48 deletions
diff --git a/examples/bigmodel-qml/MyEntity.qml b/examples/bigmodel-qml/MyEntity.qml new file mode 100644 index 000000000..fd30c1071 --- /dev/null +++ b/examples/bigmodel-qml/MyEntity.qml @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import Qt3D 2.0 +import Qt3D.Render 2.0 + +Entity { + id: root + property alias x: translation.dx + property alias y: translation.dy + property alias z: translation.dz + property alias diffuse: material.diffuse + + components: [ + Transform { Translate { id: translation } }, + SphereMesh { radius: 2 }, + PhongMaterial { id: material } + ] +} diff --git a/examples/bigmodel-qml/bigmodel-qml.pro b/examples/bigmodel-qml/bigmodel-qml.pro index e0535a346..7787217f8 100644 --- a/examples/bigmodel-qml/bigmodel-qml.pro +++ b/examples/bigmodel-qml/bigmodel-qml.pro @@ -6,7 +6,11 @@ SOURCES += \ main.cpp OTHER_FILES += \ - main.qml + main.qml \ + MyEntity.qml RESOURCES += \ bigmodel-qml.qrc + +DISTFILES += \ + MyEntity.qml diff --git a/examples/bigmodel-qml/bigmodel-qml.qrc b/examples/bigmodel-qml/bigmodel-qml.qrc index 5f6483ac3..6cc293f70 100644 --- a/examples/bigmodel-qml/bigmodel-qml.qrc +++ b/examples/bigmodel-qml/bigmodel-qml.qrc @@ -1,5 +1,6 @@ <RCC> <qresource prefix="/"> <file>main.qml</file> + <file>MyEntity.qml</file> </qresource> </RCC> diff --git a/examples/bigmodel-qml/main.cpp b/examples/bigmodel-qml/main.cpp index a8d22bcfa..6e76f1f5a 100644 --- a/examples/bigmodel-qml/main.cpp +++ b/examples/bigmodel-qml/main.cpp @@ -35,7 +35,7 @@ ****************************************************************************/ #include <Qt3DCore/window.h> -#include <Qt3DRenderer/qrenderaspect.h> +#include <Qt3DRenderer/QRenderAspect> #include <Qt3DInput/QInputAspect> #include <Qt3DQuick/QQmlAspectEngine> @@ -47,23 +47,11 @@ int main(int argc, char* argv[]) { QGuiApplication app(argc, argv); - - QCommandLineParser parser; - parser.addPositionalArgument(QStringLiteral("meshfile"), QStringLiteral("The mesh file to load")); - parser.process(app); - - const QStringList meshFileNames = parser.positionalArguments(); - if (meshFileNames.length() == 0) { - std::cerr << "Please specify mesh files to load" << std::endl; - return 1; - } - Qt3D::Window view; Qt3D::Quick::QQmlAspectEngine engine; engine.aspectEngine()->registerAspect(new Qt3D::QRenderAspect()); engine.aspectEngine()->registerAspect(new Qt3D::QInputAspect()); - engine.qmlEngine()->rootContext()->setContextProperty("_meshFileNames", meshFileNames); QVariantMap data; data.insert(QStringLiteral("surface"), QVariant::fromValue(static_cast<QSurface *>(&view))); data.insert(QStringLiteral("eventSource"), QVariant::fromValue(&view)); diff --git a/examples/bigmodel-qml/main.qml b/examples/bigmodel-qml/main.qml index 9c8604d2c..9d6a16264 100644 --- a/examples/bigmodel-qml/main.qml +++ b/examples/bigmodel-qml/main.qml @@ -41,40 +41,40 @@ import QtQuick 2.2 as QQ2 Entity { id: sceneRoot - Camera { - id: camera - lens : CameraLens { - projectionType: CameraLens.PerspectiveProjection - fieldOfView: 45 - aspectRatio: 16/9 - nearPlane : 0.1 - farPlane : 1000.0 - } + Configuration { controlledCamera: mainCamera } - transform : Transform { - LookAt { - position: Qt.vector3d( 0.0, 0.0, -20.0 ) - upVector: Qt.vector3d( 0.0, 1.0, 0.0 ) - viewCenter: Qt.vector3d( 0.0, 0.0, 0.0 ) - } - } - } + components: [ + FrameGraph { activeFrameGraph: ForwardRenderer { camera: mainCamera } } + ] - Configuration { - controlledCamera: camera + Camera { + id: mainCamera + projectionType: CameraLens.PerspectiveProjection + fieldOfView: 22.5 + aspectRatio: 16 / 9 + nearPlane: 0.01 + farPlane: 1000.0 + position: Qt.vector3d( 0.0, 25.0, 40.0 ) + viewCenter: Qt.vector3d( 0.0, 0.0, 0.0 ) + upVector: Qt.vector3d( 0.0, 1.0, 0.0 ) } - FrameGraph { - id : external_forward_renderer - activeFrameGraph : ForwardRenderer { - camera: camera - } - } + NodeInstantiator { + id: collection + property int count: 64 + property real spacing: 5 + property int cols: 8 + property int _rows: count / cols - components: [external_forward_renderer] + model: count + delegate: MyEntity { + id: myEntity + property real _lightness: 0.2 + 0.7 / collection._rows * Math.floor(index / collection.cols) + property real _hue: (index % collection.cols) / collection.cols - Instantiator { - model: _meshFileNames - delegate: SceneLoader { source: modelData; } + x: collection.spacing * (index % collection.cols - 0.5 * (collection.cols - 1)) + z: collection.spacing * (Math.floor(index / collection.cols) - 0.5 * collection._rows) + diffuse: Qt.hsla( _hue, 0.5, _lightness, 1.0 ) + } } } diff --git a/src/quick3d/imports/core/importscore.pro b/src/quick3d/imports/core/importscore.pro index 887a72147..7cfc2534a 100644 --- a/src/quick3d/imports/core/importscore.pro +++ b/src/quick3d/imports/core/importscore.pro @@ -2,7 +2,7 @@ CXX_MODULE = qml TARGET = quick3dcoreplugin TARGETPATH = Qt3D -QT += qml 3dcore 3dquick 3dquick-private +QT += qml 3dcore 3dcore-private 3dquick 3dquick-private HEADERS += \ qt3dquick3dcoreplugin.h diff --git a/src/quick3d/imports/core/qt3dquick3dcoreplugin.cpp b/src/quick3d/imports/core/qt3dquick3dcoreplugin.cpp index 77eb505de..986431b4a 100644 --- a/src/quick3d/imports/core/qt3dquick3dcoreplugin.cpp +++ b/src/quick3d/imports/core/qt3dquick3dcoreplugin.cpp @@ -47,6 +47,7 @@ #include <Qt3DQuick/quick3dentityloader.h> #include <Qt3DQuick/quick3dtransform.h> #include <Qt3DQuick/quick3dconfiguration.h> +#include <private/quick3dnodeinstantiator_p.h> #include <private/qt3dquick_global_p.h> QT_BEGIN_NAMESPACE @@ -61,6 +62,7 @@ void Qt3DQuick3DCorePlugin::registerTypes(const char *uri) qmlRegisterType<Qt3D::Quick::Quick3DConfiguration>(uri, 2, 0, "Configuration"); qmlRegisterExtendedType<Qt3D::QEntity, Qt3D::Quick::Quick3DEntity>(uri, 2, 0, "Entity"); qmlRegisterType<Qt3D::Quick::Quick3DEntityLoader>(uri, 2, 0, "EntityLoader"); + qmlRegisterType<Qt3D::Quick::Quick3DNodeInstantiator>(uri, 2, 0, "NodeInstantiator"); qmlRegisterExtendedType<Qt3D::QTransform, Qt3D::Quick::Quick3DTransform>(uri, 2, 0, "Transform"); // Ideally we want to make Node an uncreatable type // We would need qmlRegisterUncreatableExtendedType for that diff --git a/src/quick3d/quick3d/items/items.pri b/src/quick3d/quick3d/items/items.pri index edf00bd5e..0c01fe46f 100644 --- a/src/quick3d/quick3d/items/items.pri +++ b/src/quick3d/quick3d/items/items.pri @@ -4,13 +4,16 @@ HEADERS += \ $$PWD/quick3dentityloader.h \ $$PWD/quick3dentityloader_p.h \ $$PWD/quick3dtransform.h \ - $$PWD/quick3dconfiguration.h + $$PWD/quick3dconfiguration.h \ + $$PWD/quick3dnodeinstantiator_p_p.h \ + $$PWD/quick3dnodeinstantiator_p.h SOURCES += \ $$PWD/quick3dnode.cpp \ $$PWD/quick3dentity.cpp \ $$PWD/quick3dentityloader.cpp \ $$PWD/quick3dtransform.cpp \ - $$PWD/quick3dconfiguration.cpp + $$PWD/quick3dconfiguration.cpp \ + $$PWD/quick3dnodeinstantiator.cpp INCLUDEPATH += $$PWD diff --git a/src/quick3d/quick3d/items/quick3dnodeinstantiator.cpp b/src/quick3d/quick3d/items/quick3dnodeinstantiator.cpp new file mode 100644 index 000000000..4684bd212 --- /dev/null +++ b/src/quick3d/quick3d/items/quick3dnodeinstantiator.cpp @@ -0,0 +1,492 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Research In Motion. +** Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "quick3dnodeinstantiator_p.h" +#include "quick3dnodeinstantiator_p_p.h" + +#include <QtQml/QQmlContext> +#include <QtQml/QQmlComponent> +#include <QtQml/QQmlInfo> +#include <QtQml/QQmlError> +#include <QtQml/private/qqmlobjectmodel_p.h> +#include <QtQml/private/qqmldelegatemodel_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3D { +namespace Quick { + +Quick3DNodeInstantiatorPrivate::Quick3DNodeInstantiatorPrivate(QNode *qq) + : QNodePrivate(qq) + , m_componentComplete(true) + , m_effectiveReset(false) + , m_active(true) + , m_async(false) + , m_ownModel(false) + , m_model(QVariant(1)) + , m_instanceModel(0) + , m_delegate(0) +{ +} + +Quick3DNodeInstantiatorPrivate::~Quick3DNodeInstantiatorPrivate() +{ + qDeleteAll(m_objects); +} + +void Quick3DNodeInstantiatorPrivate::clear() +{ + Q_Q(Quick3DNodeInstantiator); + if (!m_instanceModel) + return; + if (!m_objects.count()) + return; + + for (int i = 0; i < m_objects.count(); i++) { + q->objectRemoved(i, m_objects[i]); + m_instanceModel->release(m_objects[i]); + } + m_objects.clear(); + q->objectChanged(); +} + +void Quick3DNodeInstantiatorPrivate::regenerate() +{ + Q_Q(Quick3DNodeInstantiator); + if (!m_componentComplete) + return; + + int prevCount = q->count(); + + clear(); + + if (!m_active || !m_instanceModel || !m_instanceModel->count() || !m_instanceModel->isValid()) { + if (prevCount) + q->countChanged(); + return; + } + + for (int i = 0; i < m_instanceModel->count(); i++) { + QObject *object = m_instanceModel->object(i, m_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 Quick3DNodeInstantiatorPrivate::_q_createdItem(int idx, QObject *item) +{ + Q_Q(Quick3DNodeInstantiator); + if (m_objects.contains(item)) //Case when it was created synchronously in regenerate + return; + item->setParent(q); + m_objects.insert(idx, item); + if (m_objects.count() == 1) + q->objectChanged(); + q->objectAdded(idx, item); +} + +void Quick3DNodeInstantiatorPrivate::_q_modelUpdated(const QQmlChangeSet &changeSet, bool reset) +{ + Q_Q(Quick3DNodeInstantiator); + + if (!m_componentComplete || m_effectiveReset) + return; + + if (reset) { + regenerate(); + if (changeSet.difference() != 0) + q->countChanged(); + return; + } + + int difference = 0; + QHash<int, QVector<QPointer<QObject> > > moved; + Q_FOREACH (const QQmlChangeSet::Change &remove, changeSet.removes()) { + int index = qMin(remove.index, m_objects.count()); + int count = qMin(remove.index + remove.count, m_objects.count()) - index; + if (remove.isMove()) { + moved.insert(remove.moveId, m_objects.mid(index, count)); + m_objects.erase( + m_objects.begin() + index, + m_objects.begin() + index + count); + } else { + while (count--) { + QObject *obj = m_objects.at(index); + m_objects.remove(index); + q->objectRemoved(index, obj); + if (obj) + m_instanceModel->release(obj); + } + } + + difference -= remove.count; + } + + Q_FOREACH (const QQmlChangeSet::Change &insert, changeSet.inserts()) { + int index = qMin(insert.index, m_objects.count()); + if (insert.isMove()) { + QVector<QPointer<QObject> > movedObjects = moved.value(insert.moveId); + m_objects = m_objects.mid(0, index) + movedObjects + m_objects.mid(index); + } else for (int i = 0; i < insert.count; ++i) { + int modelIndex = index + i; + QObject *obj = m_instanceModel->object(modelIndex, m_async); + if (obj) + _q_createdItem(modelIndex, obj); + } + difference += insert.count; + } + + if (difference != 0) + q->countChanged(); +} + +void Quick3DNodeInstantiatorPrivate::makeModel() +{ + Q_Q(Quick3DNodeInstantiator); + QQmlDelegateModel* delegateModel = new QQmlDelegateModel(qmlContext(q)); + m_instanceModel = delegateModel; + m_ownModel = true; + delegateModel->setDelegate(m_delegate); + delegateModel->classBegin(); //Pretend it was made in QML + if (m_componentComplete) + delegateModel->componentComplete(); +} + +/*! + \qmltype NodeInstantiator + \instantiates Quick3DNodeInstantiator + \inqmlmodule Quick3D + \brief Dynamically creates nodes + \since 5.5 + + A NodeInstantiator can be used to control the dynamic creation of nodes, or to dynamically + create multiple objects from a template. + + The NodeInstantiator element will manage the objects it creates. Those + objects are parented to the Instantiator and can also be deleted by the + NodeInstantiator if the NodeInstantiator's properties change. Nodes can + also be destroyed dynamically through other means, and the NodeInstantiator + will not recreate them unless the properties of the NodeInstantiator + change. + +*/ +Quick3DNodeInstantiator::Quick3DNodeInstantiator(QNode *parent) + : QNode(*(new Quick3DNodeInstantiatorPrivate(this)), parent) +{ +} + +Quick3DNodeInstantiator::~Quick3DNodeInstantiator() +{ +} + +/*! + \qmlsignal Quick3D::NodeInstantiator::objectAdded(int index, QtObject node) + + This signal is emitted when a node is added to the NodeInstantiator. The \a index + parameter holds the index which the node has been given, and the \a node + parameter holds the \l Node that has been added. + + The corresponding handler is \c onNodeAdded. +*/ + +/*! + \qmlsignal Quick3D::NodeInstantiator::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 Quick3D::NodeInstantiator::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 Quick3DNodeInstantiator::isActive() const +{ + Q_D(const Quick3DNodeInstantiator); + return d->m_active; +} + +void Quick3DNodeInstantiator::setActive(bool newVal) +{ + Q_D(Quick3DNodeInstantiator); + if (newVal == d->m_active) + return; + d->m_active = newVal; + emit activeChanged(); + d->regenerate(); +} + +/*! + \qmlproperty bool Quick3D::NodeInstantiator::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 Quick3DNodeInstantiator::isAsync() const +{ + Q_D(const Quick3DNodeInstantiator); + return d->m_async; +} + +void Quick3DNodeInstantiator::setAsync(bool newVal) +{ + Q_D(Quick3DNodeInstantiator); + if (newVal == d->m_async) + return; + d->m_async = newVal; + emit asynchronousChanged(); +} + + +/*! + \qmlproperty int Quick3D::NodeInstantiator::count + + The number of objects the Instantiator is currently managing. +*/ + +int Quick3DNodeInstantiator::count() const +{ + Q_D(const Quick3DNodeInstantiator); + return d->m_objects.count(); +} + +/*! + \qmlproperty QtQml::Component Quick3D::NodeInstantiator::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 *Quick3DNodeInstantiator::delegate() +{ + Q_D(Quick3DNodeInstantiator); + return d->m_delegate; +} + +void Quick3DNodeInstantiator::setDelegate(QQmlComponent *c) +{ + Q_D(Quick3DNodeInstantiator); + if (c == d->m_delegate) + return; + + d->m_delegate = c; + emit delegateChanged(); + + if (!d->m_ownModel) + return; + + if (QQmlDelegateModel *dModel = qobject_cast<QQmlDelegateModel*>(d->m_instanceModel)) + dModel->setDelegate(c); + if (d->m_componentComplete) + d->regenerate(); +} + +/*! + \qmlproperty variant Quick3D::NodeInstantiator::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 Quick3DNodeInstantiator::model() const +{ + Q_D(const Quick3DNodeInstantiator); + return d->m_model; +} + +void Quick3DNodeInstantiator::setModel(const QVariant &v) +{ + Q_D(Quick3DNodeInstantiator); + if (d->m_model == v) + return; + + d->m_model = v; + //Don't actually set model until componentComplete in case it wants to create its delegates immediately + if (!d->m_componentComplete) + return; + + QQmlInstanceModel *prevModel = d->m_instanceModel; + QObject *object = qvariant_cast<QObject*>(v); + QQmlInstanceModel *vim = 0; + if (object && (vim = qobject_cast<QQmlInstanceModel *>(object))) { + if (d->m_ownModel) { + delete d->m_instanceModel; + prevModel = 0; + d->m_ownModel = false; + } + d->m_instanceModel = vim; + } else if (v != QVariant(0)){ + if (!d->m_ownModel) + d->makeModel(); + + if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel *>(d->m_instanceModel)) { + d->m_effectiveReset = true; + dataModel->setModel(v); + d->m_effectiveReset = false; + } + } + + if (d->m_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->m_instanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), + this, SLOT(_q_modelUpdated(QQmlChangeSet,bool))); + connect(d->m_instanceModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(_q_createdItem(int,QObject*))); + //connect(d->m_instanceModel, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*))); + } + + d->regenerate(); + emit modelChanged(); +} + +/*! + \qmlproperty QtQml::QtObject Quick3D::NodeInstantiator::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 *Quick3DNodeInstantiator::object() const +{ + Q_D(const Quick3DNodeInstantiator); + if (d->m_objects.count()) + return d->m_objects[0]; + return 0; +} + +/*! + \qmlmethod QtQml::QtObject Quick3D::NodeInstantiator::objectAt + + Returns a reference to the object with the given \a index. +*/ +QObject *Quick3DNodeInstantiator::objectAt(int index) const +{ + Q_D(const Quick3DNodeInstantiator); + if (index >= 0 && index < d->m_objects.count()) + return d->m_objects[index]; + return 0; +} + +/*! + \internal +*/ +void Quick3DNodeInstantiator::classBegin() +{ + Q_D(Quick3DNodeInstantiator); + d->m_componentComplete = false; +} + +/*! + \internal +*/ +void Quick3DNodeInstantiator::componentComplete() +{ + Q_D(Quick3DNodeInstantiator); + d->m_componentComplete = true; + if (d->m_ownModel) { + static_cast<QQmlDelegateModel *>(d->m_instanceModel)->componentComplete(); + d->regenerate(); + } else { + QVariant realModel = d->m_model; + d->m_model = QVariant(0); + setModel(realModel); //If realModel == d->m_model this won't do anything, but that's fine since the model's 0 + //setModel calls regenerate + } +} + +void Quick3DNodeInstantiator::copy(const QNode *ref) +{ + QNode::copy(ref); + const Quick3DNodeInstantiator *instantiator = static_cast<const Quick3DNodeInstantiator*>(ref); + // We only need to clone the children as the instantiator itself has no + // corresponding backend node type. + for (int i = 0; i < instantiator->d_func()->m_objects.size(); ++i) { + QNode *n = qobject_cast<QNode *>(instantiator->d_func()->m_objects.at(i)); + if (!n) + continue; + QNode *clonedNode = QNode::clone(n); + clonedNode->setParent(this); + d_func()->m_objects.append(clonedNode); + } +} + +} // namespace Quick +} // namespace Qt3D + +QT_END_NAMESPACE + +#include "moc_quick3dnodeinstantiator_p.cpp" diff --git a/src/quick3d/quick3d/items/quick3dnodeinstantiator_p.h b/src/quick3d/quick3d/items/quick3dnodeinstantiator_p.h new file mode 100644 index 000000000..e4171b303 --- /dev/null +++ b/src/quick3d/quick3d/items/quick3dnodeinstantiator_p.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Research In Motion. +** Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT3D_QUICK_NODEINSTANTIATOR_H +#define QT3D_QUICK_NODEINSTANTIATOR_H + +#include <Qt3DQuick/qt3dquick_global.h> +#include <Qt3DCore/qnode.h> +#include <QtQml/qqmlcomponent.h> +#include <QtQml/qqmlparserstatus.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3D { +namespace Quick { + +class Quick3DNodeInstantiatorPrivate; + +class QT3DQUICKSHARED_EXPORT Quick3DNodeInstantiator : public QNode, 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: + Quick3DNodeInstantiator(QNode *parent = 0); + ~Quick3DNodeInstantiator(); + + 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); + +protected: + void copy(const QNode *ref) Q_DECL_OVERRIDE; + +private: + QT3D_CLONEABLE(Quick3DNodeInstantiator) + Q_DISABLE_COPY(Quick3DNodeInstantiator) + Q_DECLARE_PRIVATE(Quick3DNodeInstantiator) + Q_PRIVATE_SLOT(d_func(), void _q_createdItem(int, QObject *)) + Q_PRIVATE_SLOT(d_func(), void _q_modelUpdated(const QQmlChangeSet &, bool)) +}; + +} // namespace Quick +} // namespace Qt3D + +QT_END_NAMESPACE + +#endif // QT3D_QUICK_NODEINSTANTIATOR_H diff --git a/src/quick3d/quick3d/items/quick3dnodeinstantiator_p_p.h b/src/quick3d/quick3d/items/quick3dnodeinstantiator_p_p.h new file mode 100644 index 000000000..274fa752d --- /dev/null +++ b/src/quick3d/quick3d/items/quick3dnodeinstantiator_p_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Research In Motion. +** Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT3D_QUICK_QUICK3DNODEINSTANTIATOR_P_P_H +#define QT3D_QUICK_QUICK3DNODEINSTANTIATOR_P_P_H + +#include <private/qnode_p.h> +#include <private/qqmlchangeset_p.h> +#include <private/qqmlobjectmodel_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlComponent; + +namespace Qt3D { +namespace Quick { + +class Quick3DNodeInstantiatorPrivate : public QNodePrivate +{ + Q_DECLARE_PUBLIC(Quick3DNodeInstantiator) + +public: + Quick3DNodeInstantiatorPrivate(QNode *qq); + ~Quick3DNodeInstantiatorPrivate(); + + void clear(); + void regenerate(); + void makeModel(); + void _q_createdItem(int, QObject *); + void _q_modelUpdated(const QQmlChangeSet &, bool); + + bool m_componentComplete:1; + bool m_effectiveReset:1; + bool m_active:1; + bool m_async:1; + bool m_ownModel:1; + QVariant m_model; + QQmlInstanceModel *m_instanceModel; + QQmlComponent *m_delegate; + QVector<QPointer<QObject> > m_objects; +}; + +} // namespace Quick +} // namespace Qt3D + +QT_END_NAMESPACE + +#endif // QT3D_QUICK_QUICK3DNODEINSTANTIATOR_P_P_H + diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index ddf486fbc..43811f84e 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -1,4 +1,6 @@ TEMPLATE = subdirs -SUBDIRS = core \ - render +SUBDIRS = \ + core \ + render \ + quick3d diff --git a/tests/auto/quick3d/quick3d.pro b/tests/auto/quick3d/quick3d.pro new file mode 100644 index 000000000..898265ab6 --- /dev/null +++ b/tests/auto/quick3d/quick3d.pro @@ -0,0 +1,6 @@ +TEMPLATE = subdirs + +contains(QT_CONFIG, private_tests) { + SUBDIRS += \ + quick3dnodeinstantiator +} diff --git a/tests/auto/quick3d/quick3dnodeinstantiator/data/createAndRemove.qml b/tests/auto/quick3d/quick3dnodeinstantiator/data/createAndRemove.qml new file mode 100644 index 000000000..23084bfb0 --- /dev/null +++ b/tests/auto/quick3d/quick3dnodeinstantiator/data/createAndRemove.qml @@ -0,0 +1,20 @@ +import QtQml 2.1 +// To avoid clash with Component.onCompleted +// TODO: Export Qt3D::QComponent with a different name +import Qt3D 2.0 as Qt3D + +Qt3D.Entity { + Qt3D.NodeInstantiator { + objectName: "instantiator1" + model: model1 + delegate: Qt3D.Entity { + property string datum: model.text + } + } + Component.onCompleted: { + model1.add("Delta"); + model1.add("Gamma"); + model1.add("Beta"); + model1.add("Alpha"); + } +} diff --git a/tests/auto/quick3d/quick3dnodeinstantiator/data/createMultiple.qml b/tests/auto/quick3d/quick3dnodeinstantiator/data/createMultiple.qml new file mode 100644 index 000000000..ad4879c7a --- /dev/null +++ b/tests/auto/quick3d/quick3dnodeinstantiator/data/createMultiple.qml @@ -0,0 +1,10 @@ +import QtQml 2.1 +import Qt3D 2.0 + +NodeInstantiator { + model: 10 + delegate: Entity { + property bool success: true + property int idx: index + } +} diff --git a/tests/auto/quick3d/quick3dnodeinstantiator/data/createNone.qml b/tests/auto/quick3d/quick3dnodeinstantiator/data/createNone.qml new file mode 100644 index 000000000..c2ea5b650 --- /dev/null +++ b/tests/auto/quick3d/quick3dnodeinstantiator/data/createNone.qml @@ -0,0 +1,13 @@ +import QtQml 2.1 +import Qt3D 2.0 + +NodeInstantiator { + model: 0 + property bool success: true + Entity { + property bool success: true + property int idx: index + } + onObjectChanged: success = false;//Don't create intermediate objects + onCountChanged: success = false;//Don't create intermediate objects +} diff --git a/tests/auto/quick3d/quick3dnodeinstantiator/data/createSingle.qml b/tests/auto/quick3d/quick3dnodeinstantiator/data/createSingle.qml new file mode 100644 index 000000000..2816310ec --- /dev/null +++ b/tests/auto/quick3d/quick3dnodeinstantiator/data/createSingle.qml @@ -0,0 +1,9 @@ +import QtQml 2.1 +import Qt3D 2.0 + +NodeInstantiator { + Entity { + property bool success: true + property int idx: index + } +} diff --git a/tests/auto/quick3d/quick3dnodeinstantiator/data/inactive.qml b/tests/auto/quick3d/quick3dnodeinstantiator/data/inactive.qml new file mode 100644 index 000000000..1edef1e0c --- /dev/null +++ b/tests/auto/quick3d/quick3dnodeinstantiator/data/inactive.qml @@ -0,0 +1,10 @@ +import QtQml 2.1 +import Qt3D 2.0 + +NodeInstantiator { + active: false + Entity { + property bool success: true + property int idx: index + } +} diff --git a/tests/auto/quick3d/quick3dnodeinstantiator/data/stringModel.qml b/tests/auto/quick3d/quick3dnodeinstantiator/data/stringModel.qml new file mode 100644 index 000000000..55f536b1f --- /dev/null +++ b/tests/auto/quick3d/quick3dnodeinstantiator/data/stringModel.qml @@ -0,0 +1,10 @@ +import QtQml 2.1 +import Qt3D 2.0 + +NodeInstantiator { + model: ["alpha", "beta", "gamma", "delta"] + delegate: Entity { + property bool success: index == 1 ? datum.length == 4 : datum.length == 5 + property string datum: modelData + } +} diff --git a/tests/auto/quick3d/quick3dnodeinstantiator/quick3dnodeinstantiator.pro b/tests/auto/quick3d/quick3dnodeinstantiator/quick3dnodeinstantiator.pro new file mode 100644 index 000000000..f690d2c3b --- /dev/null +++ b/tests/auto/quick3d/quick3dnodeinstantiator/quick3dnodeinstantiator.pro @@ -0,0 +1,14 @@ +CONFIG += testcase +TARGET = tst_qnodeinstantiator +osx:CONFIG -= app_bundle + +INCLUDEPATH += ../../shared/ +SOURCES += \ + tst_quick3dnodeinstantiator.cpp +HEADERS += stringmodel.h + +include (../../shared/util.pri) + +TESTDATA = data/* + +QT += core-private gui-private 3dcore 3dquick 3dquick-private testlib diff --git a/tests/auto/quick3d/quick3dnodeinstantiator/stringmodel.h b/tests/auto/quick3d/quick3dnodeinstantiator/stringmodel.h new file mode 100644 index 000000000..b429866ff --- /dev/null +++ b/tests/auto/quick3d/quick3dnodeinstantiator/stringmodel.h @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Dmitrii Kosarev aka Kakadu <kakadu.hafanana@gmail.com> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef STRINGMODEL_H +#define STRINGMODEL_H + +#include <QtCore/QObject> +#include <QtCore/QAbstractItemModel> +#include <QtCore/QDebug> + +class StringModel : public QAbstractItemModel +{ + Q_OBJECT + QVector<QString> items; + QHash<int, QByteArray> roles; + QString name; + +public: + explicit StringModel(const QString& name) : QAbstractItemModel(), name(name) + { + roles.insert(555, "text"); + } + + void drop(int count) + { + beginRemoveRows(QModelIndex(), 0, count-1); + for (int i=0; i<count; i++) + items.pop_front(); + endRemoveRows(); + } + + Q_INVOKABLE void add(QString s) + { + beginInsertRows(QModelIndex(), 0, 0); + items.push_front(s); + endInsertRows(); + } + + int rowCount(const QModelIndex &) const + { + return items.count(); + } + + virtual QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE + { + return roles; + } + + virtual int columnCount(const QModelIndex &) const + { + return 1; + } + + virtual bool hasChildren(const QModelIndex &) const Q_DECL_OVERRIDE + { + return rowCount(QModelIndex()) > 0; + } + + virtual QModelIndex index(int row, int column, const QModelIndex &parent) const + { + Q_UNUSED(column); + if (row>=0 && row<rowCount(parent)) + return createIndex(row,0); + else + return QModelIndex(); + } + + virtual QModelIndex parent(const QModelIndex &) const + { + return QModelIndex(); + } + + QVariant data (const QModelIndex & index, int role) const + { + int row = index.row(); + if ((row<0) || (row>=items.count())) + return QVariant::Invalid; + + switch (role) { + case Qt::DisplayRole: + case 555: + return QVariant::fromValue(items.at(row)); + default: + return QVariant(); + } + } +}; + +#endif // STRINGMODEL_H diff --git a/tests/auto/quick3d/quick3dnodeinstantiator/tst_quick3dnodeinstantiator.cpp b/tests/auto/quick3d/quick3dnodeinstantiator/tst_quick3dnodeinstantiator.cpp new file mode 100644 index 000000000..37d8ff566 --- /dev/null +++ b/tests/auto/quick3d/quick3dnodeinstantiator/tst_quick3dnodeinstantiator.cpp @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Research In Motion. +** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "../../shared/util.h" +#include "stringmodel.h" + +#include <qtest.h> +#include <QSignalSpy> +#include <QDebug> + +#include <Qt3DQuick/private/quick3dnodeinstantiator_p.h> + +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlcomponent.h> +#include <QtQml/qqmlcontext.h> + +using namespace Qt3D::Quick; + +class tst_quick3dnodeinstantiator: public QQmlDataTest +{ + Q_OBJECT + +private slots: + void createNone(); + void createSingle(); + void createMultiple(); + void stringModel(); + void activeProperty(); + void intModelChange(); + void createAndRemove(); +}; + +void tst_quick3dnodeinstantiator::createNone() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("createNone.qml")); + Quick3DNodeInstantiator *instantiator = qobject_cast<Quick3DNodeInstantiator*>(component.create()); + QVERIFY(instantiator != 0); + QCOMPARE(instantiator->isActive(), true); + QCOMPARE(instantiator->count(), 0); + QCOMPARE(instantiator->property("success").toBool(), true); + QVERIFY(instantiator->delegate()->isReady()); +} + +void tst_quick3dnodeinstantiator::createSingle() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("createSingle.qml")); + Quick3DNodeInstantiator *instantiator = qobject_cast<Quick3DNodeInstantiator*>(component.create()); + QVERIFY(instantiator != 0); + QCOMPARE(instantiator->isActive(), true); + QCOMPARE(instantiator->count(), 1); + QVERIFY(instantiator->delegate()->isReady()); + + QObject *object = instantiator->object(); + QVERIFY(object); + QCOMPARE(object->parent(), instantiator); + QCOMPARE(object->property("success").toBool(), true); + QCOMPARE(object->property("idx").toInt(), 0); +} + +void tst_quick3dnodeinstantiator::createMultiple() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("createMultiple.qml")); + Quick3DNodeInstantiator *instantiator = qobject_cast<Quick3DNodeInstantiator*>(component.create()); + QVERIFY(instantiator != 0); + QCOMPARE(instantiator->isActive(), true); + QCOMPARE(instantiator->count(), 10); + + for (int i = 0; i < 10; i++) { + QObject *object = instantiator->objectAt(i); + QVERIFY(object); + QCOMPARE(object->parent(), instantiator); + QCOMPARE(object->property("success").toBool(), true); + QCOMPARE(object->property("idx").toInt(), i); + } +} + +void tst_quick3dnodeinstantiator::stringModel() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("stringModel.qml")); + Quick3DNodeInstantiator *instantiator = qobject_cast<Quick3DNodeInstantiator*>(component.create()); + QVERIFY(instantiator != 0); + QCOMPARE(instantiator->isActive(), true); + QCOMPARE(instantiator->count(), 4); + + for (int i = 0; i < 4; i++) { + QObject *object = instantiator->objectAt(i); + QVERIFY(object); + QCOMPARE(object->parent(), instantiator); + QCOMPARE(object->property("success").toBool(), true); + } +} + +void tst_quick3dnodeinstantiator::activeProperty() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("inactive.qml")); + Quick3DNodeInstantiator *instantiator = qobject_cast<Quick3DNodeInstantiator*>(component.create()); + QVERIFY(instantiator != 0); + QSignalSpy activeSpy(instantiator, SIGNAL(activeChanged())); + QSignalSpy countSpy(instantiator, SIGNAL(countChanged())); + QSignalSpy objectSpy(instantiator, SIGNAL(objectChanged())); + QSignalSpy modelSpy(instantiator, SIGNAL(modelChanged())); + QCOMPARE(instantiator->isActive(), false); + QCOMPARE(instantiator->count(), 0); + QVERIFY(instantiator->delegate()->isReady()); + + QCOMPARE(activeSpy.count(), 0); + QCOMPARE(countSpy.count(), 0); + QCOMPARE(objectSpy.count(), 0); + QCOMPARE(modelSpy.count(), 0); + + instantiator->setActive(true); + QCOMPARE(instantiator->isActive(), true); + QCOMPARE(instantiator->count(), 1); + + QCOMPARE(activeSpy.count(), 1); + QCOMPARE(countSpy.count(), 1); + QCOMPARE(objectSpy.count(), 1); + QCOMPARE(modelSpy.count(), 0); + + QObject *object = instantiator->object(); + QVERIFY(object); + QCOMPARE(object->parent(), instantiator); + QCOMPARE(object->property("success").toBool(), true); + QCOMPARE(object->property("idx").toInt(), 0); +} + +void tst_quick3dnodeinstantiator::intModelChange() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("createMultiple.qml")); + Quick3DNodeInstantiator *instantiator = qobject_cast<Quick3DNodeInstantiator*>(component.create()); + QVERIFY(instantiator != 0); + QSignalSpy activeSpy(instantiator, SIGNAL(activeChanged())); + QSignalSpy countSpy(instantiator, SIGNAL(countChanged())); + QSignalSpy objectSpy(instantiator, SIGNAL(objectChanged())); + QSignalSpy modelSpy(instantiator, SIGNAL(modelChanged())); + QCOMPARE(instantiator->count(), 10); + + QCOMPARE(activeSpy.count(), 0); + QCOMPARE(countSpy.count(), 0); + QCOMPARE(objectSpy.count(), 0); + QCOMPARE(modelSpy.count(), 0); + + instantiator->setModel(QVariant(2)); + QCOMPARE(instantiator->count(), 2); + + QCOMPARE(activeSpy.count(), 0); + QCOMPARE(countSpy.count(), 1); + QCOMPARE(objectSpy.count(), 2); + QCOMPARE(modelSpy.count(), 1); + + for (int i = 0; i < 2; i++) { + QObject *object = instantiator->objectAt(i); + QVERIFY(object); + QCOMPARE(object->parent(), instantiator); + QCOMPARE(object->property("success").toBool(), true); + QCOMPARE(object->property("idx").toInt(), i); + } +} + +void tst_quick3dnodeinstantiator::createAndRemove() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("createAndRemove.qml")); + StringModel *model = new StringModel("model1"); + engine.rootContext()->setContextProperty("model1", model); + QObject *rootObject = component.create(); + QVERIFY(rootObject != 0); + + Quick3DNodeInstantiator *instantiator = + qobject_cast<Quick3DNodeInstantiator*>(rootObject->findChild<QObject*>("instantiator1")); + QVERIFY(instantiator != 0); + model->drop(1); + QVector<QString> names; + names << "Beta" << "Gamma" << "Delta"; + for (int i = 0; i < 3; i++) { + QObject *object = instantiator->objectAt(i); + QVERIFY(object); + QCOMPARE(object->property("datum").toString(), names[i]); + } +} +QTEST_MAIN(tst_quick3dnodeinstantiator) + +#include "tst_quick3dnodeinstantiator.moc" diff --git a/tests/auto/shared/util.cpp b/tests/auto/shared/util.cpp new file mode 100644 index 000000000..37468f467 --- /dev/null +++ b/tests/auto/shared/util.cpp @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "util.h" + +#include <QtQml/QQmlComponent> +#include <QtQml/QQmlError> +#include <QtQml/QQmlContext> +#include <QtQml/QQmlEngine> +#include <QtCore/QTextStream> +#include <QtCore/QDebug> +#include <QtCore/QMutexLocker> + +QQmlDataTest *QQmlDataTest::m_instance = 0; + +QQmlDataTest::QQmlDataTest() : +#ifdef QT_TESTCASE_BUILDDIR + m_dataDirectory(QTest::qFindTestData("data", QT_QMLTEST_DATADIR, 0, QT_TESTCASE_BUILDDIR)), +#else + m_dataDirectory(QTest::qFindTestData("data", QT_QMLTEST_DATADIR, 0)), +#endif + + m_dataDirectoryUrl(QUrl::fromLocalFile(m_dataDirectory + QLatin1Char('/'))) +{ + m_instance = this; +} + +QQmlDataTest::~QQmlDataTest() +{ + m_instance = 0; +} + +void QQmlDataTest::initTestCase() +{ + QVERIFY2(!m_dataDirectory.isEmpty(), "'data' directory not found"); + m_directory = QFileInfo(m_dataDirectory).absolutePath(); + QVERIFY2(QDir::setCurrent(m_directory), qPrintable(QLatin1String("Could not chdir to ") + m_directory)); +} + +QString QQmlDataTest::testFile(const QString &fileName) const +{ + if (m_directory.isEmpty()) + qFatal("QQmlDataTest::initTestCase() not called."); + QString result = m_dataDirectory; + result += QLatin1Char('/'); + result += fileName; + return result; +} + +QByteArray QQmlDataTest::msgComponentError(const QQmlComponent &c, + const QQmlEngine *engine /* = 0 */) +{ + QString result; + const QList<QQmlError> errors = c.errors(); + QTextStream str(&result); + str << "Component '" << c.url().toString() << "' has " << errors.size() + << " errors: '"; + for (int i = 0; i < errors.size(); ++i) { + if (i) + str << ", '"; + str << errors.at(i).toString() << '\''; + + } + if (!engine) + if (QQmlContext *context = c.creationContext()) + engine = context->engine(); + if (engine) { + str << " Import paths: (" << engine->importPathList().join(QStringLiteral(", ")) + << ") Plugin paths: (" << engine->pluginPathList().join(QStringLiteral(", ")) + << ')'; + } + return result.toLocal8Bit(); +} + +Q_GLOBAL_STATIC(QMutex, qQmlTestMessageHandlerMutex) + +QQmlTestMessageHandler *QQmlTestMessageHandler::m_instance = 0; + +void QQmlTestMessageHandler::messageHandler(QtMsgType, const QMessageLogContext &, const QString &message) +{ + QMutexLocker locker(qQmlTestMessageHandlerMutex()); + if (QQmlTestMessageHandler::m_instance) + QQmlTestMessageHandler::m_instance->m_messages.push_back(message); +} + +QQmlTestMessageHandler::QQmlTestMessageHandler() +{ + QMutexLocker locker(qQmlTestMessageHandlerMutex()); + Q_ASSERT(!QQmlTestMessageHandler::m_instance); + QQmlTestMessageHandler::m_instance = this; + m_oldHandler = qInstallMessageHandler(messageHandler); +} + +QQmlTestMessageHandler::~QQmlTestMessageHandler() +{ + QMutexLocker locker(qQmlTestMessageHandlerMutex()); + Q_ASSERT(QQmlTestMessageHandler::m_instance); + qInstallMessageHandler(m_oldHandler); + QQmlTestMessageHandler::m_instance = 0; +} diff --git a/tests/auto/shared/util.h b/tests/auto/shared/util.h new file mode 100644 index 000000000..12a8933c4 --- /dev/null +++ b/tests/auto/shared/util.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLTESTUTILS_H +#define QQMLTESTUTILS_H + +#include <QtCore/QDir> +#include <QtCore/QUrl> +#include <QtCore/QCoreApplication> +#include <QtCore/QStringList> +#include <QtTest/QTest> + +QT_FORWARD_DECLARE_CLASS(QQmlComponent) +QT_FORWARD_DECLARE_CLASS(QQmlEngine) + +/* Base class for tests with data that are located in a "data" subfolder. */ + +class QQmlDataTest : public QObject +{ + Q_OBJECT +public: + QQmlDataTest(); + virtual ~QQmlDataTest(); + + QString testFile(const QString &fileName) const; + inline QString testFile(const char *fileName) const + { return testFile(QLatin1String(fileName)); } + inline QUrl testFileUrl(const QString &fileName) const + { return QUrl::fromLocalFile(testFile(fileName)); } + inline QUrl testFileUrl(const char *fileName) const + { return testFileUrl(QLatin1String(fileName)); } + + inline QString dataDirectory() const { return m_dataDirectory; } + inline QUrl dataDirectoryUrl() const { return m_dataDirectoryUrl; } + inline QString directory() const { return m_directory; } + + static inline QQmlDataTest *instance() { return m_instance; } + + static QByteArray msgComponentError(const QQmlComponent &, + const QQmlEngine *engine = 0); + +public slots: + virtual void initTestCase(); + +private: + static QQmlDataTest *m_instance; + + const QString m_dataDirectory; + const QUrl m_dataDirectoryUrl; + QString m_directory; +}; + +class QQmlTestMessageHandler +{ + Q_DISABLE_COPY(QQmlTestMessageHandler) +public: + QQmlTestMessageHandler(); + ~QQmlTestMessageHandler(); + + const QStringList &messages() const { return m_messages; } + const QString messageString() const { return m_messages.join(QLatin1Char('\n')); } + + void clear() { m_messages.clear(); } + +private: + static void messageHandler(QtMsgType, const QMessageLogContext &, const QString &message); + + static QQmlTestMessageHandler *m_instance; + QStringList m_messages; + QtMessageHandler m_oldHandler; +}; + +#endif // QQMLTESTUTILS_H diff --git a/tests/auto/shared/util.pri b/tests/auto/shared/util.pri new file mode 100644 index 000000000..8e82dcb33 --- /dev/null +++ b/tests/auto/shared/util.pri @@ -0,0 +1,5 @@ + +HEADERS += $$PWD/util.h +SOURCES += $$PWD/util.cpp + +DEFINES += QT_QMLTEST_DATADIR=\\\"$${_PRO_FILE_PWD_}/data\\\" |