| /**************************************************************************** |
| ** |
| ** Copyright (C) 2019 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of Qt Quick 3D. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 or (at your option) any later version |
| ** approved by the KDE Free Qt Foundation. The licenses are as published by |
| ** the Free Software Foundation and appearing in the file LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qquick3dloader_p.h" |
| |
| #include "qquick3dobject_p_p.h" |
| |
| #include <QtQml/qqmlinfo.h> |
| |
| #include <private/qqmlengine_p.h> |
| #include <private/qqmlglobal_p.h> |
| |
| #include <private/qqmlcomponent_p.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| void QQuick3DLoaderIncubator::statusChanged(QQmlIncubator::Status status) |
| { |
| m_loader->incubatorStateChanged(status); |
| } |
| |
| void QQuick3DLoaderIncubator::setInitialState(QObject *o) |
| { |
| m_loader->setInitialState(o); |
| } |
| |
| /*! |
| \qmltype Loader3D |
| \inqmlmodule QtQuick3D |
| \inherits Node |
| |
| \brief Allows dynamic loading of a 3D subtree from a URL or Component. |
| |
| Loader3D is used to dynamically load 3D QML components. |
| |
| Loader3D can load a |
| QML file (using the \l source property) or a \l Component object (using |
| the \l sourceComponent property). It is useful for delaying the creation |
| of a component until it is required: for example, when a component should |
| be created on demand, or when a component should not be created |
| unnecessarily for performance reasons. |
| |
| \note Loader3D works like \l Loader, but does not provide or load Item |
| based 2D components. |
| */ |
| |
| QQuick3DLoader::QQuick3DLoader(QQuick3DNode *parent) |
| : QQuick3DNode(parent) |
| , m_item(nullptr) |
| , m_object(nullptr) |
| , m_itemContext(nullptr) |
| , m_incubator(nullptr) |
| , m_updatingSize(false) |
| , m_active(true) |
| , m_loadingFromSource(false) |
| , m_asynchronous(false) |
| { |
| } |
| |
| QQuick3DLoader::~QQuick3DLoader() |
| { |
| delete m_itemContext; |
| m_itemContext = nullptr; |
| delete m_incubator; |
| m_incubator = nullptr; |
| disposeInitialPropertyValues(); |
| clear(); |
| } |
| |
| /*! |
| \qmlproperty bool QtQuick3D::Loader3D::active |
| This property is \c true if the Loader3D is currently active. |
| The default value for this property is \c true. |
| |
| If the Loader3D is inactive, changing the \l source or \l sourceComponent |
| will not cause the item to be instantiated until the Loader3D is made active. |
| |
| Setting the value to inactive will cause any \l item loaded by the loader |
| to be released, but will not affect the \l source or \l sourceComponent. |
| |
| The \l status of an inactive loader is always \c Null. |
| |
| \sa source, sourceComponent |
| */ |
| |
| bool QQuick3DLoader::active() const |
| { |
| return m_active; |
| } |
| |
| void QQuick3DLoader::setActive(bool newVal) |
| { |
| if (m_active == newVal) |
| return; |
| |
| m_active = newVal; |
| if (newVal == true) { |
| if (m_loadingFromSource) { |
| loadFromSource(); |
| } else { |
| loadFromSourceComponent(); |
| } |
| } else { |
| // cancel any current incubation |
| if (m_incubator) { |
| m_incubator->clear(); |
| delete m_itemContext; |
| m_itemContext = nullptr; |
| } |
| |
| // Prevent any bindings from running while waiting for deletion. Without |
| // this we may get transient errors from use of 'parent', for example. |
| QQmlContext *context = qmlContext(m_object); |
| if (context) |
| QQmlContextData::get(context)->clearContextRecursively(); |
| |
| if (m_item) { |
| // We can't delete immediately because our item may have triggered |
| // the Loader to load a different item. |
| m_item->setParentItem(nullptr); |
| m_item->setVisible(false); |
| m_item = nullptr; |
| } |
| if (m_object) { |
| m_object->deleteLater(); |
| m_object = nullptr; |
| emit itemChanged(); |
| } |
| emit statusChanged(); |
| } |
| emit activeChanged(); |
| } |
| |
| void QQuick3DLoader::setSource(QQmlV4Function *args) |
| { |
| bool ipvError = false; |
| args->setReturnValue(QV4::Encode::undefined()); |
| QV4::Scope scope(args->v4engine()); |
| QV4::ScopedValue ipv(scope, extractInitialPropertyValues(args, &ipvError)); |
| if (ipvError) |
| return; |
| |
| clear(); |
| QUrl sourceUrl = resolveSourceUrl(args); |
| if (!ipv->isUndefined()) { |
| disposeInitialPropertyValues(); |
| m_initialPropertyValues.set(args->v4engine(), ipv); |
| } |
| m_qmlCallingContext.set(scope.engine, scope.engine->qmlContext()); |
| |
| setSource(sourceUrl, false); // already cleared and set ipv above. |
| } |
| |
| /*! |
| \qmlproperty url QtQuick3D::Loader3D::source |
| This property holds the URL of the QML component to instantiate. |
| |
| To unload the currently loaded object, set this property to an empty string, |
| or set \l sourceComponent to \c undefined. Setting \c source to a |
| new URL will also cause the item created by the previous URL to be unloaded. |
| |
| \sa sourceComponent, status, progress |
| */ |
| |
| QUrl QQuick3DLoader::source() const |
| { |
| return m_source; |
| } |
| |
| void QQuick3DLoader::setSource(const QUrl &url) |
| { |
| setSource(url, true); |
| } |
| |
| /*! |
| \qmlproperty Component QtQuick3D::Loader3D::sourceComponent |
| This property holds the \l{Component} to instantiate. |
| |
| \qml |
| Item { |
| Component { |
| id: redCube |
| Model { |
| source: "#Cube" |
| materials: DefaultMaterial { |
| diffuseColor: "red" |
| } |
| } |
| } |
| |
| Loader3D { sourceComponent: redCube } |
| Loader3D { sourceComponent: redCube; x: 10 } |
| } |
| \endqml |
| |
| To unload the currently loaded object, set this property to \c undefined. |
| |
| \sa source, progress |
| */ |
| |
| /*! |
| \qmlmethod object QtQuick3D::Loader3D::setSource(url source, object properties) |
| |
| Creates an object instance of the given \a source component that will have |
| the given \a properties. The \a properties argument is optional. The instance |
| will be accessible via the \l item property once loading and instantiation |
| is complete. |
| |
| If the \l active property is \c false at the time when this function is called, |
| the given \a source component will not be loaded but the \a source and initial |
| \a properties will be cached. When the loader is made \l active, an instance of |
| the \a source component will be created with the initial \a properties set. |
| |
| Setting the initial property values of an instance of a component in this manner |
| will \b{not} trigger any associated \l{Behavior}s. |
| |
| Note that the cached \a properties will be cleared if the \l source or \l sourceComponent |
| is changed after calling this function but prior to setting the loader \l active. |
| |
| \sa source, active |
| */ |
| |
| QQmlComponent *QQuick3DLoader::sourceComponent() const |
| { |
| return m_component; |
| } |
| |
| void QQuick3DLoader::setSourceComponent(QQmlComponent *comp) |
| { |
| if (comp == m_component) |
| return; |
| |
| clear(); |
| |
| m_component.setObject(comp, this); |
| m_loadingFromSource = false; |
| |
| if (m_active) |
| loadFromSourceComponent(); |
| else |
| emit sourceComponentChanged(); |
| } |
| |
| void QQuick3DLoader::resetSourceComponent() |
| { |
| setSourceComponent(nullptr); |
| } |
| |
| /*! |
| \qmlproperty enumeration QtQuick3D::Loader3D::status |
| |
| This property holds the status of QML loading. It can be one of: |
| |
| \value Loader3D.Null The loader is inactive or no QML source has been set. |
| \value Loader3D.Ready The QML source has been loaded. |
| \value Loader3D.Loading The QML source is currently being loaded. |
| \value Loader3D.Error An error occurred while loading the QML source. |
| |
| Use this status to provide an update or respond to the status change in some way. |
| For example, you could: |
| |
| \list |
| \li Trigger a state change: |
| \qml |
| State { name: 'loaded'; when: loader.status == Loader3D.Ready } |
| \endqml |
| |
| \li Implement an \c onStatusChanged signal handler: |
| \qml |
| Loader3D { |
| id: loader |
| onStatusChanged: if (loader.status == Loader3D.Ready) console.log('Loaded') |
| } |
| \endqml |
| |
| \li Bind to the status value: |
| \qml |
| Text { text: loader.status == Loader3D.Ready ? 'Loaded' : 'Not loaded' } |
| \endqml |
| \endlist |
| |
| Note that if the source is a local file, the status will initially be Ready (or Error). While |
| there will be no onStatusChanged signal in that case, the onLoaded will still be invoked. |
| |
| \sa progress |
| */ |
| |
| QQuick3DLoader::Status QQuick3DLoader::status() const |
| { |
| if (!m_active) |
| return Null; |
| |
| if (m_component) { |
| switch (m_component->status()) { |
| case QQmlComponent::Loading: |
| return Loading; |
| case QQmlComponent::Error: |
| return Error; |
| case QQmlComponent::Null: |
| return Null; |
| default: |
| break; |
| } |
| } |
| |
| if (m_incubator) { |
| switch (m_incubator->status()) { |
| case QQmlIncubator::Loading: |
| return Loading; |
| case QQmlIncubator::Error: |
| return Error; |
| default: |
| break; |
| } |
| } |
| |
| if (m_object) |
| return Ready; |
| |
| return m_source.isEmpty() ? Null : Error; |
| } |
| |
| /*! |
| \qmlsignal QtQuick3D::Loader3D::loaded() |
| |
| This signal is emitted when the \l status becomes \c Loader3D.Ready, or on successful |
| initial load. |
| |
| The corresponding handler is \c onLoaded. |
| */ |
| |
| |
| /*! |
| \qmlproperty real QtQuick3D::Loader3D::progress |
| |
| This property holds the progress of loading QML data from the network, from |
| 0.0 (nothing loaded) to 1.0 (finished). Most QML files are quite small, so |
| this value will rapidly change from 0 to 1. |
| |
| \sa status |
| */ |
| |
| qreal QQuick3DLoader::progress() const |
| { |
| |
| if (m_object) |
| return 1.0; |
| |
| if (m_component) |
| return m_component->progress(); |
| |
| return 0.0; |
| } |
| |
| /*! |
| \qmlproperty bool QtQuick3D::Loader3D::asynchronous |
| |
| This property holds whether the component will be instantiated asynchronously. |
| By default it is \c false. |
| |
| When used in conjunction with the \l source property, loading and compilation |
| will also be performed in a background thread. |
| |
| Loading asynchronously creates the objects declared by the component |
| across multiple frames, and reduces the |
| likelihood of glitches in animation. When loading asynchronously the status |
| will change to Loader3D.Loading. Once the entire component has been created, the |
| \l item will be available and the status will change to Loader.Ready. |
| |
| Changing the value of this property to \c false while an asynchronous load is in |
| progress will force immediate, synchronous completion. This allows beginning an |
| asynchronous load and then forcing completion if the Loader3D content must be |
| accessed before the asynchronous load has completed. |
| |
| To avoid seeing the items loading progressively set \c visible appropriately, e.g. |
| |
| \code |
| Loader3D { |
| source: "mycomponent.qml" |
| asynchronous: true |
| visible: status == Loader3D.Ready |
| } |
| \endcode |
| |
| Note that this property affects object instantiation only; it is unrelated to |
| loading a component asynchronously via a network. |
| */ |
| |
| bool QQuick3DLoader::asynchronous() const |
| { |
| return m_asynchronous; |
| } |
| |
| void QQuick3DLoader::setAsynchronous(bool a) |
| { |
| if (m_asynchronous == a) |
| return; |
| |
| m_asynchronous = a; |
| |
| if (!m_asynchronous && isComponentComplete() && m_active) { |
| if (m_loadingFromSource && m_component && m_component->isLoading()) { |
| // Force a synchronous component load |
| QUrl currentSource = m_source; |
| clear(); |
| m_source = currentSource; |
| loadFromSource(); |
| } else if (m_incubator && m_incubator->isLoading()) { |
| m_incubator->forceCompletion(); |
| } |
| } |
| |
| emit asynchronousChanged(); |
| } |
| |
| /*! |
| \qmlproperty object QtQuick3D::Loader3D::item |
| This property holds the top-level object that is currently loaded. |
| */ |
| QObject *QQuick3DLoader::item() const |
| { |
| return m_object; |
| } |
| |
| void QQuick3DLoader::componentComplete() |
| { |
| QQuick3DObject::componentComplete(); |
| if (active()) { |
| if (m_loadingFromSource) { |
| QQmlComponent::CompilationMode mode = m_asynchronous ? QQmlComponent::Asynchronous : QQmlComponent::PreferSynchronous; |
| if (!m_component) |
| m_component.setObject(new QQmlComponent(qmlEngine(this), m_source, mode, this), this); |
| } |
| load(); |
| } |
| } |
| |
| void QQuick3DLoader::sourceLoaded() |
| { |
| if (!m_component || !m_component->errors().isEmpty()) { |
| if (m_component) |
| QQmlEnginePrivate::warning(qmlEngine(this), m_component->errors()); |
| if (m_loadingFromSource) |
| emit sourceChanged(); |
| else |
| emit sourceComponentChanged(); |
| emit statusChanged(); |
| emit progressChanged(); |
| emit itemChanged(); //Like clearing source, emit itemChanged even if previous item was also null |
| disposeInitialPropertyValues(); // cleanup |
| return; |
| } |
| |
| QQmlContext *creationContext = m_component->creationContext(); |
| if (!creationContext) creationContext = qmlContext(this); |
| m_itemContext = new QQmlContext(creationContext); |
| m_itemContext->setContextObject(this); |
| |
| delete m_incubator; |
| m_incubator = new QQuick3DLoaderIncubator(this, m_asynchronous ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested); |
| |
| m_component->create(*m_incubator, m_itemContext); |
| |
| if (m_incubator && m_incubator->status() == QQmlIncubator::Loading) |
| emit statusChanged(); |
| } |
| |
| void QQuick3DLoader::setSource(const QUrl &sourceUrl, bool needsClear) |
| { |
| if (m_source == sourceUrl) |
| return; |
| |
| if (needsClear) |
| clear(); |
| |
| m_source = sourceUrl; |
| m_loadingFromSource = true; |
| |
| if (m_active) |
| loadFromSource(); |
| else |
| emit sourceChanged(); |
| } |
| |
| void QQuick3DLoader::loadFromSource() |
| { |
| if (m_source.isEmpty()) { |
| emit sourceChanged(); |
| emit statusChanged(); |
| emit progressChanged(); |
| emit itemChanged(); |
| return; |
| } |
| |
| if (isComponentComplete()) { |
| QQmlComponent::CompilationMode mode = m_asynchronous ? QQmlComponent::Asynchronous : QQmlComponent::PreferSynchronous; |
| if (!m_component) |
| m_component.setObject(new QQmlComponent(qmlEngine(this), m_source, mode, this), this); |
| load(); |
| } |
| } |
| |
| void QQuick3DLoader::loadFromSourceComponent() |
| { |
| if (!m_component) { |
| emit sourceComponentChanged(); |
| emit statusChanged(); |
| emit progressChanged(); |
| emit itemChanged(); |
| return; |
| } |
| |
| if (isComponentComplete()) |
| load(); |
| } |
| |
| void QQuick3DLoader::clear() |
| { |
| disposeInitialPropertyValues(); |
| |
| if (m_incubator) |
| m_incubator->clear(); |
| |
| delete m_itemContext; |
| m_itemContext = nullptr; |
| |
| // Prevent any bindings from running while waiting for deletion. Without |
| // this we may get transient errors from use of 'parent', for example. |
| QQmlContext *context = qmlContext(m_object); |
| if (context) |
| QQmlContextData::get(context)->clearContextRecursively(); |
| |
| if (m_loadingFromSource && m_component) { |
| // disconnect since we deleteLater |
| QObject::disconnect(m_component, SIGNAL(statusChanged(QQmlComponent::Status)), |
| this, SLOT(sourceLoaded())); |
| QObject::disconnect(m_component, SIGNAL(progressChanged(qreal)), |
| this, SIGNAL(progressChanged())); |
| m_component->deleteLater(); |
| m_component.setObject(nullptr, this); |
| } else if (m_component) { |
| m_component.setObject(nullptr, this); |
| } |
| m_source = QUrl(); |
| |
| if (m_item) { |
| // We can't delete immediately because our item may have triggered |
| // the Loader to load a different item. |
| m_item->setParentItem(nullptr); |
| m_item->setVisible(false); |
| m_item = nullptr; |
| } |
| if (m_object) { |
| m_object->deleteLater(); |
| m_object = nullptr; |
| } |
| } |
| |
| void QQuick3DLoader::load() |
| { |
| |
| if (!isComponentComplete() || !m_component) |
| return; |
| |
| if (!m_component->isLoading()) { |
| sourceLoaded(); |
| } else { |
| QObject::connect(m_component, SIGNAL(statusChanged(QQmlComponent::Status)), |
| this, SLOT(sourceLoaded())); |
| QObject::connect(m_component, SIGNAL(progressChanged(qreal)), |
| this, SIGNAL(progressChanged())); |
| emit statusChanged(); |
| emit progressChanged(); |
| if (m_loadingFromSource) |
| emit sourceChanged(); |
| else |
| emit sourceComponentChanged(); |
| emit itemChanged(); |
| } |
| } |
| |
| void QQuick3DLoader::incubatorStateChanged(QQmlIncubator::Status status) |
| { |
| if (status == QQmlIncubator::Loading || status == QQmlIncubator::Null) |
| return; |
| |
| if (status == QQmlIncubator::Ready) { |
| m_object = m_incubator->object(); |
| m_item = qmlobject_cast<QQuick3DNode*>(m_object); |
| emit itemChanged(); |
| m_incubator->clear(); |
| } else if (status == QQmlIncubator::Error) { |
| if (!m_incubator->errors().isEmpty()) |
| QQmlEnginePrivate::warning(qmlEngine(this), m_incubator->errors()); |
| delete m_itemContext; |
| m_itemContext = nullptr; |
| delete m_incubator->object(); |
| m_source = QUrl(); |
| emit itemChanged(); |
| } |
| if (m_loadingFromSource) |
| emit sourceChanged(); |
| else |
| emit sourceComponentChanged(); |
| emit statusChanged(); |
| emit progressChanged(); |
| if (status == QQmlIncubator::Ready) |
| emit loaded(); |
| disposeInitialPropertyValues(); // cleanup |
| } |
| |
| void QQuick3DLoader::setInitialState(QObject *obj) |
| { |
| QQuick3DObject *item = qmlobject_cast<QQuick3DObject*>(obj); |
| if (item) { |
| item->setParentItem(this); |
| } |
| if (obj) { |
| QQml_setParent_noEvent(m_itemContext, obj); |
| QQml_setParent_noEvent(obj, this); |
| m_itemContext = nullptr; |
| } |
| |
| if (m_initialPropertyValues.isUndefined()) |
| return; |
| |
| QQmlComponentPrivate *d = QQmlComponentPrivate::get(m_component); |
| Q_ASSERT(d && d->engine); |
| QV4::ExecutionEngine *v4 = d->engine->handle(); |
| Q_ASSERT(v4); |
| QV4::Scope scope(v4); |
| QV4::ScopedValue ipv(scope, m_initialPropertyValues.value()); |
| QV4::Scoped<QV4::QmlContext> qmlContext(scope, m_qmlCallingContext.value()); |
| d->initializeObjectWithInitialProperties(qmlContext, ipv, obj); |
| } |
| |
| void QQuick3DLoader::disposeInitialPropertyValues() |
| { |
| |
| } |
| |
| QUrl QQuick3DLoader::resolveSourceUrl(QQmlV4Function *args) |
| { |
| QV4::Scope scope(args->v4engine()); |
| QV4::ScopedValue v(scope, (*args)[0]); |
| QString arg = v->toQString(); |
| if (arg.isEmpty()) |
| return QUrl(); |
| |
| QQmlContextData *context = scope.engine->callingQmlContext(); |
| Q_ASSERT(context); |
| return context->resolvedUrl(QUrl(arg)); |
| } |
| |
| QV4::ReturnedValue QQuick3DLoader::extractInitialPropertyValues(QQmlV4Function *args, bool *error) |
| { |
| QV4::Scope scope(args->v4engine()); |
| QV4::ScopedValue valuemap(scope, QV4::Encode::undefined()); |
| if (args->length() >= 2) { |
| QV4::ScopedValue v(scope, (*args)[1]); |
| if (!v->isObject() || v->as<QV4::ArrayObject>()) { |
| *error = true; |
| qmlWarning(this) << QQuick3DLoader::tr("setSource: value is not an object"); |
| } else { |
| *error = false; |
| valuemap = v; |
| } |
| } |
| |
| return valuemap->asReturnedValue(); |
| } |
| |
| QT_END_NAMESPACE |