diff options
Diffstat (limited to 'src/api/studio3d/q3dspresentation.cpp')
-rw-r--r-- | src/api/studio3d/q3dspresentation.cpp | 2100 |
1 files changed, 2100 insertions, 0 deletions
diff --git a/src/api/studio3d/q3dspresentation.cpp b/src/api/studio3d/q3dspresentation.cpp new file mode 100644 index 0000000..c07922c --- /dev/null +++ b/src/api/studio3d/q3dspresentation.cpp @@ -0,0 +1,2100 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $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 "q3dspresentation_p.h" +#include "q3dssceneelement_p.h" +#include "q3dscommandqueue_p.h" +#include "viewerqmlstreamproxy_p.h" +#include "q3dsdatainput_p.h" +#include "q3dsdataoutput_p.h" +#include "q3dsgeometry_p.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qsettings.h> +#include <QtCore/qcoreapplication.h> +#include <QtGui/qevent.h> + +QT_BEGIN_NAMESPACE + +/*! + \class Q3DSPresentation + \inmodule OpenGLRuntime + \since Qt 3D Studio 2.0 + + \brief Represents a Qt 3D Studio presentation. + + This class provides properties and methods for controlling a + presentation. + + Qt 3D Studio supports multiple presentations in one project. There + is always a main presentation and zero or more + subpresentations. The subpresentations are composed into the + main presentations either as contents of Qt 3D Studio layers or as + texture maps. + + In the filesystem each presentation corresponds to one \c{.uip} + presentation file. When present, the \c{.uia} project file ties + these together by specifying a name for each of the + (sub-)presentations and specifies which one is the main one. + + The \c{.uia} project also defines \l{DataInput}s and + \l{DataOutput}s that are exported by the presentations. + \l{DataInput}s provide a way to provide input to the presentation + to e.g. control a timeline of a subpresentation from code. + \c{DataOutput}s provide a way to get notified when an attribute + is changed in the presentation by animation timeline, + by behavior scripts or by a \l{DataInput}. + + From the API point of view Q3DSPresentation corresponds to the + main presentation. The source property can refer either to a + \c{.uia} or \c{.uip} file. When specifying a file with \c{.uip} + extension and a \c{.uia} is present with the same name, the + \c{.uia} is loaded automatically and thus sub-presentation + information is available regardless. + + \note This class should not be instantiated directly when working with the + C++ APIs. Q3DSSurfaceViewer and Q3DSWidget create a Q3DSPresentation + instance implicitly. This can be queried via + Q3DSSurfaceViewer::presentation() or Q3DSWidget::presentation(). + */ + +/*! + Constructs a new Q3DSPresentation with the given \a parent. + */ +Q3DSPresentation::Q3DSPresentation(QObject *parent) + : QObject(parent) + , d_ptr(new Q3DSPresentationPrivate(this)) +{ +} + +/*! + Destructor. + */ +Q3DSPresentation::~Q3DSPresentation() +{ +} + +/*! + \qmlproperty string Presentation::source + + Holds the name of the main presentation file (\c{*.uia} or + \c{*.uip}). This may be either a local file or qrc URL. + + The names of all further assets (image files for texture maps, qml + behavior scripts, mesh files) will be resolved relative to the + location of the presentation, unless they use absolute paths. This + allows bundling all assets next to the presentation in the Qt + resource system. + + Currently set \c{variantList} property will modify which variant groups + and tags are loaded from the presentations. See + Q3DSPresentation::variantList property. +*/ + +/*! + \property Q3DSPresentation::source + + Holds the name of the main presentation file (\c{*.uia} or + \c{*.uip}). This may be either a local file or qrc URL. + + The names of all further assets (image files for texture maps, qml + behavior scripts, mesh files) will be resolved relative to the + location of the presentation, unless they use absolute paths. This + allows bundling all assets next to the presentation in the Qt + resource system. + + Currently set variantList will modify which variant groups + and tags are loaded from the presentations. See + Q3DSPresentation::variantList property. +*/ +QUrl Q3DSPresentation::source() const +{ + return d_ptr->m_source; +} + +void Q3DSPresentation::setSource(const QUrl &source) +{ + if (d_ptr->m_source != source) { + d_ptr->setSource(source); + Q_EMIT sourceChanged(source); + } +} + +/*! + \qmlproperty list<string> Presentation::variantList + + Holds a list of (variant group):(variant) tags that are loaded when the + \c{source} property is set. If this list is left empty (default), no variant + filtering is applied and all items are loaded regardless of variant tags in + the presentation. Variant mechanism allows one presentation project to + contain multiple variants of the presentation and the decision which variant + set is loaded is determined during runtime based on the \c{variantList}. + + Variants are divided to variant groups, e.g. one variant group could be + \c{region} and the variants within that group could be e.g. \c{US, EU, CH}. + Another variant group could be e.g. \c{power} and variants within that could + be e.g. \c{gas, electric, diesel}. To filter in this example an electric + variant for the EU region, the variantList needs to contain two strings + "region:EU" and "power:electric". Also of course the presentation project + needs to contain these variant groups and tags applied appropriately to the + presentation content. + + When variant filters are used, the decision what gets loaded and what is not + loaded is based on checking every item in the presentation: + \list + \li If the item has no variant tags, it will be loaded. + \li If the item has no tags defined for the checked variant group(s), + it will be loaded. + \li If the item has tag(s) for the variant group, any of those tags must + match any of the variants defined in the filter for that group. + \endlist + + If the item doesn't fulfill the above rules it will not be loaded. +*/ + +/*! + \property Q3DSPresentation::variantList + + Holds a list of (variant group):(variant) tags that are loaded when the + \c{source} property is set. If this list is left empty (default), no variant + filtering is applied and all items are loaded regardless of variant tags in + the presentation. Variant mechanism allows one presentation project to + contain multiple variants of the presentation and the decision which variant + set is loaded is determined during runtime based on the \c{variantList}. + + Variants are divided to variant groups, e.g. one variant group could be + \c{region} and the variants within that group could be e.g. \c{US, EU, CH}. + Another variant group could be e.g. \c{power} and variants within that could + be e.g. \c{gas, electric, diesel}. To filter in this example an electric + variant for the EU region, the variantList needs to contain two strings + "region:EU" and "power:electric". Also of course the presentation project + needs to contain these variant groups and tags applied appropriately to the + presentation content. + + When variant filters are used, the decision what gets loaded and what is not + loaded is based on checking every item in the presentation: + \list + \li If the item has no variant tags, it will be loaded. + \li If the item has no tags defined for the checked variant group(s), + it will be loaded. + \li If the item has tag(s) for the variant group, any of those tags must + match any of the variants defined in the filter for that group. + \endlist + + If the item doesn't fulfill the above rules it will not be loaded. +*/ +QStringList Q3DSPresentation::variantList() const +{ + return d_ptr->m_variantList; +} + +void Q3DSPresentation::setVariantList(const QStringList &variantList) +{ + if (d_ptr->m_variantList != variantList) { + d_ptr->setVariantList(variantList); + Q_EMIT variantListChanged(variantList); + } +} + +/*! + \internal + */ +void Q3DSPresentation::registerElement(Q3DSElement *element) +{ + d_ptr->registerElement(element); +} + +/*! + \internal + */ +void Q3DSPresentation::unregisterElement(Q3DSElement *element) +{ + d_ptr->unregisterElement(element); +} + +/*! + \internal + */ +Q3DSElement *Q3DSPresentation::registeredElement(const QString &elementPath) const +{ + return d_ptr->m_elements.value(elementPath, nullptr); +} + +/*! + \internal + */ +void Q3DSPresentation::registerDataInput(Q3DSDataInput *dataInput) +{ + d_ptr->registerDataInput(dataInput); +} + +/*! + \internal + */ +void Q3DSPresentation::unregisterDataInput(Q3DSDataInput *dataInput) +{ + d_ptr->unregisterDataInput(dataInput); +} + +/*! + \internal + */ +Q3DSDataInput *Q3DSPresentation::registeredDataInput(const QString &name) const +{ + return d_ptr->m_dataInputs.value(name, nullptr); +} + +/*! + \internal + */ +void Q3DSPresentation::registerDataOutput(Q3DSDataOutput *dataOutput) +{ + d_ptr->registerDataOutput(dataOutput); +} + +/*! + \internal + */ +void Q3DSPresentation::unregisterDataOutput(Q3DSDataOutput *dataOutput) +{ + d_ptr->unregisterDataOutput(dataOutput); +} + +/*! + \internal + */ +Q3DSDataOutput *Q3DSPresentation::registeredDataOutput(const QString &name) const +{ + return d_ptr->m_dataOutputs.value(name, nullptr); +} + +/*! + Returns a list of datainputs defined for this presentation. Use setDataInputValue() + interface to set a datainput value using datainput name, or call Q3DSDataInput::setValue + directly for a specific datainput. + + \sa setDataInputValue + \sa Q3DSDataInput + */ +QVector<Q3DSDataInput *> Q3DSPresentation::dataInputs() const +{ + QVector<Q3DSDataInput *> ret; + // Just return local datainput list + const auto datainputs = d_ptr->m_dataInputs; + for (const auto &it : datainputs) + ret.append(it); + + return ret; +} + +/*! + \qmlmethod var Presentation::getDataInputs + Returns a list of datainputs defined for this presentation. Use setDataInputValue() + interface to set a datainput value using datainput name, or call Q3DSDataInput::setValue + directly for a specific datainput. + + \sa DataInput + */ + +/*! + Returns a list of datainputs defined for this presentation. Use setDataInputValue() + interface to set a datainput value using datainput name, or call Q3DSDataInput::setValue + directly for a specific datainput. + + \sa setDataInputValue + \sa Q3DSDataInput + */ +QVariantList Q3DSPresentation::getDataInputs() const +{ + QVariantList ret; + const auto datainputs = dataInputs(); + + for (const auto &it : datainputs) + ret.append(QVariant::fromValue(it)); + + return ret; +} + +/*! + Returns a list of datainputs defined for this presentation that have the specified + \a metadataKey. + + \sa setDataInputValue + \sa Q3DSDataInput + */ + +/*! + \qmlmethod var Presentation::getDataInputs + Returns a list of datainputs defined for this presentation that have the specified + \a metadataKey. + + \sa DataInput + */ +QVariantList Q3DSPresentation::getDataInputs(const QString &metadataKey) const +{ + QVariantList ret; + const auto datainputs = dataInputs(metadataKey); + + for (const auto &it : datainputs) + ret.append(QVariant::fromValue(it)); + + return ret; +} + +/*! + Returns a list of datainputs defined for this presentation that have the specified + \a metadataKey. + + \sa setDataInputValue + \sa Q3DSDataInput + */ +QVector<Q3DSDataInput *> Q3DSPresentation::dataInputs(const QString &metadataKey) const +{ + // Defer to presentation item as we want to read metadata from viewer app whenever + // possible. + return d_ptr->dataInputs(metadataKey); +} + +/*! + Returns a list of dataoutputs defined for this presentation. Use Qt's connect() method + to connect slots to the valueChanged() signal in the required \l{DataOutput}s to get notified + when the value tracked by the DataOutput is changed. + + \sa Q3DSDataOutput + */ +QVector<Q3DSDataOutput *> Q3DSPresentation::dataOutputs() const +{ + QVector<Q3DSDataOutput *> ret; + const auto datainputs = d_ptr->m_dataOutputs; + for (const auto &it : datainputs) + ret.append(it); + + return ret; +} + +/*! + \qmlmethod var Presentation::getDataOutputs + + Returns a list of dataoutputs defined for this presentation. Connect slots to the + \c{valueChanged()} signal in the required \l{DataOutput}s to get notified + when the value tracked by the DataOutput is changed. + + \sa SDataOutput + */ +/*! + * \brief Q3DSPresentation::getDataOutputs Returns \l{DataOutput}s. + Returns a list of dataoutputs defined for this presentation. Use Qt's connect() method + to connect slots to the valueChanged() signal in the required \l{DataOutput}s to get notified + when the value tracked by the DataOutput is changed. + + \sa Q3DSDataOutput + */ +QVariantList Q3DSPresentation::getDataOutputs() const +{ + QVariantList ret; + const auto dataoutputs = dataOutputs(); + + for (const auto &it : dataoutputs) + ret.append(QVariant::fromValue(it)); + + return ret; +} + +/*! + \qmlproperty bool Presentation::delayedLoading + + This property controls whether the presentation resources are loaded while loading + the presentation(false) or afterwards when they are actually used in the presentation(true). + The resources are loaded per slide basis so that all resources required by a slide will be + loaded at once. + + The resources can be images, subpresentations, materials, effects and meshes. + + Default is \c{false}. + */ + +/*! + \property Q3DSPresentation::delayedLoading + + This property controls whether the presentation resources are loaded while loading + the presentation(false) or afterwards when they are actually used in the presentation(true). + The resources are loaded per slide basis so that all resources required by a slide will be + loaded at once. + + The resources can be images, subpresentations, materials, effects and meshes. + + Default is \c{false}. + */ +bool Q3DSPresentation::delayedLoading() const +{ + return d_ptr->m_delayedLoading; +} + +void Q3DSPresentation::setDelayedLoading(bool enable) +{ + if (d_ptr->m_delayedLoading != enable) { + d_ptr->setDelayedLoading(enable); + Q_EMIT delayedLoadingChanged(enable); + } +} + +/*! + \qmlmethod Presentation::preloadSlide + Preloads slide resources to memory. All resources required by the given slide will be + loaded in the background. This function has effect only when delayed loading is enabled. + \param elementPath + */ +/*! + \brief Q3DSPresentation::preloadSlide + Preloads slide resources to memory. All resources required by the given slide will be + loaded in the background. This function has effect only when delayed loading is enabled. + \param elementPath + */ +void Q3DSPresentation::preloadSlide(const QString &elementPath) +{ + if (d_ptr->m_viewerApp) + d_ptr->m_viewerApp->preloadSlide(elementPath); + else if (d_ptr->m_commandQueue) + d_ptr->m_commandQueue->queueCommand(elementPath, CommandType_PreloadSlide); +} + +/*! + \qmlmethod Presentation::unloadSlide + Unloads slide resources from memory. If the slide is current, then the resources are unloaded + when the slide is changed. This function has effect only when delayed loading is enabled. + \param elementPath + */ + +/*! + \brief Q3DSPresentation::unloadSlide + Unloads slide resources from memory. If the slide is current, then the resources are unloaded + when the slide is changed. This function has effect only when delayed loading is enabled. + \param elementPath + */ +void Q3DSPresentation::unloadSlide(const QString &elementPath) +{ + if (d_ptr->m_viewerApp) + d_ptr->m_viewerApp->unloadSlide(elementPath); + else if (d_ptr->m_commandQueue) + d_ptr->m_commandQueue->queueCommand(elementPath, CommandType_UnloadSlide); +} + +/*! + This API is for backwards compatibility. We recommend using \l{DataInput}s to control + slide changes. \l{DataInput} provides stronger contract between the design and + code as it avoids use of elementPath (a reference to design's internal structure). + + Requests a time context (a Scene or a Component object) to change + to a specific slide by \a index. If the context is already on that + slide, playback will start over. + + If \a elementPath points to a time context, that element is + controlled. For all other element types the time context owning + that element is controlled instead. You can target the command to + a specific sub-presentation by adding "SubPresentationId:" in + front of the element path, for example \c{"SubPresentationOne:Scene"}. + */ +void Q3DSPresentation::goToSlide(const QString &elementPath, unsigned int index) +{ + if (d_ptr->m_viewerApp) { + const QByteArray path(elementPath.toUtf8()); + d_ptr->m_viewerApp->GoToSlideByIndex(path, index); + } else if (d_ptr->m_commandQueue) { + d_ptr->m_commandQueue->queueCommand(elementPath, CommandType_GoToSlide, int(index)); + } +} + +/*! + This API is for backwards compatibility. We recommend using \l{DataInput}s to control + slide changes. \l{DataInput} provides stronger contract between the design and + code as it avoids use of elementPath (a reference to design's internal structure). + + Requests a time context (a Scene or a Component object) to change + to a specific slide by \a name. If the context is already on that + slide, playback will start over. + + If \a elementPath points to a time context, that element is + controlled. For all other element types the time context owning + that element is controlled instead. You can target the command to + a specific sub-presentation by adding "SubPresentationId:" in + front of the element path, for example \c{"SubPresentationOne:Scene"}. + */ +void Q3DSPresentation::goToSlide(const QString &elementPath, const QString &name) +{ + if (d_ptr->m_viewerApp) { + const QByteArray path(elementPath.toUtf8()); + const QByteArray byteName(name.toUtf8()); + d_ptr->m_viewerApp->GoToSlideByName(path, byteName); + } else if (d_ptr->m_commandQueue) { + d_ptr->m_commandQueue->queueCommand(elementPath, CommandType_GoToSlideByName, name); + } +} + +/*! + This API is for backwards compatibility. We recommend using \l{DataInput}s to control + slide changes. \l{DataInput} provides stronger contract between the design and + code as it avoids use of elementPath (a reference to design's internal structure). + + Requests a time context (a Scene or a Component object) to change to the + next or previous slide, depending on the value of \a next. If the context + is already at the last or first slide, \a wrap defines if wrapping over to + the first or last slide, respectively, occurs. + + If \a elementPath points to a time context, that element is controlled. For + all other element types the time context owning that element is controlled + instead. You can target the command to a specific sub-presentation by + adding "SubPresentationId:" in front of the element path, for example + \c{"SubPresentationOne:Scene"}. + */ +void Q3DSPresentation::goToSlide(const QString &elementPath, bool next, bool wrap) +{ + if (d_ptr->m_viewerApp) { + const QByteArray path(elementPath.toUtf8()); + d_ptr->m_viewerApp->GoToSlideRelative(path, next, wrap); + } else if (d_ptr->m_commandQueue) { + d_ptr->m_commandQueue->queueCommand(elementPath, CommandType_GoToSlideRelative, + int(next), int(wrap)); + } +} + +/*! + This API is for backwards compatibility. We recommend using \l{DataInput}s to control + slide changes. \l{DataInput} provides stronger contract between the design and + code as it avoids use of elementPath (a reference to design's internal structure). + + Moves the timeline for a time context (a Scene or a Component element) to a + specific position. The position is given in seconds in \a timeSeconds. + + If \a elementPath points to a time context, that element is + controlled. For all other element types the time context owning + that element is controlled instead. You can target the command to + a specific sub-presentation by adding "SubPresentationId:" in + front of the element path, for example + \c{"SubPresentationOne:Scene"}. + + The behavior when specifying a time before 0 or after the end time + for the current slide depends on the play mode of the slide: + + \list + \li \c{Stop at End} - values outside the valid time range instead clamp to the boundaries. + For example, going to time -5 is the same as going to time 0. + \li \c{Looping} - values outside the valid time range mod into the valid range. For example, + going to time -4 on a 10 second slide is the same as going to time 6. + \li \c{Ping Pong} - values outside the valid time range bounce off the ends. For example, + going to time -4 is the same as going to time 4 (assuming the time context is at least 4 seconds + long), while going to time 12 on a 10 second slide is the same as going to time 8. + \li \c{Ping} - values less than 0 are treated as time 0, while values greater than the endtime + bounce off the end (eventually hitting 0.) + \endlist + */ +void Q3DSPresentation::goToTime(const QString &elementPath, float time) +{ + if (d_ptr->m_viewerApp) { + const QByteArray path(elementPath.toUtf8()); + d_ptr->m_viewerApp->GoToTime(path, time); + } else if (d_ptr->m_commandQueue) { + d_ptr->m_commandQueue->queueCommand(elementPath, CommandType_GoToTime, time); + } +} + +/*! + This API is for backwards compatibility. We recommend using \l{DataInput}s to control + attributes in the presentation. \l{DataInput} provides stronger contract between the + design and code as it avoids use of elementPath (a reference to design's + internal structure). + + Sets the \a value of an attribute (property) on the object specified by + \a elementPath. The \a attributeName is the \l{Attribute Names}{scripting + name} of the attribute. + + An element path refers to an object in the scene by name, for example, + \c{Scene.Layer.Camera}. Here the right camera object gets chosen even if + the scene contains other layers with the default camera names (for instance + \c{Scene.Layer2.Camera}). + + To reference an object stored in a property of another object, the dot + syntax can be used. The most typical example of this is changing the source + of a texture map by changing the \c sourcepath property on the object + selected by \c{SomeMaterial.diffusemap}. + + To access an object in a sub-presentation, prepend the name of the + sub-presentation followed by a colon, for example, + \c{SubPresentationOne:Scene.Layer.Camera}. + */ +void Q3DSPresentation::setAttribute(const QString &elementPath, const QString &attributeName, + const QVariant &value) +{ + if (d_ptr->m_viewerApp) { + const QByteArray path(elementPath.toUtf8()); + const QByteArray name(attributeName.toUtf8()); + + QByteArray valueStr; + float valueFloat; + + const void *theValue = nullptr; + switch (static_cast<QMetaType::Type>(value.type())) { + case QMetaType::Bool: + case QMetaType::Int: + case QMetaType::Double: + case QMetaType::Float: + valueFloat = value.toFloat(); + theValue = &valueFloat; + break; + case QMetaType::QString: + default: // Try string for other types + valueStr = value.toString().toUtf8(); + theValue = valueStr.constData(); + break; + } + d_ptr->m_viewerApp->SetAttribute(path, name, (char *)theValue); + } else if (d_ptr->m_commandQueue) { + d_ptr->m_commandQueue->queueCommand(elementPath, CommandType_SetAttribute, + attributeName, value); + } +} + +/*! + Activate or deactivate the presentation identified by \a id depending + on the value of \a active. + */ +void Q3DSPresentation::setPresentationActive(const QString &id, bool active) +{ + if (d_ptr->m_viewerApp) { + const QByteArray presId(id.toUtf8()); + d_ptr->m_viewerApp->SetPresentationActive(presId, active); + } else if (d_ptr->m_commandQueue) { + d_ptr->m_commandQueue->queueCommand(id, CommandType_SetPresentationActive, active); + } +} + +/*! + Dispatches a Qt 3D Studio presentation event with \a eventName on + scene object specified by \a elementPath. These events provide a + way to communicate with the \c .qml based \c{behavior scripts} + attached to scene objects since they can register to be notified + via Behavior::registerForEvent(). + + See setAttribute() for a description of \a elementPath. + */ +void Q3DSPresentation::fireEvent(const QString &elementPath, const QString &eventName) +{ + if (d_ptr->m_viewerApp) { + const QByteArray path(elementPath.toUtf8()); + const QByteArray name(eventName.toUtf8()); + d_ptr->m_viewerApp->FireEvent(path, name); + } else if (d_ptr->m_commandQueue) { + d_ptr->m_commandQueue->queueCommand(elementPath, CommandType_FireEvent, eventName); + } +} + +/*! + Set global animation time to manual value specified by \a milliseconds + (if non-zero) or resume normal timer (if zero). + */ +void Q3DSPresentation::setGlobalAnimationTime(qint64 milliseconds) +{ + if (d_ptr->m_viewerApp) { + d_ptr->m_viewerApp->SetGlobalAnimationTime(milliseconds); + } else { + d_ptr->m_commandQueue->m_globalAnimationTimeChanged = true; + d_ptr->m_commandQueue->m_globalAnimationTime = milliseconds; + } +} + +/*! + Sets the \a value of a data input element \a name in the presentation. + + Data input provides a higher level, designer-driven alternative to + Q3DSElement and setAttribute(). Instead of exposing a large set of + properties with their internal engine names, data input allows designers to + decide which properties should be writable by the application, and can + assign custom names to these data input entries, thus forming a + well-defined contract between the designer and the developer. + + In addition, data input also allows controlling the time line and the + current slide for time context objects (Scene or Component). Therefore it + is also an alternative to the goToSlide() and goToTime() family of APIs and + to Q3DSSceneElement. + + \sa DataInput + */ +void Q3DSPresentation::setDataInputValue(const QString &name, const QVariant &value, + Q3DSDataInput::ValueRole valueRole) +{ + if (d_ptr->m_viewerApp) { + d_ptr->m_viewerApp->SetDataInputValue(name, value, + (qt3ds::runtime::DataInputValueRole)valueRole); + } else if (d_ptr->m_commandQueue) { + d_ptr->m_commandQueue->queueCommand(QString(), CommandType_SetDataInputValue, + name, value, static_cast<int>(valueRole)); + } +} + +/*! + Adds a new child element for the element specified by \a parentElementPath to the slide + specified with \a slideName. Only model and group element creation is currently supported. + The \a properties hash table specifies the name-value pairs of the properties of the new + element. The property names are the same the setAttribute() recognizes. + + A referenced material element is also created for the new model element. The source material + name can be specified with custom "material" attribute in the \a properties hash. + The source material must exist in the same presentation where the element is created. + + The mesh for a model is specified with the \c sourcepath property. This can be a local file + path to \c .mesh file, a studio mesh primitive (e.g. \c{#Cube}), or the name of a mesh created + dynamically with createMesh(). + + A property/properties of the element can be bound to be controlled by an existing datainput. + Control bindings can be indicated with custom "controlledproperty" attribute in the + \a properties hash. The format for attribute value is "$<datainputname> <attributename>", i.e. + "$Datainput_1 rotation $Datainput_2 diffusecolor". If datainput name does not match with + any of the datainputs defined in UIA file, binding has no impact. + + The element is ready for use once elementsCreated() signal is received for it. + + \sa createElements + \sa createMaterial + \sa createMesh + \sa elementsCreated + \sa setAttribute + \sa dataInputs + */ +void Q3DSPresentation::createElement(const QString &parentElementPath, const QString &slideName, + const QHash<QString, QVariant> &properties) +{ + QVector<QHash<QString, QVariant>> theProperties; + theProperties << properties; + createElements(parentElementPath, slideName, theProperties); +} + +/*! + Adds multiple new child elements for the element specified by \a parentElementPath to the slide + specified with \a slideName. Element properties are specified in \a properties. + For more details, see createElement(). + + \sa createElement + \sa elementsCreated + */ +void Q3DSPresentation::createElements(const QString &parentElementPath, const QString &slideName, + const QVector<QHash<QString, QVariant>> &properties) +{ + if (d_ptr->m_viewerApp) { + d_ptr->m_viewerApp->createElements(parentElementPath, slideName, properties); + } else if (d_ptr->m_commandQueue) { + // We need to copy the properties map as queue takes ownership of it + QVector<QHash<QString, QVariant>> *theProperties + = new QVector<QHash<QString, QVariant>>(properties); + d_ptr->m_commandQueue->queueCommand(parentElementPath, CommandType_CreateElements, + slideName, theProperties); + } +} + +/*! + Deletes the element specified by \a elementPath and all its child elements. + Deleting elements is supported only for elements that have been dynamically created with + createElement() or createElements(). + + \sa deleteElements + \sa createElement + */ +void Q3DSPresentation::deleteElement(const QString &elementPath) +{ + QStringList elementPaths; + elementPaths << elementPath; + deleteElements(elementPaths); +} + +/*! + Deletes multiple elements specified by \a elementPaths and all their child elements. + Deleting elements is supported only for elements that have been dynamically created with + createElement() or createElements(). + + \sa deleteElement + */ +void Q3DSPresentation::deleteElements(const QStringList &elementPaths) +{ + if (d_ptr->m_viewerApp) { + d_ptr->m_viewerApp->deleteElements(elementPaths); + } else if (d_ptr->m_commandQueue) { + // We need to copy the list as queue takes ownership of it + QStringList *theElementPaths = new QStringList(elementPaths); + d_ptr->m_commandQueue->queueCommand(CommandType_DeleteElements, theElementPaths); + } + for (const auto &elementPath : elementPaths) + d_ptr->m_createdElements.removeAll(elementPath); +} + +/*! + \qmlproperty list<string> Presentation::createdElements + + This property contains a list of all dynamically created elements on this presentation. + + This property is read-only. + + \note Elements can only be dynamically created via C++ API. + + \sa createElement + \sa createElements +*/ + +/*! + \property Q3DSPresentation::createdElements + + This property contains a list of all dynamically created elements on this presentation. + + This property is read-only. + + \sa createElement + \sa createElements +*/ +QStringList Q3DSPresentation::createdElements() const +{ + return d_ptr->m_createdElements; +} + +/*! + Creates a material specified by the \a materialDefinition parameter into the subpresentation + specified by the \a subPresId parameter. If \a subPresId is empty, the material + is created into the main presentation. + + The \a materialDefinition parameter can contain either the file path to a Qt 3D Studio + material definition file or the actual material definition in the + Qt 3D Studio material definition format. The material definition is a XML file that specifies + the material properties. They are not meant to be hand crafted - to create one, simply create + a new basic material in the Qt 3D Studio editor and edit the material properties in the + inspector. The properties are stored into \c{materialName.materialdef} file in \c materials + folder of the project, which you can include into the resources of you application and + use with this method. + + After creation, the material can be used for new elements created via createElement() by + setting \c material property of the new element to the name of the created material. + The material name is specified by the \c name property of the material definition. + + The material is ready for use once materialsCreated() signal is received for it. + + \note Creating materials that utilise custom shaders with mipmapped textures can in some cases + corrupt the textures on other elements if the same textures are already used by existing basic + materials in the scene, as basic materials do not create mipmaps for their textures. + Typical symptom of this is black texture on another element after creating a new element using + the custom material. + + \sa createMaterials + \sa createElement + \sa materialsCreated + */ +void Q3DSPresentation::createMaterial(const QString &materialDefinition, + const QString &subPresId) +{ + QStringList materialDefinitions; + materialDefinitions << materialDefinition; + createMaterials(materialDefinitions, subPresId); +} + +/*! + Creates multiple materials specified by the \a materialDefinitions parameter into the + subpresentation specified by the \a subPresId parameter. If \a subPresId is empty, + the materials are created into the main presentation. + + For more details, see createMaterial(). + + \sa createMaterial + \sa materialsCreated + */ +void Q3DSPresentation::createMaterials(const QStringList &materialDefinitions, + const QString &subPresId) +{ + if (d_ptr->m_viewerApp) { + d_ptr->m_viewerApp->createMaterials(subPresId, materialDefinitions); + } else if (d_ptr->m_commandQueue) { + // We need to copy the list as queue takes ownership of it + QStringList *theMaterialDefinitions = new QStringList(materialDefinitions); + d_ptr->m_commandQueue->queueCommand(subPresId, CommandType_CreateMaterials, + theMaterialDefinitions); + } +} + +/*! + Deletes the material specified by \a materialName from the presentation. + To delete material from a subpresentation, prefix \a materialName with the subpresentation ID + similarly to the element paths. For example: \c{"SubPresentationOne:MyMaterial"}. + + Deleting materials is supported only for materials that have been dynamically created with + createMaterial() or createMaterials(). + + \sa deleteMaterials + \sa createMaterial + */ +void Q3DSPresentation::deleteMaterial(const QString &materialName) +{ + QStringList materialNames; + materialNames << materialName; + deleteMaterials(materialNames); +} + +/*! + Deletes materials specified by \a materialNames from the presentation. + To delete material from a subpresentation, prefix the material name with the subpresentation ID + similarly to the element paths. For example: \c{"SubPresentationOne:MyMaterial"}. + + Deleting materials is supported only for materials that have been dynamically created with + createMaterial() or createMaterials(). + + \sa deleteMaterial + */ +void Q3DSPresentation::deleteMaterials(const QStringList &materialNames) +{ + if (d_ptr->m_viewerApp) { + d_ptr->m_viewerApp->deleteMaterials(materialNames); + } else if (d_ptr->m_commandQueue) { + // We need to copy the list as queue takes ownership of it + QStringList *theMaterialNames = new QStringList(materialNames); + d_ptr->m_commandQueue->queueCommand(CommandType_DeleteMaterials, theMaterialNames); + } + for (const auto &name : materialNames) + d_ptr->m_createdMaterials.removeAll(name); +} + +/*! + \qmlproperty list<string> Presentation::createdMaterials + + This property contains a list of all dynamically created materials on this presentation. + + This property is read-only. + + \note Materials can only be dynamically created via C++ API. + + \sa createMaterial + \sa createMaterials +*/ + +/*! + \property Q3DSPresentation::createdMaterials + + This property contains a list of all dynamically created materials on this presentation. + + This property is read-only. + + \sa createMaterial + \sa createMaterials +*/ +QStringList Q3DSPresentation::createdMaterials() const +{ + return d_ptr->m_createdMaterials; +} + +/*! + Creates a mesh specified by given \a geometry. The given \a meshName can be used as + \c sourcepath property value for model elements created with future createElement() calls. + + The mesh is ready for use once meshesCreated() signal is received for it. + + \sa createElement + \sa createMeshes + \sa meshesCreated +*/ +void Q3DSPresentation::createMesh(const QString &meshName, const Q3DSGeometry &geometry) +{ + QHash<QString, const Q3DSGeometry *> meshData; + meshData.insert(meshName, &geometry); + createMeshes(meshData); +} + +/*! + Creates multiple meshes specified by given \a meshData. The data is mesh name and geometry + pairs. For more details, see createMesh(). + + The ownership of supplied geometries stays with the caller. + + \sa createMesh + \sa meshesCreated +*/ +void Q3DSPresentation::createMeshes(const QHash<QString, const Q3DSGeometry *> &meshData) +{ + // We can't refer to API class Q3DSGeometry on the runtime side, so let's grab the meshdata + // from Q3DSGeometryPrivate that is in runtime approved format and pass that on instead + auto theMeshData = new QHash<QString, Q3DSViewer::MeshData>; + QHashIterator<QString, const Q3DSGeometry *> it(meshData); + while (it.hasNext()) { + it.next(); + theMeshData->insert(it.key(), it.value()->d_ptr->meshData()); + } + + if (d_ptr->m_viewerApp) { + d_ptr->m_viewerApp->createMeshes(*theMeshData); + delete theMeshData; + } else if (d_ptr->m_commandQueue) { + d_ptr->m_commandQueue->queueCommand(CommandType_CreateMeshes, theMeshData); + } +} + +/*! + Deletes the mesh specified by \a meshName. + Deleting meshes is supported only for meshes that have been dynamically created with + createMesh() or createMeshes(). + + \sa deleteMeshes + \sa createMesh + */ +void Q3DSPresentation::deleteMesh(const QString &meshName) +{ + QStringList meshNames; + meshNames << meshName; + deleteMeshes(meshNames); +} + +/*! + Deletes meshes specified by \a meshNames. + Deleting meshes is supported only for meshes that have been dynamically created with + createMesh() or createMeshes(). + + \sa deleteMesh + */ +void Q3DSPresentation::deleteMeshes(const QStringList &meshNames) +{ + if (d_ptr->m_viewerApp) { + d_ptr->m_viewerApp->deleteMeshes(meshNames); + } else if (d_ptr->m_commandQueue) { + // We need to copy the list as queue takes ownership of it + QStringList *theMeshNames = new QStringList(meshNames); + d_ptr->m_commandQueue->queueCommand(CommandType_DeleteMeshes, theMeshNames); + } + for (const auto &name : meshNames) + d_ptr->m_createdMeshes.removeAll(name); +} + +/*! + \qmlproperty list<string> Presentation::createdMeshes + + This property contains a list of all dynamically created meshes on this presentation. + + This property is read-only. + + \note Meshes can only be dynamically created via C++ API. + + \sa createMesh + \sa createMeshes +*/ + +/*! + \property Q3DSPresentation::createdMeshes + + This property contains a list of all dynamically created meshes on this presentation. + + This property is read-only. + + \sa createMesh + \sa createMeshes +*/ +QStringList Q3DSPresentation::createdMeshes() const +{ + return d_ptr->m_createdMeshes; +} + +/*! + * \internal + */ +void Q3DSPresentation::mousePressEvent(QMouseEvent *e) +{ + if (d_ptr->m_viewerApp) { + d_ptr->m_viewerApp->HandleMousePress(e->x(), e->y(), e->button(), true); + } else if (d_ptr->m_commandQueue) { + d_ptr->m_commandQueue->queueCommand(QString(), CommandType_MousePress, + e->x(), e->y(), int(e->button())); + } +} + +/*! + * \internal + */ +void Q3DSPresentation::mouseReleaseEvent(QMouseEvent *e) +{ + if (d_ptr->m_viewerApp) { + d_ptr->m_viewerApp->HandleMousePress(e->x(), e->y(), e->button(), false); + } else if (d_ptr->m_commandQueue) { + d_ptr->m_commandQueue->queueCommand(QString(), CommandType_MouseRelease, + e->x(), e->y(), int(e->button())); + } +} + +/*! + * \internal + */ +void Q3DSPresentation::mouseMoveEvent(QMouseEvent *e) +{ + if (d_ptr->m_viewerApp) { + d_ptr->m_viewerApp->HandleMouseMove(e->x(), e->y(), true); + } else if (d_ptr->m_commandQueue) { + d_ptr->m_commandQueue->queueCommand(QString(), CommandType_MouseMove, + e->x(), e->y()); + } +} + +/*! + * \internal + */ +void Q3DSPresentation::wheelEvent(QWheelEvent *e) +{ + QPoint pixelData = e->pixelDelta(); + int numSteps = 0; + if (pixelData.isNull()) { + if (e->orientation() == Qt::Vertical) + numSteps = e->angleDelta().y() / 8; + else + numSteps = e->angleDelta().x() / 8; + } else { + // trackpad, pixel = one step in scroll wheel. + if (e->orientation() == Qt::Vertical) + numSteps = pixelData.y(); + else + numSteps = pixelData.x(); + } + if (numSteps != 0) { + if (d_ptr->m_viewerApp) { + d_ptr->m_viewerApp->HandleMouseWheel(e->x(), e->y(), + e->orientation() == Qt::Vertical ? 0 : 1, + numSteps); + } else if (d_ptr->m_commandQueue) { + d_ptr->m_commandQueue->queueCommand(QString(), CommandType_MouseWheel, + e->x(), e->y(), + int(e->orientation() == Qt::Vertical), numSteps); + } + } +} + +/*! + * \internal + */ +void Q3DSPresentation::keyPressEvent(QKeyEvent *e) +{ + if (d_ptr->m_viewerApp) { + d_ptr->m_viewerApp->HandleKeyInput(d_ptr->getScanCode(e), true); + } else if (d_ptr->m_commandQueue) { + d_ptr->m_commandQueue->queueCommand(QString(), CommandType_KeyPress, + d_ptr->getScanCode(e)); + } +} + +/*! + * \internal + */ +void Q3DSPresentation::keyReleaseEvent(QKeyEvent *e) +{ + if (d_ptr->m_viewerApp) { + d_ptr->m_viewerApp->HandleKeyInput(d_ptr->getScanCode(e), false); + } else if (d_ptr->m_commandQueue) { + d_ptr->m_commandQueue->queueCommand(QString(), CommandType_KeyRelease, + d_ptr->getScanCode(e)); + } +} + +// #TODO: QT3DS-3562 Most Presentation signals missing documentation +/*! + * \qmlsignal Presentation::slideEntered + * Emitted when + * \param elementPath + * \param index + * \param name + */ + +/*! + * \fn Q3DSPresentation::slideEntered + * Emitted when + * \param elementPath + * \param index + * \param name + */ + +/*! + * \qmlsignal Presentation::slideExited + * Emitted when + * \param elementPath + * \param index + * \param name + */ + +/*! + * \fn Q3DSPresentation::slideExited + * Emitted when + * \param elementPath + * \param index + * \param name + */ + +/*! + * \fn Q3DSPresentation::dataInputsReady + * Emitted when \l{DataInput}s in the Studio project have been parsed and data inputs are available + * through dataInputs() and getDataInputs() methods. + */ + +/*! + * \fn Q3DSPresentation::dataOutputsReady + * Emitted when \l{DataOutput}s in the Studio project have been parsed and data outputs are available + * through dataOutputs() and getDataOutputs() methods. + */ + +/*! + * \qmlsignal Presentation::customSignalEmitted + * Emitted when + * \param elementPath + * \param name + */ + +/*! + * \fn Q3DSPresentation::customSignalEmitted + * Emitted when + * \param elementPath + * \param name + */ + +/*! + \qmlsignal Presentation::elementsCreated + Emitted when one or more elements have been created in response to createElement() + or createElements() calls. The \a elementPaths list contains the element paths of the created + elements. If creation failed, \a error string indicates the reason. + + \sa createElement + \sa createElements + */ + +/*! + \fn Q3DSPresentation::elementsCreated + Emitted when one or more elements have been created in response to createElement() + or createElements() calls. The \a elementPaths list contains the element paths of the created + elements. If creation failed, \a error string indicates the reason. + + \sa createElement + \sa createElements + */ + +/*! + \qmlsignal Presentation::materialsCreated + Emitted when one or more materials have been created in response to createMaterial() + or createMaterials() calls. The \a materialNames list contains the names of the created + materials. If the material is created into a subpresentation, the name is prefixed with + subpresentation ID followed by a colon. + If creation failed, \a error string indicates the reason. + + \sa createMaterial + \sa createMaterials + */ + +/*! + \fn Q3DSPresentation::materialsCreated + Emitted when one or more materials have been created in response to createMaterial() + or createMaterials() calls. The \a materialNames list contains the names of the created + materials. If creation failed, \a error string indicates the reason. + + \sa createMaterial + \sa createMaterials + */ + +/*! + \qmlsignal Presentation::meshesCreated + Emitted when one or more meshes have been created in response to createMesh() + or createMeshes() calls. The \a meshNames list contains the names of the created + meshes. If creation failed, \a error string indicates the reason. + + \sa createMesh + \sa createMeshes + */ + +/*! + \fn Q3DSPresentation::meshesCreated + Emitted when one or more meshes have been created in response to createMesh() + or createMeshes() calls. The \a meshNames list contains the names of the created + meshes. If creation failed, \a error string indicates the reason. + + \sa createMesh + \sa createMeshes + */ + +/*! + * \internal + */ +Q3DSPresentationPrivate::Q3DSPresentationPrivate(Q3DSPresentation *q) + : QObject(q) + , q_ptr(q) + , m_viewerApp(nullptr) + , m_commandQueue(nullptr) + , m_streamProxy(nullptr) + , m_delayedLoading(false) +{ +} + +Q3DSPresentationPrivate::~Q3DSPresentationPrivate() +{ + unregisterAllElements(); + unregisterAllDataInputs(); + unregisterAllDataOutputs(); + delete m_streamProxy; +} + +void Q3DSPresentationPrivate::setSource(const QUrl &source) +{ + m_source = source; + if (m_commandQueue) { + m_commandQueue->m_sourceChanged = true; + m_commandQueue->m_source = source; + } +} + +void Q3DSPresentationPrivate::setVariantList(const QStringList &variantList) +{ + m_variantList = variantList; + if (m_commandQueue) { + m_commandQueue->m_variantListChanged = true; + m_commandQueue->m_variantList = variantList; + } +} + +void Q3DSPresentationPrivate::setViewerApp(Q3DSViewer::Q3DSViewerApp *app, bool connectApp) +{ + Q3DSViewer::Q3DSViewerApp *oldApp = m_viewerApp; + m_viewerApp = app; + + const auto elements = m_elements.values(); + for (Q3DSElement *element : elements) + element->d_ptr->setViewerApp(app); + + if (m_viewerApp) { + const auto dataInputs = m_viewerApp->dataInputs(); + for (const auto &name : dataInputs) { + if (!m_dataInputs.contains(name)) { + // Name is sufficient for C++ side APIs, as other parameters + // (max/min) are queried synchronously. + auto *di = new Q3DSDataInput(name, nullptr); + registerDataInput(di); + } + } + Q_EMIT q_ptr->dataInputsReady(); + } + + if (connectApp) { + if (app) { + connect(app, &Q3DSViewer::Q3DSViewerApp::SigSlideEntered, + this, &Q3DSPresentationPrivate::handleSlideEntered); + connect(app, &Q3DSViewer::Q3DSViewerApp::SigSlideExited, + q_ptr, &Q3DSPresentation::slideExited); + connect(app, &Q3DSViewer::Q3DSViewerApp::SigCustomSignal, + q_ptr, &Q3DSPresentation::customSignalEmitted); + connect(app, &Q3DSViewer::Q3DSViewerApp::SigDataOutputValueUpdated, + this, &Q3DSPresentationPrivate::handleDataOutputValueUpdate); + connect(app, &Q3DSViewer::Q3DSViewerApp::SigElementsCreated, + this, &Q3DSPresentationPrivate::handleElementsCreated); + connect(app, &Q3DSViewer::Q3DSViewerApp::SigMaterialsCreated, + this, &Q3DSPresentationPrivate::handleMaterialsCreated); + connect(app, &Q3DSViewer::Q3DSViewerApp::SigMeshesCreated, + this, &Q3DSPresentationPrivate::handleMeshesCreated); + } + if (oldApp) { + disconnect(oldApp, &Q3DSViewer::Q3DSViewerApp::SigSlideEntered, + this, &Q3DSPresentationPrivate::handleSlideEntered); + disconnect(oldApp, &Q3DSViewer::Q3DSViewerApp::SigSlideExited, + q_ptr, &Q3DSPresentation::slideExited); + disconnect(oldApp, &Q3DSViewer::Q3DSViewerApp::SigCustomSignal, + q_ptr, &Q3DSPresentation::customSignalEmitted); + disconnect(oldApp, &Q3DSViewer::Q3DSViewerApp::SigDataOutputValueUpdated, + this, &Q3DSPresentationPrivate::handleDataOutputValueUpdate); + disconnect(oldApp, &Q3DSViewer::Q3DSViewerApp::SigElementsCreated, + this, &Q3DSPresentationPrivate::handleElementsCreated); + disconnect(oldApp, &Q3DSViewer::Q3DSViewerApp::SigMaterialsCreated, + this, &Q3DSPresentationPrivate::handleMaterialsCreated); + disconnect(oldApp, &Q3DSViewer::Q3DSViewerApp::SigMeshesCreated, + this, &Q3DSPresentationPrivate::handleMeshesCreated); + } + } +} + +void Q3DSPresentationPrivate::setCommandQueue(CommandQueue *queue) +{ + m_commandQueue = queue; + + const auto elements = m_elements.values(); + const auto dataInputs = m_dataInputs.values(); + for (Q3DSElement *element : elements) + element->d_ptr->setCommandQueue(queue); + for (Q3DSDataInput *di : dataInputs) + di->d_ptr->setCommandQueue(queue); + + if (m_commandQueue) { + setDelayedLoading(m_delayedLoading); + setVariantList(m_variantList); + // Queue a request ASAP for datainputs and outputs defined in UIA file so that + // getDataInputs has up-to-date info at the earliest and that data outputs + // connect from source to destination + m_commandQueue->queueCommand({}, CommandType_RequestDataInputs); + m_commandQueue->queueCommand({}, CommandType_RequestDataOutputs); + setSource(m_source); + } +} + +void Q3DSPresentationPrivate::setDelayedLoading(bool enable) +{ + m_delayedLoading = enable; + if (m_commandQueue) { + m_commandQueue->m_delayedLoading = enable; + m_commandQueue->m_delayedLoadingChanged = true; + } +} + +void Q3DSPresentationPrivate::requestResponseHandler(CommandType commandType, void *requestData) +{ + switch (commandType) { + case CommandType_RequestDataInputs: { + QVariantList *response = reinterpret_cast<QVariantList *>(requestData); + + for (int i = 0; i < response->size(); ++i) { + // Check and append to QML-side list if the (UIA) presentation has additional datainputs + // that are not explicitly defined in QML code. + auto receivedDI = response->at(i).value<Q3DSDataInput *>(); + // For QML behind async command queue, we cache min/max and metadata values in addition + // to name, in order to be able to return values initially set in UIA file (in QML + // getters). + if (!m_dataInputs.contains(receivedDI->name())) { + auto newDI = new Q3DSDataInput(receivedDI->name(), nullptr); + newDI->d_ptr->m_min = receivedDI->d_ptr->m_min; + newDI->d_ptr->m_max = receivedDI->d_ptr->m_max; + newDI->d_ptr->m_metadata = receivedDI->d_ptr->m_metadata; + registerDataInput(newDI); + } else { + m_dataInputs[receivedDI->name()]->d_ptr->m_min = receivedDI->d_ptr->m_min; + m_dataInputs[receivedDI->name()]->d_ptr->m_max = receivedDI->d_ptr->m_max; + m_dataInputs[receivedDI->name()]->d_ptr->m_metadata = receivedDI->d_ptr->m_metadata; + } + } + delete response; + Q_EMIT q_ptr->dataInputsReady(); + break; + } + case CommandType_RequestDataOutputs: { + QVariantList *response = reinterpret_cast<QVariantList *>(requestData); + + for (int i = 0; i < response->size(); ++i) { + // Check and append to QML-side list if the (UIA) presentation has additional + // dataoutputs that are not explicitly defined in QML code. + if (!m_dataOutputs.contains(response->at(i).value<QString>())) + registerDataOutput(new Q3DSDataOutput(response->at(i).value<QString>(), nullptr)); + } + delete response; + Q_EMIT q_ptr->dataOutputsReady(); + break; + } + default: + Q_ASSERT(false); + break; + } +} + +// Doc note: The ownership of the registered scenes remains with the caller, who needs to +// ensure that registered scenes are alive as long as the presentation is alive. +void Q3DSPresentationPrivate::registerElement(Q3DSElement *element) +{ + Q_ASSERT(!element->elementPath().isEmpty()); + + // Allow only single registration for each element path and scene object + QMutableHashIterator<QString, Q3DSElement *> i(m_elements); + while (i.hasNext()) { + i.next(); + if (i.value() == element) { + // If the same scene object is already registered with different path, + // remove it from the map to avoid duplication. + if (i.key() != element->elementPath()) + i.remove(); + } else if (i.key() == element->elementPath()) { + // If the same element path is registered by another scene object, the old + // scene object is unregistered. + i.value()->d_ptr->setViewerApp(nullptr); + i.value()->d_ptr->setPresentation(nullptr); + i.remove(); + } + } + + element->d_ptr->setViewerApp(m_viewerApp); + element->d_ptr->setCommandQueue(m_commandQueue); + element->d_ptr->setPresentation(this); + + m_elements.insert(element->elementPath(), element); +} + +void Q3DSPresentationPrivate::unregisterElement(Q3DSElement *element) +{ + Q3DSElement *oldScene = m_elements.value(element->elementPath()); + if (oldScene == element) { + element->d_ptr->setViewerApp(nullptr); + element->d_ptr->setCommandQueue(nullptr); + element->d_ptr->setPresentation(nullptr); + m_elements.remove(element->elementPath()); + } +} + +void Q3DSPresentationPrivate::unregisterAllElements() +{ + for (Q3DSElement *element : m_elements.values()) { + element->d_ptr->setViewerApp(nullptr); + element->d_ptr->setCommandQueue(nullptr); + element->d_ptr->setPresentation(nullptr); + } + m_elements.clear(); +} + +void Q3DSPresentationPrivate::registerDataInput(Q3DSDataInput *dataInput) +{ + Q_ASSERT(!dataInput->name().isEmpty()); + + // Allow only single registration for each DataInput + QMutableHashIterator<QString, Q3DSDataInput *> i(m_dataInputs); + while (i.hasNext()) { + i.next(); + if (i.value() == dataInput) { + // If the same DataInput object is already registered with different name, + // remove it from the map to avoid duplication. + if (i.key() != dataInput->name()) + i.remove(); + } else if (i.key() == dataInput->name()) { + // If the same name is registered by another DataInput object, the old + // DataInput object is unregistered. + i.value()->d_ptr->setViewerApp(nullptr); + i.value()->d_ptr->setPresentation(nullptr); + i.remove(); + } + } + + dataInput->d_ptr->setPresentation(q_ptr); + dataInput->d_ptr->setViewerApp(m_viewerApp); + dataInput->d_ptr->setCommandQueue(m_commandQueue); + + m_dataInputs.insert(dataInput->name(), dataInput); +} + +void Q3DSPresentationPrivate::unregisterDataInput(Q3DSDataInput *dataInput) +{ + Q3DSDataInput *oldDi = m_dataInputs.value(dataInput->name()); + if (oldDi == dataInput) { + dataInput->d_ptr->setCommandQueue(nullptr); + dataInput->d_ptr->setViewerApp(nullptr); + dataInput->d_ptr->setPresentation(nullptr); + m_dataInputs.remove(dataInput->name()); + } +} + +void Q3DSPresentationPrivate::unregisterAllDataInputs() +{ + for (Q3DSDataInput *di : m_dataInputs.values()) { + di->d_ptr->setViewerApp(nullptr); + di->d_ptr->setCommandQueue(nullptr); + di->d_ptr->setPresentation(nullptr); + } + m_dataInputs.clear(); +} +bool Q3DSPresentationPrivate::isValidDataInput(const Q3DSDataInput *dataInput) const +{ + // For QML instance separated from runtime engine by command queue, + // check locally cached list for this datainput (initialised at presentation load). + if (!m_viewerApp) { + if (m_dataInputs.contains(dataInput->name())) + return true; + else + return false; + } + + return m_viewerApp->dataInputs().contains(dataInput->name()); +} + +float Q3DSPresentationPrivate::dataInputMin(const QString &name) const +{ + // For QML instance separated from runtime engine by command queue, + // return locally cached value (initialised at presentation load). + if (!m_viewerApp) { + if (m_dataInputs.contains(name)) + return m_dataInputs[name]->d_ptr->m_min; + else + return 0.0f; + } + return m_viewerApp->dataInputMin(name); +} + +float Q3DSPresentationPrivate::dataInputMax(const QString &name) const +{ + // For QML instance separated from runtime engine by command queue, + // return locally cached value (initialised at presentation load). + if (!m_viewerApp) { + if (m_dataInputs.contains(name)) + return m_dataInputs[name]->d_ptr->m_max; + else + return 0.0f; + } + return m_viewerApp->dataInputMax(name); +} + +QHash<QString, QString> Q3DSPresentationPrivate::dataInputMetadata(const QString &name) const +{ + // For QML instance separated from runtime engine by command queue, + // return locally cached value (initialised at presentation load). + if (!m_viewerApp) { + if (m_dataInputs.contains(name)) + return m_dataInputs[name]->d_ptr->m_metadata; + else + return {}; + } + return m_viewerApp->dataInputMetadata(name); +} + +QVector<Q3DSDataInput *> Q3DSPresentationPrivate::dataInputs(const QString &key) const +{ + QVector<Q3DSDataInput *> ret; + // For QML instance separated from runtime engine by command queue, + // return locally cached value (initialised at presentation load). + if (!m_viewerApp) { + for (const auto &it : m_dataInputs) { + if (it->metadataKeys().contains(key)) + ret.append(it); + } + } else { + // Otherwise, defer to viewer app. + const auto &diList = m_viewerApp->dataInputs(); + // We fetch the metadata(s) from the source (viewer app) but + // return the corresponding datainput object(s) held by presentation item. + for (const auto &it : diList) { + if (m_viewerApp->dataInputMetadata(it).contains(key)) + ret.append(m_dataInputs[it]); + } + } + + return ret; +} + +void Q3DSPresentationPrivate::registerDataOutput(Q3DSDataOutput *dataOutput) +{ + Q_ASSERT(!dataOutput->name().isEmpty()); + + // Allow only single registration for each DataOutput + QMutableHashIterator<QString, Q3DSDataOutput *> i(m_dataOutputs); + while (i.hasNext()) { + i.next(); + if (i.value() == dataOutput) { + // If the same DataOutput object is already registered with different name, + // remove it from the map to avoid duplication. + if (i.key() != dataOutput->name()) + i.remove(); + } else if (i.key() == dataOutput->name()) { + // If the same name is registered by another DataOutput object, the old + // DataOutput object is unregistered. + i.value()->d_ptr->setViewerApp(nullptr); + i.value()->d_ptr->setPresentation(nullptr); + i.value()->d_ptr->setCommandQueue(nullptr); + i.remove(); + } + } + + dataOutput->d_ptr->setPresentation(q_ptr); + dataOutput->d_ptr->setViewerApp(m_viewerApp); + dataOutput->d_ptr->setCommandQueue(m_commandQueue); + + m_dataOutputs.insert(dataOutput->name(), dataOutput); +} + +void Q3DSPresentationPrivate::unregisterDataOutput(Q3DSDataOutput *dataOutput) +{ + Q3DSDataOutput *oldDout = m_dataOutputs.value(dataOutput->name()); + if (oldDout == dataOutput) { + dataOutput->d_ptr->setCommandQueue(nullptr); + dataOutput->d_ptr->setViewerApp(nullptr); + dataOutput->d_ptr->setPresentation(nullptr); + m_dataOutputs.remove(dataOutput->name()); + } +} + +void Q3DSPresentationPrivate::unregisterAllDataOutputs() +{ + const auto values = m_dataOutputs.values(); + for (Q3DSDataOutput *dout : values) { + dout->d_ptr->setViewerApp(nullptr); + dout->d_ptr->setCommandQueue(nullptr); + dout->d_ptr->setPresentation(nullptr); + } + m_dataOutputs.clear(); +} + +bool Q3DSPresentationPrivate::isValidDataOutput(const Q3DSDataOutput *dataOutput) const +{ + if (!m_viewerApp) + return false; + + return m_viewerApp->dataOutputs().contains(dataOutput->name()); +} + +Q3DStudio::EKeyCode Q3DSPresentationPrivate::getScanCode(QKeyEvent *e) +{ + enum { + RIGHT_SHIFT = 0x036, + RIGHT_CTRL = 0x11d, + RIGHT_ALT = 0x138, + }; + + Qt::Key keyScanCode = static_cast<Qt::Key>(e->key()); + + Q3DStudio::EKeyCode newScanCode = Q3DStudio::KEY_NOKEY; + switch (keyScanCode) { + case Qt::Key_Down: + newScanCode = Q3DStudio::KEY_DOWN; + break; + case Qt::Key_Up: + newScanCode = Q3DStudio::KEY_UP; + break; + case Qt::Key_Left: + newScanCode = Q3DStudio::KEY_LEFT; + break; + case Qt::Key_Right: + newScanCode = Q3DStudio::KEY_RIGHT; + break; + case Qt::Key_Return: + case Qt::Key_Enter: + newScanCode = e->modifiers() == Qt::KeypadModifier ? Q3DStudio::KEY_NUMPADENTER + : Q3DStudio::KEY_RETURN; + break; + case Qt::Key_Backspace: + newScanCode = Q3DStudio::KEY_BACK; + break; + case Qt::Key_Tab: + newScanCode = Q3DStudio::KEY_TAB; + break; + case Qt::Key_Escape: + newScanCode = Q3DStudio::KEY_ESCAPE; + break; + case Qt::Key_A: + newScanCode = Q3DStudio::KEY_A; + break; + case Qt::Key_B: + newScanCode = Q3DStudio::KEY_B; + break; + case Qt::Key_C: + newScanCode = Q3DStudio::KEY_C; + break; + case Qt::Key_D: + newScanCode = Q3DStudio::KEY_D; + break; + case Qt::Key_E: + newScanCode = Q3DStudio::KEY_E; + break; + case Qt::Key_F: + newScanCode = Q3DStudio::KEY_F; + break; + case Qt::Key_G: + newScanCode = Q3DStudio::KEY_G; + break; + case Qt::Key_H: + newScanCode = Q3DStudio::KEY_H; + break; + case Qt::Key_I: + newScanCode = Q3DStudio::KEY_I; + break; + case Qt::Key_J: + newScanCode = Q3DStudio::KEY_J; + break; + case Qt::Key_K: + newScanCode = Q3DStudio::KEY_K; + break; + case Qt::Key_L: + newScanCode = Q3DStudio::KEY_L; + break; + case Qt::Key_M: + newScanCode = Q3DStudio::KEY_M; + break; + case Qt::Key_N: + newScanCode = Q3DStudio::KEY_N; + break; + case Qt::Key_O: + newScanCode = Q3DStudio::KEY_O; + break; + case Qt::Key_P: + newScanCode = Q3DStudio::KEY_P; + break; + case Qt::Key_Q: + newScanCode = Q3DStudio::KEY_Q; + break; + case Qt::Key_R: + newScanCode = Q3DStudio::KEY_R; + break; + case Qt::Key_S: + newScanCode = Q3DStudio::KEY_S; + break; + case Qt::Key_T: + newScanCode = Q3DStudio::KEY_T; + break; + case Qt::Key_U: + newScanCode = Q3DStudio::KEY_U; + break; + case Qt::Key_V: + newScanCode = Q3DStudio::KEY_V; + break; + case Qt::Key_W: + newScanCode = Q3DStudio::KEY_W; + break; + case Qt::Key_X: + newScanCode = Q3DStudio::KEY_X; + break; + case Qt::Key_Y: + newScanCode = Q3DStudio::KEY_Y; + break; + case Qt::Key_Z: + newScanCode = Q3DStudio::KEY_Z; + break; + case Qt::Key_0: + newScanCode = + e->modifiers() == Qt::KeypadModifier ? Q3DStudio::KEY_NUMPAD0 : Q3DStudio::KEY_0; + break; + case Qt::Key_1: + newScanCode = + e->modifiers() == Qt::KeypadModifier ? Q3DStudio::KEY_NUMPAD1 : Q3DStudio::KEY_1; + break; + case Qt::Key_2: + newScanCode = + e->modifiers() == Qt::KeypadModifier ? Q3DStudio::KEY_NUMPAD2 : Q3DStudio::KEY_2; + break; + case Qt::Key_3: + newScanCode = + e->modifiers() == Qt::KeypadModifier ? Q3DStudio::KEY_NUMPAD3 : Q3DStudio::KEY_3; + break; + case Qt::Key_4: + newScanCode = + e->modifiers() == Qt::KeypadModifier ? Q3DStudio::KEY_NUMPAD4 : Q3DStudio::KEY_4; + break; + case Qt::Key_5: + newScanCode = + e->modifiers() == Qt::KeypadModifier ? Q3DStudio::KEY_NUMPAD5 : Q3DStudio::KEY_5; + break; + case Qt::Key_6: + newScanCode = + e->modifiers() == Qt::KeypadModifier ? Q3DStudio::KEY_NUMPAD6 : Q3DStudio::KEY_6; + break; + case Qt::Key_7: + newScanCode = + e->modifiers() == Qt::KeypadModifier ? Q3DStudio::KEY_NUMPAD7 : Q3DStudio::KEY_7; + break; + case Qt::Key_8: + newScanCode = + e->modifiers() == Qt::KeypadModifier ? Q3DStudio::KEY_NUMPAD8 : Q3DStudio::KEY_8; + break; + case Qt::Key_9: + newScanCode = + e->modifiers() == Qt::KeypadModifier ? Q3DStudio::KEY_NUMPAD9 : Q3DStudio::KEY_9; + break; + case Qt::Key_Minus: + newScanCode = e->modifiers() == Qt::KeypadModifier ? Q3DStudio::KEY_NUMPADSUBTRACT + : Q3DStudio::KEY_SUBTRACT; + break; + case Qt::Key_Plus: + newScanCode = e->modifiers() == Qt::KeypadModifier ? Q3DStudio::KEY_NUMPADADD + : Q3DStudio::KEY_EQUALS; + break; + case Qt::Key_NumLock: + newScanCode = Q3DStudio::KEY_NUMLOCK; + break; + case Qt::Key_ScrollLock: + newScanCode = Q3DStudio::KEY_SCROLL; + break; + case Qt::Key_CapsLock: + newScanCode = Q3DStudio::KEY_CAPITAL; + break; + case Qt::Key_Pause: + newScanCode = Q3DStudio::KEY_PAUSE; + break; + case Qt::Key_Print: + newScanCode = Q3DStudio::KEY_PRINTSCREEN; + break; + case Qt::Key_Insert: + newScanCode = Q3DStudio::KEY_INSERT; + break; + case Qt::Key_Delete: + newScanCode = Q3DStudio::KEY_DELETE; + break; + case Qt::Key_Home: + newScanCode = Q3DStudio::KEY_HOME; + break; + case Qt::Key_End: + newScanCode = Q3DStudio::KEY_END; + break; + case Qt::Key_PageUp: + newScanCode = Q3DStudio::KEY_PGUP; + break; + case Qt::Key_PageDown: + newScanCode = Q3DStudio::KEY_PGDN; + break; + case Qt::Key_F1: + newScanCode = Q3DStudio::KEY_F1; + break; + case Qt::Key_F2: + newScanCode = Q3DStudio::KEY_F2; + break; + case Qt::Key_F3: + newScanCode = Q3DStudio::KEY_F3; + break; + case Qt::Key_F4: + newScanCode = Q3DStudio::KEY_F4; + break; + case Qt::Key_F5: + newScanCode = Q3DStudio::KEY_F5; + break; + case Qt::Key_F6: + newScanCode = Q3DStudio::KEY_F6; + break; + case Qt::Key_F7: + newScanCode = Q3DStudio::KEY_F7; + break; + case Qt::Key_F8: + newScanCode = Q3DStudio::KEY_F8; + break; + case Qt::Key_F9: + newScanCode = Q3DStudio::KEY_F9; + break; + case Qt::Key_F10: + newScanCode = Q3DStudio::KEY_F10; + break; + case Qt::Key_F11: + newScanCode = Q3DStudio::KEY_F11; + break; + case Qt::Key_F12: + newScanCode = Q3DStudio::KEY_F12; + break; + case Qt::Key_F13: + newScanCode = Q3DStudio::KEY_F13; + break; + case Qt::Key_F14: + newScanCode = Q3DStudio::KEY_F14; + break; + case Qt::Key_QuoteLeft: + newScanCode = Q3DStudio::KEY_GRAVE; + break; + case Qt::Key_Asterisk: + newScanCode = Q3DStudio::KEY_MULTIPLY; + break; + case Qt::Key_BracketRight: + newScanCode = Q3DStudio::KEY_RBRACKET; + break; + case Qt::Key_BracketLeft: + newScanCode = Q3DStudio::KEY_LBRACKET; + break; + case Qt::Key_Semicolon: + newScanCode = Q3DStudio::KEY_SEMICOLON; + break; + case Qt::Key_Comma: + newScanCode = Q3DStudio::KEY_COMMA; + break; + case Qt::Key_Period: + newScanCode = e->modifiers() == Qt::KeypadModifier ? Q3DStudio::KEY_NUMPADDECIMAL + : Q3DStudio::KEY_PERIOD; + break; + case Qt::Key_Apostrophe: + newScanCode = Q3DStudio::KEY_APOSTROPHE; + break; + case Qt::Key_Slash: + newScanCode = Q3DStudio::KEY_SLASH; + break; + case Qt::Key_Backslash: + newScanCode = Q3DStudio::KEY_BACKSLASH; + break; + case Qt::Key_Equal: + newScanCode = Q3DStudio::KEY_EQUALS; + break; + case Qt::Key_Space: + newScanCode = Q3DStudio::KEY_SPACE; + break; + case Qt::Key_Shift: + newScanCode = + e->nativeScanCode() == RIGHT_SHIFT ? Q3DStudio::KEY_RSHIFT : Q3DStudio::KEY_LSHIFT; + break; + case Qt::Key_Control: + newScanCode = e->nativeScanCode() == RIGHT_CTRL ? Q3DStudio::KEY_RCONTROL + : Q3DStudio::KEY_LCONTROL; + break; + case Qt::Key_Alt: + newScanCode = + e->nativeScanCode() == RIGHT_ALT ? Q3DStudio::KEY_RALT : Q3DStudio::KEY_LALT; + break; + default: + break; + } + + return newScanCode; +} + +ViewerQmlStreamProxy *Q3DSPresentationPrivate::streamProxy() +{ + if (!m_streamProxy) + m_streamProxy = new ViewerQmlStreamProxy(); + return m_streamProxy; +} + +void Q3DSPresentationPrivate::handleSlideEntered(const QString &elementPath, unsigned int index, + const QString &name) +{ + Q3DSSceneElement *scene = qobject_cast<Q3DSSceneElement *>(m_elements.value(elementPath)); + if (scene) + scene->d_func()->handleSlideEntered(index, name); + Q_EMIT q_ptr->slideEntered(elementPath, index, name); +} + +void Q3DSPresentationPrivate::handleDataOutputValueUpdate(const QString &name, + const QVariant &newValue) +{ + if (!m_dataOutputs.contains(name)) + return; + + Q3DSDataOutput *node = m_dataOutputs[name]; + node->setValue(newValue); +} + +void Q3DSPresentationPrivate::handleElementsCreated(const QStringList &elementPaths, + const QString &error) +{ + if (error.isEmpty()) + m_createdElements << elementPaths; + + Q_EMIT q_ptr->elementsCreated(elementPaths, error); +} + +void Q3DSPresentationPrivate::handleMaterialsCreated(const QStringList &materialNames, + const QString &error) +{ + if (error.isEmpty()) + m_createdMaterials << materialNames; + + Q_EMIT q_ptr->materialsCreated(materialNames, error); +} + +void Q3DSPresentationPrivate::handleMeshesCreated(const QStringList &meshNames, + const QString &error) +{ + if (error.isEmpty()) + m_createdMeshes << meshNames; + + Q_EMIT q_ptr->meshesCreated(meshNames, error); +} + +QT_END_NAMESPACE |