diff options
Diffstat (limited to 'src/Authoring/Qt3DStudio/Palettes')
232 files changed, 41563 insertions, 0 deletions
diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/ActionContextMenu.cpp b/src/Authoring/Qt3DStudio/Palettes/Action/ActionContextMenu.cpp new file mode 100644 index 00000000..1e194d79 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/ActionContextMenu.cpp @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) 2005 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSCommonPrecompile.h" + +#include "ActionContextMenu.h" +#include "StudioClipboard.h" + +CActionContextMenu::CActionContextMenu(QList<QAction *> actions, QWidget *parent) + : QMenu(parent) +{ + addActions(actions); +} + +CActionContextMenu::~CActionContextMenu() +{ +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/ActionContextMenu.h b/src/Authoring/Qt3DStudio/Palettes/Action/ActionContextMenu.h new file mode 100644 index 00000000..99d785aa --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/ActionContextMenu.h @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** Copyright (C) 2005 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef INCLUDED_ACTION_CONTEXT_MENU_H +#define INCLUDED_ACTION_CONTEXT_MENU_H 1 + +#include <QtWidgets/qmenu.h> + +class CActionContextMenu : public QMenu +{ + Q_OBJECT +public: + explicit CActionContextMenu(QList<QAction *> actions, QWidget *parent = nullptr); + virtual ~CActionContextMenu(); +}; +#endif // INCLUDED_ACTION_CONTEXT_MENU_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/ActionModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Action/ActionModel.cpp new file mode 100644 index 00000000..556215de --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/ActionModel.cpp @@ -0,0 +1,235 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "ActionModel.h" + +#include "Qt3DSCommonPrecompile.h" +#include "ClientDataModelBridge.h" +#include "CmdDataModelActionSetValue.h" +#include "Core.h" +#include "Doc.h" +#include "StudioApp.h" +#include "Qt3DSDMActionSystem.h" +#include "Qt3DSDMStudioSystem.h" + +ActionModel::ActionModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +void ActionModel::setInstanceHandle(const qt3dsdm::Qt3DSDMInstanceHandle &handle) +{ + beginResetModel(); + m_handle = handle; + auto doc = g_StudioApp.GetCore()->GetDoc(); + m_actions.clear(); + if (handle.Valid()) { + doc->GetStudioSystem()->GetActionSystem()->GetActions(doc->GetActiveSlide(), + handle, m_actions); + } + + endResetModel(); +} + +QHash<int, QByteArray> ActionModel::roleNames() const +{ + auto names = QAbstractItemModel::roleNames(); + names.insert(DescriptionRole, "description"); + names.insert(VisibleRole, "visible"); + + return names; +} + + +int ActionModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return int(m_actions.size()); +} + +QVariant ActionModel::data(const QModelIndex &index, int role) const +{ + if (!hasIndex(index.row(), index.column(), index.parent())) + return {}; + + const auto action = m_actions.at(index.row()); + auto system = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem(); + if (action.Valid()) { + auto actionCore = system->GetActionCore(); + + // Ensure the handle is still valid on the back-end, as some undo scenarios may cause this + // function to be called for already deleted actions + if (actionCore->HandleValid(action)) { + switch (role) + { + case DescriptionRole: + return actionString(action); + case VisibleRole: + return system->GetActionSystem()->GetActionEyeballValue(activeSlide(), action); + default: + return {}; + } + } + } + return {}; +} + +bool ActionModel::setData(const QModelIndex &index, const QVariant &data, int role) +{ + if (!hasIndex(index.row(), index.column(), index.parent())) + return false; + + const auto action = m_actions.at(index.row()); + + if (role == VisibleRole) { + auto doc = g_StudioApp.GetCore()->GetDoc(); + CCmd *theCmd = new CCmdDataModelActionSetEyeball(doc, activeSlide(), action, data.toBool()); + g_StudioApp.GetCore()->ExecuteCommand(theCmd); + Q_EMIT dataChanged(index, index, {VisibleRole}); + } + + + return false; +} + +void ActionModel::addAction(const Qt3DSDMActionHandle &action) +{ + if (std::find(m_actions.begin(), m_actions.end(), action) == m_actions.end()) { + const auto count = rowCount(); + beginInsertRows({}, count, count); + m_actions.push_back(action); + endInsertRows(); + } +} + +void ActionModel::removeAction(const Qt3DSDMActionHandle &action) +{ + // KDAB_FIXME use beginRemoveRows + beginResetModel(); + m_actions.erase(std::remove(m_actions.begin(), m_actions.end(), action), m_actions.end()); + endResetModel(); +} + +void ActionModel::updateAction(const Qt3DSDMActionHandle &action) +{ + for (unsigned i = 0; i < m_actions.size(); i++) { + if (m_actions[i] == action) { + auto idx = index(i, 0); + Q_EMIT dataChanged(idx, idx, {}); + } + } +} + +const Qt3DSDMActionHandle ActionModel::actionAt(int row) +{ + if (row >= 0 && static_cast<unsigned>(row) < m_actions.size()) + return m_actions[row]; + + return {}; +} + +const SActionInfo ActionModel::actionInfoAt(int row) +{ + const auto action = actionAt(row); + if (!action.Valid()) + return {}; + auto actionCore = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetActionCore(); + return actionCore->GetActionInfo(action); +} + +qt3dsdm::IActionSystem *ActionModel::actionSystem() const +{ + return g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetActionSystem(); +} + +qt3dsdm::Qt3DSDMSlideHandle ActionModel::activeSlide() const +{ + return g_StudioApp.GetCore()->GetDoc()->GetActiveSlide(); +} + +QString ActionModel::actionString(const Qt3DSDMActionHandle &action) const +{ + QString result; + if (action.Valid()) { + auto core = g_StudioApp.GetCore(); + auto doc = core->GetDoc(); + auto actionCore = doc->GetStudioSystem()->GetActionCore(); + auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge(); + + const SActionInfo &actionInfo = actionCore->GetActionInfo(action); + + // Query the event name + QString eventFormalName(tr("[Unknown Event]")); + Qt3DSDMEventHandle eventHandle = bridge->ResolveEvent(actionInfo); + if (eventHandle.Valid()) + eventFormalName = + QString::fromWCharArray(bridge->GetEventInfo(eventHandle).m_FormalName.wide_str()); + + // Query the asset name + QString assetName = tr("[Unknown]"); + assetName = bridge->GetName(actionInfo.m_Owner).toQString(); + + const auto sourceInstance = + bridge->GetInstance(actionInfo.m_Owner, actionInfo.m_TriggerObject); + const auto targetInstance = + bridge->GetInstance(actionInfo.m_Owner, actionInfo.m_TargetObject); + QString sourceName = sourceInstance.Valid() + ? bridge->GetName(sourceInstance).toQString() + : tr("[Unknown Source]"); + QString targetName = targetInstance.Valid() + ? bridge->GetName(targetInstance).toQString() + : tr("[Unknown Target]"); + + // Query the action name + QString handlerFormalName(tr("[Unknown Handler]")); + const auto handlerHandle = bridge->ResolveHandler(actionInfo); + if (handlerHandle.Valid()) + handlerFormalName = QString::fromWCharArray(bridge->GetHandlerInfo(handlerHandle).m_FormalName.wide_str()); + + // Format the strings + if (actionInfo.m_Owner == sourceInstance) { + if (sourceInstance == targetInstance) { + result = tr("Listen for '%1', '%2'").arg(eventFormalName, handlerFormalName); + } else { + result = tr("Listen for '%1', tell %2 to '%3'").arg(eventFormalName, targetName, + handlerFormalName); + } + } else if (actionInfo.m_Owner == targetInstance) { + result = tr("Listen to '%1' for '%2', '%3'").arg(sourceName, eventFormalName, + handlerFormalName); + } else { + result = tr("Listen to '%1' for '%2', tell %3 to '%4'").arg(sourceName, + eventFormalName, + targetName, + handlerFormalName); + } + } + return result; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/ActionModel.h b/src/Authoring/Qt3DStudio/Palettes/Action/ActionModel.h new file mode 100644 index 00000000..966c5782 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/ActionModel.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef ACTIONMODEL_H +#define ACTIONMODEL_H + +#include <QAbstractListModel> + +#include "Qt3DSDMActionInfo.h" +#include "Qt3DSDMHandles.h" + +namespace qt3dsdm { + class IActionSystem; +} + +class ActionModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit ActionModel(QObject *parent = nullptr); + + void setInstanceHandle(const qt3dsdm::Qt3DSDMInstanceHandle &handle); + + enum Roles { + DescriptionRole = Qt::DisplayRole, + VisibleRole = Qt::UserRole + 1, + + }; + + QHash<int, QByteArray> roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &data, int role = Qt::EditRole) override; + + void addAction(const qt3dsdm::Qt3DSDMActionHandle &action); + void removeAction(const qt3dsdm::Qt3DSDMActionHandle &action); + void updateAction(const qt3dsdm::Qt3DSDMActionHandle &action); + const qt3dsdm::Qt3DSDMActionHandle actionAt(int row); + const qt3dsdm::SActionInfo actionInfoAt(int row); + +private: + qt3dsdm::IActionSystem *actionSystem() const; + qt3dsdm::Qt3DSDMSlideHandle activeSlide() const; + QString actionString(const qt3dsdm::Qt3DSDMActionHandle &action) const; + + qt3dsdm::Qt3DSDMInstanceHandle m_handle; + qt3dsdm::TActionHandleList m_actions; +}; + +#endif // ACTIONMODEL_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/ActionView.cpp b/src/Authoring/Qt3DStudio/Palettes/Action/ActionView.cpp new file mode 100644 index 00000000..41d67789 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/ActionView.cpp @@ -0,0 +1,1203 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "ActionView.h" +#include "ActionContextMenu.h" +#include "ActionModel.h" +#include "CmdDataModelActionSetValue.h" +#include "ClientDataModelBridge.h" +#include "Core.h" +#include "Dialogs.h" +#include "Dispatch.h" +#include "Doc.h" +#include "IDocumentEditor.h" +#include "IDocumentReader.h" +#include "IObjectReferenceHelper.h" +#include "Literals.h" +#include "ObjectListModel.h" +#include "StudioUtils.h" +#include "StudioApp.h" +#include "StudioClipboard.h" +#include "StudioObjectTypes.h" +#include "StudioPreferences.h" +#include "Qt3DSFileTools.h" +#include "Qt3DSDMActionCore.h" +#include "Qt3DSDMDataTypes.h" +#include "Qt3DSDMSlides.h" + +#include <QtCore/qcoreapplication.h> +#include <QtQml/qqmlcontext.h> +#include <QtQml/qqmlengine.h> +#include <QtCore/qtimer.h> +#include <QtWidgets/qdesktopwidget.h> + +ActionView::ActionView(const QSize &preferredSize, QWidget *parent) + : QQuickWidget(parent) + , TabNavigable() + , m_actionsModel(new ActionModel(this)) + , m_preferredSize(preferredSize) +{ + setResizeMode(QQuickWidget::SizeRootObjectToView); + QTimer::singleShot(0, this, &ActionView::initialize); + + g_StudioApp.GetCore()->GetDispatch()->AddPresentationChangeListener(this); + + connect(this, &ActionView::actionChanged, this, [this] { + if (!m_itemHandle.Valid()) + return; + + if (!m_propertyModel) + m_propertyModel = new PropertyModel(this); + + const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex); + if (actionInfo.m_Handler == L"Set Property") { + setPropertyValueInvalid(true); + m_currentPropertyNameHandle = actionInfo.m_HandlerArgs.at(0); + m_currentPropertyValueHandle = actionInfo.m_HandlerArgs.at(1); + m_propertyModel->setAction(m_actionsModel->actionAt(m_currentActionIndex)); + m_propertyModel->setNameHandle(m_currentPropertyNameHandle); + m_propertyModel->setValueHandle(m_currentPropertyValueHandle); + m_currentPropertyIndex = m_propertyModel->defaultPropertyIndex(); + Q_EMIT propertyChanged(); + Q_EMIT propertyModelChanged(); + setPropertyValueInvalid(false); + } + }); + + m_actionChangedCompressionTimer.setInterval(20); + m_actionChangedCompressionTimer.setSingleShot(true); + connect(&m_actionChangedCompressionTimer, &QTimer::timeout, this, [this] { + updateHandlerArguments(); + updateFiredEvent(); + Q_EMIT actionChanged(); + }); + + QString ctrlKey(QStringLiteral("Ctrl+")); + QString shiftKey(QStringLiteral("Shift+")); +#ifdef Q_OS_MACOS + ctrlKey = "⌘"; + shiftKey = "⇧"; +#endif + + // These actions will be passed to the context menu. Some of them need to me members, as we + // have to change their enabled state based on selection and previous actions. + QAction *action = new QAction(tr("New Action\t%1A").arg(shiftKey)); + action->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_A)); + connect(action, &QAction::triggered, this, &ActionView::addAction); + QQuickWidget::addAction(action); + + m_actionCopy = new QAction(tr("Copy Action\t%1C").arg(ctrlKey)); + connect(m_actionCopy, &QAction::triggered, this, &ActionView::copyAction); + QQuickWidget::addAction(m_actionCopy); + + m_actionPaste = new QAction(tr("Paste Action\t%1V").arg(ctrlKey)); + connect(m_actionPaste, &QAction::triggered, this, &ActionView::pasteAction); + QQuickWidget::addAction(m_actionPaste); + + m_actionCut = new QAction(tr("Cut Action\t%1X").arg(ctrlKey)); + connect(m_actionCut, &QAction::triggered, this, &ActionView::cutAction); + QQuickWidget::addAction(m_actionCut); + + m_actionDel = new QAction(tr("Delete Action\tDel")); + connect(m_actionDel, &QAction::triggered, [=](){ deleteAction(m_currentActionIndex); }); + QQuickWidget::addAction(m_actionDel); +} + +ActionView::~ActionView() +{ +} + +QSize ActionView::sizeHint() const +{ + return m_preferredSize; +} + +void ActionView::focusInEvent(QFocusEvent *event) +{ + updateActionStates(); + QQuickWidget::focusInEvent(event); +} + +void ActionView::mousePressEvent(QMouseEvent *event) +{ + g_StudioApp.setLastActiveView(this); + QQuickWidget::mousePressEvent(event); +} + +bool ActionView::event(QEvent *event) +{ + if (event->type() == QEvent::ShortcutOverride) { + QKeyEvent *ke = static_cast<QKeyEvent *>(event); + if (m_currentActionIndex >= 0 && (ke->key() == Qt::Key_Delete + || (ke->modifiers() == Qt::ControlModifier + && (ke->key() == Qt::Key_C || ke->key() == Qt::Key_V + || ke->key() == Qt::Key_X)))) { + auto focusItem = quickWindow()->activeFocusItem(); + if (focusItem && (focusItem->objectName() == QStringLiteral("actionListDelegate") + || focusItem->objectName() == QStringLiteral("focusEater"))) { + if (ke->key() == Qt::Key_Delete) { + if (m_actionDel->isEnabled()) + deleteAction(m_currentActionIndex); + } else if (ke->modifiers() == Qt::ControlModifier) { + if (ke->key() == Qt::Key_C) { + if (m_actionCopy->isEnabled()) + copyAction(); + } else if (ke->key() == Qt::Key_V) { + if (m_actionPaste->isEnabled()) + pasteAction(); + } else if (ke->key() == Qt::Key_X) { + if (m_actionCut->isEnabled()) + cutAction(); + } + } + event->accept(); + return true; + } + } + } + return QQuickWidget::event(event); +} + +void ActionView::setItem(const qt3dsdm::Qt3DSDMInstanceHandle &handle) +{ + if (!m_activeBrowser.isNull() && m_activeBrowser->isVisible()) { + m_activeBrowser->close(); + m_activeBrowser.clear(); + } + + m_objRefHelper = GetDoc()->GetDataModelObjectReferenceHelper(); + m_itemHandle = handle; + m_actionsModel->setInstanceHandle(handle); + if (m_itemHandle.Valid() != m_hasItem) { + m_hasItem = m_itemHandle.Valid(); + Q_EMIT hasItemChanged(); + } + emitActionChanged(); + Q_EMIT itemChanged(); + Q_EMIT itemTextChanged(); +} + +QString ActionView::itemIcon() const +{ + if (!m_itemHandle.Valid()) + return QString(); + + auto info = m_objRefHelper->GetInfo(m_itemHandle); + return CStudioObjectTypes::GetNormalIconName(info.m_Type); +} + +QString ActionView::itemText() const +{ + if (!m_itemHandle.Valid()) + return tr("No Object Selected"); + + const auto data = m_objRefHelper->GetInfo(m_itemHandle); + return data.m_Name.toQString(); +} + +QColor ActionView::itemColor() const +{ + if (!m_itemHandle.Valid()) + return Qt::white; + + auto info = m_objRefHelper->GetInfo(m_itemHandle); + if (info.m_Master) + return CStudioPreferences::masterColor(); + else + return CStudioPreferences::textColor(); +} + +QAbstractItemModel *ActionView::actionsModel() const +{ + return m_actionsModel; +} + +QAbstractItemModel *ActionView::propertyModel() const +{ + return m_propertyModel; +} + +QString ActionView::targetObjectName() const +{ + if (!GetDoc()->isValid() || !m_itemHandle.Valid()) + return QString(); + + const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex); + + const auto targetInstance = + GetBridge()->GetInstance(actionInfo.m_Owner, actionInfo.m_TargetObject); + + QString targetName = targetInstance.Valid() + ? GetBridge()->GetName(targetInstance).toQString() + : tr("[Unknown Target]"); + + return targetName; +} + +QString ActionView::triggerObjectName() const +{ + if (!GetDoc()->isValid() || !m_itemHandle.Valid()) + return QString(); + + const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex); + + const auto sourceInstance = + GetBridge()->GetInstance(actionInfo.m_Owner, actionInfo.m_TriggerObject); + + QString sourceName = sourceInstance.Valid() + ? GetBridge()->GetName(sourceInstance).toQString() + : tr("[Unknown Source]"); + + return sourceName; +} + +QString ActionView::eventName() const +{ + if (!GetDoc()->isValid() || !m_itemHandle.Valid()) + return QString(); + + const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex); + const auto bridge = GetBridge(); + const auto eventHandle = bridge->ResolveEvent(actionInfo); + const auto eventInfo = bridge->GetEventInfo(eventHandle); + + const QString formalName = QString::fromWCharArray(eventInfo.m_FormalName.wide_str()); + return formalName.isEmpty() ? tr("[Unknown Event]") : formalName; +} + +QString ActionView::handlerName() const +{ + if (!GetDoc()->isValid() || !m_itemHandle.Valid()) + return QString(); + + const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex); + const auto bridge = GetBridge(); + const auto handlerHandle = bridge->ResolveHandler(actionInfo); + + if (handlerHandle.Valid()) { + const auto handlerInfo = bridge->GetHandlerInfo(handlerHandle); + return QString::fromWCharArray(handlerInfo.m_FormalName.wide_str()); + } + + return tr("[Unknown Handler]"); +} + +QVariantList ActionView::handlerArguments() const +{ + return m_handlerArguments; +} + +PropertyInfo ActionView::property() const +{ + if (!m_propertyModel) + return {}; + return m_propertyModel->property(m_currentPropertyIndex); +} + +bool ActionView::isPropertyValueInvalid() const +{ + return m_propertyValueInvalid; +} + +void ActionView::setCurrentActionIndex(int index) +{ + if (index == m_currentActionIndex) + return; + + m_currentActionIndex = index; + emitActionChanged(); + + updateActionStates(); +} + +void ActionView::setCurrentPropertyIndex(int handle, int index) +{ + setPropertyValueInvalid(true); + // Make sure propertymodel name & value handles are always up-to-date, + // even when index is same as before + m_currentPropertyValueHandle = 0; + m_currentPropertyNameHandle = handle; + for (int i = 0; i < m_handlerArguments.size(); ++i) { + auto handlerArg = m_handlerArguments[i].value<HandlerArgument>(); + if (handlerArg.m_handle.GetHandleValue() == handle && i < m_handlerArguments.size() - 1) { + m_currentPropertyValueHandle + = m_handlerArguments[i + 1].value<HandlerArgument>().m_handle; + if (m_propertyModel) { + m_propertyModel->setNameHandle(m_currentPropertyNameHandle); + m_propertyModel->setValueHandle(m_currentPropertyValueHandle); + } + } + } + + if (index == m_currentPropertyIndex) + return; + + m_currentPropertyIndex = index; + + // set the property for the handler + if (m_propertyModel && handle != 0) { + qt3dsdm::SValue sValue(QVariant(m_propertyModel->property(index).m_nameId)); + qt3dsdm::SValue oldValue; + GetDoc()->GetStudioSystem()->GetActionCore()->GetHandlerArgumentValue(handle, oldValue); + + if (!Equals(oldValue, sValue)) { + CCmd *theCmd = + new CCmdDataModelActionSetArgumentValue(GetDoc(), handle, sValue); + g_StudioApp.GetCore()->ExecuteCommand(theCmd); + } + } + + Q_EMIT propertyChanged(); + // Clear the value invalid flag asynchronously as the value doesn't actually change until + // backend tells us it does + QTimer::singleShot(0, this, &ActionView::clearPropertyValueInvalid); +} + +void ActionView::addAction() +{ + if (m_itemHandle.Valid()) { + // Query data model bridge to see the applicable events and actions for this instance. + CClientDataModelBridge *theBridge = GetBridge(); + + std::wstring theEventName = theBridge->GetDefaultEvent(m_itemHandle); + std::wstring theHandlerName = theBridge->GetDefaultHandler(m_itemHandle); + + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*GetDoc(), QObject::tr("Add Action")) + ->AddAction(GetDoc()->GetActiveSlide(), m_itemHandle, theEventName, + theHandlerName); + } + updateActionStates(); +} + +void ActionView::deleteAction(int index) +{ + if (!m_itemHandle.Valid()) + return; + + const auto action = m_actionsModel->actionAt(index); + if (action.Valid()) { + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*GetDoc(), + QObject::tr("Delete Action"))->DeleteAction(action); + emitActionChanged(); + } + updateActionStates(); +} + +QObject *ActionView::showTriggerObjectBrowser(const QPoint &point) +{ + if (!m_itemHandle.Valid()) + return nullptr; + + if (!m_objectsModel) { + m_objectsModel = new ObjectListModel(g_StudioApp.GetCore(), + GetDoc()->GetSceneInstance(), this); + } + + if (!m_triggerObjectBrowser) + m_triggerObjectBrowser = new ObjectBrowserView(this); + + m_triggerObjectBrowser->setModel(m_objectsModel); + + const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex); + const auto instanceHandle = GetBridge()->GetInstance(actionInfo.m_Owner, + actionInfo.m_TriggerObject); + m_triggerObjectBrowser->disconnect(); + m_triggerObjectBrowser->selectAndExpand(instanceHandle, actionInfo.m_Owner); + CDialogs::showWidgetBrowser(this, m_triggerObjectBrowser, point); + m_activeBrowser = m_triggerObjectBrowser; + + connect(m_triggerObjectBrowser, &ObjectBrowserView::selectionChanged, + this, &ActionView::OnTriggerSelectionChanged); + // update also pathtype in the trigger object when changed from UI + connect(m_triggerObjectBrowser, &ObjectBrowserView::pathTypeChanged, + this, &ActionView::OnTriggerSelectionChanged); + + return m_triggerObjectBrowser; +} + +QObject *ActionView::showTargetObjectBrowser(const QPoint &point) +{ + if (!m_itemHandle.Valid()) + return nullptr; + + if (!m_objectsModel) { + m_objectsModel = new ObjectListModel(g_StudioApp.GetCore(), + GetDoc()->GetSceneInstance(), this); + } + + if (!m_targetObjectBrowser) + m_targetObjectBrowser = new ObjectBrowserView(this); + + m_targetObjectBrowser->setModel(m_objectsModel); + + const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex); + const auto instanceHandle = GetBridge()->GetInstance(actionInfo.m_Owner, + actionInfo.m_TargetObject); + m_targetObjectBrowser->disconnect(); + m_targetObjectBrowser->selectAndExpand(instanceHandle, actionInfo.m_Owner); + CDialogs::showWidgetBrowser(this, m_targetObjectBrowser, point); + m_activeBrowser = m_targetObjectBrowser; + + connect(m_targetObjectBrowser, &ObjectBrowserView::selectionChanged, + this, &ActionView::OnTargetSelectionChanged); + // update also pathtype in the target object when changed from UI + connect(m_targetObjectBrowser, &ObjectBrowserView::pathTypeChanged, + this, &ActionView::OnTargetSelectionChanged); + + return m_targetObjectBrowser; +} + +void ActionView::OnTargetSelectionChanged() +{ + auto selectedItem = m_targetObjectBrowser->selectedHandle(); + setTargetObject(m_objRefHelper->GetAssetRefValue( + selectedItem, m_itemHandle, + (CRelativePathTools::EPathType)(m_targetObjectBrowser->pathType()))); + resetFiredEvent(); +} + +void ActionView::OnTriggerSelectionChanged() +{ + auto selectedItem = m_triggerObjectBrowser->selectedHandle(); + setTriggerObject(m_objRefHelper->GetAssetRefValue( + selectedItem, m_itemHandle, + (CRelativePathTools::EPathType)(m_triggerObjectBrowser->pathType()))); + resetFiredEvent(); +} + +void ActionView::showContextMenu(int x, int y) +{ + updateActionStates(); + CActionContextMenu contextMenu(QQuickWidget::actions(), this); + contextMenu.exec(mapToGlobal({x, y})); +} + +QObject *ActionView::showEventBrowser(const QPoint &point) +{ + if (!m_itemHandle.Valid()) + return nullptr; + + const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex); + const auto bridge = GetBridge(); + const auto instanceHandle = bridge->GetInstance(actionInfo.m_Owner, actionInfo.m_TriggerObject); + + if (!instanceHandle.Valid()) + return nullptr; + + if (!m_eventsModel) + m_eventsModel = new EventsModel(this); + + qt3dsdm::TEventHandleList eventList; + bridge->GetEvents(instanceHandle, eventList); + m_eventsModel->setEventList(eventList); + + if (!m_eventsBrowser) + m_eventsBrowser = new EventsBrowserView(this); + + m_eventsBrowser->setModel(m_eventsModel); + + m_eventsBrowser->disconnect(); + m_eventsBrowser->selectAndExpand(QString::fromStdWString(actionInfo.m_Event)); + CDialogs::showWidgetBrowser(this, m_eventsBrowser, point); + m_activeBrowser = m_eventsBrowser; + + connect(m_eventsBrowser, &EventsBrowserView::selectionChanged, + this, [this] { + if (m_eventsBrowser->canCommit()) + setEvent(qt3dsdm::Qt3DSDMEventHandle(m_eventsBrowser->selectedHandle())); + }); + + return m_eventsBrowser; +} + +QObject *ActionView::showHandlerBrowser(const QPoint &point) +{ + if (!m_itemHandle.Valid()) + return nullptr; + + const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex); + const auto bridge = GetBridge(); + const auto instanceHandle = bridge->GetInstance(actionInfo.m_Owner, actionInfo.m_TargetObject); + + if (!instanceHandle.Valid()) + return nullptr; + + if (!m_handlersModel) + m_handlersModel = new EventsModel(this); + + qt3dsdm::THandlerHandleList handlerList; + bridge->GetHandlers(instanceHandle, handlerList); + m_handlersModel->setHandlerList(handlerList); + + if (!m_handlerBrowser) + m_handlerBrowser = new EventsBrowserView(this); + + m_handlerBrowser->setModel(m_handlersModel); + + m_handlerBrowser->disconnect(); + m_handlerBrowser->selectAndExpand(QString::fromStdWString(actionInfo.m_Handler)); + CDialogs::showWidgetBrowser(this, m_handlerBrowser, point); + m_activeBrowser = m_handlerBrowser; + + connect(m_handlerBrowser, &EventsBrowserView::selectionChanged, + this, [this] { + if (m_handlerBrowser->canCommit()) + setHandler(qt3dsdm::Qt3DSDMHandlerHandle(m_handlerBrowser->selectedHandle())); + }); + + return m_handlerBrowser; +} + +QObject *ActionView::showEventBrowserForArgument(int handle, const QPoint &point) +{ + if (!m_itemHandle.Valid()) + return nullptr; + + const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex); + const auto bridge = GetBridge(); + const auto instanceHandle = bridge->GetInstance(actionInfo.m_Owner, actionInfo.m_TargetObject); + + if (!instanceHandle.Valid()) + return nullptr; + + if (!m_fireEventsModel) + m_fireEventsModel = new EventsModel(this); + + qt3dsdm::TEventHandleList eventList; + bridge->GetEvents(instanceHandle, eventList); + m_fireEventsModel->setEventList(eventList); + + if (!m_fireEventsBrowser) + m_fireEventsBrowser = new EventsBrowserView(this); + + m_fireEventsBrowser->setModel(m_fireEventsModel); + m_fireEventsBrowser->setHandle(handle); + + qt3dsdm::SValue oldValue; + GetDoc()->GetStudioSystem()->GetActionCore()->GetHandlerArgumentValue(handle, oldValue); + + QString eventName; + for (Qt3DSDMEventHandle eventHandle : eventList) { + if (oldValue == eventHandle.GetHandleValue()) { + qt3dsdm::SEventInfo eventInfo = bridge->GetEventInfo(eventHandle); + eventName = QString::fromWCharArray(eventInfo.m_FormalName.wide_str()); + if (eventName.isEmpty()) + eventName = QString::fromWCharArray(eventInfo.m_Name.wide_str()); + } + } + m_fireEventsBrowser->disconnect(); + m_fireEventsBrowser->selectAndExpand(eventName); + CDialogs::showWidgetBrowser(this, m_fireEventsBrowser, point); + m_activeBrowser = m_fireEventsBrowser; + + connect(m_fireEventsBrowser, &EventsBrowserView::selectionChanged, + this, [this, handle] { + setArgumentValue(handle, qt3dsdm::Qt3DSDMEventHandle( + m_fireEventsBrowser->selectedHandle()).GetHandleValue()); + }); + + return m_fireEventsBrowser; +} + +void ActionView::updateFiredEvent() +{ + if (!m_itemHandle.Valid()) + return; + + const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex); + if (actionInfo.m_Handler != L"Fire Event") { + m_firedEvent = tr("[Unknown event]"); + return; + } + + const auto doc = GetDoc(); + if (!doc->isValid()) + return; + + const auto bridge = GetBridge(); + const auto handlerHandle = bridge->ResolveHandler(actionInfo); + IActionCore *actionCore = doc->GetStudioSystem()->GetActionCore(); + + if (handlerHandle.Valid()) { + for (const auto &argHandle: actionInfo.m_HandlerArgs) { + const auto &argumentInfo = actionCore->GetHandlerArgumentInfo(argHandle); + DataModelDataType::Value theArgType(GetValueType(argumentInfo.m_Value)); + SValue theArgValue(argumentInfo.m_Value); + if (argumentInfo.m_ArgType == HandlerArgumentType::Event) { + theArgType = DataModelDataType::String; + auto theEventHandle = get<qt3ds::QT3DSI32>(argumentInfo.m_Value); + theArgValue = SValue(std::make_shared<CDataStr>( + bridge->GetEventInfo(theEventHandle).m_Name.wide_str())); + m_firedEvent = theArgValue.toQVariant().toString(); + Q_EMIT firedEventChanged(); + } + } + } +} + +void ActionView::updateFiredEventFromHandle(int handle) +{ + m_firedEvent = QString::fromWCharArray( + GetBridge()->GetEventInfo(handle).m_FormalName.wide_str()); + Q_EMIT firedEventChanged(); +} + +void ActionView::resetFiredEvent() +{ + m_firedEvent = tr("[Unknown Event]"); + Q_EMIT firedEventChanged(); +} + +void ActionView::OnNewPresentation() +{ + // Register callback + qt3dsdm::IStudioFullSystemSignalProvider *theSignalProvider = + g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetFullSystemSignalProvider(); + m_connections.push_back(theSignalProvider->ConnectActionCreated( + std::bind(&ActionView::OnActionAdded, this, + std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3))); + m_connections.push_back(theSignalProvider->ConnectActionDeleted( + std::bind(&ActionView::OnActionDeleted, this, + std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3))); + m_connections.push_back(theSignalProvider->ConnectTriggerObjectSet( + std::bind(&ActionView::OnActionModified, this, + std::placeholders::_1))); + m_connections.push_back(theSignalProvider->ConnectTargetObjectSet( + std::bind(&ActionView::OnActionModified, this, + std::placeholders::_1))); + m_connections.push_back(theSignalProvider->ConnectEventSet( + std::bind(&ActionView::OnActionModified, this, + std::placeholders::_1))); + m_connections.push_back(theSignalProvider->ConnectHandlerSet( + std::bind(&ActionView::OnActionModified, this, + std::placeholders::_1))); + m_connections.push_back(theSignalProvider->ConnectHandlerArgumentValueSet( + std::bind(&ActionView::OnHandlerArgumentModified, this, + std::placeholders::_1))); + m_connections.push_back(theSignalProvider->ConnectInstancePropertyValue( + std::bind(&ActionView::OnInstancePropertyValueChanged, this, + std::placeholders::_1, + std::placeholders::_2))); + m_connections.push_back(theSignalProvider->ConnectInstanceDeleted( + std::bind(&ActionView::OnInstanceDeleted, this, + std::placeholders::_1))); + CDispatch *theDispatch = g_StudioApp.GetCore()->GetDispatch(); + m_connections.push_back(theDispatch->ConnectSelectionChange( + std::bind(&ActionView::OnSelectionSet, this, + std::placeholders::_1))); + + auto assetGraph = g_StudioApp.GetCore()->GetDoc()->GetAssetGraph(); + m_connections.push_back(assetGraph->ConnectChildAdded( + std::bind(&ActionView::onAssetGraphChanged, this))); + m_connections.push_back(assetGraph->ConnectChildRemoved( + std::bind(&ActionView::onAssetGraphChanged, this))); +} + +void ActionView::OnClosingPresentation() +{ + m_connections.clear(); +} + +void ActionView::OnSelectionSet(Q3DStudio::SSelectedValue inSelectable) +{ + qt3dsdm::Qt3DSDMInstanceHandle theInstance; + std::vector<qt3dsdm::Qt3DSDMInstanceHandle> instances; + + switch (inSelectable.getType()) { + case Q3DStudio::SelectedValueTypes::Instance: + theInstance = inSelectable.getData<qt3dsdm::Qt3DSDMInstanceHandle>(); + break; + case Q3DStudio::SelectedValueTypes::MultipleInstances: + instances = inSelectable.getData<std::vector<qt3dsdm::Qt3DSDMInstanceHandle>>(); + // handling only if we have one selected element. + if (instances.size() == 1) + theInstance = instances[0]; + break; + case Q3DStudio::SelectedValueTypes::Slide: { + qt3dsdm::Qt3DSDMSlideHandle theSlideHandle = + inSelectable.getData<Q3DStudio::SSlideInstanceWrapper>().m_Slide; + // Get the owning component instance + CClientDataModelBridge *theBridge = GetBridge(); + qt3dsdm::SLong4 theComponentGuid = theBridge->GetComponentGuid(theSlideHandle); + Q_ASSERT(theComponentGuid.Valid()); + theInstance = theBridge->GetInstanceByGUID(theComponentGuid); + Q_ASSERT(theInstance.Valid()); + } + break; + default: + // Clear selection on selecting other types or nothing at all + break; + }; + + setItem(theInstance); +} + +void ActionView::OnActionAdded(qt3dsdm::Qt3DSDMActionHandle inAction, + qt3dsdm::Qt3DSDMSlideHandle inSlide, + qt3dsdm::Qt3DSDMInstanceHandle inOwner) +{ + CDoc *theDoc = GetDoc(); + qt3dsdm::CStudioSystem *theStudioSystem = theDoc->GetStudioSystem(); + + qt3dsdm::Qt3DSDMSlideHandle theCurrentSlide = theDoc->GetActiveSlide(); + qt3dsdm::Qt3DSDMSlideHandle theMasterSlideOfAction = + theStudioSystem->GetSlideSystem()->GetMasterSlide(inSlide); + qt3dsdm::Qt3DSDMSlideHandle theMasterOfCurrentSlide = + theStudioSystem->GetSlideSystem()->GetMasterSlide(theCurrentSlide); + + if (!m_activeBrowser.isNull() && m_activeBrowser->isVisible()) { + m_activeBrowser->close(); + m_activeBrowser.clear(); + } + + if (inOwner == m_itemHandle // the action is added to current viewed instance + && (theCurrentSlide == inSlide // and is added to the current viewed slide + || (theMasterSlideOfAction == inSlide + && theMasterOfCurrentSlide == theMasterSlideOfAction))) { + // or it is added to the master of the current viewed slide + m_actionsModel->addAction(inAction); + } +} + +void ActionView::OnActionDeleted(qt3dsdm::Qt3DSDMActionHandle inAction, + qt3dsdm::Qt3DSDMSlideHandle inSlide, + qt3dsdm::Qt3DSDMInstanceHandle inOwner) +{ + Q_UNUSED(inSlide); + Q_UNUSED(inOwner); + + if (!m_activeBrowser.isNull() && m_activeBrowser->isVisible()) { + m_activeBrowser->close(); + m_activeBrowser.clear(); + } + m_actionsModel->removeAction(inAction); +} + +void ActionView::OnActionModified(qt3dsdm::Qt3DSDMActionHandle inAction) +{ + if (!m_itemHandle.Valid()) + return; + + if (GetDoc()->GetStudioSystem()->GetActionCore()->HandleValid(inAction)) { + if (!m_activeBrowser.isNull() && m_activeBrowser->isVisible()) { + const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex); + if (!actionInfo.m_Instance.Valid()) { + m_activeBrowser->close(); + m_activeBrowser.clear(); + } else { + // Update the selection in an active browser dialog + if (m_activeBrowser == m_triggerObjectBrowser) { + const auto instanceHandle = GetBridge()->GetInstance( + actionInfo.m_Owner, actionInfo.m_TriggerObject); + m_triggerObjectBrowser->selectAndExpand(instanceHandle, actionInfo.m_Owner); + } else if (m_activeBrowser == m_targetObjectBrowser) { + const auto instanceHandle = GetBridge()->GetInstance( + actionInfo.m_Owner, actionInfo.m_TargetObject); + m_targetObjectBrowser->selectAndExpand(instanceHandle, actionInfo.m_Owner); + } else if (m_activeBrowser == m_eventsBrowser) { + m_eventsBrowser->selectAndExpand(QString::fromStdWString(actionInfo.m_Event)); + } else if (m_activeBrowser == m_handlerBrowser) { + m_handlerBrowser->selectAndExpand( + QString::fromStdWString(actionInfo.m_Handler)); + } + } + } + m_actionsModel->updateAction(inAction); + emitActionChanged(); + } +} + +void ActionView::OnHandlerArgumentModified(qt3dsdm::Qt3DSDMHandlerArgHandle inHandlerArgument) +{ + if (!m_itemHandle.Valid()) + return; + + if (!m_fireEventsBrowser.isNull() && m_activeBrowser == m_fireEventsBrowser + && m_activeBrowser->isVisible()) { + const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex); + + // m_fireEventsBrowser needs to be closed if another type of target handler is chosen. + // Other browsers will remain valid always as long as the action is selected. + if (actionInfo.m_Handler != L"Fire Event") { + m_activeBrowser->close(); + m_activeBrowser.clear(); + } else { + // Update the selection in an active browser dialog + const auto bridge = GetBridge(); + const auto instanceHandle = bridge->GetInstance(actionInfo.m_Owner, + actionInfo.m_TargetObject); + qt3dsdm::TEventHandleList eventList; + bridge->GetEvents(instanceHandle, eventList); + qt3dsdm::SValue value; + GetDoc()->GetStudioSystem()->GetActionCore()->GetHandlerArgumentValue( + m_fireEventsBrowser->handle(), value); + QString eventName; + for (Qt3DSDMEventHandle eventHandle : eventList) { + if (value == eventHandle.GetHandleValue()) { + qt3dsdm::SEventInfo eventInfo = bridge->GetEventInfo(eventHandle); + eventName = QString::fromWCharArray(eventInfo.m_FormalName.wide_str()); + if (eventName.isEmpty()) + eventName = QString::fromWCharArray(eventInfo.m_Name.wide_str()); + } + } + m_fireEventsBrowser->selectAndExpand(eventName); + } + } + emitActionChanged(); +} + +void ActionView::OnInstancePropertyValueChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance, + qt3dsdm::Qt3DSDMPropertyHandle inProperty) +{ + if (!m_itemHandle.Valid() || m_itemHandle != inInstance) + return; + + auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge(); + if (inProperty == bridge->GetNameProperty()) + Q_EMIT itemTextChanged(); + + emitActionChanged(); +} + +void ActionView::OnInstanceDeleted(qt3dsdm::Qt3DSDMInstanceHandle inInstance) +{ + // Clear the model on instance deletion + if (inInstance == m_itemHandle) { + qt3dsdm::Qt3DSDMInstanceHandle noInstance; + setItem(noInstance); + } +} + +void ActionView::copyAction() +{ + if (!m_itemHandle.Valid()) + return; + + auto theTempAPFile = + GetDoc()->GetDocumentReader().CopyAction(m_actionsModel->actionAt(m_currentActionIndex), + GetDoc()->GetActiveSlide()); + Qt3DSFile theFile(theTempAPFile); + CStudioClipboard::CopyActionToClipboard(theFile); + updateActionStates(); +} + +void ActionView::cutAction() +{ + if (!m_itemHandle.Valid()) + return; + + copyAction(); + auto action = m_actionsModel->actionAt(m_currentActionIndex); + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*GetDoc(), QObject::tr("Cut Action"))->DeleteAction(action); + updateActionStates(); +} + +void ActionView::pasteAction() +{ + if (!m_itemHandle.Valid()) + return; + + Qt3DSFile theTempAPFile = CStudioClipboard::GetActionFromClipboard(); + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*GetDoc(), QObject::tr("Paste Action")) + ->PasteAction(theTempAPFile.GetAbsolutePath(), m_itemHandle); + updateActionStates(); +} + +void ActionView::setTriggerObject(const qt3dsdm::SObjectRefType &object) +{ + auto action = m_actionsModel->actionAt(m_currentActionIndex); + if (!action.Valid()) + return; + + if (!m_triggerObjectBrowser.isNull() && m_triggerObjectBrowser->canCommit()) { + auto core = g_StudioApp.GetCore(); + auto theBridge = GetBridge(); + + auto theCmd = new CCmdDataModelActionSetTriggerObject(GetDoc(), action, object); + const SActionInfo &theActionInfo + = GetDoc()->GetStudioSystem()->GetActionCore()->GetActionInfo(action); + + Qt3DSDMInstanceHandle theBaseInstance = theActionInfo.m_Owner; + Qt3DSDMInstanceHandle theObjectInstance = theBridge->GetInstance(theBaseInstance, object); + Qt3DSDMInstanceHandle theOldInstance = theBridge->GetInstance(theBaseInstance, + theActionInfo.m_TargetObject); + // old instance and object instance could be the same, for example if user changes the type + // from Absolute to Path. In this case we don't need to reset handler or event. + if (theOldInstance != theObjectInstance) { + theCmd->ResetEvent( + theBridge->GetDefaultEvent(theObjectInstance, theActionInfo.m_Event)); + } + + core->ExecuteCommand(theCmd); + } + emitActionChanged(); +} + +void ActionView::setTargetObject(const qt3dsdm::SObjectRefType &object) +{ + auto action = m_actionsModel->actionAt(m_currentActionIndex); + if (!action.Valid()) + return; + + if (!m_targetObjectBrowser.isNull() && m_targetObjectBrowser->canCommit()) { + auto core = g_StudioApp.GetCore(); + auto doc = GetDoc(); + auto theBridge = GetBridge(); + + auto theCmd = new CCmdDataModelActionSetTargetObject(doc, action, object); + const SActionInfo &theActionInfo = doc->GetStudioSystem()->GetActionCore()->GetActionInfo( + action); + + Qt3DSDMInstanceHandle theBaseInstance = theActionInfo.m_Owner; + Qt3DSDMInstanceHandle theObjectInstance = theBridge->GetInstance(theBaseInstance, object); + Qt3DSDMInstanceHandle theOldInstance = theBridge->GetInstance(theBaseInstance, + theActionInfo.m_TargetObject); + // old instance and object instance could be the same, for example if user changes the type + // from Absolute to Path. In this case we don't need to reset handler or event. + if (theOldInstance != theObjectInstance) { + theCmd->ResetHandler( + theBridge->GetDefaultHandler(theObjectInstance, theActionInfo.m_Handler)); + } + + core->ExecuteCommand(theCmd); + } + emitActionChanged(); +} + +void ActionView::setEvent(const Qt3DSDMEventHandle &event) +{ + if (!event.Valid()) + return; + + auto doc = GetDoc(); + const auto action = m_actionsModel->actionAt(m_currentActionIndex); + CCmd *theCmd = new CCmdDataModelActionSetEvent(doc, action, + doc->GetStudioSystem() + ->GetActionMetaData() + ->GetEventInfo(event) + ->m_Name.wide_str()); + g_StudioApp.GetCore()->ExecuteCommand(theCmd); +} + +void ActionView::setHandler(const Qt3DSDMHandlerHandle &handler) +{ + if (!handler.Valid()) + return; + + auto doc = GetDoc(); + const auto action = m_actionsModel->actionAt(m_currentActionIndex); + wstring handlerName(doc->GetStudioSystem()->GetActionMetaData()->GetHandlerInfo(handler) + ->m_Name.wide_str()); + CCmdDataModelActionSetHandler *theCmd = + new CCmdDataModelActionSetHandler(doc, action, handlerName); + theCmd->ResetHandler(handlerName); // reset the handler args + + g_StudioApp.GetCore()->ExecuteCommand(theCmd); +} + +QVariant ActionView::handlerArgumentValue(int handle) const +{ + qt3dsdm::SValue value; + GetDoc()->GetStudioSystem()->GetActionCore()->GetHandlerArgumentValue(handle, value); + return value.toQVariant(); +} + +void ActionView::updateHandlerArguments() +{ + m_currentPropertyValueHandle = 0; + m_currentPropertyNameHandle = 0; + m_handlerArguments.clear(); + const auto doc = GetDoc(); + if (!doc->isValid() || !m_itemHandle.Valid()) + return; + + const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex); + const auto bridge = GetBridge(); + const auto handlerHandle = bridge->ResolveHandler(actionInfo); + IActionCore *actionCore = doc->GetStudioSystem()->GetActionCore(); + + if (handlerHandle.Valid()) { + auto newMetaData = doc->GetStudioSystem()->GetActionMetaData(); + + for (const auto &argHandle: actionInfo.m_HandlerArgs) { + const auto &argumentInfo = actionCore->GetHandlerArgumentInfo(argHandle); + Option<SMetaDataHandlerArgumentInfo> argMetaData( + newMetaData->FindHandlerArgumentByName(handlerHandle, argumentInfo.m_Name)); + + HandlerArgument argument; + argument.m_handle = argHandle; + argument.m_type = argMetaData->m_ArgType; + argument.m_name = QString::fromWCharArray(argumentInfo.m_Name.wide_str()); + argument.m_value = argumentInfo.m_Value.toQVariant(); + argument.m_completeType = argMetaData->m_CompleteType; + m_handlerArguments.append(QVariant::fromValue(argument)); + } + } +} + +void ActionView::emitActionChanged() +{ + m_actionChangedCompressionTimer.start(); +} + +void ActionView::setArgumentValue(int handle, const QVariant &value) +{ + if (!m_itemHandle.Valid()) + return; + + if (handle == 0) + return; + + qt3dsdm::SValue sValue(value); + qt3dsdm::SValue oldValue; + GetDoc()->GetStudioSystem()->GetActionCore()->GetHandlerArgumentValue(handle, oldValue); + + if (!Equals(oldValue, sValue)) { + CCmd *theCmd = + new CCmdDataModelActionSetArgumentValue(GetDoc(), handle, sValue); + g_StudioApp.GetCore()->ExecuteCommand(theCmd); + } + + const auto actionInfo = m_actionsModel->actionInfoAt(m_currentActionIndex); + if (actionInfo.m_Handler == L"Fire Event") { + if (value.toInt()) + updateFiredEventFromHandle(value.toInt()); + } +} + +CDoc *ActionView::GetDoc() +{ + return g_StudioApp.GetCore()->GetDoc(); +} + +CClientDataModelBridge *ActionView::GetBridge() +{ + return GetDoc()->GetStudioSystem()->GetClientDataModelBridge(); +} + +void ActionView::initialize() +{ + CStudioPreferences::setQmlContextProperties(rootContext()); + rootContext()->setContextProperty(QStringLiteral("_parentView"), this); + rootContext()->setContextProperty(QStringLiteral("_resDir"), StudioUtils::resourceImageUrl()); + rootContext()->setContextProperty(QStringLiteral("_tabOrderHandler"), tabOrderHandler()); + rootContext()->setContextProperty(QStringLiteral("_mouseHelper"), &m_mouseHelper); + m_mouseHelper.setWidget(this); + + QString shiftKey(QStringLiteral("Shift+")); +#ifdef Q_OS_MACOS + shiftKey = "⇧"; +#endif + rootContext()->setContextProperty(QStringLiteral("_shiftKey"), shiftKey); + qmlRegisterUncreatableType<qt3dsdm::HandlerArgumentType>( + "Qt3DStudio", 1, 0, "HandlerArgumentType", + QStringLiteral("HandlerArgumentType is an enum container")); + qmlRegisterUncreatableType<qt3dsdm::DataModelDataType>( + "Qt3DStudio", 1, 0, "DataModelDataType", + QStringLiteral("DataModelDataType is an enum container")); + qmlRegisterUncreatableType<qt3dsdm::AdditionalMetaDataType>( + "Qt3DStudio", 1, 0, "AdditionalMetaDataType", + QStringLiteral("AdditionalMetaDataType is an enum container")); + qmlRegisterUncreatableType<PropertyInfo>( + "Qt3DStudio", 1, 0, "PropertyInfo", + QStringLiteral("PropertyInfo is not creatable in QML")); + qmlRegisterUncreatableType<qt3dsdm::CompleteMetaDataType>( + "Qt3DStudio", 1, 0, "CompleteMetaDataType", + QStringLiteral("CompleteMetaDataType is an enum container")); + engine()->addImportPath(StudioUtils::qmlImportPath()); + setSource(QUrl(QStringLiteral("qrc:/Palettes/Action/ActionView.qml"))); +} + +QStringList ActionView::slideNames() +{ + if (!m_itemHandle.Valid()) + return {}; + + std::list<Q3DStudio::CString> outSlideNames; + QStringList slideNames; + CClientDataModelBridge *theBridge = GetBridge(); + const auto action = m_actionsModel->actionAt(m_currentActionIndex); + + theBridge->GetSlideNamesOfAction(action, outSlideNames); + + for (auto slideName : outSlideNames) + slideNames.append(slideName.toQString()); + + return slideNames; +} + +int ActionView::slideNameToIndex(const QString &name) +{ + const auto slides = slideNames(); // KDAB_TODO cache it + return slides.indexOf(name); +} + +bool ActionView::toolTipsEnabled() +{ + return CStudioPreferences::ShouldShowTooltips(); +} + +void ActionView::updateActionStates() +{ + bool hasValidAction = (m_currentActionIndex != -1) && m_itemHandle.Valid(); + m_actionCopy->setEnabled(hasValidAction); + m_actionCut->setEnabled(hasValidAction); + m_actionDel->setEnabled(hasValidAction); + // Allow paste action even if item is not valid (list of actions is empty) + m_actionPaste->setEnabled(CStudioClipboard::CanPasteAction()); +} + +// m_propertyValueInvalid flag indicates that property value is changing and +// may not be valid if queried at the moment. It is used to prevent QML errors +// about invalid value types when changing property handlers. +void ActionView::setPropertyValueInvalid(bool invalid) +{ + if (invalid != m_propertyValueInvalid) { + m_propertyValueInvalid = invalid; + Q_EMIT propertyValueInvalidChanged(); + } +} + +// This is used to set m_propertyValueInvalid to false asynchronously +void ActionView::clearPropertyValueInvalid() +{ + setPropertyValueInvalid(false); +} + +void ActionView::onAssetGraphChanged() +{ + // Changes to asset graph invalidate the object browser model, so close it if it is open + if (!m_activeBrowser.isNull() && m_activeBrowser->isVisible() + && (m_activeBrowser == m_targetObjectBrowser + || m_activeBrowser == m_triggerObjectBrowser)) { + m_activeBrowser->close(); + m_activeBrowser.clear(); + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/ActionView.h b/src/Authoring/Qt3DStudio/Palettes/Action/ActionView.h new file mode 100644 index 00000000..ab2976f3 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/ActionView.h @@ -0,0 +1,231 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef ACTIONVIEW_H +#define ACTIONVIEW_H + +#include <QtQuickWidgets/qquickwidget.h> +#include <QtGui/qcolor.h> +#include <QtCore/qpointer.h> +#include <QtCore/qtimer.h> + +#include "Qt3DSCommonPrecompile.h" +#include "DispatchListeners.h" +#include "EventsBrowserView.h" +#include "EventsModel.h" +#include "ObjectBrowserView.h" +#include "ObjectListModel.h" +#include "PropertyModel.h" +#include "SelectedValueImpl.h" +#include "Qt3DSDMHandles.h" +#include "Qt3DSDMSignals.h" +#include "Qt3DSDMStudioSystem.h" +#include "Qt3DSDMMetaDataTypes.h" +#include "TabOrderHandler.h" +#include "MouseHelper.h" + +class ActionModel; +class CClientDataModelBridge; +class CCore; +class CDoc; +class IObjectReferenceHelper; + +QT_FORWARD_DECLARE_CLASS(QAbstractItemModel) + +struct HandlerArgument { + Q_PROPERTY(qt3dsdm::HandlerArgumentType::Value type MEMBER m_type FINAL) + Q_PROPERTY(QString name MEMBER m_name FINAL) + Q_PROPERTY(int handle MEMBER m_handle FINAL) + Q_PROPERTY(QVariant value MEMBER m_value FINAL) + Q_PROPERTY(qt3dsdm::CompleteMetaDataType::Enum completeType MEMBER m_completeType FINAL) + + qt3dsdm::Qt3DSDMHandlerArgHandle m_handle; + qt3dsdm::HandlerArgumentType::Value m_type; + qt3dsdm::CompleteMetaDataType::Enum m_completeType; + QString m_name; + QVariant m_value; + + Q_GADGET +}; + +Q_DECLARE_METATYPE(HandlerArgument) + +class ActionView : public QQuickWidget, + public CPresentationChangeListener, + public TabNavigable +{ + Q_OBJECT + + Q_PROPERTY(QAbstractItemModel *actionsModel READ actionsModel NOTIFY itemChanged FINAL) + Q_PROPERTY(QAbstractItemModel *propertyModel READ propertyModel NOTIFY propertyModelChanged FINAL) + Q_PROPERTY(QString itemIcon READ itemIcon NOTIFY itemChanged FINAL) + Q_PROPERTY(QString itemText READ itemText NOTIFY itemTextChanged FINAL) + Q_PROPERTY(QColor itemColor READ itemColor NOTIFY itemChanged FINAL) + Q_PROPERTY(bool hasItem MEMBER m_hasItem NOTIFY hasItemChanged FINAL) + Q_PROPERTY(QString triggerObjectName READ triggerObjectName NOTIFY actionChanged FINAL) + Q_PROPERTY(QString targetObjectName READ targetObjectName NOTIFY actionChanged FINAL) + Q_PROPERTY(QString eventName READ eventName NOTIFY actionChanged FINAL) + Q_PROPERTY(QString handlerName READ handlerName NOTIFY actionChanged FINAL) + Q_PROPERTY(QVariantList handlerArguments READ handlerArguments NOTIFY actionChanged FINAL) + Q_PROPERTY(PropertyInfo property READ property NOTIFY propertyChanged FINAL) + Q_PROPERTY(QString firedEvent MEMBER m_firedEvent NOTIFY firedEventChanged FINAL) + Q_PROPERTY(bool propertyValueInvalid READ isPropertyValueInvalid NOTIFY propertyValueInvalidChanged FINAL) + +public: + ActionView(const QSize &preferredSize, QWidget *parent = nullptr); + ~ActionView() override; + + QSize sizeHint() const override; + + void setItem(const qt3dsdm::Qt3DSDMInstanceHandle &handle); + QString itemIcon() const; + QString itemText() const; + QColor itemColor() const; + QAbstractItemModel *actionsModel() const; + QAbstractItemModel *propertyModel() const; + QString targetObjectName() const; + QString triggerObjectName() const; + QString eventName() const; + QString handlerName() const; + QVariantList handlerArguments() const; + PropertyInfo property() const; + bool isPropertyValueInvalid() const; + + Q_INVOKABLE void setCurrentActionIndex(int index); + Q_INVOKABLE void setCurrentPropertyIndex(int handle, int index); + Q_INVOKABLE void addAction(); + Q_INVOKABLE void deleteAction(int index); + Q_INVOKABLE QObject *showTriggerObjectBrowser(const QPoint &point); + Q_INVOKABLE QObject *showTargetObjectBrowser(const QPoint &point); + Q_INVOKABLE void showContextMenu(int x, int y); + Q_INVOKABLE QObject *showEventBrowser(const QPoint &point); + Q_INVOKABLE QObject *showHandlerBrowser(const QPoint &point); + Q_INVOKABLE QObject *showEventBrowserForArgument(int handle, const QPoint &point); + Q_INVOKABLE void setArgumentValue(int handle, const QVariant &value); + Q_INVOKABLE QStringList slideNames(); + Q_INVOKABLE int slideNameToIndex(const QString &name); + Q_INVOKABLE bool toolTipsEnabled(); + + // CPresentationChangeListener + void OnNewPresentation() override; + void OnClosingPresentation() override; + + // ISelectionChangeListener + void OnSelectionSet(Q3DStudio::SSelectedValue inSelectable); + + // Action callback + void OnActionAdded(qt3dsdm::Qt3DSDMActionHandle inAction, qt3dsdm::Qt3DSDMSlideHandle inSlide, + qt3dsdm::Qt3DSDMInstanceHandle inOwner); + void OnActionDeleted(qt3dsdm::Qt3DSDMActionHandle inAction, qt3dsdm::Qt3DSDMSlideHandle inSlide, + qt3dsdm::Qt3DSDMInstanceHandle inOwner); + void OnActionModified(qt3dsdm::Qt3DSDMActionHandle inAction); + void OnHandlerArgumentModified(qt3dsdm::Qt3DSDMHandlerArgHandle inHandlerArgument); + void OnInstancePropertyValueChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance, + qt3dsdm::Qt3DSDMPropertyHandle inProperty); + void OnInstanceDeleted(qt3dsdm::Qt3DSDMInstanceHandle inInstance); + void OnTargetSelectionChanged(); + void OnTriggerSelectionChanged(); + +protected: + void focusInEvent(QFocusEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + bool event(QEvent *event) override; + +Q_SIGNALS: + void itemChanged(); + void itemTextChanged(); + void actionChanged(); + void propertyModelChanged(); + void propertyChanged(); + void firedEventChanged(); + void hasItemChanged(); + void propertyValueInvalidChanged(); + void dialogCurrentColorChanged(const QColor &newColor); + +private Q_SLOTS: + void copyAction(); + void cutAction(); + void pasteAction(); + +private: + void setTriggerObject(const qt3dsdm::SObjectRefType &object); + void setTargetObject(const qt3dsdm::SObjectRefType &object); + void setEvent(const qt3dsdm::Qt3DSDMEventHandle &event); + void setHandler(const qt3dsdm::Qt3DSDMHandlerHandle &handler); + QVariant handlerArgumentValue(int handle) const; + void updateHandlerArguments(); + void emitActionChanged(); + void updateFiredEvent(); + void resetFiredEvent(); + void updateFiredEventFromHandle(int handle); + void updateActionStates(); + void setPropertyValueInvalid(bool invalid); + void clearPropertyValueInvalid(); + void onAssetGraphChanged(); + + static CDoc *GetDoc(); + static CClientDataModelBridge *GetBridge(); + + void initialize(); + QColor m_baseColor = QColor::fromRgb(75, 75, 75); + QColor m_selectColor = Qt::transparent; + qt3dsdm::Qt3DSDMInstanceHandle m_itemHandle; + IObjectReferenceHelper *m_objRefHelper = nullptr; + ActionModel *m_actionsModel = nullptr; + PropertyModel *m_propertyModel = nullptr; + std::vector<std::shared_ptr<qt3dsdm::ISignalConnection>> + m_connections; /// connections to the DataModel + QPointer<ObjectListModel> m_objectsModel; + QPointer<ObjectBrowserView> m_triggerObjectBrowser; + QPointer<ObjectBrowserView> m_targetObjectBrowser; + QPointer<EventsModel> m_eventsModel; + QPointer<EventsModel> m_handlersModel; + QPointer<EventsModel> m_fireEventsModel; + QPointer<EventsBrowserView> m_eventsBrowser; + QPointer<EventsBrowserView> m_handlerBrowser; + QPointer<EventsBrowserView> m_fireEventsBrowser; + int m_currentActionIndex = -1; + int m_currentPropertyIndex = -1; + qt3dsdm::Qt3DSDMHandlerArgHandle m_currentPropertyNameHandle; + qt3dsdm::Qt3DSDMHandlerArgHandle m_currentPropertyValueHandle; + QVariantList m_handlerArguments; + QTimer m_actionChangedCompressionTimer; + QString m_firedEvent; + MouseHelper m_mouseHelper; + QSize m_preferredSize; + bool m_hasItem = false; + QAction *m_actionDel; + QAction *m_actionCopy; + QAction *m_actionCut; + QAction *m_actionPaste; + bool m_propertyValueInvalid = true; + QColor m_currentColor; + QPointer<QWidget> m_activeBrowser = nullptr; +}; + +#endif // ACTIONVIEW_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/ActionView.qml b/src/Authoring/Qt3DStudio/Palettes/Action/ActionView.qml new file mode 100644 index 00000000..a5b905b3 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/ActionView.qml @@ -0,0 +1,468 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import Qt3DStudio 1.0 +import "../controls" + +Rectangle { + id: root + + color: _backgroundColor + + Item { + id: focusEater + objectName: "focusEater" + // Used to eat keyboard focus when user clicks outside any property control + } + + Flickable { + id: actionFlickable + ScrollBar.vertical: ScrollBar { + id: scrollBar + visible: size < 1.0 + } + + MouseArea { + anchors.fill: parent + z: -10 + onPressed: { + mouse.accepted = false + focusEater.forceActiveFocus(); + } + } + + anchors.fill: parent + contentHeight: contentColumn.height + + property bool scrollToBottom: false + + onContentHeightChanged: { + if (scrollToBottom) { + scrollToBottom = false; + if (contentHeight > height) + contentY = contentHeight - height; + } + } + + Column { + id: contentColumn + width: parent.width + spacing: 4 + + RowLayout { + height: _controlBaseHeight + 8 + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 4 + anchors.rightMargin: 12 + + Image { + id: headerImage + source: _parentView.itemIcon !== "" ? _resDir + _parentView.itemIcon : "" + } + + StyledLabel { + Layout.fillWidth: true + text: _parentView.itemText + color: _parentView.itemColor + } + + StyledToolButton { + enabled: actionsList.currentIndex !== -1 + enabledImage: "Action-Trash-Normal.png" + disabledImage: "Action-Trash-Disabled.png" + toolTipText: qsTr("Delete (Del)") + + onClicked: _parentView.deleteAction(actionsList.currentIndex) + } + + StyledToolButton { + enabledImage: "add.png" + disabledImage: "add-disabled.png" + toolTipText: qsTr("New Action (") + _shiftKey + "A)" + enabled: _parentView.hasItem + + onClicked: _parentView.addAction() + } + } + ListView { + id: actionsList + width: parent.width + height: count == 0 ? _controlBaseHeight : count * _controlBaseHeight + clip: true + + Connections { + target: _parentView + // Clear the action selection on item selection change + onItemChanged: actionsList.currentIndex = -1 + } + + MouseArea { + anchors.fill: parent + enabled: parent.count == 0 + + acceptedButtons: Qt.RightButton + + onClicked: { + if (mouse.button == Qt.RightButton) { + var updateMousePosition = mapToItem(actionsList, mouse.x, mouse.y) + _parentView.showContextMenu( + updateMousePosition.x, updateMousePosition.y); + } + } + } + boundsBehavior: Flickable.StopAtBounds + model: _parentView.actionsModel + + delegate: Rectangle { + id: delegateItem + objectName: "actionListDelegate" + + width: actionsList.width + height: _controlBaseHeight + color: model.index === actionsList.currentIndex ? _selectionColor + : "transparent" + + Row { + x: 10 + y: 5 + height: parent.height + width: parent.width - x + spacing: 4 + + Image { + id: visibilityIcon + + source: model.visible ? _resDir + "Toggle-HideShow.png" + : _resDir + "Toggle-HideShow-disabled.png" + + MouseArea { + anchors.fill: parent + onClicked: model.visible = !model.visible + } + } + + StyledLabel { + text: model.description + } + } + + MouseArea { + anchors.fill: parent + + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onPressed: { + actionsList.forceActiveFocus(); + } + + onClicked: { + actionFlickable.scrollToBottom = false; + actionsList.currentIndex = model.index; + _parentView.setCurrentActionIndex(model.index); + if (mouse.button == Qt.LeftButton && mouse.x < visibilityIcon.width + 10) + model.visible = !model.visible; + + if (mouse.button == Qt.RightButton) { + var updateMousePosition = mapToItem(actionsList, mouse.x, mouse.y) + _parentView.showContextMenu(updateMousePosition.x, updateMousePosition.y); + } + } + onDoubleClicked: { + actionFlickable.scrollToBottom = false; + if (mouse.button == Qt.LeftButton && mouse.x > visibilityIcon.width + 10) { + // Scroll down to bottom to show properties on double click + if (actionFlickable.contentHeight > actionFlickable.height) { + actionFlickable.contentY = (actionFlickable.contentHeight + - actionFlickable.height) + } + // Since loading new property fields takes a moment, we want + // to keep the view scrolled to bottom + // when the content height changes the next time. + actionFlickable.scrollToBottom = true; + } + } + } + } + + onCountChanged: { + if (currentIndex >= count) + currentIndex = count - 1; + } + + onCurrentIndexChanged: _parentView.setCurrentActionIndex(currentIndex); + } + + StyledMenuSeparator { + leftPadding: 12 + rightPadding: 12 + } + + Column { + anchors.left: parent.left + anchors.right: parent.right + height: childrenRect.height + visible: actionsList.currentIndex !== -1 + spacing: 4 + + RowLayout { + x: 12 + StyledLabel { + text: qsTr("Trigger Object") + } + BrowserCombo { + value: _parentView.triggerObjectName + onShowBrowser: activeBrowser = _parentView.showTriggerObjectBrowser( + mapToGlobal(width, 0)); + } + } + + RowLayout { + x: 12 + StyledLabel { + text: qsTr("Event") + } + BrowserCombo { + value: _parentView.eventName + onShowBrowser: activeBrowser = _parentView.showEventBrowser( + mapToGlobal(width, 0)) + } + } + } + + StyledMenuSeparator { + visible: actionsList.currentIndex !== -1 + leftPadding: 12 + rightPadding: 12 + } + + Column { + visible: actionsList.currentIndex !== -1 + width: parent.width + height: childrenRect.height + spacing: 4 + + RowLayout { + x: 12 + StyledLabel { + text: qsTr("Target Object") + } + + BrowserCombo { + value: _parentView.targetObjectName + onShowBrowser: activeBrowser = _parentView.showTargetObjectBrowser( + mapToGlobal(width, 0)) + } + } + + RowLayout { + x: 12 + StyledLabel { + text: qsTr("Handler") + } + + BrowserCombo { + value: _parentView.handlerName + onShowBrowser: activeBrowser = _parentView.showHandlerBrowser( + mapToGlobal(width, 0)) + } + } + + Component { + id: genericHandlerComponent + + HandlerGenericText { + label: parent && parent.argument.name ? parent.argument.name : "" + value: parent && parent.argument.value ? parent.argument.value : "" + + onEditingFinished: { + if (parent) + _parentView.setArgumentValue(parent.argument.handle, value) + } + } + } + + Component { + id: floatHandlerComponent + + HandlerGenericText { + label: parent && parent.argument.name ? parent.argument.name : "" + value: parent && parent.argument.value ? parent.argument.value : 0.0 + validator: DoubleValidator { + decimals: 3 + notation: DoubleValidator.StandardNotation + } + + onEditingFinished: { + if (parent) + _parentView.setArgumentValue(parent.argument.handle, value) + } + } + } + + Component { + id: signalHandlerComponent + + HandlerGenericText { + label: parent && parent.argument.name ? parent.argument.name : "" + value: parent && parent.argument.value ? parent.argument.value : "" + + onEditingFinished: { + if (parent) + _parentView.setArgumentValue(parent.argument.handle, value); + } + } + } + + Component { + id: eventHandlerComponent + + HandlerFireEvent { + label: parent && parent.argument.name ? parent.argument.name : "" + value: _parentView.firedEvent === "" ? qsTr("[Unknown Event]") + : _parentView.firedEvent + + onShowBrowser: { + if (parent && parent.argument.handle) { + activeBrowser = _parentView.showEventBrowserForArgument( + parent.argument.handle, mapToGlobal(width, 0)) + } + } + } + } + + Component { + id: slideHandlerComponent + + HandlerGoToSlide { + slideModel: _parentView.slideNames() + defaultSlideIndex: parent && parent.argument.value ? _parentView.slideNameToIndex(parent.argument.value) + : 0 + + onActivated: { + if (parent && parent.argument.handle && currentSlide) + _parentView.setArgumentValue(parent.argument.handle, currentSlide) + } + } + } + + Component { + id: checkboxHandlerComponent + + HandlerGenericCheckbox { + label: parent && parent.argument.name ? parent.argument.name : "" + checked: parent && parent.argument.value ? parent.argument.value : false + + onClicked: { + if (parent && parent.argument.handle) + _parentView.setArgumentValue(parent.argument.handle, !checked) + } + } + } + + Component { + id: propertyHandlerComponent + + HandlerProperty { + propertyModel: _parentView.propertyModel + defaultPropertyIndex: propertyModel ? propertyModel.defaultPropertyIndex : 0 + + onPropertySelected: { + if (parent && parent.argument.handle) + _parentView.setCurrentPropertyIndex(parent.argument.handle, index); + } + } + } + + Repeater { + model: _parentView.handlerArguments.length + + Loader { + x: 12 + + readonly property var argument:_parentView.handlerArguments[model.index] + + onLoaded: { + // HandlerProperty does its own tab order handling + if (argument.type !== HandlerArgumentType.Property) { + // Dynamic actions use group 0. + // We assume there is always just one tabbable argument per action, + // and the rest are dependent types. + _tabOrderHandler.clear(); + if (item.tabItem1 !== undefined) { + _tabOrderHandler.addItem(0, item.tabItem1) + if (item.tabItem2 !== undefined) { + _tabOrderHandler.addItem(0, item.tabItem2) + if (item.tabItem3 !== undefined) + _tabOrderHandler.addItem(0, item.tabItem3) + } + } + } + } + + sourceComponent: { + const handlerType = argument.type; + switch (handlerType) { + case HandlerArgumentType.None: + switch (argument.completeType) { + case CompleteMetaDataType.Boolean: + return checkboxHandlerComponent; + case CompleteMetaDataType.Float: + return floatHandlerComponent; + default: + return genericHandlerComponent; + } + case HandlerArgumentType.Event: + return eventHandlerComponent; + case HandlerArgumentType.Property: + return propertyHandlerComponent; + case HandlerArgumentType.Dependent: + return null; // no UI for Dependent type, they are the value for a property + case HandlerArgumentType.Signal: + return signalHandlerComponent; + case HandlerArgumentType.Slide: + return slideHandlerComponent + default: console.warn("KDAB_TODO implement handler for type: ", handlerType) + } + return null; + } + } + } + } + + StyledMenuSeparator { + visible: actionsList.count > 0 && actionsList.currentIndex !== -1 + leftPadding: 12 + rightPadding: 12 + } + } + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowser.qml b/src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowser.qml new file mode 100644 index 00000000..e5cb3998 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowser.qml @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +Rectangle { + id: root + + color: _backgroundColor + border.color: _studioColor3 + + ColumnLayout { + anchors.fill: parent + + ListView { + id: eventsList + + Layout.margins: 5 + Layout.fillWidth: true + Layout.fillHeight: true + + ScrollBar.vertical: ScrollBar {} + + boundsBehavior: Flickable.StopAtBounds + clip: true + currentIndex: _eventsBrowserView.selection + + model: _eventsBrowserView.model + + delegate: Item { + id: delegateItem + + readonly property bool isCategory: model.isCategory + + width: parent.width + height: model.parentExpanded ? _controlBaseHeight : 0 + visible: height > 0 + + Behavior on height { + NumberAnimation { + duration: 100 + easing.type: Easing.OutQuad + } + } + + Rectangle { + width: parent.width + height: parent.height + color: model.index === eventsList.currentIndex ? _selectionColor + : "transparent" + Row { + id: row + width: parent.width + height: parent.height + spacing: 5 + + Image { + id: arrow + anchors.verticalCenter: parent.verticalCenter + source: { + if (!delegateItem.isCategory) + return ""; + model.expanded ? _resDir + "arrow_down.png" + : _resDir + "arrow.png"; + } + + MouseArea { + anchors.fill: parent + onClicked: model.expanded = !model.expanded + } + } + + Image { // group icon + anchors.verticalCenter: parent.verticalCenter + source: model.icon + } + + StyledLabel { + id: name + leftPadding: isCategory ? 0 : 45 + anchors.verticalCenter: parent.verticalCenter + text: model.name + } + } + + MouseArea { + id: delegateArea + anchors.fill: parent + anchors.leftMargin: arrow.width + hoverEnabled: true + onClicked: { + if (!delegateItem.isCategory) + eventsList.currentIndex = model.index; + } + onEntered: itemDescription.text = model.description + onExited: itemDescription.text = "" + onDoubleClicked: { + if (!delegateItem.isCategory) { + eventsList.currentIndex = model.index; + _eventsBrowserView.close(); + } else { + model.expanded = !model.expanded + } + } + } + } + + } + onCurrentIndexChanged: _eventsBrowserView.selection = currentIndex + + Connections { + target: _eventsBrowserView + onSelectionChanged: { + if (eventsList.currentIndex !== _eventsBrowserView.selection) + eventsList.currentIndex = _eventsBrowserView.selection; + } + } + } + + StyledMenuSeparator { + bottomPadding: 0 + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: _controlBaseHeight + 4 + Rectangle { + anchors.fill: parent + anchors.margins: 2 + + color: _backgroundColor + + StyledLabel { + id: itemDescription + leftPadding: 6 + anchors.fill: parent + } + } + } + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowserView.cpp b/src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowserView.cpp new file mode 100644 index 00000000..b7151af2 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowserView.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "EventsBrowserView.h" + +#include "EventsModel.h" +#include "StudioUtils.h" +#include "StudioPreferences.h" + +#include <QtCore/qcoreapplication.h> +#include <QtCore/qtimer.h> +#include <QtQml/qqmlcontext.h> +#include <QtQml/qqmlengine.h> + +EventsBrowserView::EventsBrowserView(QWidget *parent) : QQuickWidget(parent) +{ + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + setResizeMode(QQuickWidget::SizeRootObjectToView); + QTimer::singleShot(0, this, &EventsBrowserView::initialize); +} + +QAbstractItemModel *EventsBrowserView::model() const +{ + return m_model; +} + +void EventsBrowserView::setModel(EventsModel *model) +{ + if (m_model != model) { + m_model = model; + Q_EMIT modelChanged(); + } +} + +qt3dsdm::CDataModelHandle EventsBrowserView::selectedHandle() const +{ + const auto handleId = m_model->handleForRow(m_selection); + return handleId; +} + +void EventsBrowserView::selectAndExpand(const QString &event) +{ + // All categories are expanded by default, so let's just select + m_blockCommit = true; + setSelection(m_model->rowForEventName(event)); + m_blockCommit = false; + +} + +void EventsBrowserView::setSelection(int index) +{ + auto handleId = m_model->handleForRow(index); + if (!handleId.Valid()) { + m_selection = -1; + Q_EMIT selectionChanged(); + } else if (m_selection != index) { + m_selection = index; + Q_EMIT selectionChanged(); + } +} + +void EventsBrowserView::focusOutEvent(QFocusEvent *event) +{ + QQuickWidget::focusOutEvent(event); + QTimer::singleShot(0, this, &EventsBrowserView::close); +} + +void EventsBrowserView::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Escape) + QTimer::singleShot(0, this, &EventsBrowserView::close); + + QQuickWidget::keyPressEvent(event); +} + +void EventsBrowserView::initialize() +{ + CStudioPreferences::setQmlContextProperties(rootContext()); + rootContext()->setContextProperty(QStringLiteral("_eventsBrowserView"), this); + rootContext()->setContextProperty(QStringLiteral("_resDir"), + StudioUtils::resourceImageUrl()); + engine()->addImportPath(StudioUtils::qmlImportPath()); + setSource(QUrl(QStringLiteral("qrc:/Palettes/Action/EventsBrowser.qml"))); +} + diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowserView.h b/src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowserView.h new file mode 100644 index 00000000..f8a2b7fa --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/EventsBrowserView.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ +#ifndef EVENTSBROWSERVIEW_H +#define EVENTSBROWSERVIEW_H + +#include <QQuickWidget> + +#include "Qt3DSDMHandles.h" + +class EventsModel; + +QT_FORWARD_DECLARE_CLASS(QAbstractItemModel) + +class EventsBrowserView : public QQuickWidget +{ + Q_OBJECT + Q_PROPERTY(QAbstractItemModel *model READ model NOTIFY modelChanged FINAL) + Q_PROPERTY(int selection READ selection WRITE setSelection NOTIFY selectionChanged FINAL) +public: + explicit EventsBrowserView(QWidget *parent = nullptr); + + QAbstractItemModel *model() const; + void setModel(EventsModel *model); + qt3dsdm::CDataModelHandle selectedHandle() const; + + void selectAndExpand(const QString &event); + + int selection() const { return m_selection; } + void setSelection(int index); + + void setHandle(int handle) { m_handle = handle; } + int handle() const { return m_handle; } + + bool canCommit() const { return !m_blockCommit; } + +Q_SIGNALS: + void modelChanged(); + void selectionChanged(); + +protected: + void focusOutEvent(QFocusEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + +private: + void initialize(); + EventsModel *m_model = nullptr; + QColor m_baseColor = QColor::fromRgb(75, 75, 75); + QColor m_selectColor; + int m_selection = -1; + int m_handle = -1; + bool m_blockCommit = false; +}; + +#endif // EVENTSBROWSERVIEW_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/EventsModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Action/EventsModel.cpp new file mode 100644 index 00000000..981ab95a --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/EventsModel.cpp @@ -0,0 +1,276 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "EventsModel.h" + +#include "ClientDataModelBridge.h" +#include "Core.h" +#include "Doc.h" +#include "StudioUtils.h" +#include "StudioApp.h" +#include "Qt3DSDMStudioSystem.h" + +EventsModel::EventsModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +void EventsModel::setEventList(const qt3dsdm::TEventHandleList &eventList) +{ + beginResetModel(); + + m_rowCount = 0; + m_events.clear(); + m_categories.clear(); + + auto studioSystem = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem(); + auto theBridge = studioSystem->GetClientDataModelBridge(); + auto thePos = eventList.begin(); + for (; thePos != eventList.end(); ++thePos) { + qt3dsdm::SEventInfo theEvent = theBridge->GetEventInfo(*thePos); + + CategoryInfo category; + category.name = QString::fromWCharArray(theEvent.m_Category.wide_str()); + if (!m_events.contains(category.name)) { + qt3dsdm::SCategoryInfo theCategoryMetaData = studioSystem->GetActionMetaData() + ->GetEventCategory(theEvent.m_Category); + category.icon = QString::fromWCharArray(theCategoryMetaData.m_Icon.wide_str()); + category.highlightIcon = QString::fromWCharArray(theCategoryMetaData.m_HighlightIcon.wide_str()); + category.description = QString::fromWCharArray(theCategoryMetaData.m_Description.wide_str()); + m_categories.append(category); + m_rowCount++; + } + + EventInfo eventInfo; + // Use the formal name to display, but if the formal name is not set, use the name instead + eventInfo.name = QString::fromWCharArray(theEvent.m_FormalName.wide_str()); + if (eventInfo.name.isEmpty()) + eventInfo.name = QString::fromWCharArray(theEvent.m_Name.wide_str()); + eventInfo.handle = *thePos; + + eventInfo.description = QString::fromWCharArray(theEvent.m_Description.wide_str()); + m_events[category.name].append(eventInfo); + m_rowCount++; + + //KDAB_TODO set the selection to the current event + } + + endResetModel(); +} + +void EventsModel::setHandlerList(const qt3dsdm::THandlerHandleList &handlerList) +{ + beginResetModel(); + m_rowCount = 0; + m_events.clear(); + m_categories.clear(); + + auto studioSystem = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem(); + auto theBridge = studioSystem->GetClientDataModelBridge(); + auto thePos = handlerList.begin(); + for (; thePos != handlerList.end(); ++thePos) { + qt3dsdm::SHandlerInfo handlerInfo = theBridge->GetHandlerInfo(*thePos); + + CategoryInfo category; + category.name = QString::fromWCharArray(handlerInfo.m_Category.wide_str()); + if (!m_events.contains(category.name)) { + qt3dsdm::SCategoryInfo theCategoryMetaData = studioSystem->GetActionMetaData() + ->GetHandlerCategory(handlerInfo.m_Category); + category.icon = QString::fromWCharArray(theCategoryMetaData.m_Icon.wide_str()); + category.highlightIcon = QString::fromWCharArray(theCategoryMetaData.m_HighlightIcon.wide_str()); + category.description = QString::fromWCharArray(theCategoryMetaData.m_Description.wide_str()); + m_categories.append(category); + m_rowCount++; + } + + EventInfo eventInfo; + // Use the formal name to display, but if the formal name is not set, use the name instead + eventInfo.name = QString::fromWCharArray(handlerInfo.m_FormalName.wide_str()); + if (eventInfo.name.isEmpty()) + eventInfo.name = QString::fromWCharArray(handlerInfo.m_Name.wide_str()); + eventInfo.handle = *thePos; + + eventInfo.description = QString::fromWCharArray(handlerInfo.m_Description.wide_str()); + m_events[category.name].append(eventInfo); + m_rowCount++; + + //KDAB_TODO set the selection to the current event + } + + endResetModel(); +} + +int EventsModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return m_rowCount; +} + +QVariant EventsModel::data(const QModelIndex &index, int role) const +{ + if (!hasIndex(index.row(), index.column(), index.parent())) + return {}; + + const auto row = index.row(); + auto category = categoryForRow(row); + + bool isCategory = category.isValid(); + EventInfo event; + if (!isCategory) + event = eventForRow(row); + + switch (role) { + case NameRole: + return isCategory ? category.name : event.name; + case DescriptionRole: + return isCategory ? category.description: event.description; + case IconRole: + return isCategory ? StudioUtils::resourceImageUrl() + category.icon : QString(); + case HighlightedIconRole: + return isCategory ? StudioUtils::resourceImageUrl() + category.highlightIcon : QString(); + case ExpandedRole: + return isCategory ? category.expanded : false; + case ParentExpandedRole: { + if (isCategory) + return true; + for (int i = row - 1; i >= 0; i--) { + auto parentCategory = categoryForRow(i); + if (parentCategory.isValid()) + return parentCategory.expanded; + } + return false; + } + case IsCategoryRole: + return isCategory; + } + + return QVariant(); +} + +bool EventsModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (role == ExpandedRole) { + int catRow = categoryRowForRow(index.row()); + if (catRow != -1) { + auto category = &m_categories[catRow]; + category->expanded = value.toBool(); + Q_EMIT dataChanged(this->index(0, 0), this->index(rowCount() - 1, 0), {}); + return true; + } + } + return false; +} + +QHash<int, QByteArray> EventsModel::roleNames() const +{ + auto names = QAbstractItemModel::roleNames(); + names.insert(NameRole, "name"); + names.insert(DescriptionRole, "description"); + names.insert(IconRole, "icon"); + names.insert(HighlightedIconRole, "highlightedIcon"); + names.insert(IsCategoryRole, "isCategory"); + names.insert(ExpandedRole, "expanded"); + names.insert(ParentExpandedRole, "parentExpanded"); + + return names; +} + +qt3dsdm::CDataModelHandle EventsModel::handleForRow(int row) const +{ + if (row < 0 || row >= m_rowCount) + return {}; + + auto event = eventForRow(row); + if (event.isValid()) + return event.handle; + + return {}; +} + +int EventsModel::rowForEventName(const QString &event) const +{ + int i = 0; + for (const auto &category: m_categories) { + i++; + const auto events = m_events[category.name]; + for (int j = 0; j < events.size(); j++, i++) { + if (events[j].name == event) + return i; + } + } + return i; +} + +EventsModel::CategoryInfo EventsModel::categoryForRow(int row) const +{ + int i = 0; + for (const auto &category: m_categories) { + if (i == row) + return category; + i += m_events[category.name].size(); + i++; + } + + return {}; +} + +int EventsModel::categoryRowForRow(int row) const +{ + int i = 0; + int catRow = 0; + for (const auto &category: m_categories) { + if (i == row) + return catRow; + i += m_events[category.name].size(); + i++; + catRow++; + } + + return -1; +} + +EventsModel::EventInfo EventsModel::eventForRow(int row) const +{ + if (row == 0) // first line is not an event, but a category + return {}; + + int i = 0; + for (const auto &category: m_categories) { + i++; + const auto events = m_events[category.name]; + const int index = (row - i); + if (row < i + events.size() && (index >= 0) ) { + return events[index]; + } + i += events.size(); + } + + return {}; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/EventsModel.h b/src/Authoring/Qt3DStudio/Palettes/Action/EventsModel.h new file mode 100644 index 00000000..b0f2f4fb --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/EventsModel.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef EVENTSMODEL_H +#define EVENTSMODEL_H + +#include <QAbstractListModel> + +#include "Qt3DSDMHandles.h" + +/** Model for both action events and action handlers */ +class EventsModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit EventsModel(QObject *parent = nullptr); + + void setEventList(const qt3dsdm::TEventHandleList &eventList); + void setHandlerList(const qt3dsdm::THandlerHandleList &handlerList); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole) override; + + enum Roles { + NameRole = Qt::DisplayRole, + DescriptionRole = Qt::UserRole + 1, + IconRole, + HighlightedIconRole, + ExpandedRole, + ParentExpandedRole, + IsCategoryRole + }; + + QHash<int, QByteArray> roleNames() const override; + + qt3dsdm::CDataModelHandle handleForRow(int row) const; + int rowForEventName(const QString &event) const; + +private: + struct EventInfo { + qt3dsdm::CDataModelHandle handle; + QString name; + QString description; + + bool isValid() const { return handle.Valid(); } + }; + + struct CategoryInfo { + QString name; + QString icon; + QString description; + QString highlightIcon; + bool expanded = true; + + bool isValid() const { return !name.isEmpty(); } + }; + + CategoryInfo categoryForRow(int row) const; + int categoryRowForRow(int row) const; + EventInfo eventForRow(int row) const; + + QHash<QString, QVector<EventInfo> > m_events; + QVector<CategoryInfo> m_categories; + int m_rowCount = 0; +}; + +#endif // EVENTSMODEL_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerBaseMultilineText.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerBaseMultilineText.qml new file mode 100644 index 00000000..fbab75cb --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerBaseMultilineText.qml @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +ScrollView { + id: root + signal editingFinished + signal textChanged + property alias value: textArea.text + property Item tabItem1: textArea + + clip: true + + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical.policy: ScrollBar.AsNeeded + + TextArea { + id: textArea + property bool ignoreHotkeys: true + + horizontalAlignment: TextInput.AlignLeft + verticalAlignment: TextInput.AlignTop + font.pixelSize: _fontSize + color: _textColor + selectionColor: _selectionColor + selectedTextColor: _textColor + + topPadding: 6 + bottomPadding: 6 + rightPadding: 6 + + wrapMode: TextEdit.WrapAnywhere + background: Rectangle { + anchors.fill: parent + color: textArea.enabled ? _studioColor2 : "transparent" + border.width: textArea.activeFocus ? 1 : 0 + border.color: textArea.activeFocus ? _selectionColor : _disabledColor + } + + MouseArea { + id: mouseArea + property int clickedPos + + anchors.fill: parent + preventStealing: true + onPressed: { + textArea.forceActiveFocus() + clickedPos = textArea.positionAt(mouse.x, mouse.y) + textArea.cursorPosition = clickedPos + } + onDoubleClicked: textArea.selectAll() + onPositionChanged: { + textArea.cursorPosition = textArea.positionAt(mouse.x, mouse.y) + textArea.select(clickedPos, textArea.cursorPosition) + } + } + onTextChanged: root.textChanged() + onEditingFinished: root.editingFinished() + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerEmitSignal.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerEmitSignal.qml new file mode 100644 index 00000000..16eac96e --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerEmitSignal.qml @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +RowLayout { + id: root + + property alias label: labelField.text + property alias value: textField.text + property Item tabItem1: textfield + + StyledLabel { + id: labelField + text: qsTr("Signal Name") + } + + StyledTextField { + id: textField + Layout.preferredWidth: _valueWidth + } +} + diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerFireEvent.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerFireEvent.qml new file mode 100644 index 00000000..5b2ca0f6 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerFireEvent.qml @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +RowLayout { + id: root + + property alias label: labelField.text + property alias value: comboField.value + property alias activeBrowser: comboField.activeBrowser + + signal showBrowser + + StyledLabel { + id: labelField + text: qsTr("Event") + } + + BrowserCombo { + id: comboField + Layout.preferredWidth: _valueWidth + value: qsTr("[Unknown Event]") + onShowBrowser: root.showBrowser() + } +} + diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericBaseColor.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericBaseColor.qml new file mode 100644 index 00000000..8c184409 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericBaseColor.qml @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.3 + +RowLayout { + id: root + + property alias color: rect.color + property color selectedColor: "black" + property bool listenToColorChanges: false + + signal colorSelected() + signal previewColorSelected() + + Connections { + target: _parentView + onDialogCurrentColorChanged: { + if (root.listenToColorChanges) { + root.selectedColor = newColor; + root.previewColorSelected(); + } + } + } + + Rectangle { + id: rect + + width: _valueWidth / 4 + height: _controlBaseHeight + + border { + width: 1 + color: _studioColor2 + } + + MouseArea { + id: mouseArea + + anchors.fill: parent + onClicked: { + root.listenToColorChanges = true; + _inspectorModel.suspendMaterialRename(true); + root.selectedColor = _parentView.showColorDialog(rect.color, instance, handle); + root.listenToColorChanges = false; + _inspectorModel.suspendMaterialRename(false); + root.colorSelected(); + } + } + + Image { + id: img + // Source image size is 16x16 pixels + x: parent.width - 18 + y: parent.height / 2 - 8 + source: _resDir + "arrow_down.png" + } + } + + Item { + Layout.fillWidth: true + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericCheckbox.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericCheckbox.qml new file mode 100644 index 00000000..8446f761 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericCheckbox.qml @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +RowLayout { + id: root + + property bool checked: false + property alias label: labelField.text + + signal clicked() + + StyledLabel { + id: labelField + text: qsTr("Pause") + } + + Image { + source: _resDir + (checked ? "checkbox-checked.png" : "checkbox-unchecked.png") + + MouseArea { + anchors.fill: parent + onClicked: root.clicked() + } + } + + Item { + Layout.fillWidth: true + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericColor.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericColor.qml new file mode 100644 index 00000000..cad079ee --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericColor.qml @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +RowLayout { + id: root + + property alias label: labelField.text + property alias color: handlerGenericColor.color + property alias selectedColor: handlerGenericColor.selectedColor + + signal colorSelected() + signal previewColorSelected() + + StyledLabel { + id: labelField + text: qsTr("New Value") + } + + HandlerGenericBaseColor { + id: handlerGenericColor + + onColorSelected: root.colorSelected(); + onPreviewColorSelected: root.previewColorSelected(); + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericFloat.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericFloat.qml new file mode 100644 index 00000000..11ac38a5 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericFloat.qml @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +RowLayout { + id: root + + property alias label: labelField.text + property real desiredValue: Number(floatField.text) + property real value: 0 + property int numberOfDecimal: 3 + property Item tabItem1: floatField + + signal editingFinished + signal previewValueChanged + + onValueChanged: { + // FloatTextField can set its text internally, thus breaking the binding, so + // let's set the text value explicitly each time value changes + floatField.text = Number(value).toFixed(numberOfDecimal); + } + + StyledLabel { + id: labelField + } + + FloatTextField { + id: floatField + Layout.preferredWidth: _valueWidth + decimalValue: numberOfDecimal + onEditingFinished: root.editingFinished() + onPreviewValueChanged: root.previewValueChanged() + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericText.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericText.qml new file mode 100644 index 00000000..738bf6a3 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGenericText.qml @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +RowLayout { + id: root + + property alias label: labelField.text + property alias value: textField.text + property alias validator: textField.validator + property Item tabItem1: textField + + signal editingFinished + + onValueChanged: { + textField.text = value; + } + + StyledLabel { + id: labelField + } + + StyledTextField { + id: textField + onEditingFinished: root.editingFinished(); + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGoToSlide.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGoToSlide.qml new file mode 100644 index 00000000..6ad1564b --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerGoToSlide.qml @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +RowLayout { + id: root + + property alias label: labelField.text + property alias slideModel: comboSlide.model + property string currentSlide + property int defaultSlideIndex: 0 + + signal activated() + + onDefaultSlideIndexChanged: comboSlide.currentIndex = defaultSlideIndex + + StyledLabel { + id: labelField + text: qsTr("Slide") + } + + StyledComboBox { + id: comboSlide + Layout.preferredWidth: _valueWidth + model: slideModel + + onActivated: { + currentSlide = comboSlide.textAt(currentIndex); + root.activated(); + } + } +} + diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerMultilineText.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerMultilineText.qml new file mode 100644 index 00000000..834b1083 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerMultilineText.qml @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +RowLayout { + id: root + + property alias label: labelField.text + property alias value: multiLine.value + property alias tabItem1: multiLine.tabItem1 + + signal editingFinished + signal textChanged + + onValueChanged: { + multiLine.value = value; + } + + StyledLabel { + id: labelField + } + + HandlerBaseMultilineText { + id: multiLine + + Layout.preferredWidth: _valueWidth + Layout.preferredHeight: _controlBaseHeight * 3 + + onTextChanged: root.textChanged(); + onEditingFinished: root.editingFinished(); + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerProperty.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerProperty.qml new file mode 100644 index 00000000..a101488e --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerProperty.qml @@ -0,0 +1,290 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import Qt3DStudio 1.0 +import "../controls" + +ColumnLayout { + id: root + + property alias propertyModel: propertyCombo.model + property int defaultPropertyIndex: 0 + + signal propertySelected(int index) + + onDefaultPropertyIndexChanged: propertyCombo.currentIndex = defaultPropertyIndex + + RowLayout { + + Layout.fillWidth: true + + StyledLabel { + text: qsTr("Property") + } + + StyledComboBox { + id: propertyCombo + textRole: "name" + onActivated: root.propertySelected(currentIndex) + onModelChanged: currentIndex = root.defaultPropertyIndex + } + } + + Component { + id: multiLineComponent + + HandlerMultilineText { + readonly property var actionProperty: parent ? _parentView.property : null + + label: parent ? parent.label : "" + value: propertyModel && !_parentView.propertyValueInvalid + && propertyModel.value !== undefined ? propertyModel.value : "" + onEditingFinished: _parentView.setArgumentValue(propertyModel.valueHandle, value) + } + } + + Component { + id: fontSizeComponent + + HandlerPropertyCombo { + readonly property var actionProperty: parent ? _parentView.property : null + property var propertyValue: propertyModel && !_parentView.propertyValueInvalid + && propertyModel.value !== undefined + ? propertyModel.value : "" + + label: parent ? parent.label : "" + comboModel: ["8", "9", "10", "11", "12", "14", "16", "18", "20", "22", "24", "26", + "28", "36", "48", "72", "96", "120"]; + + onValueChanged: _parentView.setArgumentValue(propertyModel.valueHandle, value) + onPropertyValueChanged: currentIndex = find(propertyValue) + } + } + + Component { + id: xyzPropertyComponent + + HandlerPropertyXYZ { + readonly property var propValue: propertyModel && !_parentView.propertyValueInvalid + && propertyModel.value !== undefined + ? propertyModel.value : undefined + label: parent ? parent.label : "" + valueX: propValue !== undefined ? Number(propValue.x).toFixed(numberOfDecimal) : "0.000" + valueY: propValue !== undefined ? Number(propValue.y).toFixed(numberOfDecimal) : "0.000" + valueZ: propValue !== undefined ? Number(propValue.z).toFixed(numberOfDecimal) : "0.000" + + onPropValueChanged: { + // FloatTextField can set its text internally, thus breaking the binding, so + // let's set the text value explicitly each time value changes + if (propValue !== undefined) { + valueX = Number(propValue.x).toFixed(numberOfDecimal); + valueY = Number(propValue.y).toFixed(numberOfDecimal); + valueZ = Number(propValue.z).toFixed(numberOfDecimal); + } + } + + onEditingFinished: { + _parentView.setArgumentValue(propertyModel.valueHandle, + Qt.vector3d(valueX, valueY, valueZ), true); + } + } + } + + Component { + id: sliderPropertyComponent + + HandlerPropertySlider { + readonly property var actionProperty: parent ? _parentView.property : null + + sliderMin: actionProperty ? actionProperty.min : 0 + sliderMax: actionProperty ? actionProperty.max : 100 + intSlider: actionProperty ? actionProperty.type === DataModelDataType.Long : false + value: propertyModel && !_parentView.propertyValueInvalid + && propertyModel.value !== undefined ? propertyModel.value : sliderMin + label: parent ? parent.label : "" + + // We don't need to care about preview for action sliders + onCommitValue: _parentView.setArgumentValue(propertyModel.valueHandle, desiredValue) + } + } + + Component { + id: comboPropertyComponent + + HandlerPropertyCombo { + readonly property var actionProperty: parent ? _parentView.property : null + property var propertyValue: propertyModel && !_parentView.propertyValueInvalid + && propertyModel.value !== undefined + ? propertyModel.value : "" + + label: parent ? parent.label : "" + comboModel: actionProperty ? actionProperty.possibleValues : null + + onValueChanged: _parentView.setArgumentValue(propertyModel.valueHandle, value) + onPropertyValueChanged: currentIndex = find(propertyValue) + } + } + + Component { + id: booleanComponent + + HandlerGenericCheckbox { + label: parent ? parent.label : "" + checked: propertyModel && !_parentView.propertyValueInvalid + && propertyModel.value !== undefined ? propertyModel.value : false + + onClicked: { + _parentView.setArgumentValue(propertyModel.valueHandle, !checked) + } + } + } + + Component { + id: colorBox + + HandlerGenericColor { + readonly property var propValue: propertyModel && !_parentView.propertyValueInvalid + ? propertyModel.value : undefined + + label: parent ? parent.label : "" + color: "black" + onColorSelected: { + color = selectedColor; + _parentView.setArgumentValue(propertyModel.valueHandle, selectedColor); + } + onPreviewColorSelected: color = selectedColor + onPropValueChanged: { + color = propValue ? Qt.rgba(propValue.x, propValue.y, propValue.z, 1) + : "black"; + } + } + } + + Component { + id: genericTextComponent + + HandlerGenericText { + label: parent ? parent.label : "" + value: propertyModel && !_parentView.propertyValueInvalid + && propertyModel.value !== undefined ? propertyModel.value : "" + onEditingFinished: _parentView.setArgumentValue(propertyModel.valueHandle, value) + } + } + + Component { + id: floatPropertyComponent + + HandlerGenericFloat { + label: parent ? parent.label : "" + value: propertyModel && !_parentView.propertyValueInvalid + && propertyModel.value !== undefined + ? Number(propertyModel.value).toFixed(numberOfDecimal) : 0 + + onEditingFinished: _parentView.setArgumentValue(propertyModel.valueHandle, desiredValue) + } + } + + Loader { + readonly property string label: qsTr("New Value") + readonly property var actionProperty: _parentView.property + + Layout.fillWidth: true + + onLoaded: { + _tabOrderHandler.clear(); + if (item.tabItem1 !== undefined) { + _tabOrderHandler.addItem(0, item.tabItem1) + if (item.tabItem2 !== undefined) { + _tabOrderHandler.addItem(0, item.tabItem2) + if (item.tabItem3 !== undefined) + _tabOrderHandler.addItem(0, item.tabItem3) + } + } + } + + sourceComponent: { + // KDAB_TODO Handle additionaltype + switch (actionProperty.type) { + case DataModelDataType.Float: + switch (actionProperty.additionalType) { + case AdditionalMetaDataType.FontSize: + return fontSizeComponent; + case AdditionalMetaDataType.Range: + return sliderPropertyComponent; + default: + return floatPropertyComponent; + } + case DataModelDataType.Long: + return sliderPropertyComponent; + case DataModelDataType.Float3: + switch (actionProperty.additionalType) { + case AdditionalMetaDataType.None: + case AdditionalMetaDataType.Rotation: + return xyzPropertyComponent; + default: + console.warn("KDAB_TODO implement property handler for additional " + + "typeDataModelDataType.Float3: ", actionProperty.additionalType); + return xyzPropertyComponent; + } + case DataModelDataType.Float4: + if (actionProperty.additionalType === AdditionalMetaDataType.Color) + return colorBox; + break; + + case DataModelDataType.String: + switch (actionProperty.additionalType) { + case AdditionalMetaDataType.StringList: + return comboPropertyComponent; + case AdditionalMetaDataType.MultiLine: + return multiLineComponent; + case AdditionalMetaDataType.Font: + return comboPropertyComponent; + case AdditionalMetaDataType.Import: + case AdditionalMetaDataType.Renderable: + case AdditionalMetaDataType.String: + return genericTextComponent; + default: + console.warn("KDAB_TODO implement property handler for additional type: ", + actionProperty.additionalType) + return null; + } + case DataModelDataType.Bool: + return booleanComponent; + case DataModelDataType.None: + return null; + default: console.warn("KDAB_TODO implement property handler for type: ", + actionProperty.type) + + } + return null; + } + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseSlider.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseSlider.qml new file mode 100644 index 00000000..7019dff2 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseSlider.qml @@ -0,0 +1,241 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +/* +* Use for: Opacity, Edge Tesselation Value, Inner Tesselation Value ... +* For the latter two set sliderMax to 64 +*/ + +Row { + id: root + + property real value: 0 // This is the value coming from backend + property alias desiredValue: slider.value // This is value adjusted by user + property alias sliderMin: slider.from + property alias sliderMax: slider.to + property real sliderDecimals: -1 + property bool intSlider: false + property int decimalSlider: sliderDecimals >= 0 ? sliderDecimals + : Math.min(precision(slider.stepSize), 3) + property Item tabItem1: textField + + signal previewValue // Indicates desiredValue contains a preview value + signal commitValue // Indicates desiredValue contains a final value to be committed + + spacing: 5 + width: _valueWidth + + function doCommitValue() { + wheelCommitTimer.stop(); + if (rateLimiter.running) + rateLimiter.stop(); + textField.setTextFieldValue(); + root.commitValue(); + } + + // get the number of decimals in a float/double + function precision(a) { + if (!isFinite(a)) return 0; + var e = 1, p = 0; + while (Math.round(a * e) / e !== a) { e *= 10; p++; } + return p; + } + + onValueChanged: { + slider.value = value; + textField.setTextFieldValue(); + } + + Keys.onPressed: { + if (event.key === Qt.Key_Up || event.key === Qt.Key_Down) { + event.accepted = true + var delta = 1.0; + if (intSlider) { + if (event.key === Qt.Key_Down) + delta = -delta; + slider.value = Number(slider.value + delta).toFixed(0); + } else { + if (event.modifiers === Qt.ControlModifier) + delta = 0.1; + else if (event.modifiers === Qt.ShiftModifier) + delta = 10.0; + if (event.key === Qt.Key_Down) + delta = -delta; + slider.value = Number(slider.value + delta).toFixed(doubleValidator.decimals); + } + wheelCommitTimer.stop(); + if (!rateLimiter.running) + rateLimiter.start(); + textField.setTextFieldValue(); + } + } + + Slider { + id: slider + + leftPadding: 0 + + background: Rectangle { + x: slider.leftPadding + y: slider.topPadding + slider.availableHeight / 2 - height / 2 + implicitWidth: _valueWidth - textField.width - 5 + implicitHeight: 6 + height: implicitHeight + radius: 2 + color: _studioColor2 + } + handle: Rectangle { + x: slider.leftPadding + slider.visualPosition * slider.availableWidth + y: slider.topPadding + slider.availableHeight / 2 - height / 2 + implicitWidth: 6 + implicitHeight: 12 + color: _studioColor3 + radius: 2 + } + + from: 0 + to: 100 + stepSize: root.intSlider ? 1 : sliderStepFromRange(slider.to, slider.from, 100) + snapMode: root.intSlider ? Slider.SnapAlways : Slider.NoSnap + + function sliderStepFromRange(top, bottom, steps) { + return ((top - bottom) / steps); + } + + onMoved: { + wheelCommitTimer.stop(); + if (!rateLimiter.running) + rateLimiter.start(); + textField.setTextFieldValue(); + } + + // onPressedChanged is triggered both mouse clicks and arrow keys, so adjusting with arrow + // keys will create undo point for each tick slider moves (even when holding the key down) + onPressedChanged: { + if (!pressed) + root.doCommitValue(); + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + + onWheel: { + var delta = (wheel.angleDelta.x != 0) ? wheel.angleDelta.x + : wheel.angleDelta.y; + + if (delta > 0) + slider.increase(); + else + slider.decrease(); + if (!rateLimiter.running) + rateLimiter.start(); + textField.setTextFieldValue(); + + // Leaving a transaction open can interfere with other editor functionality, + // so commit the wheel transaction after a brief delay + wheelCommitTimer.restart(); + } + Timer { + id: wheelCommitTimer + interval: 1000 + onTriggered: { + root.doCommitValue(); + } + } + } + } + + Timer { + id: rateLimiter + interval: 10 + onTriggered: { + root.previewValue(); + } + } + + DoubleValidator { + id: doubleValidator + + decimals: decimalSlider + bottom: slider.from + top: slider.to + locale: "C" + } + + IntValidator { + id: intValidator + + bottom: slider.from + top: slider.to + } + + StyledTextField { + id: textField + + height: _controlBaseHeight + width: 55 + text: intSlider ? slider.value.toFixed(0) : slider.value.toFixed(decimalSlider) + + validator: intSlider ? intValidator : doubleValidator + + onTextEdited: { + if (!intSlider && text.search(",")) { + text = text.replace(",",".") + } + if (intSlider) { + // handle limiting integer values when entered value is less than + // minimum value since IntValidator doesn't handle this + if (text.length >= sliderMin.toString().length && text < sliderMin) + text = text.substring(0, text.length - 1) + } + } + + onEditingFinished: { + if (text > sliderMax) + text = sliderMax + else if (text < sliderMin) + text = sliderMin + slider.value = text + root.doCommitValue(); + } + + function setTextFieldValue() { + text = intSlider ? slider.value.toFixed(0) : slider.value.toFixed(decimalSlider) + } + onActiveFocusChanged: { + if (!activeFocus) + setTextFieldValue() + } + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseXY.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseXY.qml new file mode 100644 index 00000000..4406703f --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseXY.qml @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +// Used for: Tiling + +RowLayout { + id: root + + property alias valueX: textFieldX.text + property alias valueY: textFieldY.text + property int numberOfDecimal: 3 + property Item tabItem1: textFieldX + property Item tabItem2: textFieldY + + signal editingFinished + signal previewValueChanged + + spacing: 0 + + StyledLabel { + Layout.preferredWidth: 10 + text: qsTr("X") + color: _xAxisColor + } + + FloatTextField { + id: textFieldX + Layout.preferredWidth: (_valueWidth - 40) / 2 + decimalValue: numberOfDecimal + onEditingFinished: root.editingFinished() + onPreviewValueChanged: root.previewValueChanged() + } + + Item { width: 20 } + + StyledLabel { + Layout.preferredWidth: 10 + text: qsTr("Y") + color: _yAxisColor + } + + FloatTextField { + id: textFieldY + Layout.preferredWidth: (_valueWidth - 40) / 2 + decimalValue: numberOfDecimal + onEditingFinished: root.editingFinished() + onPreviewValueChanged: root.previewValueChanged() + } +} + diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseXYZ.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseXYZ.qml new file mode 100644 index 00000000..50440dba --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyBaseXYZ.qml @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +/* Use for: Position, Rotation, Scale, Pivot ... */ + +RowLayout { + id: root + + property alias valueX: textFieldX.text + property alias valueY: textFieldY.text + property alias valueZ: textFieldZ.text + property int numberOfDecimal: 3 + property Item tabItem1: textFieldX + property Item tabItem2: textFieldY + property Item tabItem3: textFieldZ + + signal editingFinished + signal previewValueChanged + transformOrigin: Item.Center + spacing: 0 + + StyledLabel { + Layout.preferredWidth: 10 + text: qsTr("X") + color: _xAxisColor + } + + FloatTextField { + id: textFieldX + Layout.preferredWidth: (_valueWidth - 50) / 3 + decimalValue: numberOfDecimal + onEditingFinished: root.editingFinished() + onPreviewValueChanged: root.previewValueChanged() + } + + Item { width: 10 } + + StyledLabel { + Layout.preferredWidth: 10 + text: qsTr("Y") + color: _yAxisColor + } + + FloatTextField { + id: textFieldY + Layout.preferredWidth: (_valueWidth - 50) / 3 + decimalValue: numberOfDecimal + onEditingFinished: root.editingFinished() + onPreviewValueChanged: root.previewValueChanged() + } + + Item { width: 10 } + + StyledLabel { + Layout.preferredWidth: 10 + text: qsTr("Z") + color: _zAxisColor + } + + FloatTextField { + id: textFieldZ + Layout.preferredWidth: (_valueWidth - 50) / 3 + decimalValue: numberOfDecimal + onEditingFinished: root.editingFinished() + onPreviewValueChanged: root.previewValueChanged() + } +} + diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyCombo.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyCombo.qml new file mode 100644 index 00000000..37e76aa9 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyCombo.qml @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +/* Use for Tesselation mode, Horizontal alignment, Vertical alignment ... */ + +RowLayout { + id: root + + property alias label: labelField.text + property alias comboModel : comboBox.model + property alias comboTextRole: comboBox.textRole + property alias currentIndex: comboBox.currentIndex + property string value + + function find(text) { + return comboBox.find(text); + } + + StyledLabel { + id: labelField + text: qsTr("New Value") + } + + StyledComboBox { + id: comboBox + + Layout.fillWidth: true + onActivated: value = comboBox.textAt(currentIndex) + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertySlider.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertySlider.qml new file mode 100644 index 00000000..db6f88f2 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertySlider.qml @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +/* +* Use for: Opacity, Edge Tesselation Value, Inner Tesselation Value ... +* For the latter two set sliderMax to 64 +*/ + +GridLayout { + id: root + + property alias value: propertySlider.value + property alias desiredValue: propertySlider.desiredValue + property alias sliderMin: propertySlider.sliderMin + property alias sliderMax: propertySlider.sliderMax + property alias label: labelItem.text + property alias intSlider: propertySlider.intSlider + property alias decimalSlider: propertySlider.decimalSlider + property alias tabItem1: propertySlider.tabItem1 + + signal previewValue + signal commitValue + + columns: 3 + + StyledLabel { + id: labelItem + text: label + } + + HandlerPropertyBaseSlider { + id: propertySlider + // proxy the signal upwards + onCommitValue: root.commitValue() + onPreviewValue: root.previewValue() + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyXYZ.qml b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyXYZ.qml new file mode 100644 index 00000000..6571f1d0 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/HandlerPropertyXYZ.qml @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +/* Use for: Position, Rotation, Scale, Pivot ... */ + +RowLayout { + id: root + + property alias valueX: propertyXYZ.valueX + property alias valueY: propertyXYZ.valueY + property alias valueZ: propertyXYZ.valueZ + property alias label: labelItem.text + property alias tabItem1: propertyXYZ.tabItem1 + property alias tabItem2: propertyXYZ.tabItem2 + property alias tabItem3: propertyXYZ.tabItem3 + property alias numberOfDecimal: propertyXYZ.numberOfDecimal + + signal editingFinished + signal previewValueChanged + + StyledLabel { + id: labelItem + Layout.alignment: Qt.AlignTop | Qt.AlignLeft + text: qsTr("New Value") + } + + HandlerPropertyBaseXYZ { + id: propertyXYZ + Layout.alignment: Qt.AlignRight + + onEditingFinished: root.editingFinished() + onPreviewValueChanged: root.previewValueChanged() + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/PropertyModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Action/PropertyModel.cpp new file mode 100644 index 00000000..8024204d --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/PropertyModel.cpp @@ -0,0 +1,255 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "PropertyModel.h" + +#include "ClientDataModelBridge.h" +#include "Core.h" +#include "Doc.h" +#include "StudioApp.h" + +#include "Qt3DSDMActionCore.h" +#include "Qt3DSDMActionInfo.h" +#include "Qt3DSDMDataCore.h" +#include "Qt3DSDMMetaData.h" +#include "Qt3DSDMStudioSystem.h" + + +PropertyModel::PropertyModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +void PropertyModel::setAction(const qt3dsdm::Qt3DSDMActionHandle &action) +{ + beginResetModel(); + m_action = action; + m_valueHandle = 0; + m_nameHandle = 0; + m_properties.clear(); + + if (action.Valid()) { + auto doc = g_StudioApp.GetCore()->GetDoc(); + auto studioSystem = doc->GetStudioSystem(); + auto propertySystem = studioSystem->GetPropertySystem(); + auto bridge = studioSystem->GetClientDataModelBridge(); + + auto actionInfo = studioSystem->GetActionCore()->GetActionInfo(action); + + qt3dsdm::IMetaData &metaData(*studioSystem->GetActionMetaData()); + qt3dsdm::TMetaDataPropertyHandleList metaProperties; + const auto instance = bridge->GetInstance(actionInfo.m_Owner, actionInfo.m_TargetObject); + if (instance.Valid()) { + metaData.GetMetaDataProperties(instance, metaProperties); + + for (const auto &metaProperty: metaProperties) { + auto propertyMetaInfo = metaData.GetMetaDataPropertyInfo(metaProperty); + if (propertyMetaInfo->m_IsHidden == false) { + PropertyInfo property; + property.m_handle = propertyMetaInfo->m_Property; + property.m_name = QString::fromWCharArray( + propertySystem->GetFormalName(instance, + property.m_handle).wide_str()); + property.m_nameId = QString::fromWCharArray( + propertySystem->GetName(property.m_handle).wide_str()); + property.m_type = propertyMetaInfo->GetDataType(); + property.m_additionalType = propertyMetaInfo->GetAdditionalType(); + + const auto additionalMetaDataType = + propertySystem->GetAdditionalMetaDataType(instance, property.m_handle); + switch (additionalMetaDataType) { + case qt3dsdm::AdditionalMetaDataType::Range: { + const qt3dsdm::TMetaDataData &metaDataData = + propertySystem->GetAdditionalMetaDataData(instance, + property.m_handle); + qt3dsdm::SMetaDataRange minMax = + qt3dsdm::get<qt3dsdm::SMetaDataRange>(metaDataData); + property.m_min = minMax.m_min; + property.m_max = minMax.m_max; + break; + } + case qt3dsdm::AdditionalMetaDataType::StringList: { + const qt3dsdm::TMetaDataData &metaDataData = + propertySystem->GetAdditionalMetaDataData(instance, + property.m_handle); + auto values = qt3dsdm::get<qt3dsdm::TMetaDataStringList>(metaDataData); + QStringList possibleValues; + for (const auto &value: values) + possibleValues.append(QString::fromWCharArray(value.wide_str())); + property.m_possibleValues = possibleValues; + break; + } + case qt3dsdm::AdditionalMetaDataType::Font: { + std::vector<QString> fontNames; + doc->GetProjectFonts(fontNames); + QStringList possibleValues; + for (const auto &fontName: fontNames) + possibleValues.append(fontName); + property.m_possibleValues = possibleValues; + break; + } + default: + break; + } + // Skip Name, we don't want to allow changing that + // TODO: To be localized when/if we add support for metadata localization + if (property.m_name != QLatin1String("Name")) + m_properties.append(property); + } + } + } + } + endResetModel(); + + Q_EMIT valueHandleChanged(); +} + +void PropertyModel::setNameHandle(const qt3dsdm::Qt3DSDMHandlerArgHandle &handle) +{ + m_nameHandle = handle; +} + +void PropertyModel::setValueHandle(const qt3dsdm::Qt3DSDMHandlerArgHandle &handle) +{ + if (m_valueHandle != handle) { + m_valueHandle = handle; + updateDefaultPropertyIndex(); + updateValue(); + Q_EMIT valueHandleChanged(); + } +} + +int PropertyModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return m_properties.size(); +} + + +QVariant PropertyModel::data(const QModelIndex &index, int role) const +{ + if (!hasIndex(index.row(), index.column(), index.parent())) + return {}; + + const auto property = m_properties.at(index.row()); + + switch (role) + { + case NameRole: + return property.m_name; + case HandleRole: + return property.m_handle.GetHandleValue(); + default: + return {}; + } + + return QVariant(); +} + +QHash<int, QByteArray> PropertyModel::roleNames() const +{ + auto names = QAbstractItemModel::roleNames(); + names.insert(NameRole, "name"); + names.insert(HandleRole, "handle"); + + return names; +} + +PropertyInfo PropertyModel::property(int index) const +{ + if (index < 0 || index >= m_properties.size()) + return {}; + return m_properties[index]; +} + +int PropertyModel::valueHandle() const +{ + return m_valueHandle; +} + +QVariant PropertyModel::value() const +{ + return m_value; +} + +void PropertyModel::updateDefaultPropertyIndex() +{ + if (!m_nameHandle.Valid()) { + m_defaultPropertyIndex = -1; + Q_EMIT defaultPropertyIndexChanged(); + return; + } + + qt3dsdm::SValue sValue; + auto doc = g_StudioApp.GetCore()->GetDoc(); + auto studioSystem = doc->GetStudioSystem(); + studioSystem->GetActionCore()->GetHandlerArgumentValue(m_nameHandle, sValue); + + if (sValue.getType() != qt3dsdm::DataModelDataType::String) { + m_defaultPropertyIndex = -1; + Q_EMIT defaultPropertyIndexChanged(); + return; + } + + auto propertyName = qt3dsdm::get<QString>(sValue); + auto iter = std::find_if(m_properties.constBegin(), m_properties.constEnd(), + [&propertyName](const PropertyInfo &info) + { + return (info.m_nameId == propertyName); + }); + + auto index = std::distance(m_properties.constBegin(), iter); + + if (m_defaultPropertyIndex != index) { + m_defaultPropertyIndex = index; + Q_EMIT defaultPropertyIndexChanged(); + } +} + +int PropertyModel::defaultPropertyIndex() const +{ + return m_defaultPropertyIndex; +} + +void PropertyModel::updateValue() +{ + const auto oldValue = m_value; + if (!m_valueHandle.Valid()) { + m_value.clear(); + } else { + qt3dsdm::SValue sValue; + auto doc = g_StudioApp.GetCore()->GetDoc(); + auto studioSystem = doc->GetStudioSystem(); + studioSystem->GetActionCore()->GetHandlerArgumentValue(m_valueHandle, sValue); + m_value = sValue.toQVariant(); + } + if (oldValue != m_value) + Q_EMIT valueChanged(); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Action/PropertyModel.h b/src/Authoring/Qt3DStudio/Palettes/Action/PropertyModel.h new file mode 100644 index 00000000..a833752b --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Action/PropertyModel.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef PROPERTYMODEL_H +#define PROPERTYMODEL_H + +#include <QAbstractListModel> + +#include "Qt3DSDMHandles.h" +#include "Qt3DSDMDataTypes.h" +#include "Qt3DSDMMetaDataTypes.h" + +struct PropertyInfo { + Q_PROPERTY(QString name MEMBER m_name CONSTANT FINAL) + Q_PROPERTY(float min MEMBER m_min CONSTANT FINAL) + Q_PROPERTY(float max MEMBER m_max CONSTANT FINAL) + Q_PROPERTY(qt3dsdm::DataModelDataType::Value type MEMBER m_type CONSTANT FINAL) + Q_PROPERTY(qt3dsdm::AdditionalMetaDataType::Value additionalType MEMBER m_additionalType CONSTANT FINAL) + Q_PROPERTY(QStringList possibleValues MEMBER m_possibleValues CONSTANT FINAL) + + qt3dsdm::Qt3DSDMPropertyHandle m_handle; + QString m_name; + QString m_nameId; + qt3dsdm::DataModelDataType::Value m_type; + qt3dsdm::AdditionalMetaDataType::Value m_additionalType; + QStringList m_possibleValues; + float m_min = 0.0f; + float m_max = 0.0f; + + Q_GADGET +}; + +class PropertyModel : public QAbstractListModel +{ + Q_PROPERTY(int valueHandle READ valueHandle NOTIFY valueHandleChanged FINAL) + Q_PROPERTY(QVariant value READ value NOTIFY valueChanged FINAL) + Q_PROPERTY(int defaultPropertyIndex READ defaultPropertyIndex NOTIFY defaultPropertyIndexChanged FINAL) + Q_OBJECT + +public: + explicit PropertyModel(QObject *parent = nullptr); + + enum Roles { + NameRole = Qt::DisplayRole, + HandleRole = Qt::UserRole + 1 + }; + + void setAction(const qt3dsdm::Qt3DSDMActionHandle &action); + void setNameHandle(const qt3dsdm::Qt3DSDMHandlerArgHandle &valueHandle); + void setValueHandle(const qt3dsdm::Qt3DSDMHandlerArgHandle &valueHandle); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QHash<int, QByteArray> roleNames() const override; + + PropertyInfo property(int index) const; + qt3dsdm::Qt3DSDMActionHandle action() const { return m_action; } + int valueHandle() const; + + QVariant value() const; + int defaultPropertyIndex() const; + +Q_SIGNALS: + void valueHandleChanged(); + void valueChanged(); + void defaultPropertyIndexChanged(); + +private: + void updateValue(); + void updateDefaultPropertyIndex(); + + QVector<PropertyInfo> m_properties; + qt3dsdm::Qt3DSDMActionHandle m_action; + qt3dsdm::Qt3DSDMHandlerArgHandle m_nameHandle; + qt3dsdm::Qt3DSDMHandlerArgHandle m_valueHandle; + int m_defaultPropertyIndex = -1; + QVariant m_value; +}; + +Q_DECLARE_METATYPE(PropertyInfo) + +#endif // PROPERTYMODEL_H diff --git a/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsModel.cpp b/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsModel.cpp new file mode 100644 index 00000000..8694fda7 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsModel.cpp @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "BasicObjectsModel.h" +#include "DropSource.h" +#include "Literals.h" +#include "StudioUtils.h" + +#include <QCoreApplication> +#include <QDataStream> +#include <QMimeData> + +BasicObjectsModel::BasicObjectsModel(QObject *parent) : QAbstractListModel(parent) +{ + initialize(); +} + +void BasicObjectsModel::initialize() +{ + m_ObjectItems = InitializeObjectModel(); +} + +const QVector<BasicObjectItem> BasicObjectsModel::InitializeObjectModel() +{ + return { + {tr("Rectangle"), "Asset-Rectangle-Normal.png"_L1, OBJTYPE_MODEL, PRIMITIVETYPE_RECT}, + {tr("Sphere"), "Asset-Sphere-Normal.png"_L1, OBJTYPE_MODEL, PRIMITIVETYPE_SPHERE}, + {tr("Cube"), "Asset-Cube-Normal.png"_L1, OBJTYPE_MODEL, PRIMITIVETYPE_BOX}, + {tr("Cylinder"), "Asset-Cylinder-Normal.png"_L1, OBJTYPE_MODEL, PRIMITIVETYPE_CYLINDER}, + {tr("Cone"), "Asset-Cone-Normal.png"_L1, OBJTYPE_MODEL, PRIMITIVETYPE_CONE}, + {tr("Component"), "Asset-Component-Normal.png"_L1, OBJTYPE_COMPONENT, PRIMITIVETYPE_UNKNOWN}, + {tr("Group"), "Asset-Group-Normal.png"_L1, OBJTYPE_GROUP, PRIMITIVETYPE_UNKNOWN}, + {tr("Text"), "Asset-Text-Normal.png"_L1, OBJTYPE_TEXT, PRIMITIVETYPE_UNKNOWN}, + {tr("Camera"), "Asset-Camera-Normal.png"_L1, OBJTYPE_CAMERA, PRIMITIVETYPE_UNKNOWN}, + {tr("Light"), "Asset-Light-Normal.png"_L1, OBJTYPE_LIGHT, PRIMITIVETYPE_UNKNOWN}, + // Hide alias until it will be replaced with prefabs + //{tr("Alias"), "Asset-Alias-Normal.png"_L1, OBJTYPE_ALIAS, PRIMITIVETYPE_UNKNOWN}, + }; +} + +// Returns meshes part of basic objects +const QVector<BasicObjectItem> BasicObjectsModel::BasicMeshesModel() +{ + return InitializeObjectModel().mid(0, 5); +} + +QVariant BasicObjectsModel::data(const QModelIndex &index, int role) const +{ + if (!hasIndex(index.row(), index.column(),index.parent())) + return {}; + + const auto row = index.row(); + + switch (role) { + case NameRole: return m_ObjectItems.at(row).name(); + case IconRole: return StudioUtils::resourceImageUrl() + + m_ObjectItems.at(row).icon(); + case ObjectTypeRole: return m_ObjectItems.at(row).objectType(); + case PrimitiveTypeRole: return m_ObjectItems.at(row).primitiveType(); + } + + return {}; +} + +int BasicObjectsModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + return m_ObjectItems.count(); +} + +QHash<int, QByteArray> BasicObjectsModel::roleNames() const +{ + auto names = QAbstractListModel::roleNames(); + names.insert(NameRole, "name"); + names.insert(IconRole, "icon"); + + return names; +} + +Qt::ItemFlags BasicObjectsModel::flags(const QModelIndex &index) const { + if (index.isValid()) + return Qt::ItemIsDragEnabled; + + return QAbstractListModel::flags(index); +} + +QStringList BasicObjectsModel::mimeTypes() const +{ + return { m_MimeType }; +} + +QMimeData *BasicObjectsModel::mimeData(const QModelIndexList &indexes) const +{ + + const auto row = indexes.first().row(); // we support only one item for D&D + auto object = m_ObjectItems.at(row); + + auto *data = CDropSourceFactory::Create(object.GetFlavor(), &object); + return data; +} + +BasicObjectItem::~BasicObjectItem() +{ +} diff --git a/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsModel.h b/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsModel.h new file mode 100644 index 00000000..2e2faa83 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsModel.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef BASICOBJECTSMODEL_H +#define BASICOBJECTSMODEL_H + +#include <QAbstractListModel> + +#include "IDragable.h" +#include "StudioObjectTypes.h" + +class BasicObjectItem : public IDragable { + +public: + BasicObjectItem() {} + BasicObjectItem(const QString &name, const QString &icon, + EStudioObjectType objectType, EPrimitiveType primitiveType) + : m_name(name), m_icon(icon) + , m_objectType(objectType), m_primitiveType(primitiveType) + { } + + virtual ~BasicObjectItem(); + + QString name() const { return m_name; } + QString icon() const { return m_icon; } + + EStudioObjectType objectType() const { return m_objectType; } + EPrimitiveType primitiveType() const { return m_primitiveType; } + + void setName(const QString &name) { m_name = name; } + void setIcon(const QString &icon) { m_icon = icon; } + void setObjectType(EStudioObjectType type) { m_objectType = type; } + void setPrimitveType(EPrimitiveType type) { m_primitiveType = type; } + + long GetFlavor() const override {return QT3DS_FLAVOR_BASIC_OBJECTS;} + +private: + QString m_name; + QString m_icon; + EStudioObjectType m_objectType; + EPrimitiveType m_primitiveType; +}; + +class BasicObjectsModel : public QAbstractListModel +{ + Q_OBJECT +public: + BasicObjectsModel(QObject *parent = nullptr); + + enum Roles { + NameRole = Qt::DisplayRole, + IconRole = Qt::DecorationRole, + ObjectTypeRole = Qt::UserRole + 1, + PrimitiveTypeRole + }; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QHash<int, QByteArray> roleNames() const override; + + Qt::ItemFlags flags(const QModelIndex &index) const override; + QStringList mimeTypes() const override; + QMimeData *mimeData(const QModelIndexList &indexes) const override; + + static const QVector<BasicObjectItem> BasicMeshesModel(); + +private: + void initialize(); + static const QVector<BasicObjectItem> InitializeObjectModel(); + + QVector<BasicObjectItem> m_ObjectItems; + + const QString m_MimeType = QLatin1String("application/x-basic-object"); +}; + +#endif // BASICOBJECTSMODEL_H diff --git a/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.cpp b/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.cpp new file mode 100644 index 00000000..e23423fe --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.cpp @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "BasicObjectsView.h" +#include "BasicObjectsModel.h" +#include "CColor.h" +#include "Literals.h" +#include "StudioPreferences.h" +#include "StudioUtils.h" +#include "StudioApp.h" + +#include <QtCore/qcoreapplication.h> +#include <QtCore/qtimer.h> +#include <QtGui/qdrag.h> +#include <QtQml/qqmlcontext.h> +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlfile.h> +#include <QtQuick/qquickitem.h> + +BasicObjectsView::BasicObjectsView(QWidget *parent) : QQuickWidget(parent) + , m_ObjectsModel(new BasicObjectsModel(this)) + +{ + setResizeMode(QQuickWidget::SizeRootObjectToView); + QTimer::singleShot(0, this, &BasicObjectsView::initialize); +} + +QSize BasicObjectsView::sizeHint() const +{ + return {150, 500}; +} + +void BasicObjectsView::startDrag(QQuickItem *item, int row) +{ + item->grabMouse(); // Grab to make sure we can ungrab after the drag + const auto index = m_ObjectsModel->index(row); + + QDrag drag(this); + drag.setMimeData(m_ObjectsModel->mimeData({index})); + drag.setPixmap(QPixmap(QQmlFile::urlToLocalFileOrQrc( + index.data(BasicObjectsModel::IconRole).toUrl()))); + drag.exec(Qt::CopyAction); + QTimer::singleShot(0, item, &QQuickItem::ungrabMouse); +} + +void BasicObjectsView::mousePressEvent(QMouseEvent *event) +{ + g_StudioApp.setLastActiveView(this); + QQuickWidget::mousePressEvent(event); +} + +void BasicObjectsView::initialize() +{ + CStudioPreferences::setQmlContextProperties(rootContext()); + rootContext()->setContextProperty(QStringLiteral("_basicObjectsModel"), m_ObjectsModel); + rootContext()->setContextProperty(QStringLiteral("_basicObjectsView"), this); + rootContext()->setContextProperty(QStringLiteral("_resDir"), StudioUtils::resourceImageUrl()); + + engine()->addImportPath(StudioUtils::qmlImportPath()); + setSource(QUrl(QStringLiteral("qrc:/Palettes/BasicObjects/BasicObjectsView.qml"))); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.h b/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.h new file mode 100644 index 00000000..19e8ddc3 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef BASICOBJECTSVIEW_H +#define BASICOBJECTSVIEW_H + +#include <QQuickWidget> + +class BasicObjectsModel; +QT_FORWARD_DECLARE_CLASS(QQuickItem) + +class BasicObjectsView : public QQuickWidget +{ + Q_OBJECT +public: + explicit BasicObjectsView(QWidget *parent = nullptr); + + QSize sizeHint() const override; + + Q_INVOKABLE void startDrag(QQuickItem *item, int row); + +protected: + void mousePressEvent(QMouseEvent *event) override; + +private: + void initialize(); + + BasicObjectsModel *m_ObjectsModel = nullptr; + QColor m_BaseColor = QColor::fromRgb(75, 75, 75); +}; + +#endif // BASICOBJECTSVIEW_H diff --git a/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.qml b/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.qml new file mode 100644 index 00000000..5f469a5a --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/BasicObjects/BasicObjectsView.qml @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import "../controls" + +Rectangle { + + color: _backgroundColor + + ListView { + anchors { + fill: parent + leftMargin: 8 + } + boundsBehavior: Flickable.StopAtBounds + + model: _basicObjectsModel + spacing: 2 + + ScrollBar.vertical: ScrollBar {} + + delegate: Item { + height: contentRow.height + width: contentRow.width + Item { + id: dragItem + anchors.fill: parent + + MouseArea { + id: dragArea + property bool dragStarted: false + property point pressPoint + + anchors.fill: parent + onPressed: { + pressPoint = Qt.point(mouse.x, mouse.y); + dragStarted = false; + } + onPositionChanged: { + if (!dragStarted && (Math.abs(mouse.x - pressPoint.x) > 4 + || Math.abs(mouse.y - pressPoint.y) > 4)) { + dragStarted = true; + _basicObjectsView.startDrag(dragItem, index); + } + } + } + } + Row { + id: contentRow + spacing: 4 + Image { + id: assetIcon + width: 24 + height: 24 + fillMode: Image.Pad + source: model.icon + } + StyledLabel { + y: (assetIcon.height - height) / 2 + text: model.name + } + } + } + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserDelegate.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserDelegate.qml new file mode 100644 index 00000000..71462f48 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserDelegate.qml @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +Rectangle { + id: item + + signal clicked(string filePath) + signal doubleClicked(string filePath) + + width: parent.width + height: 20 + color: isCurrentFile ? _selectionColor : "transparent" + + Row { + x: depth * 28 - (item.width <= _valueWidth ? 14 : 0) + anchors.verticalCenter: item.verticalCenter + + Image { + source: _resDir + (expanded ? "arrow_down.png" : "arrow.png") + opacity: isExpandable ? 1 : 0 + + MouseArea { + visible: listView.model && isExpandable + anchors.fill: parent + onClicked: { + if (expanded) + listView.model.collapse(index) + else + listView.model.expand(index) + } + } + } + + Image { + source: listView.model ? fileIcon : "" + width: 16 + height: 16 + } + + StyledLabel { + text: listView.model ? fileName : "" + color: _textColor + leftPadding: 5 + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton + onClicked: { + if (isSelectable) { + listView.model.setCurrentFile(filePath); + item.clicked(filePath); + } + } + onDoubleClicked: { + if (isSelectable) { + listView.model.setCurrentFile(filePath); + item.doubleClicked(filePath); + } else if (isExpandable) { + if (expanded) + listView.model.collapse(index); + else + listView.model.expand(index); + } + } + } + } + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserModelBase.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserModelBase.cpp new file mode 100644 index 00000000..3a1a008b --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserModelBase.cpp @@ -0,0 +1,630 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 <QtCore/qset.h> + +#include "Qt3DSCommonPrecompile.h" + +#include "ChooserModelBase.h" +#include "Core.h" +#include "Dispatch.h" +#include "Doc.h" +#include "StudioUtils.h" +#include "Qt3DSFileTools.h" +#include "ImportUtils.h" +#include "StudioApp.h" + +ChooserModelBase::ChooserModelBase(QObject *parent) : QAbstractListModel(parent) + , m_model(new QFileSystemModel(this)) +{ + connect(m_model, &QAbstractItemModel::rowsInserted, this, &ChooserModelBase::modelRowsInserted); + connect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ChooserModelBase::modelRowsRemoved); + connect(m_model, &QAbstractItemModel::layoutChanged, this, &ChooserModelBase::modelLayoutChanged); + + g_StudioApp.GetCore()->GetDispatch()->AddPresentationChangeListener(this); + + rebuild(); +} + +ChooserModelBase::~ChooserModelBase() +{ + g_StudioApp.GetCore()->GetDispatch()->RemovePresentationChangeListener(this); +} + +QHash<int, QByteArray> ChooserModelBase::roleNames() const +{ + auto modelRoleNames = m_model->roleNames(); + modelRoleNames.insert(IsExpandableRole, "isExpandable"); + modelRoleNames.insert(DepthRole, "depth"); + modelRoleNames.insert(ExpandedRole, "expanded"); + modelRoleNames.insert(IsSelectableRole, "isSelectable"); + modelRoleNames.insert(IsCurrentFile, "isCurrentFile"); + return modelRoleNames; +} + +int ChooserModelBase::rowCount(const QModelIndex &) const +{ + return getFixedItems().count() + m_items.count(); +} + +QVariant ChooserModelBase::data(const QModelIndex &index, int role) const +{ + const int row = index.row(); + + const auto fixedItems = getFixedItems(); + const int fixedItemCount = fixedItems.count(); + + if (row < fixedItemCount) { + const auto &item = fixedItems.at(row); + + switch (role) { + case Qt::DecorationRole: + if (!item.iconSource.isEmpty()) { + return StudioUtils::resourceImageUrl() + item.iconSource; + } else { + return StudioUtils::resourceImageUrl() + + CStudioObjectTypes::GetNormalIconName(item.iconType); + } + + case IsExpandableRole: + return false; + + case DepthRole: + return 0; + + case ExpandedRole: + return false; + + case IsSelectableRole: + return true; + + case IsCurrentFile: + return item.name == m_currentFile; + + default: + return item.name; + } + } else { + const auto &item = m_items.at(row - fixedItemCount); + + switch (role) { + case Qt::DecorationRole: { + QString path = item.index.data(QFileSystemModel::FilePathRole).toString(); + return StudioUtils::resourceImageUrl() + getIconName(path); + } + + case IsExpandableRole: { + QFileInfo fileInfo(item.index.data(QFileSystemModel::FilePathRole).toString()); + return fileInfo.isDir(); + } + + case DepthRole: + return item.depth; + + case ExpandedRole: + return item.expanded; + + case IsSelectableRole: { + QFileInfo fileInfo(item.index.data(QFileSystemModel::FilePathRole).toString()); + return fileInfo.isFile(); + } + + case IsCurrentFile: { + QString path = item.index.data(QFileSystemModel::FilePathRole).toString(); + return path == m_currentFile; + } + + case QFileSystemModel::FileNameRole: { + QString displayName = specialDisplayName(item); + if (displayName.isEmpty()) + displayName = m_model->data(item.index, QFileSystemModel::FileNameRole).toString(); + return displayName; + } + + default: + return m_model->data(item.index, role).toString(); + } + } +} + +void ChooserModelBase::setCurrentFile(const QString &path) +{ + const auto fixedItems = getFixedItems(); + const int fixedItemCount = fixedItems.count(); + const auto getFixedItemIndex = [fixedItemCount, &fixedItems](const QString &path) -> int { + for (int i = 0; i < fixedItemCount; ++i) { + const auto &item = fixedItems.at(i); + if (item.name == path) + return i; + } + return -1; + }; + int fixedItemIndex = getFixedItemIndex(path); + + const auto doc = g_StudioApp.GetCore()->GetDoc(); + const QDir documentDir(doc->GetDocumentDirectory()); + const QString fullPath = fixedItemIndex == -1 ? QDir::cleanPath(documentDir.filePath(path)) + : path; + + if (fullPath != m_currentFile) { + const auto fileRow = [this, getFixedItemIndex, fixedItemCount](const QString &path) -> int + { + const int fixedItemIndex = getFixedItemIndex(path); + if (fixedItemIndex != -1) + return fixedItemIndex; + + const int itemCount = m_items.count(); + + for (int i = 0; i < itemCount; ++i) { + const auto &item = m_items.at(i); + + if (item.index.data(QFileSystemModel::FilePathRole).toString() == path) + return fixedItemCount + i; + } + + return -1; + }; + + int previousRow = fileRow(m_currentFile); + int currentRow = fileRow(fullPath); + + m_currentFile = fullPath; + + if (previousRow != -1) + Q_EMIT dataChanged(index(previousRow), index(previousRow)); + + if (currentRow != -1) + Q_EMIT dataChanged(index(currentRow), index(currentRow)); + } + + // expand parent folder if current file is hidden + auto matched = m_model->match(m_rootIndex, QFileSystemModel::FilePathRole, path, 1, + Qt::MatchExactly|Qt::MatchRecursive); + if (!matched.isEmpty()) { + auto modelIndex = matched.first(); + if (modelIndexRow(modelIndex) == -1) + expand(m_model->parent(modelIndex)); + } +} + +void ChooserModelBase::expand(const QModelIndex &modelIndex) +{ + if (modelIndex == m_rootIndex) + return; + + int row = modelIndexRow(modelIndex); + if (row == -1) { + QModelIndex parentIndex = m_model->parent(modelIndex); + expand(parentIndex); + + row = modelIndexRow(modelIndex); + Q_ASSERT(row != -1); + } + + if (!m_items.at(row).expanded) + expand(row + getFixedItems().count()); +} + +void ChooserModelBase::setRootPath(const QString &path) +{ + // Delete the old model. If the new project is in a totally different directory tree, not + // doing this will result in unexplicable crashes when trying to parse something that should + // not be parsed. + disconnect(m_model, &QAbstractItemModel::rowsInserted, + this, &ChooserModelBase::modelRowsInserted); + disconnect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved, + this, &ChooserModelBase::modelRowsRemoved); + disconnect(m_model, &QAbstractItemModel::layoutChanged, + this, &ChooserModelBase::modelLayoutChanged); + delete m_model; + m_model = new QFileSystemModel(this); + connect(m_model, &QAbstractItemModel::rowsInserted, + this, &ChooserModelBase::modelRowsInserted); + connect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved, + this, &ChooserModelBase::modelRowsRemoved); + connect(m_model, &QAbstractItemModel::layoutChanged, + this, &ChooserModelBase::modelLayoutChanged); + + setRootIndex(m_model->setRootPath(path)); +} + +void ChooserModelBase::setRootIndex(const QModelIndex &rootIndex) +{ + if (rootIndex != m_rootIndex) { + clearModelData(); + m_rootIndex = rootIndex; + showModelTopLevelItems(); + } +} + +void ChooserModelBase::clearModelData() +{ + if (!m_items.isEmpty()) { + const auto fixedItemCount = getFixedItems().count(); + beginRemoveRows({}, fixedItemCount, fixedItemCount + m_items.count() - 1); + m_items.clear(); + endRemoveRows(); + } +} + +void ChooserModelBase::showModelTopLevelItems() +{ + int rowCount = m_model->rowCount(m_rootIndex); + + if (rowCount == 0) { + if (m_model->hasChildren(m_rootIndex) && m_model->canFetchMore(m_rootIndex)) + m_model->fetchMore(m_rootIndex); + } else { + showModelChildItems(m_rootIndex, 0, rowCount - 1); + + for (int i = 0; i < rowCount; ++i) { + const auto &childIndex = m_model->index(i, 0, m_rootIndex); + if (m_model->hasChildren(childIndex) && m_model->canFetchMore(childIndex)) + m_model->fetchMore(childIndex); + } + } +} + +void ChooserModelBase::showModelChildItems(const QModelIndex &parentIndex, int start, int end) +{ + QVector<QModelIndex> rowsToInsert; + for (int i = start; i <= end; ++i) { + const auto &childIndex = m_model->index(i, 0, parentIndex); + if (isVisible(childIndex)) + rowsToInsert.append(childIndex); + } + + const int insertCount = rowsToInsert.count(); + + if (insertCount != 0) { + TreeItem *parent; + int depth, startRow; + + if (parentIndex == m_rootIndex) { + parent = nullptr; + depth = 0; + startRow = 0; + } else { + const int parentRow = modelIndexRow(parentIndex); + Q_ASSERT(parentRow != -1 && isVisible(parentIndex)); + parent = &m_items[parentRow]; + depth = parent->depth + 1; + startRow = parentRow + parent->childCount + 1; + } + + const int fixedItemCount = getFixedItems().count(); + beginInsertRows({}, startRow + fixedItemCount, startRow + fixedItemCount + insertCount - 1); + + for (auto it = rowsToInsert.rbegin(); it != rowsToInsert.rend(); ++it) + m_items.insert(startRow, { *it, depth, false, parent, 0 }); + + for (; parent != nullptr; parent = parent->parent) + parent->childCount += insertCount; + + endInsertRows(); + } +} + +void ChooserModelBase::expand(int row) +{ + const int fixedItemCount = getFixedItems().count(); + Q_ASSERT(row >= fixedItemCount && row < fixedItemCount + m_items.count()); + + auto &item = m_items[row - fixedItemCount]; + Q_ASSERT(item.expanded == false); + + const auto &modelIndex = item.index; + + const int rowCount = m_model->rowCount(modelIndex); + if (rowCount == 0) { + if (m_model->hasChildren(modelIndex) && m_model->canFetchMore(modelIndex)) + m_model->fetchMore(modelIndex); + } else { + showModelChildItems(modelIndex, 0, rowCount - 1); + } + + item.expanded = true; + Q_EMIT dataChanged(index(row), index(row)); +} + +void ChooserModelBase::collapse(int row) +{ + const int fixedItemCount = getFixedItems().count(); + Q_ASSERT(row >= fixedItemCount && row < fixedItemCount + m_items.count()); + + auto &item = m_items[row - fixedItemCount]; + Q_ASSERT(item.expanded == true); + + const int childCount = item.childCount; + + if (childCount > 0) { + beginRemoveRows({}, row + 1, row + childCount); + + auto first = std::begin(m_items) + row - fixedItemCount + 1; + m_items.erase(first, first + childCount); + + for (auto parent = &item; parent != nullptr; parent = parent->parent) + parent->childCount -= childCount; + + endRemoveRows(); + } + + item.expanded = false; + Q_EMIT dataChanged(index(row), index(row)); +} + +void ChooserModelBase::OnNewPresentation() +{ + rebuild(); +} + +int ChooserModelBase::modelIndexRow(const QModelIndex &modelIndex) const +{ + auto it = std::find_if(std::begin(m_items), std::end(m_items), + [&modelIndex](const TreeItem &item) + { + return item.index == modelIndex; + }); + + return it != std::end(m_items) ? std::distance(std::begin(m_items), it) : -1; +} + +bool ChooserModelBase::isExpanded(const QModelIndex &modelIndex) const +{ + if (modelIndex == m_rootIndex) { + return true; + } else { + const int row = modelIndexRow(modelIndex); + return row != -1 && m_items.at(row).expanded; + } +} + +EStudioObjectType ChooserModelBase::getIconType(const QString &path) const +{ + return Q3DStudio::ImportUtils::GetObjectFileTypeForFile(path).m_IconType; +} + +QString ChooserModelBase::specialDisplayName(const ChooserModelBase::TreeItem &item) const +{ + Q_UNUSED(item) + return {}; +} + +QString ChooserModelBase::getIconName(const QString &path) const +{ + QString iconName; + + QFileInfo fileInfo(path); + if (fileInfo.isFile()) { + EStudioObjectType type = getIconType(path); + if (type != OBJTYPE_UNKNOWN) + iconName = CStudioObjectTypes::GetNormalIconName(type); + else + iconName = QStringLiteral("Objects-Layer-Normal.png"); + } else { + iconName = QStringLiteral("Objects-Folder-Normal.png"); + } + + return iconName; +} + +bool ChooserModelBase::isVisible(const QModelIndex &modelIndex) const +{ + QString path = modelIndex.data(QFileSystemModel::FilePathRole).toString(); + QFileInfo fileInfo(path); + + if (fileInfo.isFile()) { + return isVisible(path); + } else { + return hasVisibleChildren(modelIndex); + } +} + +bool ChooserModelBase::hasVisibleChildren(const QModelIndex &modelIndex) const +{ + int rowCount = m_model->rowCount(modelIndex); + + for (int i = 0; i < rowCount; ++i) { + const auto &childIndex = m_model->index(i, 0, modelIndex); + + if (m_model->hasChildren(childIndex)) { + if (hasVisibleChildren(childIndex)) + return true; + } else { + QString path = childIndex.data(QFileSystemModel::FilePathRole).toString(); + QFileInfo fileInfo(path); + if (fileInfo.isFile() && isVisible(path)) + return true; + } + } + + return false; +} + +void ChooserModelBase::modelRowsInserted(const QModelIndex &parentIndex, int start, int end) +{ + if (!m_rootIndex.isValid()) + return; + + if (isExpanded(parentIndex)) { + showModelChildItems(parentIndex, start, end); + } else { + if (modelIndexRow(parentIndex) == -1) { + // parent wasn't inserted in model yet, check if any of the new rows is visible + + bool visible = false; + + for (int i = start; i <= end; ++i) { + const auto &childIndex = m_model->index(i, 0, parentIndex); + QString path = childIndex.data(QFileSystemModel::FilePathRole).toString(); + QFileInfo fileInfo(path); + if (fileInfo.isFile() && isVisible(path)) { + visible = true; + break; + } + } + + // if any of the new rows is visible, insert parent folder index into model + + if (visible) { + QModelIndex index = parentIndex, parent = m_model->parent(parentIndex); + + while (parent != m_rootIndex && modelIndexRow(parent) == -1) { + index = parent; + parent = m_model->parent(parent); + } + + if (isExpanded(parent) && modelIndexRow(index) == -1) { + const int row = index.row(); + showModelChildItems(parent, row, row); + } + } + } + + // if one of the new rows is the current file expand parent folder + + bool containsCurrent = false; + + for (int i = start; i <= end; ++i) { + const auto &childIndex = m_model->index(i, 0, parentIndex); + if (childIndex.data(QFileSystemModel::FilePathRole).toString() == m_currentFile) { + containsCurrent = true; + break; + } + } + + if (containsCurrent) + expand(parentIndex); + } + + // fetch children so we're notified when files are added or removed + + for (int i = start; i <= end; ++i) { + const auto &childIndex = m_model->index(i, 0, parentIndex); + if (m_model->hasChildren(childIndex) && m_model->canFetchMore(childIndex)) + m_model->fetchMore(childIndex); + } +} + +void ChooserModelBase::modelRowsRemoved(const QModelIndex &parentIndex, int start, int end) +{ + if (!m_rootIndex.isValid()) + return; + + if (isExpanded(parentIndex)) { + const auto fixedItems = getFixedItems(); + + const auto removeRow = [this, &fixedItems](int row) + { + const auto &item = m_items.at(row); + + const int fixedItemCount = fixedItems.count(); + beginRemoveRows({}, row + fixedItemCount, row + fixedItemCount + item.childCount); + + for (auto parent = item.parent; parent != nullptr; parent = parent->parent) + parent->childCount -= 1 + item.childCount; + + m_items.erase(std::begin(m_items) + row, std::begin(m_items) + row + item.childCount + 1); + + endRemoveRows(); + }; + + // remove rows + + for (int i = start; i <= end; ++i) { + const int row = modelIndexRow(m_model->index(i, 0, parentIndex)); + if (row != -1) + removeRow(row); + } + + // also remove folder row if there are no more visible children + + QModelIndex index = parentIndex; + + while (index != m_rootIndex && !hasVisibleChildren(index)) { + const int row = modelIndexRow(index); + Q_ASSERT(row != -1); + removeRow(row); + index = m_model->parent(index); + } + } +} + +void ChooserModelBase::modelLayoutChanged() +{ + if (!m_rootIndex.isValid()) + return; + + QSet<QPersistentModelIndex> expandedItems; + for (const auto &item : m_items) { + if (item.expanded) + expandedItems.insert(item.index); + } + + const std::function<int(const QModelIndex &, TreeItem *)> insertChildren = + [this, &expandedItems, &insertChildren](const QModelIndex &parentIndex, TreeItem *parent) + { + Q_ASSERT(parentIndex == m_rootIndex || isVisible(parentIndex)); + + const int rowCount = m_model->rowCount(parentIndex); + const int depth = parent == nullptr ? 0 : parent->depth + 1; + + int childCount = 0; + + for (int i = 0; i < rowCount; ++i) { + const auto &childIndex = m_model->index(i, 0, parentIndex); + if (isVisible(childIndex)) { + const bool expanded = expandedItems.contains(childIndex); + m_items.append({ childIndex, depth, expanded, parent, 0 }); + auto &item = m_items.last(); + if (expanded) { + item.childCount = insertChildren(childIndex, &item); + childCount += item.childCount; + } + ++childCount; + } + } + + return childCount; + }; + + const int itemCount = m_items.count(); + + m_items.clear(); + m_items.reserve(itemCount); + + insertChildren(m_rootIndex, nullptr); + Q_ASSERT(m_items.count() == itemCount); + + const int fixedItemCount = getFixedItems().count(); + Q_EMIT dataChanged(index(fixedItemCount), index(fixedItemCount + itemCount - 1)); +} + +void ChooserModelBase::rebuild() +{ + setRootPath(g_StudioApp.GetCore()->getProjectFile().getProjectPath()); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserModelBase.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserModelBase.h new file mode 100644 index 00000000..01553ca4 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ChooserModelBase.h @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ +#ifndef CHOOSERMODELBASE_H +#define CHOOSERMODELBASE_H + +#include "DispatchListeners.h" +#include "StudioObjectTypes.h" + +#include <QFileSystemModel> +#include <QAbstractListModel> +#include <QList> +#include <QVector> + +QT_FORWARD_DECLARE_CLASS(QFileSystemModel) + +class ChooserModelBase : public QAbstractListModel, public CPresentationChangeListener +{ + Q_OBJECT + +public: + explicit ChooserModelBase(QObject *parent = nullptr); + ~ChooserModelBase(); + + enum { + IsExpandableRole = QFileSystemModel::FilePermissions + 1, + DepthRole, + ExpandedRole, + IsSelectableRole, + IsCurrentFile + }; + + QHash<int, QByteArray> roleNames() const override; + int rowCount(const QModelIndex &parent = {}) const override; + QVariant data(const QModelIndex &index, int role) const override; + + Q_INVOKABLE void expand(int row); + Q_INVOKABLE void collapse(int row); + + Q_INVOKABLE void setCurrentFile(const QString &path); + static QString noneString() { return tr("[None]"); } + + // CPresentationChangeListener + void OnNewPresentation() override; + +Q_SIGNALS: + void modelChanged(QAbstractItemModel *model); + +protected: + EStudioObjectType getIconType(const QString &path) const; + + virtual bool isVisible(const QString &path) const = 0; + + struct FixedItem + { + EStudioObjectType iconType; + QString iconSource; + QString name; + }; + + struct TreeItem { + QPersistentModelIndex index; + int depth; + bool expanded; + TreeItem *parent; + int childCount; + }; + + virtual const QVector<FixedItem> getFixedItems() const = 0; + virtual QString specialDisplayName(const TreeItem &item) const; + +private: + void setRootPath(const QString &path); + void setRootIndex(const QModelIndex &rootIndex); + void clearModelData(); + void showModelTopLevelItems(); + void showModelChildItems(const QModelIndex &parentItem, int start, int end); + int modelIndexRow(const QModelIndex &modelIndex) const; + bool isExpanded(const QModelIndex &modelIndex) const; + QString getIconName(const QString &path) const; + bool isVisible(const QModelIndex &modelIndex) const; + bool hasVisibleChildren(const QModelIndex &modelIndex) const; + void expand(const QModelIndex &modelIndex); + + void modelRowsInserted(const QModelIndex &parent, int start, int end); + void modelRowsRemoved(const QModelIndex &parent, int start, int end); + void modelRowsMoved(const QModelIndex &parent, int start, int end); + void modelLayoutChanged(); + + void rebuild(); + + QFileSystemModel *m_model; + QPersistentModelIndex m_rootIndex; + QList<TreeItem> m_items; + QString m_currentFile; +}; + +#endif // CHOOSERMODELBASE_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/DataInputChooser.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/DataInputChooser.qml new file mode 100644 index 00000000..3681a102 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/DataInputChooser.qml @@ -0,0 +1,212 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.8 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.3 +import "../controls" + +Rectangle { + id: root + + color: _backgroundColor + + border.color: _studioColor3 + + StyledLabel { + id: title + color: _dataInputColor + text: qsTr("Select Controlling Data Input") + leftPadding: 8 + height: 20 + } + + StyledMenuSeparator { + id: separator + anchors.top: title.bottom + leftPadding: 8 + rightPadding: 8 + } + + ColumnLayout { + anchors.fill: parent + anchors.topMargin: 30 + spacing: 10 + RowLayout { + Layout.fillHeight: true + Layout.fillWidth: true + StyledComboBox { + id: filterCombo + readonly property int numOfFixedChoices: 2 + Layout.leftMargin: 8 + Layout.preferredWidth: 150 + + // Data type list must match with EDataType enum so we can use enum + // index directly without going through string -> int table lookup + model: [qsTr("[Compatible types]"), qsTr("[All types]"), qsTr("Boolean"), + qsTr("Float"), qsTr("Ranged Number"), qsTr("String"), qsTr("Variant"), + qsTr("Vector2"), qsTr("Vector3")] + + onCurrentIndexChanged: _parentView.setTypeFilter(currentIndex - numOfFixedChoices); + + MouseArea { + id: filterBoxMouseArea + anchors.fill: parent + hoverEnabled: true + // pass through mouse click to Combobox + onPressed: { + mouse.accepted = false; + } + } + + StyledTooltip { + text: qsTr("Filter the list by Data Input type or\n" + + "by compatibility with current property") + enabled: filterBoxMouseArea.containsMouse && !filterCombo.popup.activeFocus + } + Connections { + target: _parentView + // Filter type can be changed also from cpp side + onFilterChanged: { + filterCombo.currentIndex = _parentView.typeFilter + + filterCombo.numOfFixedChoices; + } + } + } + + StyledTextField { + Layout.leftMargin: 8 + Layout.rightMargin: 8 + Layout.preferredWidth: 200 + Layout.fillWidth: true + id: searchField + placeholderText: qsTr("[search]") + horizontalAlignment: TextInput.AlignLeft + + property string value + + rightPadding: clearText.width + 2 + + onTextChanged: _parentView.setSearchString(text); + + MouseArea { + id: searchMouseArea + anchors.fill: parent + propagateComposedEvents: true + hoverEnabled: true + onClicked: { + searchField.forceActiveFocus(); + } + } + + StyledTooltip { + id: searchTt + text: qsTr("Search for Data Input") + enabled: searchMouseArea.containsMouse && !searchField.focus + } + + Image { + anchors { verticalCenter: parent.verticalCenter; right: parent.right; } + id: clearText + fillMode: Image.PreserveAspectFit + smooth: true; + source: _resDir + "add.png" + rotation: 45 + + MouseArea { + id: clear + anchors { + horizontalCenter: parent.horizontalCenter; + verticalCenter: parent.verticalCenter + } + height: clearText.height; width: clearText.height + onClicked: { + searchField.text = "" + searchField.forceActiveFocus() + } + } + } + } + } + + ListView { + id: listView + Layout.leftMargin: 8 + Layout.fillHeight: true + Layout.fillWidth: true + + boundsBehavior: Flickable.StopAtBounds + spacing: 4 + clip: true + + ScrollBar.vertical: ScrollBar {} + + model: _dataInputSelectModel + + delegate: Row { + height: 20 + Image { + // do not show item icon for fixed items + visible: index >= _dataInputSelectModel.fixedItemCount + source: index === _parentView.selected + ? _dataInputSelectModel.getActiveIconPath() + : _dataInputSelectModel.getInactiveIconPath(); + } + StyledLabel { + leftPadding: 5 + text: model.display + width: listView.width / 2; + color: (index >= _dataInputSelectModel.fixedItemCount) + && (index === _parentView.selected) + ? _dataInputColor : _textColor; + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton + onClicked: _parentView.setSelection(index) + } + } + StyledLabel { + leftPadding: 5 + visible: index >= _dataInputSelectModel.fixedItemCount + text: "(" + model.datatype + ")" + color: (index >= _dataInputSelectModel.fixedItemCount) + && (index === _parentView.selected) + ? _dataInputColor : _textColor; + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton + onClicked: _parentView.setSelection(index) + } + } + } + } + } +} + diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooser.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooser.qml new file mode 100644 index 00000000..b9ef07ab --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooser.qml @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +Rectangle { + id: root + + color: _backgroundColor + border.color: _studioColor3 + + ColumnLayout { + anchors.fill: parent + + spacing: 10 + ListView { + id: listView + + Layout.fillWidth: true + Layout.fillHeight: true + Layout.margins: 4 + + boundsBehavior: Flickable.StopAtBounds + spacing: 4 + clip: true + + ScrollBar.vertical: ScrollBar {} + + model: _fileChooserModel + + delegate: ChooserDelegate { + onClicked: { + _fileChooserView.fileSelected(_fileChooserView.handle, + _fileChooserView.instance, filePath); + } + onDoubleClicked: { + _fileChooserView.fileSelected(_fileChooserView.handle, + _fileChooserView.instance, filePath); + _fileChooserView.hide(); + } + } + } + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserModel.cpp new file mode 100644 index 00000000..46e3b3a6 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserModel.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "FileChooserModel.h" + +FileChooserModel::FileChooserModel(QObject *parent) + : ChooserModelBase(parent) +{ + +} + +FileChooserModel::~FileChooserModel() +{ +} + +bool FileChooserModel::isVisible(const QString &path) const +{ + return getIconType(path) == OBJTYPE_GROUP; +} + +const QVector<ChooserModelBase::FixedItem> FileChooserModel::getFixedItems() const +{ + static const QVector<FixedItem> items = { { OBJTYPE_GROUP, "", noneString() } }; + return items; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserModel.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserModel.h new file mode 100644 index 00000000..c019c4a1 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserModel.h @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef FILECHOOSERMODEL_H +#define FILECHOOSERMODEL_H + +#include "ChooserModelBase.h" + +class FileChooserModel : public ChooserModelBase +{ + Q_OBJECT + +public: + explicit FileChooserModel(QObject *parent = nullptr); + virtual ~FileChooserModel(); +private: + bool isVisible(const QString &path) const override; + const QVector<FixedItem> getFixedItems() const override; +}; + +#endif // IMAGECHOOSERMODEL_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserView.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserView.cpp new file mode 100644 index 00000000..fc4612c9 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserView.cpp @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "FileChooserView.h" +#include "FileChooserModel.h" +#include "Literals.h" +#include "StudioUtils.h" +#include "IDocumentEditor.h" +#include "Qt3DSDMStudioSystem.h" +#include "Qt3DSDMValue.h" +#include "Core.h" +#include "Doc.h" +#include "StudioApp.h" +#include "StudioPreferences.h" + +#include <QtQml/qqmlcontext.h> +#include <QtQml/qqmlengine.h> +#include <QtCore/qtimer.h> + +FileChooserView::FileChooserView(QWidget *parent) + : QQuickWidget(parent) + , m_model(new FileChooserModel(this)) +{ + setWindowTitle(tr("Imports")); + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + setResizeMode(QQuickWidget::SizeRootObjectToView); + QTimer::singleShot(0, this, &FileChooserView::initialize); +} + +void FileChooserView::initialize() +{ + CStudioPreferences::setQmlContextProperties(rootContext()); + rootContext()->setContextProperty(QStringLiteral("_resDir"), + StudioUtils::resourceImageUrl()); + rootContext()->setContextProperty(QStringLiteral("_fileChooserView"), this); + rootContext()->setContextProperty(QStringLiteral("_fileChooserModel"), m_model); + engine()->addImportPath(StudioUtils::qmlImportPath()); + setSource(QUrl(QStringLiteral("qrc:/Palettes/Inspector/FileChooser.qml"))); +} + +void FileChooserView::setHandle(int handle) +{ + m_handle = handle; +} + +int FileChooserView::handle() const +{ + return m_handle; +} + +void FileChooserView::setInstance(int instance) +{ + m_instance = instance; +} + +int FileChooserView::instance() const +{ + return m_instance; +} + +void FileChooserView::updateSelection() +{ + const auto doc = g_StudioApp.GetCore()->GetDoc(); + const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem(); + + qt3dsdm::SValue value; + propertySystem->GetInstancePropertyValue(m_instance, m_handle, value); + + QString valueStr = qt3dsdm::get<QString>(value); + if (valueStr.isEmpty()) + valueStr = ChooserModelBase::noneString(); + + m_model->setCurrentFile(valueStr); +} + +void FileChooserView::focusOutEvent(QFocusEvent *event) +{ + QQuickWidget::focusOutEvent(event); + // Don't lose focus because of progress dialog pops up which happens e.g. when importing mesh + // in response to file selection + if (g_StudioApp.isOnProgress()) { + if (!m_focusOutTimer) { + m_focusOutTimer = new QTimer(this); + connect(m_focusOutTimer, &QTimer::timeout, [this]() { + // Periodically check if progress is done to refocus the chooser view + if (!g_StudioApp.isOnProgress()) { + m_focusOutTimer->stop(); + m_focusOutTimer->deleteLater(); + m_focusOutTimer = nullptr; + this->activateWindow(); + } + }); + m_focusOutTimer->start(250); + } + } else { + QTimer::singleShot(0, this, &FileChooserView::close); + } +} + +void FileChooserView::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Escape) + QTimer::singleShot(0, this, &FileChooserView::close); + + QQuickWidget::keyPressEvent(event); +} + +void FileChooserView::showEvent(QShowEvent *event) +{ + updateSelection(); + QQuickWidget::showEvent(event); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserView.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserView.h new file mode 100644 index 00000000..6b99343e --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/FileChooserView.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef FILECHOOSERVIEW_H +#define FILECHOOSERVIEW_H + +#include <QtQuickWidgets/qquickwidget.h> +#include <QtCore/qtimer.h> + +class FileChooserModel; + +class FileChooserView : public QQuickWidget +{ + Q_OBJECT + Q_PROPERTY(int instance READ instance) + Q_PROPERTY(int handle READ handle) + +public: + explicit FileChooserView(QWidget *parent = nullptr); + + void setHandle(int handle); + int handle() const; + + void setInstance(int instance); + int instance() const; + + void updateSelection(); + +Q_SIGNALS: + void fileSelected(int handle, int instance, const QString &name); + +protected: + void focusOutEvent(QFocusEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + +private: + void showEvent(QShowEvent *event) override; + void initialize(); + int m_handle = -1; + int m_instance = -1; + FileChooserModel *m_model = nullptr; + QTimer *m_focusOutTimer = nullptr; +}; + +#endif // IMAGECHOOSERVIEW_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/GuideInspectable.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/GuideInspectable.cpp new file mode 100644 index 00000000..b10e4c1e --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/GuideInspectable.cpp @@ -0,0 +1,351 @@ +/**************************************************************************** +** +** Copyright (C) 1999-2002 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "GuideInspectable.h" +#include "InspectableBase.h" +#include "StudioApp.h" +#include "Core.h" +#include "Doc.h" +#include "Qt3DSDMGuides.h" +#include "InspectorGroup.h" +#include "IDocumentEditor.h" +#include "Qt3DSDMDataTypes.h" +#include "IInspectableItem.h" +#include "Qt3DSDMValue.h" + +typedef std::function<qt3dsdm::SValue()> TGetterFunc; +typedef std::function<void(qt3dsdm::SValue)> TSetterFunc; +typedef std::function<void()> TCommitFunc; +typedef std::function<void()> TCancelFunc; + +struct SInspectableDataInfo +{ + QString m_Name; + QString m_FormalName; + QString m_Description; + TGetterFunc m_Getter; + TSetterFunc m_Setter; + TCommitFunc m_Commit; + TCancelFunc m_Cancel; + + SInspectableDataInfo(const QString &name, const QString &formalName, + const QString &description, TGetterFunc getter, TSetterFunc setter, + TCommitFunc commit, TCancelFunc inCancel) + : m_Name(name) + , m_FormalName(formalName) + , m_Description(description) + , m_Getter(getter) + , m_Setter(setter) + , m_Commit(commit) + , m_Cancel(inCancel) + { + } +}; + +struct SComboAttItem : public IInspectableAttributeItem +{ + SInspectableDataInfo m_BaseInspectableInfo; + qt3dsdm::TMetaDataStringList m_MetaDataTypes; + SComboAttItem(const SInspectableDataInfo &inInfo, const qt3dsdm::TMetaDataStringList &inTypes) + : m_BaseInspectableInfo(inInfo) + , m_MetaDataTypes(inTypes) + { + } + qt3dsdm::HandlerArgumentType::Value GetInspectableSubType() const override + { + return qt3dsdm::HandlerArgumentType::Property; + } + QString GetInspectableName() const override { return m_BaseInspectableInfo.m_Name; } + QString GetInspectableFormalName() const override + { + return m_BaseInspectableInfo.m_FormalName; + } + QString GetInspectableDescription() const override + { + return m_BaseInspectableInfo.m_Description; + } + + qt3dsdm::SValue GetInspectableData() const override { return m_BaseInspectableInfo.m_Getter(); } + void SetInspectableData(const qt3dsdm::SValue &inValue) override + { + m_BaseInspectableInfo.m_Setter(inValue); + m_BaseInspectableInfo.m_Commit(); + } + + void ChangeInspectableData(const qt3dsdm::SValue &inValue) override + { + m_BaseInspectableInfo.m_Setter(inValue); + } + void CancelInspectableData() override { m_BaseInspectableInfo.m_Cancel(); } + + float GetInspectableMin() const override { return 0; } + float GetInspectableMax() const override { return 0; } + qt3dsdm::TMetaDataStringList GetInspectableList() const override { return m_MetaDataTypes; } + qt3dsdm::DataModelDataType::Value GetInspectableType() const override + { + return qt3dsdm::DataModelDataType::String; + } + qt3dsdm::AdditionalMetaDataType::Value GetInspectableAdditionalType() const override + { + return qt3dsdm::AdditionalMetaDataType::StringList; + } +}; + +struct SFloatIntItem : public IInspectableAttributeItem +{ + SInspectableDataInfo m_BaseInspectableInfo; + qt3dsdm::DataModelDataType::Value m_DataType; + float m_Min; + float m_Max; + SFloatIntItem(const SInspectableDataInfo &inInfo, qt3dsdm::DataModelDataType::Value inType, + float inMin = 0, float inMax = 0) + : m_BaseInspectableInfo(inInfo) + , m_DataType(inType) + , m_Min(inMin) + , m_Max(inMax) + { + } + qt3dsdm::HandlerArgumentType::Value GetInspectableSubType() const override + { + return qt3dsdm::HandlerArgumentType::Property; + } + QString GetInspectableName() const override { return m_BaseInspectableInfo.m_Name; } + QString GetInspectableFormalName() const override + { + return m_BaseInspectableInfo.m_FormalName; + } + QString GetInspectableDescription() const override + { + return m_BaseInspectableInfo.m_Description; + } + + qt3dsdm::SValue GetInspectableData() const override { return m_BaseInspectableInfo.m_Getter(); } + void SetInspectableData(const qt3dsdm::SValue &inValue) override + { + m_BaseInspectableInfo.m_Setter(inValue); + m_BaseInspectableInfo.m_Commit(); + } + + void ChangeInspectableData(const qt3dsdm::SValue &inValue) override + { + m_BaseInspectableInfo.m_Setter(inValue); + } + void CancelInspectableData() override { m_BaseInspectableInfo.m_Cancel(); } + + float GetInspectableMin() const override { return m_Min; } + float GetInspectableMax() const override { return m_Max; } + qt3dsdm::TMetaDataStringList GetInspectableList() const override + { + return qt3dsdm::TMetaDataStringList(); + } + qt3dsdm::DataModelDataType::Value GetInspectableType() const override { return m_DataType; } + qt3dsdm::AdditionalMetaDataType::Value GetInspectableAdditionalType() const override + { + if (m_Max > 0) + return qt3dsdm::AdditionalMetaDataType::Range; + else + return qt3dsdm::AdditionalMetaDataType::None; + } +}; + +GuideInspectable::GuideInspectable(qt3dsdm::Qt3DSDMGuideHandle inGuide) + : m_Guide(inGuide) + , m_Editor(*g_StudioApp.GetCore()->GetDoc()) +{ +} + +Q3DStudio::IDocumentReader &GuideInspectable::Reader() const +{ + return g_StudioApp.GetCore()->GetDoc()->GetDocumentReader(); +} + +EStudioObjectType GuideInspectable::getObjectType() const +{ + return OBJTYPE_GUIDE; +} + +Q3DStudio::CString GuideInspectable::getName() +{ + return L"Guide"; +} + +long GuideInspectable::getGroupCount() const +{ + return 1; +} + +CInspectorGroup *GuideInspectable::getGroup(long) +{ + TCommitFunc theCommiter = std::bind(&GuideInspectable::Commit, this); + TCancelFunc theCanceler = std::bind(&GuideInspectable::Rollback, this); + m_Properties.push_back( + std::make_shared<SFloatIntItem>( + SInspectableDataInfo(QStringLiteral("Position"), QObject::tr("Position"), + QObject::tr("Position of the guide"), + std::bind(&GuideInspectable::GetPosition, this), + std::bind(&GuideInspectable::SetPosition, this, + std::placeholders::_1), + theCommiter, theCanceler), + qt3dsdm::DataModelDataType::Float)); + qt3dsdm::TMetaDataStringList theComboItems; + theComboItems.push_back(L"Horizontal"); + theComboItems.push_back(L"Vertical"); + + m_Properties.push_back( + std::make_shared<SComboAttItem>( + SInspectableDataInfo(QStringLiteral("Orientation"), QObject::tr("Orientation"), + QObject::tr("Orientation of the guide"), + std::bind(&GuideInspectable::GetDirection, this), + std::bind(&GuideInspectable::SetDirection, this, + std::placeholders::_1), + theCommiter, theCanceler), + theComboItems)); + + m_Properties.push_back( + std::make_shared<SFloatIntItem>( + SInspectableDataInfo(QStringLiteral("Width"), QObject::tr("Width"), + QObject::tr("Width of the guide"), + std::bind(&GuideInspectable::GetWidth, this), + std::bind(&GuideInspectable::SetWidth, this, + std::placeholders::_1), + theCommiter, theCanceler), + qt3dsdm::DataModelDataType::Long, 1.0f, 50.0f)); + + CInspectorGroup *theNewGroup = new CInspectorGroup(QObject::tr("Basic")); + return theNewGroup; +} + +bool GuideInspectable::isValid() const +{ + return Reader().IsGuideValid(m_Guide); +} + +bool GuideInspectable::isMaster() const +{ + return true; +} + +qt3dsdm::Qt3DSDMInstanceHandle GuideInspectable::getInstance() const +{ + return 0; // guide has no instance +} + +void GuideInspectable::SetDirection(const qt3dsdm::SValue &inValue) +{ + qt3dsdm::TDataStrPtr theData = inValue.getData<qt3dsdm::TDataStrPtr>(); + qt3dsdm::SGuideInfo theSetter(Reader().GetGuideInfo(m_Guide)); + if (theData) { + if (qt3dsdm::AreEqual(theData->GetData(), L"Horizontal")) + theSetter.m_Direction = qt3dsdm::GuideDirections::Horizontal; + else if (qt3dsdm::AreEqual(theData->GetData(), L"Vertical")) + theSetter.m_Direction = qt3dsdm::GuideDirections::Vertical; + } + Editor().UpdateGuide(m_Guide, theSetter); + FireRefresh(); +} + +qt3dsdm::SValue GuideInspectable::GetDirection() +{ + switch (Reader().GetGuideInfo(m_Guide).m_Direction) { + case qt3dsdm::GuideDirections::Horizontal: + return std::make_shared<qt3dsdm::CDataStr>(L"Horizontal"); + case qt3dsdm::GuideDirections::Vertical: + return std::make_shared<qt3dsdm::CDataStr>(L"Vertical"); + default: + return std::make_shared<qt3dsdm::CDataStr>(L""); + } +} + +void GuideInspectable::SetPosition(const qt3dsdm::SValue &inValue) +{ + float thePos = inValue.getData<float>(); + qt3dsdm::SGuideInfo theSetter(Reader().GetGuideInfo(m_Guide)); + theSetter.m_Position = thePos; + Editor().UpdateGuide(m_Guide, theSetter); + FireRefresh(); +} + +qt3dsdm::SValue GuideInspectable::GetPosition() +{ + qt3dsdm::SGuideInfo theSetter(Reader().GetGuideInfo(m_Guide)); + return theSetter.m_Position; +} + +void GuideInspectable::SetWidth(const qt3dsdm::SValue &inValue) +{ + auto theData = inValue.getData<qt3ds::QT3DSI32>(); + + qt3dsdm::SGuideInfo theSetter(Reader().GetGuideInfo(m_Guide)); + theSetter.m_Width = theData; + Editor().UpdateGuide(m_Guide, theSetter); + FireRefresh(); + +} + +qt3dsdm::SValue GuideInspectable::GetWidth() +{ + qt3dsdm::SGuideInfo theSetter(Reader().GetGuideInfo(m_Guide)); + return theSetter.m_Width; +} + +Q3DStudio::IDocumentEditor &GuideInspectable::Editor() +{ + return m_Editor.EnsureEditor(QObject::tr("Set Property"), __FILE__, __LINE__); +} + +void GuideInspectable::Commit() +{ + m_Editor.CommitEditor(); +} + +void GuideInspectable::Rollback() +{ + m_Editor.RollbackEditor(); +} + +void GuideInspectable::FireRefresh() +{ + m_Editor.FireImmediateRefresh(qt3dsdm::Qt3DSDMInstanceHandle()); +} + +void GuideInspectable::Destroy() +{ + m_Editor.EnsureEditor(QObject::tr("Delete Guide"), __FILE__, __LINE__).DeleteGuide(m_Guide); + m_Editor.CommitEditor(); +} + +bool GuideInspectable::isHorizontal() const +{ + return Reader().GetGuideInfo(m_Guide).m_Direction == qt3dsdm::GuideDirections::Horizontal; +} + +const std::vector<std::shared_ptr<IInspectableAttributeItem>> & +GuideInspectable::properties() const +{ + return m_Properties; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/GuideInspectable.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/GuideInspectable.h new file mode 100644 index 00000000..7c9c0c77 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/GuideInspectable.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 1999-2002 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ +#ifndef GUIDEINSPECTABLE_H +#define GUIDEINSPECTABLE_H + +#include "Qt3DSDMHandles.h" +#include "InspectableBase.h" +#include "IDocumentEditor.h" +#include "IInspectableItem.h" + +class GuideInspectable : public CInspectableBase +{ +public: + GuideInspectable(qt3dsdm::Qt3DSDMGuideHandle inGuide); + + Q3DStudio::IDocumentReader &Reader() const; + // Interface + EStudioObjectType getObjectType() const override; + Q3DStudio::CString getName() override; + long getGroupCount() const override; + CInspectorGroup *getGroup(long) override; + bool isValid() const override; + bool isMaster() const override; + qt3dsdm::Qt3DSDMInstanceHandle getInstance() const override; + + // Implementation to get/set properties + + void SetDirection(const qt3dsdm::SValue &inValue); + void SetPosition(const qt3dsdm::SValue &inValue); + void SetWidth(const qt3dsdm::SValue &inValue); + + qt3dsdm::SValue GetDirection(); + qt3dsdm::SValue GetPosition(); + qt3dsdm::SValue GetWidth(); + + Q3DStudio::IDocumentEditor &Editor(); + void Commit(); + void Rollback(); + void FireRefresh(); + void Destroy(); + + bool isHorizontal() const; + + const std::vector<std::shared_ptr<IInspectableAttributeItem>> &properties() const; + +private: + qt3dsdm::Qt3DSDMGuideHandle m_Guide; + Q3DStudio::CUpdateableDocumentEditor m_Editor; + std::vector<std::shared_ptr<IInspectableAttributeItem>> m_Properties; +}; + +#endif diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/HandlerFilesChooser.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/HandlerFilesChooser.qml new file mode 100644 index 00000000..b1514d03 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/HandlerFilesChooser.qml @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.8 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.2 +import "../controls" + +RowLayout { + id: root + + signal showBrowser + property string value: "" + property alias activeBrowser: browser.activeBrowser + + BrowserCombo { + id: browser + Layout.preferredWidth: _valueWidth + Layout.fillWidth: true + value: root.value === "" ? qsTr("Select...") : root.value + onShowBrowser: root.showBrowser() + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/HandlerGenericChooser.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/HandlerGenericChooser.qml new file mode 100644 index 00000000..15857584 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/HandlerGenericChooser.qml @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.8 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.2 +import "../controls" + +RowLayout { + id: root + + signal showBrowser + property alias value: browser.value + property alias activeBrowser: browser.activeBrowser + + BrowserCombo { + id: browser + Layout.preferredWidth: _valueWidth + Layout.fillWidth: true + onShowBrowser: root.showBrowser() + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/IInspectableItem.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/IInspectableItem.h new file mode 100644 index 00000000..9ed87f73 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/IInspectableItem.h @@ -0,0 +1,211 @@ +/**************************************************************************** +** +** Copyright (C) 1999-2002 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +//============================================================================== +// Prefix +//============================================================================== +#ifndef __IINSPECTABLEITEM_H__ +#define __IINSPECTABLEITEM_H__ + +//============================================================================== +// Includes +//============================================================================== +#include "Qt3DSDMDataTypes.h" +#include "Qt3DSDMHandles.h" +#include "Qt3DSDMActionInfo.h" +#include "Qt3DSDMMetaData.h" +#include "Qt3DSString.h" + +//============================================================================== +// Forwards +//============================================================================== +class CStudioApp; +class IInspectableItem; + +//============================================================================== +// Abstract Base Classes +//============================================================================== + +enum EInspectableItemTypes { + INSPECTABLEITEMTYPE_VANILLA = 1, + INSPECTABLEITEMTYPE_PROPERTY, + INSPECTABLEITEMTYPE_DEPENDENT, + INSPECTABLEITEMTYPE_SLIDE, + INSPECTABLEITEMTYPE_OBJECTREFERENCE, + INSPECTABLEITEMTYPE_EVENTSOURCE, + INSPECTABLEITEMTYPE_ACTION, + INSPECTABLEITEMTYPE_CONDITIONS, +}; + +//============================================================================== +/** + * @class IInspectableItemChangeListener + * @brief Listener class for inspectable item changes. + */ +class IInspectableItemChangeListener +{ +public: + virtual void OnInspectablePropertyChanged(IInspectableItem *inProperty) = 0; +}; + +class IInspectableObject +{ +public: + virtual qt3dsdm::Qt3DSDMInstanceHandle GetInspectableBaseInstance() = 0; + virtual void SetInspectableObject(const qt3dsdm::SObjectRefType &) = 0; + virtual qt3dsdm::SObjectRefType GetInspectableObject() = 0; +}; + +class IInspectableEvent +{ +public: + virtual qt3dsdm::Qt3DSDMInstanceHandle GetInspectableInstance() = 0; + virtual qt3dsdm::Qt3DSDMEventHandle GetInspectableEvent() = 0; + virtual void SetInspectableEvent(const qt3dsdm::Qt3DSDMEventHandle &inEventHandle) = 0; +}; + +class IInspectableTargetSection : public IInspectableObject +{ +public: + virtual qt3dsdm::Qt3DSDMActionHandle GetInspectableAction() const = 0; +}; + +class IInspectableEventSection : public IInspectableObject, public IInspectableEvent +{ +public: + virtual qt3dsdm::Qt3DSDMActionHandle GetInspectableAction() const = 0; +}; + +class IInspectableHandlerSection +{ +public: + virtual qt3dsdm::Qt3DSDMActionHandle GetInspectableAction() const = 0; + virtual qt3dsdm::Qt3DSDMHandlerHandle GetInspectableHandler() = 0; + virtual void SetInspectableHandler(const qt3dsdm::Qt3DSDMHandlerHandle &inHandlerHandle) = 0; + + virtual qt3dsdm::THandlerHandleList GetInspectableHandlerList() = 0; + virtual long GetArgumentCount() = 0; + virtual IInspectableItem *GetArgument(long inIndex) = 0; + virtual Q3DStudio::CString GetInspectableDescription() = 0; +}; + +//============================================================================== +/** + * @class IInspectableItem + * @brief Abstract base class for inspectable items. + */ +class IInspectableItem +{ +public: + virtual ~IInspectableItem() {} + virtual EInspectableItemTypes GetInspectableKind() { return INSPECTABLEITEMTYPE_VANILLA; } + + virtual qt3dsdm::HandlerArgumentType::Value + GetInspectableSubType() const = 0; // TODO : Make this method name correct + virtual QString GetInspectableName() const = 0; + virtual QString GetInspectableFormalName() const = 0; + virtual QString GetInspectableDescription() const = 0; + + virtual qt3dsdm::SValue GetInspectableData() const = 0; + virtual void SetInspectableData(const qt3dsdm::SValue &) = 0; + + // TODO: Remove from here onwards after cleaning up the rest of the UI classes + // This is the non-commital version of SetInspectableData, which must be called + // after ChangeInspectableData to commit the action. + virtual bool GetInspectableReadOnly() const { return false; } + + virtual void ChangeInspectableData(const qt3dsdm::SValue & /*inAttr*/){}; + virtual void CancelInspectableData(){} + + virtual void AddInspectableChangeListener(IInspectableItemChangeListener * /*inListener*/){}; + virtual void RemoveInspectableChangeListener(IInspectableItemChangeListener * /*inListener*/){}; +}; + +//============================================================================== +/** + * Property specialization + */ +class IInspectablePropertyItem : public IInspectableItem +{ +public: + EInspectableItemTypes GetInspectableKind() override { return INSPECTABLEITEMTYPE_PROPERTY; } + virtual void GetInspectablePropertyList(qt3dsdm::TPropertyHandleList &outList) = 0; + virtual qt3dsdm::Qt3DSDMInstanceHandle GetInspectableInstance() = 0; +}; + +//============================================================================== +/** + * Attribute specialization + */ +class IInspectableAttributeItem : public IInspectableItem +{ +public: + EInspectableItemTypes GetInspectableKind() override { return INSPECTABLEITEMTYPE_DEPENDENT; } + virtual float GetInspectableMin() const = 0; + virtual float GetInspectableMax() const = 0; + virtual qt3dsdm::TMetaDataStringList GetInspectableList() const = 0; + virtual qt3dsdm::DataModelDataType::Value GetInspectableType() const = 0; + virtual qt3dsdm::AdditionalMetaDataType::Value GetInspectableAdditionalType() const = 0; +}; + +//============================================================================== +/** + * Slide specialization + */ +class IInspectableSlideItem : public IInspectableItem +{ +public: + EInspectableItemTypes GetInspectableKind() override { return INSPECTABLEITEMTYPE_SLIDE; } + virtual void GetSlideNames(std::list<Q3DStudio::CString> &outSlideNames) = 0; +}; + +//============================================================================== +/** + * ObjectReference specialiaztion + */ +class IInspectableObjectRefItem : public IInspectableObject, public IInspectableItem +{ +public: + EInspectableItemTypes GetInspectableKind() override + { + return INSPECTABLEITEMTYPE_OBJECTREFERENCE; + } +}; + +//============================================================================== +/** + * Event specialization + */ +class IInspectableEventItem : public IInspectableEvent, public IInspectableItem +{ +public: + EInspectableItemTypes GetInspectableKind() override { return INSPECTABLEITEMTYPE_EVENTSOURCE; } +}; + +#endif // #ifndef __IINSPECTABLEITEM_H__ diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooser.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooser.qml new file mode 100644 index 00000000..6cfab327 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooser.qml @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +Rectangle { + id: root + + color: _backgroundColor + border.color: _studioColor3 + + ColumnLayout { + anchors.fill: parent + + spacing: 10 + ListView { + id: listView + + Layout.fillWidth: true + Layout.fillHeight: true + Layout.margins: 4 + + boundsBehavior: Flickable.StopAtBounds + spacing: 4 + clip: true + + ScrollBar.vertical: ScrollBar {} + + model: _imageChooserModel + + delegate: ChooserDelegate { + onClicked: { + _imageChooserView.imageSelected(_imageChooserView.handle, + _imageChooserView.instance, filePath); + } + onDoubleClicked: { + _imageChooserView.imageSelected(_imageChooserView.handle, + _imageChooserView.instance, filePath); + _imageChooserView.hide(); + } + } + } + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserModel.cpp new file mode 100644 index 00000000..dd7ed605 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserModel.cpp @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "ImageChooserModel.h" +#include "StudioApp.h" +#include "ProjectFile.h" +#include "Core.h" + +ImageChooserModel::ImageChooserModel(bool showQmls, QObject *parent) + : ChooserModelBase(parent) + , m_showQmls(showQmls) +{ + connect(&g_StudioApp.GetCore()->getProjectFile(), &ProjectFile::presentationIdChanged, + this, &ImageChooserModel::handlePresentationIdChange); +} + +ImageChooserModel::~ImageChooserModel() +{ +} + +bool ImageChooserModel::isVisible(const QString &path) const +{ + return getIconType(path) == OBJTYPE_IMAGE + || !g_StudioApp.getPresentationId(path).isEmpty() + || (m_showQmls && !g_StudioApp.getQmlId(path).isEmpty()); +} + +const QVector<ChooserModelBase::FixedItem> ImageChooserModel::getFixedItems() const +{ + static const QVector<FixedItem> items = { { OBJTYPE_IMAGE, "", noneString() } }; + return items; +} + +QString ImageChooserModel::specialDisplayName(const ChooserModelBase::TreeItem &item) const +{ + // Renderable items display the id instead of file name + const QString path = item.index.data(QFileSystemModel::FilePathRole).toString(); + return g_StudioApp.getRenderableId(path); +} + +void ImageChooserModel::handlePresentationIdChange(const QString &path, const QString &id) +{ + Q_UNUSED(path) + Q_UNUSED(id) + + // Simply update the filename for all rows + Q_EMIT dataChanged(index(0), index(rowCount() - 1), {QFileSystemModel::FileNameRole}); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserModel.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserModel.h new file mode 100644 index 00000000..ceb34b29 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserModel.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef IMAGECHOOSERMODEL_H +#define IMAGECHOOSERMODEL_H + +#include "ChooserModelBase.h" + +class ImageChooserModel : public ChooserModelBase +{ + Q_OBJECT + +public: + explicit ImageChooserModel(bool showQmls, QObject *parent = nullptr); + virtual ~ImageChooserModel(); + +private: + bool isVisible(const QString &path) const override; + const QVector<FixedItem> getFixedItems() const override; + QString specialDisplayName(const TreeItem &item) const override; + void handlePresentationIdChange(const QString &path, const QString &id); + + bool m_showQmls = false; +}; + +#endif // IMAGECHOOSERMODEL_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserView.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserView.cpp new file mode 100644 index 00000000..e8180f94 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserView.cpp @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "ImageChooserView.h" +#include "ImageChooserModel.h" +#include "StudioPreferences.h" +#include "Literals.h" +#include "StudioUtils.h" +#include "IDocumentEditor.h" +#include "Qt3DSDMStudioSystem.h" +#include "Qt3DSDMValue.h" +#include "Core.h" +#include "Doc.h" +#include "StudioApp.h" + +#include <QtCore/qtimer.h> +#include <QtQml/qqmlcontext.h> +#include <QtQml/qqmlengine.h> + +ImageChooserView::ImageChooserView(QWidget *parent) + : QQuickWidget(parent) + , m_model(new ImageChooserModel(true, this)) +{ + setWindowTitle(tr("Images")); + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + setResizeMode(QQuickWidget::SizeRootObjectToView); + QTimer::singleShot(0, this, &ImageChooserView::initialize); +} + +void ImageChooserView::initialize() +{ + CStudioPreferences::setQmlContextProperties(rootContext()); + rootContext()->setContextProperty(QStringLiteral("_resDir"), + StudioUtils::resourceImageUrl()); + rootContext()->setContextProperty(QStringLiteral("_imageChooserView"), this); + rootContext()->setContextProperty(QStringLiteral("_imageChooserModel"), m_model); + engine()->addImportPath(StudioUtils::qmlImportPath()); + setSource(QUrl(QStringLiteral("qrc:/Palettes/Inspector/ImageChooser.qml"))); +} + +void ImageChooserView::setHandle(int handle) +{ + m_handle = handle; +} + +int ImageChooserView::handle() const +{ + return m_handle; +} + +void ImageChooserView::setInstance(int instance) +{ + m_instance = instance; +} + +int ImageChooserView::instance() const +{ + return m_instance; +} + +QString ImageChooserView::currentDataModelPath() const +{ + QString cleanPath; + const auto doc = g_StudioApp.GetCore()->GetDoc(); + const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem(); + + qt3dsdm::SValue value; + propertySystem->GetInstancePropertyValue(m_instance, m_handle, value); + + const auto guid = qt3dsdm::get<qt3dsdm::SLong4>(value); + + const auto imageInstance = doc->GetDocumentReader().GetInstanceForGuid(guid); + if (imageInstance.Valid()) { + const QString path = doc->GetDocumentReader().GetSourcePath(imageInstance).toQString(); + + // If path is renderable id, we need to resolve the actual path + const QString renderablePath = g_StudioApp.getRenderableAbsolutePath(path); + + if (renderablePath.isEmpty()) + cleanPath = path; + else + cleanPath = renderablePath; + + cleanPath = QDir::cleanPath(QDir(doc->GetDocumentDirectory()).filePath(cleanPath)); + } else { + cleanPath = ChooserModelBase::noneString(); + } + + return cleanPath; +} + +void ImageChooserView::updateSelection() +{ + m_model->setCurrentFile(currentDataModelPath()); +} + +bool ImageChooserView::isFocused() const +{ + return hasFocus(); +} + +void ImageChooserView::focusInEvent(QFocusEvent *event) +{ + QQuickWidget::focusInEvent(event); + emit focusChanged(); +} + +void ImageChooserView::focusOutEvent(QFocusEvent *event) +{ + QQuickWidget::focusOutEvent(event); + emit focusChanged(); + QTimer::singleShot(0, this, &QQuickWidget::close); +} + +void ImageChooserView::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Escape) + QTimer::singleShot(0, this, &ImageChooserView::close); + + QQuickWidget::keyPressEvent(event); +} + +void ImageChooserView::showEvent(QShowEvent *event) +{ + updateSelection(); + QQuickWidget::showEvent(event); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserView.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserView.h new file mode 100644 index 00000000..bbf8eff5 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ImageChooserView.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef IMAGECHOOSERVIEW_H +#define IMAGECHOOSERVIEW_H + +#include <QQuickWidget> + +class ImageChooserModel; + +class ImageChooserView : public QQuickWidget +{ + Q_OBJECT + Q_PROPERTY(bool focused READ isFocused NOTIFY focusChanged) + Q_PROPERTY(int instance READ instance) + Q_PROPERTY(int handle READ handle) + +public: + explicit ImageChooserView(QWidget *parent = nullptr); + + void setHandle(int handle); + int handle() const; + + void setInstance(int instance); + int instance() const; + QString currentDataModelPath() const; + + void updateSelection(); + +Q_SIGNALS: + void imageSelected(int handle, int instance, const QString &name); + +protected: + void focusInEvent(QFocusEvent *event) override; + void focusOutEvent(QFocusEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + +Q_SIGNALS: + void focusChanged(); + +private: + void showEvent(QShowEvent *event) override; + void initialize(); + bool isFocused() const; + + int m_handle = -1; + int m_instance = -1; + ImageChooserModel *m_model = nullptr; +}; + +#endif // IMAGECHOOSERVIEW_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectableBase.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectableBase.h new file mode 100644 index 00000000..fa21b57e --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectableBase.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 1999-2002 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ +#ifndef INSPECTABLEBASE_H +#define INSPECTABLEBASE_H + +class CInspectorGroup; + +#include "StudioObjectTypes.h" +#include "Qt3DSString.h" +#include "Qt3DSDMHandles.h" + + // Parent class of Inspectable types that will appear in the Inspector Palette. +class CInspectableBase +{ +public: + CInspectableBase() {} + virtual ~CInspectableBase() {} + + virtual EStudioObjectType getObjectType() const = 0; + virtual Q3DStudio::CString getName() = 0; + virtual long getGroupCount() const = 0; + virtual CInspectorGroup *getGroup(long inIndex) = 0; + virtual bool isValid() const = 0; + virtual bool isMaster() const = 0; + virtual qt3dsdm::Qt3DSDMInstanceHandle getInstance() const = 0; +}; + +#endif // INSPECTABLEBASE_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlModel.cpp new file mode 100644 index 00000000..53519573 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlModel.cpp @@ -0,0 +1,1973 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "InspectorControlModel.h" +#include "StudioApp.h" +#include "Core.h" +#include "Doc.h" +#include "InspectorGroup.h" +#include "Qt3DSDMInspectorGroup.h" +#include "Qt3DSDMInspectorRow.h" +#include "Qt3DSDMStudioSystem.h" +#include "Qt3DSDMInspectable.h" +#include "Qt3DSDMDataCore.h" +#include "IDocumentEditor.h" +#include "Qt3DSDMMetaData.h" +#include "Qt3DSDMSignals.h" +#include "CmdDataModelDeanimate.h" +#include "GuideInspectable.h" +#include "Qt3DSDMDataTypes.h" +#include "IObjectReferenceHelper.h" +#include "SlideSystem.h" +#include "Qt3DSDMMaterialInspectable.h" +#include "ClientDataModelBridge.h" +#include "IDocumentReader.h" +#include "IStudioRenderer.h" +#include "Dialogs.h" +#include "Dispatch.h" +#include "VariantsGroupModel.h" +#include "StudioProjectSettings.h" +#include "Literals.h" + +#include <QtCore/qfileinfo.h> + +static QStringList renderableItems() +{ + QStringList renderables; + renderables.push_back(QObject::tr("No renderable item")); + const CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + Q3DStudio::CString docDir = Q3DStudio::CString::fromQString(doc->GetDocumentDirectory()); + + for (SubPresentationRecord r : qAsConst(g_StudioApp.m_subpresentations)) + renderables.push_back(r.m_id); + + // second step, find the renderable plugins. + { + Q3DStudio::CFilePath pluginDir + = Q3DStudio::CFilePath::CombineBaseAndRelative(docDir, "plugins"); + if (pluginDir.Exists() && pluginDir.IsDirectory()) { + std::vector<Q3DStudio::CFilePath> dirFiles; + pluginDir.ListFilesAndDirectories(dirFiles); + for (size_t idx = 0, end = dirFiles.size(); idx < end; ++idx) { + if (dirFiles[idx].IsFile()) { + Q3DStudio::CFilePath relPath = + Q3DStudio::CFilePath::GetRelativePathFromBase(docDir, dirFiles[idx]); + renderables.push_back(relPath.toQString()); + } + } + } + } + std::sort(renderables.begin() + 1, renderables.end()); + return renderables; +} + +static std::pair<bool, bool> getSlideCharacteristics(qt3dsdm::Qt3DSDMInstanceHandle instance, + const qt3dsdm::ISlideCore &slideCore, + const qt3dsdm::ISlideSystem &slideSystem) +{ + // Get the slide from the instance. + qt3dsdm::Qt3DSDMSlideHandle slide = slideCore.GetSlideByInstance(instance); + qt3dsdm::Qt3DSDMSlideHandle master = slideSystem.GetMasterSlide(slide); + int index = int(slideSystem.GetSlideIndex(slide)); + int count = int(slideSystem.GetSlideCount(master)); + bool hasNextSlide = index > 0 && index < count - 1; + bool hasPreviousSlide = index > 1; + return std::make_pair(hasNextSlide, hasPreviousSlide); +} + +InspectorControlModel::InspectorControlModel(VariantsGroupModel *variantsModel, QObject *parent) + : QAbstractListModel(parent) + , m_UpdatableEditor(*g_StudioApp.GetCore()->GetDoc()) + , m_variantsModel(variantsModel) +{ + m_modifiedProperty.first = 0; + m_modifiedProperty.second = 0; +} + +void InspectorControlModel::setInspectable(CInspectableBase *inInspectable) +{ + // Commit any pending transactions on selection change + m_UpdatableEditor.CommitEditor(); + m_modifiedProperty.first = 0; + m_modifiedProperty.second = 0; + m_previouslyCommittedValue = {}; + + if (m_inspectableBase != inInspectable) { + m_inspectableBase = inInspectable; + rebuildTree(); + } +} + +CInspectableBase *InspectorControlModel::inspectable() const +{ + return m_inspectableBase; +} + +qt3dsdm::Qt3DSDMInstanceHandle InspectorControlModel::getReferenceMaterial( + CInspectableBase *inspectable) const +{ + if (inspectable) + return getBridge()->getMaterialReference(inspectable->getInstance()); + + return 0; +} + +void InspectorControlModel::notifyPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance, + qt3dsdm::Qt3DSDMPropertyHandle inProperty) +{ + auto doc = g_StudioApp.GetCore()->GetDoc(); + if (!getBridge()->IsSceneGraphInstance(inInstance)) + return; + + if (inProperty == getBridge()->getVariantsProperty(inInstance)) { + // variants model is updated upon edit but this is needed to handle undoing + m_variantsModel->refresh(); + return; + } + + bool changed = false; + for (int row = 0; row < m_groupElements.count(); ++row) { + auto group = m_groupElements[row]; + for (int p = 0; p < group.controlElements.count(); ++p) { + QVariant& element = group.controlElements[p]; + InspectorControlBase *property = element.value<InspectorControlBase *>(); + qt3dsdm::Qt3DSDMInstanceHandle imageInstance; + if (property->m_dataType == qt3dsdm::DataModelDataType::Long4 + && property->m_property.Valid()) { + imageInstance = doc->GetDocumentReader().GetImageInstanceForProperty( + property->m_instance, property->m_property); + } + if (property->m_property == inProperty || imageInstance == inInstance) { + updatePropertyValue(property); + changed = true; + } + } + } + if (changed) + Q_EMIT dataChanged(index(0), index(rowCount() - 1)); +} + +bool InspectorControlModel::hasInstanceProperty(long instance, int handle) +{ + for (const auto &group : qAsConst(m_groupElements)) { + for (const auto &element : qAsConst(group.controlElements)) { + InspectorControlBase *property = element.value<InspectorControlBase *>(); + if (property->m_property == qt3dsdm::CDataModelHandle(handle) + && property->m_instance == qt3dsdm::CDataModelHandle(instance)) { + return true; + } + } + } + return false; +} + +bool InspectorControlModel::isInsideMaterialContainer() const +{ + return isInsideMaterialContainer(m_inspectableBase); +} + +bool InspectorControlModel::isInsideMaterialContainer(CInspectableBase *inspectable) const +{ + if (!inspectable || !inspectable->getInstance()) + return false; + + return getBridge()->isInsideMaterialContainer(inspectable->getInstance()); +} + +bool InspectorControlModel::isDefaultMaterial() const +{ + if (m_inspectableBase) + return getBridge()->isDefaultMaterial(m_inspectableBase->getInstance()); + + return false; +} + +bool InspectorControlModel::isAnimatableMaterial() const +{ + return isAnimatableMaterial(m_inspectableBase); +} + +bool InspectorControlModel::isAnimatableMaterial(CInspectableBase *inspectable) const +{ + if (!inspectable) + return false; + + return inspectable->getObjectType() & (OBJTYPE_CUSTOMMATERIAL | OBJTYPE_MATERIAL); +} + +bool InspectorControlModel::isBasicMaterial() const +{ + return isBasicMaterial(m_inspectableBase); +} + +bool InspectorControlModel::isBasicMaterial(CInspectableBase *inspectable) const +{ + if (!inspectable) + return false; + + if (inspectable->getObjectType() == OBJTYPE_REFERENCEDMATERIAL) { + const auto instance = inspectable->getInstance(); + if (!instance.Valid()) + return false; + + const auto refMaterial = getBridge()->getMaterialReference(instance); + if (refMaterial.Valid() && getBridge()->isInsideMaterialContainer(refMaterial)) + return true; + } + + return false; +} + +bool InspectorControlModel::isMaterial() const +{ + if (m_inspectableBase) + return m_inspectableBase->getObjectType() & OBJTYPE_IS_MATERIAL; + + return false; +} + +void InspectorControlModel::addMaterial() +{ + const auto doc = g_StudioApp.GetCore()->GetDoc(); + qt3dsdm::Qt3DSDMInstanceHandle instance = m_inspectableBase->getInstance(); + if (!instance.Valid()) + return; + + QString path = doc->getSceneEditor()->getMaterialDirectoryPath() + QStringLiteral("/Material"); + QString extension = QStringLiteral(".materialdef"); + + auto absPath = path + extension; + int i = 1; + while (QFileInfo(absPath).exists()) { + i++; + absPath = path + QString::number(i) + extension; + } + + qt3dsdm::Qt3DSDMInstanceHandle newMaterial; + { + newMaterial = Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, {}) + ->getOrCreateMaterial(absPath, false); + } + // Several aspects of the editor are not updated correctly + // if the data core is changed without a transaction + // The above scope completes the transaction for creating a new material + // Next the added undo has to be popped from the stack + // TODO: Find a way to update the editor fully without a transaction + g_StudioApp.GetCore()->GetCmdStack()->RemoveLastUndo(); + + saveIfMaterial(newMaterial); + + Q3DStudio::ScopedDocumentEditor sceneEditor( + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, tr("Set Material Type"))); + doc->SelectDataModelObject(newMaterial); + + const auto type = getBridge()->GetObjectType(instance); + if (type == OBJTYPE_REFERENCEDMATERIAL) { + sceneEditor->setMaterialReferenceByPath(instance, absPath); + const auto relPath = doc->GetRelativePathToDoc(absPath); + sceneEditor->setMaterialSourcePath(instance, Q3DStudio::CString::fromQString(relPath)); + sceneEditor->SetName(instance, getBridge()->GetName(newMaterial, true)); + + doc->GetStudioSystem()->GetFullSystemSignalSender()->SendInstancePropertyValue( + instance, getBridge()->GetNameProperty()); + } +} + +void InspectorControlModel::duplicateMaterial() +{ + qt3dsdm::Qt3DSDMInstanceHandle instance = m_inspectableBase->getInstance(); + if (!instance.Valid()) + return; + + const auto doc = g_StudioApp.GetCore()->GetDoc(); + const auto type = m_inspectableBase->getObjectType(); + + if (type & ~OBJTYPE_IS_MATERIAL) + return; + + auto material = instance; + if (type == OBJTYPE_REFERENCEDMATERIAL) + material = getReferenceMaterial(m_inspectableBase); + + if (material.Valid()) { + const auto sceneEditor = doc->getSceneEditor(); + auto originalMaterialName = sceneEditor->GetName(material).toQString() + + QStringLiteral(" Copy"); + int slashIndex = originalMaterialName.lastIndexOf(QLatin1Char('/')); + if (slashIndex != -1) + originalMaterialName = originalMaterialName.mid(slashIndex + 1); + auto materialName = originalMaterialName; + int i = 1; + auto absPath = sceneEditor->getMaterialFilePath(materialName); + while (QFileInfo(absPath).exists()) { + i++; + materialName = originalMaterialName + QString::number(i); + absPath = sceneEditor->getMaterialFilePath(materialName); + } + + qt3dsdm::Qt3DSDMInstanceHandle duplicate; + { + Q3DStudio::ScopedDocumentEditor scopedEditor( + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, {})); + duplicate = scopedEditor->getOrCreateMaterial(materialName, false); + scopedEditor->copyMaterialProperties(material, duplicate); + } + // Several aspects of the editor are not updated correctly + // if the data core is changed without a transaction + // The above scope completes the transaction for creating a new material + // Next the added undo has to be popped from the stack + // TODO: Find a way to update the editor fully without a transaction + g_StudioApp.GetCore()->GetCmdStack()->RemoveLastUndo(); + + saveIfMaterial(duplicate); + + Q3DStudio::ScopedDocumentEditor scopedEditor( + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, tr("Set Material Type"))); + doc->SelectDataModelObject(duplicate); + + if (type == OBJTYPE_REFERENCEDMATERIAL) { + scopedEditor->setMaterialReferenceByPath(instance, absPath); + const auto relPath = doc->GetRelativePathToDoc(absPath); + scopedEditor->setMaterialSourcePath(instance, Q3DStudio::CString::fromQString(relPath)); + scopedEditor->SetName(instance, getBridge()->GetName(duplicate, true)); + doc->GetStudioSystem()->GetFullSystemSignalSender()->SendInstancePropertyValue( + instance, getBridge()->GetNameProperty()); + } + } +} + +void InspectorControlModel::updateMaterialValues(const QStringList &values, int elementIndex, + bool updatingShaders) +{ + // Find if there are any material items and update the values of those + int startIndex = 0; + bool isReferenced = !isAnimatableMaterial() && updatingShaders; + if (isReferenced && m_groupElements.count() > 0) + startIndex = m_groupElements.count() - 1; // Update the last group for referenced materials + for (int row = startIndex; row < m_groupElements.count(); ++row) { + const CInspectorGroup *inspectorGroup = m_inspectableBase->getGroup(row); + const auto group = dynamic_cast<const Qt3DSDMInspectorGroup *>(inspectorGroup); + const auto materialGroup = dynamic_cast<const Qt3DSDMMaterialInspectorGroup *>(group); + if (materialGroup && (materialGroup->isMaterialGroup() || isReferenced)) { + if (m_groupElements[row].controlElements.size()) { + auto item = m_groupElements[row].controlElements[elementIndex] + .value<InspectorControlBase *>(); + item->m_values = values; + Q_EMIT item->valuesChanged(); + // Changing values resets the selected index, so pretend the value has also changed + Q_EMIT item->valueChanged(); + } + } + } +} + +void InspectorControlModel::updateShaderValues() +{ + int index = 0; + if (isAnimatableMaterial() && !isInsideMaterialContainer()) + index = 1; + updateMaterialValues(shaderValues(), index, true); +} + +void InspectorControlModel::updateMatDataValues() +{ + int index = 0; + if (!isInsideMaterialContainer()) + index = 1; + updateMaterialValues(matDataValues(), index); +} + +void InspectorControlModel::setMaterials(std::vector<Q3DStudio::CFilePath> &materials) +{ + m_materials.clear(); + for (Q3DStudio::CFilePath &path : materials) { + const QString absolutePath = g_StudioApp.GetCore()->GetDoc()->GetResolvedPathToDoc(path); + const QString name = g_StudioApp.GetCore()->GetDoc()->GetDocumentReader() + .GetCustomMaterialName(absolutePath).toQString(); + + m_materials.push_back({name, path.toQString()}); + } + + if (!isDefaultMaterial()) + updateShaderValues(); +} + +void InspectorControlModel::setMatDatas(const std::vector<Q3DStudio::CFilePath> &matDatas) +{ + m_matDatas.clear(); + + const auto doc = g_StudioApp.GetCore()->GetDoc(); + bool isDocModified = doc->isModified(); + const auto sceneEditor = doc->getSceneEditor(); + if (!sceneEditor) + return; + + bool newMaterialSelected = false; + for (const Q3DStudio::CFilePath &path : matDatas) { + bool isNewFile = true; + for (auto &oldPath : m_cachedMatDatas) { + if (path.toQString() == oldPath.toQString()) { + isNewFile = false; + break; + } + } + + const QString relativePath = path.toQString(); + const Q3DStudio::CFilePath absolutePath + = Q3DStudio::CFilePath::CombineBaseAndRelative(doc->GetDocumentDirectory(), path); + + QString name; + QMap<QString, QString> values; + QMap<QString, QMap<QString, QString>> textureValues; + sceneEditor->getMaterialInfo( + absolutePath.toQString(), name, values, textureValues); + + m_matDatas.push_back({name, relativePath, values, textureValues}); + + bool needRewrite = false; + if (values.contains(QStringLiteral("path"))) { + const QString oldPath = values[QStringLiteral("path")]; + needRewrite = oldPath != absolutePath.toQString(); + if (!QFileInfo(oldPath).exists()) { + const auto instance = sceneEditor->getMaterial(oldPath); + if (instance.Valid()) { + const QString oldName = sceneEditor->GetName(instance).toQString(); + const QString newName = sceneEditor + ->getMaterialNameFromFilePath(relativePath); + const QString actualPath = sceneEditor + ->getFilePathFromMaterialName(oldName); + if (actualPath == oldPath) { + doc->queueMaterialRename(oldName, newName); + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, tr("Set Name")) + ->setMaterialNameByPath(instance, relativePath); + } + } + } + } + + auto material = sceneEditor->getMaterial(relativePath); + if (isNewFile && !newMaterialSelected && !material.Valid()) { + { + material = Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, QString()) + ->getOrCreateMaterial(relativePath, false); + } + // Several aspects of the editor are not updated correctly + // if the data core is changed without a transaction + // The above scope completes the transaction for creating a new material + // Next the added undo has to be popped from the stack + // TODO: Find a way to update the editor fully without a transaction + g_StudioApp.GetCore()->GetCmdStack()->RemoveLastUndo(); + } + + if (material.Valid()) + sceneEditor->setMaterialValues(relativePath, values, textureValues); + + if (isNewFile && !newMaterialSelected) { + doc->SelectDataModelObject(material); + newMaterialSelected = true; + } + + if (needRewrite && material.Valid()) + sceneEditor->writeMaterialFile(material, name, false, absolutePath.toQString()); + } + + if (isBasicMaterial()) + updateMatDataValues(); + + sceneEditor->removeDeletedFromMaterialContainer(); + // Modified flag has to be restored because of the hidden transaction + doc->SetModifiedFlag(isDocModified); + + m_cachedMatDatas = matDatas; +} + +QString InspectorControlModel::getBasicMaterialString() const +{ + return QObject::tr("Basic Material"); +} + +QString InspectorControlModel::getAnimatableMaterialString() const +{ + return QObject::tr("Animatable Material"); +} + +QString InspectorControlModel::getReferencedMaterialString() const +{ + return QObject::tr("Referenced Material"); +} + +QString InspectorControlModel::getStandardMaterialString() const +{ + return QObject::tr("Standard"); +} + +QString InspectorControlModel::getDefaultMaterialString() const +{ + return QObject::tr("Default"); +} + +bool InspectorControlModel::isGroupCollapsed(int groupIdx) const +{ + if (m_inspectableBase) { + auto instance = m_inspectableBase->getInstance(); + if (instance && groupIdx > -1 && groupIdx < m_groupElements.size() + && m_collapseMap.contains(instance)) { + return m_collapseMap[instance].contains(groupIdx); + } + } + + return false; +} + +void InspectorControlModel::updateGroupCollapseState(int groupIdx, bool isCollapsed) +{ + if (m_inspectableBase) { + auto instance = m_inspectableBase->getInstance(); + if (instance && groupIdx > -1 && groupIdx < m_groupElements.size()) { + if (isCollapsed) + m_collapseMap[instance][groupIdx] = true; + else + m_collapseMap[instance].remove(groupIdx); + } + } +} + +void InspectorControlModel::updateFontValues(InspectorControlBase *element) const +{ + // Find if there are any font items and update the values of those + QVector<InspectorControlBase *> fontElements; + if (element) { + fontElements.append(element); + } else { + for (int row = 0; row < m_groupElements.count(); ++row) { + auto group = m_groupElements[row]; + for (int p = 0; p < group.controlElements.count(); ++p) { + QVariant &element = group.controlElements[p]; + InspectorControlBase *property = element.value<InspectorControlBase *>(); + if (property->m_propertyType == qt3dsdm::AdditionalMetaDataType::Font) + fontElements.append(property); + } + } + } + + if (fontElements.size()) { + std::vector<QString> fontNames; + g_StudioApp.GetCore()->GetDoc()->GetProjectFonts(fontNames); + QStringList possibleValues; + for (const auto &fontName : fontNames) + possibleValues.append(fontName); + for (auto fontElement : qAsConst(fontElements)) { + fontElement->m_values = possibleValues; + Q_EMIT fontElement->valuesChanged(); + // Changing values resets the selected index, so pretend the value has also changed + Q_EMIT fontElement->valueChanged(); + } + } +} + +QStringList InspectorControlModel::materialTypeValues() const +{ + QStringList values; + values.push_back(getBasicMaterialString()); + values.push_back(getAnimatableMaterialString()); + values.push_back(getReferencedMaterialString()); + return values; +} + +QStringList InspectorControlModel::shaderValues() const +{ + QStringList values; + values.push_back(getStandardMaterialString()); + for (size_t matIdx = 0, end = m_materials.size(); matIdx < end; ++matIdx) + values.push_back(m_materials[matIdx].m_name); + return values; +} + +QStringList InspectorControlModel::matDataValues() const +{ + QStringList values; + QStringList names; + const QString defaultMaterialShownName = getDefaultMaterialString(); + values.push_back(defaultMaterialShownName); + names.push_back(defaultMaterialShownName); + for (size_t matIdx = 0, end = m_matDatas.size(); matIdx < end; ++matIdx) { + QString shownName = m_matDatas[matIdx].m_name; + int slashIndex = shownName.lastIndexOf(QLatin1Char('/')); + if (slashIndex != -1) + shownName = shownName.mid(slashIndex + 1); + if (names.contains(shownName)) + shownName += QLatin1String(" (") + m_matDatas[matIdx].m_relativePath + QLatin1Char(')'); + else + names.push_back(shownName); + values.push_back(shownName); + } + return values; +} + +InspectorControlBase *InspectorControlModel::createMaterialTypeItem( + Qt3DSDMInspectable *inspectable, int groupIndex) +{ + InspectorControlBase *item = new InspectorControlBase; + item->m_instance = inspectable->GetGroupInstance(groupIndex); + + item->m_title = tr("Material Type"); + item->m_dataType = qt3dsdm::DataModelDataType::StringRef; + item->m_propertyType = qt3dsdm::AdditionalMetaDataType::None; + item->m_tooltip = tr("Type of material being used"); + item->m_animatable = false; + + const QStringList values = materialTypeValues(); + item->m_values = values; + + QString sourcePath = getBridge()->GetSourcePath(item->m_instance); + + switch (inspectable->getObjectType()) { + case OBJTYPE_MATERIAL: + case OBJTYPE_CUSTOMMATERIAL: + item->m_value = getAnimatableMaterialString(); + break; + + case OBJTYPE_REFERENCEDMATERIAL: + item->m_value = getReferencedMaterialString(); + if (sourcePath == getBridge()->getDefaultMaterialName()) + item->m_value = getBasicMaterialString(); + for (int matIdx = 0, end = int(m_matDatas.size()); matIdx < end; ++matIdx) { + if (QString::compare(m_matDatas[matIdx].m_relativePath, + sourcePath, Qt::CaseInsensitive) == 0) { + item->m_value = getBasicMaterialString(); + } + } + break; + + default: + break; + } + + return item; +} + +InspectorControlBase *InspectorControlModel::createShaderItem( + Qt3DSDMInspectable *inspectable, int groupIndex) +{ + InspectorControlBase *item = new InspectorControlBase; + item->m_instance = inspectable->GetGroupInstance(groupIndex); + + item->m_title = tr("Shader"); + item->m_dataType = qt3dsdm::DataModelDataType::StringRef; + item->m_propertyType = qt3dsdm::AdditionalMetaDataType::Renderable; + item->m_tooltip = tr("Shader being used"); + item->m_animatable = false; + + const QStringList values = shaderValues(); + item->m_values = values; + + QString sourcePath = getBridge()->GetSourcePath(item->m_instance); + + item->m_value = values[0]; + for (int matIdx = 0, end = int(m_materials.size()); matIdx < end; ++matIdx) { + if (m_materials[matIdx].m_relativePath == sourcePath) + item->m_value = values[matIdx + 1]; + } + + return item; +} + +InspectorControlBase *InspectorControlModel::createMatDataItem( + Qt3DSDMInspectable *inspectable, int groupIndex) +{ + InspectorControlBase *item = new InspectorControlBase; + item->m_instance = inspectable->GetGroupInstance(groupIndex); + + item->m_title = tr("Source Material"); + item->m_dataType = qt3dsdm::DataModelDataType::StringRef; + item->m_propertyType = qt3dsdm::AdditionalMetaDataType::ObjectRef; + item->m_tooltip = tr("Source material definitions used"); + item->m_animatable = false; + + const QStringList values = matDataValues(); + item->m_values = values; + + QString sourcePath = getBridge()->GetSourcePath(item->m_instance); + + item->m_value = getDefaultMaterialString(); + for (int matIdx = 0, end = int(m_matDatas.size()); matIdx < end; ++matIdx) { + if (QString::compare(m_matDatas[matIdx].m_relativePath, + sourcePath, Qt::CaseInsensitive) == 0) { + item->m_value = values[matIdx + 1]; // + 1 for Default basic material + } + } + + return item; +} + +InspectorControlBase* InspectorControlModel::createItem(Qt3DSDMInspectable *inspectable, + Q3DStudio::Qt3DSDMInspectorRow *row, + int groupIndex) +{ + return createItem(inspectable, row->GetMetaDataPropertyInfo(), groupIndex); +} + +InspectorControlBase* InspectorControlModel::createItem(Qt3DSDMInspectable *inspectable, + const qt3dsdm::SMetaDataPropertyInfo &metaProperty, + int groupIndex) +{ + const auto studio = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem(); + if (metaProperty.m_IsHidden) + return nullptr; + + Q3DStudio::CString title; + title.Assign(metaProperty.m_FormalName.c_str()); + if (title.IsEmpty()) + title.Assign(metaProperty.m_Name.c_str()); + + // Hide name for basic materials + if (title == "Name" && isBasicMaterial()) + return nullptr; + + InspectorControlBase *item = new InspectorControlBase; + item->m_property = metaProperty.m_Property; + item->m_instance = inspectable->GetGroupInstance(groupIndex); + item->m_metaProperty = metaProperty; + + item->m_title = title.toQString(); + + const auto propertySystem = studio->GetPropertySystem(); + item->m_dataType = propertySystem->GetDataType(metaProperty.m_Property); + item->m_propertyType = static_cast<qt3dsdm::AdditionalMetaDataType::Value> + (propertySystem->GetAdditionalMetaDataType(item->m_instance, metaProperty.m_Property)); + item->m_tooltip = Q3DStudio::CString(metaProperty.m_Description.c_str()).toQString(); + // \n is parsed as \\n from the material and effect files. Replace them to fix multi-line + // tooltips + item->m_tooltip.replace(QLatin1String("\\n"), QLatin1String("\n")); + + item->m_animatable = metaProperty.m_Animatable && + studio->GetAnimationSystem()->IsPropertyAnimatable(item->m_instance, + metaProperty.m_Property); + // If a property is animatable, it should be controllable in addition to + // properties explicitly set as controllable in metadata + item->m_controllable = item->m_animatable || metaProperty.m_Controllable; + + // disable IBL Override for reference materials + if (item->m_title == QLatin1String("IBL Override") + && getBridge()->GetObjectType(item->m_instance) == OBJTYPE_REFERENCEDMATERIAL) { + item->m_enabled = false; + } + auto signalProvider = studio->GetFullSystemSignalProvider(); + if (item->m_animatable) { + item->m_animated = studio->GetAnimationSystem()->IsPropertyAnimated(item->m_instance, + metaProperty.m_Property); + + // Update the Animate Toggle on undo/redo + item->m_connections.push_back(signalProvider->ConnectAnimationCreated( + std::bind(&InspectorControlModel::updateAnimateToggleState, + this, item))); + + item->m_connections.push_back(signalProvider->ConnectAnimationDeleted( + std::bind(&InspectorControlModel::updateAnimateToggleState, + this, item))); + } + + if (item->m_controllable) { + // Set the name of current controller + item->m_controller = currentControllerValue(item->m_instance, item->m_property); + // Update UI icon state and tooltip + updateControlledToggleState(item); + item->m_connections.push_back(signalProvider->ConnectControlledToggled( + std::bind(&InspectorControlModel::updateControlledToggleState, + this, item))); + } + + // synchronize the value itself + updatePropertyValue(item); + return item; +} + +qt3dsdm::SValue InspectorControlModel::currentPropertyValue(long instance, int handle) const +{ + auto propertySystem = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetPropertySystem(); + qt3dsdm::SValue value; + propertySystem->GetInstancePropertyValue(instance, handle, value); + + return value; +} + +QString InspectorControlModel::currentControllerValue(long instance, int handle) const +{ + return g_StudioApp.GetCore()->GetDoc()->GetCurrentController(instance, handle); +} + +void InspectorControlModel::updateControlledToggleState(InspectorControlBase* inItem) const +{ + if (inItem->m_instance) { + const auto studio = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem(); + // toggle if controlledproperty contains the name of this property + qt3dsdm::SValue currPropVal = currentPropertyValue( + inItem->m_instance, studio->GetPropertySystem()->GetAggregateInstancePropertyByName( + inItem->m_instance, qt3dsdm::TCharStr(L"controlledproperty"))); + Q3DStudio::CString currPropValStr; + if (!currPropVal.empty()) + currPropValStr = qt3dsdm::get<qt3dsdm::TDataStrPtr>(currPropVal)->GetData(); + // Restore original tool tip from metadata when turning control off + if (!currPropValStr.size()) { + inItem->m_controlled = false; + inItem->m_controller = ""; + } else { + Q3DStudio::CString propName + = studio->GetPropertySystem()->GetName(inItem->m_property).c_str(); + // Search specifically for whitespace followed with registered property name. + // This avoids finding datainput with same name as the property, as datainput + // name is always prepended with "$" + long propNamePos = currPropValStr.find(" " + propName); + if ((propNamePos == currPropValStr.ENDOFSTRING) + && (propNamePos != 0)) { + inItem->m_controlled = false; + inItem->m_controller = ""; + } else { + inItem->m_controlled = true; + // controller name is prepended with "$" to differentiate from property + // with same name. Reverse find specifically for $. + long posCtrlr = currPropValStr.substr(0, propNamePos).ReverseFind("$"); + + // this is the first controller - property pair in controlledproperty + if (posCtrlr < 0) + posCtrlr = 0; + + // remove $ from controller name for showing it in UI + posCtrlr++; + const QString ctrlName = currPropValStr.substr( + posCtrlr, propNamePos - posCtrlr).toQString(); + + inItem->m_controller = ctrlName; + } + } + + Q_EMIT inItem->tooltipChanged(); + // Emit signal always to trigger updating of controller name in UI + // also when user switches from one controller to another + Q_EMIT inItem->controlledChanged(); + } +} + +void InspectorControlModel::updateAnimateToggleState(InspectorControlBase* inItem) +{ + const auto studio = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem(); + bool animated = studio->GetAnimationSystem()->IsPropertyAnimated(inItem->m_instance, + inItem->m_property); + if (animated != inItem->m_animated) { + inItem->m_animated = animated; + Q_EMIT inItem->animatedChanged(); + } +} + +bool InspectorControlModel::isTreeRebuildRequired(CInspectableBase* inspectBase) +{ + if (inspectBase != m_inspectableBase || !inspectBase) + return true; + + long theCount = m_inspectableBase->getGroupCount(); + auto refMaterial = getReferenceMaterial(inspectBase); + if (refMaterial != m_refMaterial) + return true; + long refMaterialGroupCount = 0; + if (refMaterial.Valid()) + refMaterialGroupCount = 1; // Only the last group of the refMaterial is used + + if (m_groupElements.size() != theCount + refMaterialGroupCount) + return true; + + for (long theIndex = 0; theIndex < theCount; ++theIndex) { + const CInspectorGroup *theInspectorGroup = m_inspectableBase->getGroup(theIndex); + if (m_groupElements.at(theIndex).groupTitle != theInspectorGroup->GetName()) + return true; + } + + return false; +} + +bool InspectorControlModel::isGroupRebuildRequired(CInspectableBase *inspectable, + int theIndex) const +{ + Q_ASSERT(theIndex < m_groupElements.size()); + const CInspectorGroup *theInspectorGroup = inspectable->getGroup(theIndex); + const auto existingGroup = m_groupElements.at(theIndex); + if (existingGroup.groupTitle != theInspectorGroup->GetName()) + return true; + + if (const auto cdmInspectable = dynamic_cast<Qt3DSDMInspectable *>(inspectable)) { + int existingIndex = 0; + if (const auto group = dynamic_cast<const Qt3DSDMInspectorGroup *>(theInspectorGroup)) { + const auto materialGroup = dynamic_cast<const Qt3DSDMMaterialInspectorGroup *>(group); + if (materialGroup && materialGroup->isMaterialGroup()) { + auto i = existingGroup.controlElements.at(existingIndex++).value<InspectorControlBase*>(); + if (i->m_instance != cdmInspectable->GetGroupInstance(theIndex)) + return true; + if (!isInsideMaterialContainer()) + existingIndex++; // Add material type dropdown to existing elements + } + + if ((existingGroup.controlElements.size() - existingIndex) != group->GetRows().size()) + return true; + + for (const auto row : group->GetRows()) { + auto i = existingGroup.controlElements.at(existingIndex++).value<InspectorControlBase*>(); + if (i->m_instance != cdmInspectable->GetGroupInstance(theIndex)) + return true; + + if (i->m_property != row->GetMetaDataPropertyInfo().m_Property) + return true; + } + } + } + + return false; +} + +CClientDataModelBridge *InspectorControlModel::getBridge() const +{ + return g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge(); +} + +auto InspectorControlModel::computeTree(CInspectableBase *inspectBase) + -> QVector<GroupInspectorControl> +{ + QVector<GroupInspectorControl> result; + + if (inspectBase) { + qt3dsdm::Qt3DSDMInstanceHandle instance = inspectBase->getInstance(); + bool isMatFromFile = instance.Valid() && getBridge()->isInsideMaterialContainer(instance); + long groupCount = inspectBase->getGroupCount(); + for (long idx = 0; idx < groupCount; ++idx) + result.append(computeGroup(inspectBase, idx, isMatFromFile, false)); + + if (isDefaultMaterial() && result.size() > 0) { + result[result.size() - 1].groupInfo = tr("\nDefault material cannot be edited.\n\n" + "Create new or import material, then apply."); + } + + //Show original material properties for referenced materials + auto refMaterial = getReferenceMaterial(inspectBase); + if (refMaterial.Valid()) { + auto refMaterialInspectable = g_StudioApp.getInspectableFromInstance(refMaterial); + if (refMaterialInspectable) { + QString materialSrcPath; + if (instance.Valid()) + materialSrcPath = getBridge()->GetSourcePath(instance); + + if (materialSrcPath != getBridge()->getDefaultMaterialName() + && getBridge()->GetSourcePath(refMaterial) + != getBridge()->getDefaultMaterialName()) { + result.append(computeGroup(refMaterialInspectable, + refMaterialInspectable->getGroupCount() - 1, + true, true)); + } + } + } + } + + return result; +} + +auto InspectorControlModel::computeGroup(CInspectableBase *inspectable, int theIndex, + bool disableAnimation, bool isReference) + -> GroupInspectorControl +{ + CInspectorGroup *theInspectorGroup = inspectable->getGroup(theIndex); + GroupInspectorControl result; + result.groupTitle = theInspectorGroup->GetName(); + result.groupInfo.clear(); + + if (isReference) + result.groupTitle += tr(" (Reference)"); + + if (const auto cdmInspectable = dynamic_cast<Qt3DSDMInspectable *>(inspectable)) { + if (const auto group = dynamic_cast<Qt3DSDMInspectorGroup *>(theInspectorGroup)) { + const auto materialGroup = dynamic_cast<Qt3DSDMMaterialInspectorGroup *>(group); + bool isMatData = isBasicMaterial(cdmInspectable); + if (materialGroup && materialGroup->isMaterialGroup()) { + InspectorControlBase *item = nullptr; + + if (!isInsideMaterialContainer(cdmInspectable) && !isReference) { + item = createMaterialTypeItem(cdmInspectable, theIndex); + if (item) + result.controlElements.push_back(QVariant::fromValue(item)); + } + + if (isAnimatableMaterial(cdmInspectable)) { + item = createShaderItem(cdmInspectable, theIndex); + if (item) + result.controlElements.push_back(QVariant::fromValue(item)); + } else if (isMatData) { + item = createMatDataItem(cdmInspectable, theIndex); + if (item) + result.controlElements.push_back(QVariant::fromValue(item)); + } + } + + for (const auto row : group->GetRows()) { + InspectorControlBase *item = createItem(cdmInspectable, row, theIndex); + if (!item) + continue; + + if (disableAnimation) + item->m_animatable = false; + + if (!isMatData || item->m_title != getReferencedMaterialString()) + result.controlElements.push_back(QVariant::fromValue(item)); + } + } + } else if (const auto guideInspectable = dynamic_cast<GuideInspectable *>(inspectable)) { + // Guide properties don't come from metadata as they are not actual objects + m_guideInspectable = guideInspectable; + const auto &properties = m_guideInspectable->properties(); + for (int i = 0, count = int(properties.size()); i < count; ++i) { + auto &prop = properties[i]; + InspectorControlBase *item = new InspectorControlBase; + item->m_title = prop->GetInspectableFormalName(); + item->m_dataType = prop->GetInspectableType(); + item->m_propertyType = prop->GetInspectableAdditionalType(); + item->m_tooltip = prop->GetInspectableDescription(); + item->m_animatable = false; + item->m_controllable = false; + item->m_property = i + 1; // Zero property is considered invalid, so +1 + result.controlElements.push_back(QVariant::fromValue(item)); + updatePropertyValue(item); + } + } + + return result; +} + +void InspectorControlModel::rebuildTree() +{ + beginResetModel(); + m_guideInspectable = nullptr; + QVector<QObject *> deleteVector; + for (int i = 0; i < m_groupElements.count(); ++i) { + auto group = m_groupElements[i]; + for (int p = 0; p < group.controlElements.count(); ++p) + deleteVector.append(group.controlElements[p].value<QObject *>()); + } + m_groupElements = computeTree(m_inspectableBase); + endResetModel(); + + // Clean the old objects after reset is done so that qml will not freak out about null pointers + for (int i = 0; i < deleteVector.count(); ++i) + deleteVector[i]->deleteLater(); + + m_refMaterial = getReferenceMaterial(m_inspectableBase); +} + +int InspectorControlModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return m_groupElements.count(); +} + +void InspectorControlModel::updatePropertyValue(InspectorControlBase *element) const +{ + const auto doc = g_StudioApp.GetCore()->GetDoc(); + auto studioSystem = doc->GetStudioSystem(); + const auto propertySystem = studioSystem->GetPropertySystem(); + qt3dsdm::SValue value; + const auto instance = element->m_instance; + qt3dsdm::Option<qt3dsdm::SMetaDataPropertyInfo> info; + if (m_guideInspectable) { + value = m_guideInspectable->properties() + [handleToGuidePropIndex(element->m_property)]->GetInspectableData(); + } else { + if (!propertySystem->HandleValid(instance)) + return; + propertySystem->GetInstancePropertyValue(instance, element->m_property, value); + + if (value.getType() == qt3dsdm::DataModelDataType::None) + return; + + const auto metaDataProvider = doc->GetStudioSystem()->GetActionMetaData(); + info = metaDataProvider->GetMetaDataPropertyInfo( + metaDataProvider->GetMetaDataProperty(instance, element->m_property)); + } + + bool skipEmits = false; + switch (element->m_dataType) { + case qt3dsdm::DataModelDataType::String: { + QString stringValue = qt3dsdm::get<QString>(value); + if (getBridge()->isInsideMaterialContainer(element->m_instance)) { + int index = stringValue.lastIndexOf(QLatin1Char('/')); + if (index != -1) + stringValue = stringValue.mid(index + 1); + } + + element->m_value = stringValue; + } + Q_FALLTHROUGH(); // fall-through for other String-derived datatypes + + case qt3dsdm::DataModelDataType::StringOrInt: + if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::StringList) { + QStringList stringlist; + if (m_guideInspectable) { + const auto strings = m_guideInspectable->properties() + [handleToGuidePropIndex(element->m_property)]->GetInspectableList(); + for (auto &str : strings) + stringlist.append(QString::fromWCharArray(str.wide_str())); + } else { + stringlist = qt3dsdm::get<QStringList>(info->m_MetaDataData); + } + auto slideSystem = studioSystem->GetSlideSystem(); + + if (element->m_title == QLatin1String("Play Mode")) { + std::pair<bool, bool> slideData( + getSlideCharacteristics(element->m_instance, *studioSystem->GetSlideCore(), + *slideSystem)); + bool hasNextSlide(slideData.first); + bool hasPreviousSlide(slideData.second); + if (!hasNextSlide && !hasPreviousSlide) + stringlist.removeAll("Play Through To..."); + } else if (element->m_title == QLatin1String("Play Through To")) { + // the code duplication is intentional as we may ask for slide characteristics + // only if the property refers to slides + std::pair<bool, bool> slideData( + getSlideCharacteristics(element->m_instance, *studioSystem->GetSlideCore(), + *slideSystem)); + bool hasNextSlide(slideData.first); + bool hasPreviousSlide(slideData.second); + if (!hasNextSlide) + stringlist.removeAll("Next"); + if (!hasPreviousSlide) + stringlist.removeAll("Previous"); + + auto itemCount = stringlist.count(); + QString listOpt; + int selectedSlideHandle = 0; + int selectedIndex = -1; + qt3dsdm::SStringOrInt stringOrInt = qt3dsdm::get<qt3dsdm::SStringOrInt>(value); + if (stringOrInt.GetType() == qt3dsdm::SStringOrIntTypes::String) + listOpt = QString::fromWCharArray(qt3dsdm::get<qt3dsdm::TDataStrPtr> + (stringOrInt.m_Value)->GetData()); + else + selectedSlideHandle = qt3dsdm::get<long>(stringOrInt.m_Value); + + selectedIndex = stringlist.indexOf(listOpt); + // Add the slide names (exclude the master slide) + auto slideHandle = slideSystem->GetSlideByInstance(instance); + auto masterSlide = slideSystem->GetMasterSlide(slideHandle); + long slideCount = long(slideSystem->GetSlideCount(masterSlide)); + for (long slideIndex = 1; slideIndex < slideCount; ++slideIndex) { + auto currentSlide = slideSystem->GetSlideByIndex(masterSlide, slideIndex); + auto currentInstance = slideSystem->GetSlideInstance(currentSlide); + + QString slideName = getBridge()->GetName(currentInstance).toQString(); + //hack to add a separator before the item + if (slideIndex == 1 && itemCount > 0) + slideName += "|separator"; + stringlist.append(slideName); + + if (currentSlide.GetHandleValue() == selectedSlideHandle) + selectedIndex = slideIndex + itemCount - 1; + } + + element->m_value = QString(selectedIndex > 0 ? stringlist[selectedIndex] + : stringlist.first()).replace(QLatin1String("|separator"), + QString()); + } + element->m_values = stringlist; + } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Import) { + QStringList stringlist = qt3dsdm::get<QStringList>(info->m_MetaDataData); + element->m_values = stringlist; + } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Renderable) { + element->m_values = renderableItems(); + if (element->m_value.toString().isEmpty()) + element->m_value = element->m_values.toStringList().at(0); + } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::MultiLine) { + element->m_value = qt3dsdm::get<QString>(value); + } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Font) { + updateFontValues(element); + skipEmits = true; // updateFontValues handles emits in correct order + } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Mesh) { + QString meshValue = QFileInfo(qt3dsdm::get<QString>(value)).fileName(); + element->m_value = meshValue.startsWith('#'_L1) ? meshValue.mid(1) : meshValue; + } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Texture) { + QFileInfo fileInfo(qt3dsdm::get<QString>(value)); + element->m_value = fileInfo.fileName(); + } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::PathBuffer) { + element->m_value = qt3dsdm::get<QString>(value); + } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::String) { + // Basic string already handled, do not warn about that. + // If we hit any other datatypes then give a warning + } else { + qWarning() << "KDAB_TODO: InspectorControlModel::updatePropertyValue: need to implement:" + << element->m_dataType << " element->m_propertyType : " + << element->m_propertyType; + } + break; + + case qt3dsdm::DataModelDataType::StringRef: + if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::None) { + element->m_value = qt3dsdm::get<QString>(value); + } + break; + + case qt3dsdm::DataModelDataType::Bool: + element->m_value = qt3dsdm::get<bool>(value); + break; + + case qt3dsdm::DataModelDataType::Long4: + if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Image) { + qt3dsdm::Option<qt3dsdm::SLong4> guid = qt3dsdm::get<qt3dsdm::SLong4>(value); + qt3dsdm::Qt3DSDMInstanceHandle imageInstance = doc->GetDocumentReader() + .GetInstanceForGuid(guid); + if (imageInstance.Valid()) { + Q3DStudio::CString path = doc->GetDocumentReader().GetSourcePath(imageInstance); + Q3DStudio::CFilePath relPath(path); + element->m_value = QVariant(relPath.GetFileName().toQString()); + } else { + element->m_value = QVariant(QString()); + } + } else { + qWarning() << "KDAB_TODO: InspectorControlModel::updatePropertyValue: need to implement:" + << element->m_dataType << " " << element->m_title; + } + break; + + case qt3dsdm::DataModelDataType::Long: + if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Range) { + element->m_value = qt3dsdm::get<int>(value); + qt3dsdm::SMetaDataRange ranges; + if (m_guideInspectable) { + const auto prop = m_guideInspectable->properties() + [handleToGuidePropIndex(element->m_property)]; + ranges.m_min = prop->GetInspectableMin(); + ranges.m_max = prop->GetInspectableMax(); + } else { + ranges = qt3dsdm::get<qt3dsdm::SMetaDataRange>(info->m_MetaDataData); + } + const QList<double> rangesValues{ranges.m_min, ranges.m_max, double(ranges.m_decimals)}; + element->m_values = QVariant::fromValue<QList<double> >(rangesValues); + } + else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::ShadowMapResolution) { + element->m_value = qt3dsdm::get<int>(value); + } else { + qWarning() << "KDAB_TODO: InspectorControlModel::updatePropertyValue: need to implement:" + << element->m_dataType; + } + break; + + case qt3dsdm::DataModelDataType::Float3: + if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Color) { + element->m_value = qt3dsdm::get<QColor>(value); + } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Rotation) { + const QVector3D theFloat3 = qt3dsdm::get<QVector3D>(value); + const QList<double> float3Values{theFloat3.x(), theFloat3.y(), theFloat3.z()}; + element->m_values = QVariant::fromValue<QList<double> >(float3Values); + } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::None) { + const QVector3D theFloat3 = qt3dsdm::get<QVector3D>(value); + const QList<double> float3Values{theFloat3.x(), theFloat3.y(), theFloat3.z()}; + element->m_values = QVariant::fromValue<QList<double> >(float3Values); + } + break; + + case qt3dsdm::DataModelDataType::Float4: + if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Color) { + element->m_value = qt3dsdm::get<QColor>(value); + } + break; + + case qt3dsdm::DataModelDataType::Float2: + if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::None) { + const QVector2D theFloat2 = qt3dsdm::get<QVector2D>(value); + const QList<double> float2Values{theFloat2.x(), theFloat2.y()}; + element->m_values = QVariant::fromValue<QList<double> >(float2Values); + } else { + qWarning() << "TODO: InspectorControlModel::updatePropertyValue: need to implement:" + << element->m_dataType << element->m_propertyType; + } + break; + + case qt3dsdm::DataModelDataType::Float: + if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::None) { + element->m_value = qt3dsdm::get<float>(value); + } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Range) { + element->m_value = qt3dsdm::get<float>(value); + const qt3dsdm::SMetaDataRange ranges = qt3dsdm::get<qt3dsdm::SMetaDataRange>(info->m_MetaDataData); + const QList<double> rangesValues{ranges.m_min, ranges.m_max, double(ranges.m_decimals)}; + element->m_values = QVariant::fromValue<QList<double> >(rangesValues); + } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::FontSize) { + element->m_value = qt3dsdm::get<float>(value); + } + break; + + case qt3dsdm::DataModelDataType::ObjectRef: + if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::ObjectRef) { + IObjectReferenceHelper *objRefHelper = doc->GetDataModelObjectReferenceHelper(); + if (objRefHelper) { + qt3dsdm::Qt3DSDMInstanceHandle refInstance = objRefHelper->Resolve(value, instance); + QString refName = objRefHelper->LookupObjectFormalName(refInstance).toQString(); + if (getBridge()->IsReferencedMaterialInstance(instance) && !refName.isEmpty()) { + // get the material's object name (parent) + auto parentInstance = getBridge()->GetParentInstance(refInstance); + qt3dsdm::SValue vParent; + propertySystem->GetInstancePropertyValue(parentInstance, + getBridge()->GetObjectDefinitions().m_Named.m_NameProp, vParent); + QString parentName = qt3dsdm::get<QString>(vParent); + refName.append(QLatin1String(" (") + parentName + QLatin1String(")")); + } + element->m_value = refName; + } + } + break; + + default: + qWarning() << "TODO: InspectorControlModel::updatePropertyValue: I've no idea how to handle this datatype" + << element->m_dataType; + break; + } + + if (!skipEmits) { + Q_EMIT element->valueChanged(); + Q_EMIT element->valuesChanged(); + } + + // Controlled state must be manually set after undo operations, + // as only the "controlledproperty" is restored in undo, + // not the controlled flag nor the tooltip + if (element->m_controllable) + updateControlledToggleState(element); +} + +void InspectorControlModel::refreshRenderables() +{ + for (int row = 0; row < m_groupElements.count(); ++row) { + auto group = m_groupElements[row]; + for (int p = 0; p < group.controlElements.count(); ++p) { + QVariant& element = group.controlElements[p]; + InspectorControlBase *property = element.value<InspectorControlBase *>(); + if (property->m_property.Valid() + && property->m_propertyType == qt3dsdm::AdditionalMetaDataType::Renderable) { + updatePropertyValue(property); + } + } + } +} + +void InspectorControlModel::refreshTree() +{ + //check if the structure has changed + if (isTreeRebuildRequired(m_inspectableBase)) { + rebuildTree(); + } else { + // group structure is intact, let's walk to see which rows changed + QVector<QObject *> deleteVector; + long theCount = m_inspectableBase->getGroupCount(); + for (long theIndex = 0; theIndex < theCount; ++theIndex) { + if (isGroupRebuildRequired(m_inspectableBase, theIndex)) { + auto group = m_groupElements[theIndex]; + for (int p = 0; p < group.controlElements.count(); ++p) + deleteVector.append(group.controlElements[p].value<QObject *>()); + m_groupElements[theIndex] = computeGroup(m_inspectableBase, theIndex); + Q_EMIT dataChanged(index(theIndex), index(theIndex)); + } + } + } +} + +void InspectorControlModel::refresh() +{ + refreshTree(); + // update values + for (int row = 0; row < m_groupElements.count(); ++row) { + auto group = m_groupElements[row]; + for (int p = 0; p < group.controlElements.count(); ++p) { + QVariant& element = group.controlElements[p]; + InspectorControlBase *property = element.value<InspectorControlBase *>(); + if (property->m_property.Valid()) { + updatePropertyValue(property); + updateControlledToggleState(property); + } + } + } + Q_EMIT dataChanged(index(0), index(rowCount() - 1)); +} + +void InspectorControlModel::saveIfMaterial(qt3dsdm::Qt3DSDMInstanceHandle instance) +{ + if (!instance.Valid()) + return; + + const auto doc = g_StudioApp.GetCore()->GetDoc(); + const auto sceneEditor = doc->getSceneEditor(); + + const auto studio = doc->GetStudioSystem(); + EStudioObjectType type = getBridge()->GetObjectType(instance); + + auto material = instance; + if (type == EStudioObjectType::OBJTYPE_IMAGE) + material = sceneEditor->GetParent(instance); + + if (!material.Valid()) + return; + + const auto refMaterial = getBridge()->getMaterialReference(material); + if (refMaterial.Valid()) + material = refMaterial; + + if (!getBridge()->isInsideMaterialContainer(material)) + return; + + type = getBridge()->GetObjectType(material); + + if (type == EStudioObjectType::OBJTYPE_MATERIAL + || type == EStudioObjectType::OBJTYPE_CUSTOMMATERIAL) { + qt3dsdm::SValue value; + studio->GetPropertySystem()->GetInstancePropertyValue( + material, getBridge()->GetObjectDefinitions().m_Named.m_NameProp, value); + qt3dsdm::TDataStrPtr namePtr(qt3dsdm::get<qt3dsdm::TDataStrPtr>(value)); + QString materialName = QString::fromWCharArray(namePtr->GetData(), + int(namePtr->GetLength())); + QString sourcePath; + for (int i = 0; i < m_matDatas.size(); ++i) { + if (QString::compare(m_matDatas[i].m_name, materialName, Qt::CaseInsensitive) == 0) { + sourcePath = doc->GetDocumentDirectory() + QLatin1Char('/') + + m_matDatas[i].m_relativePath; + } + } + + sceneEditor->writeMaterialFile(material, materialName, sourcePath.isEmpty(), sourcePath); + } +} + +void InspectorControlModel::setMaterialTypeValue(long instance, int handle, const QVariant &value) +{ + Q_UNUSED(handle) + + const QString typeValue = value.toString(); + QString v; + + const auto doc = g_StudioApp.GetCore()->GetDoc(); + const auto sceneEditor = doc->getSceneEditor(); + const Q3DStudio::CString oldType = sceneEditor->GetObjectTypeName(instance); + qt3dsdm::Qt3DSDMInstanceHandle refMaterial; + if (oldType == "ReferencedMaterial") + refMaterial = getBridge()->getMaterialReference(instance); + + bool changeMaterialFile = false; + bool canCopyProperties = false; + if (typeValue == getAnimatableMaterialString()) { + v = QStringLiteral("Standard Material"); + if (refMaterial.Valid()) { + const auto refSourcePath = getBridge()->GetSourcePath(refMaterial); + for (auto &material : m_materials) { + if (refSourcePath == material.m_relativePath) { + v = material.m_relativePath; + break; + } + } + } + canCopyProperties = true; + } else if (typeValue == getBasicMaterialString()) { + v = QStringLiteral("Referenced Material"); + changeMaterialFile = true; + } else if (typeValue == getReferencedMaterialString()) { + v = QStringLiteral("Referenced Material"); + } + + Q3DStudio::ScopedDocumentEditor scopedEditor( + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, tr("Set Material Type"))); + + scopedEditor->SetMaterialType(instance, v); + + if (refMaterial.Valid() && canCopyProperties) { + const Q3DStudio::CString newType = sceneEditor->GetObjectTypeName(instance); + const Q3DStudio::CString refType = sceneEditor->GetObjectTypeName(refMaterial); + if (refType == newType) + scopedEditor->copyMaterialProperties(refMaterial, instance); + + if (getBridge()->isInsideMaterialContainer(refMaterial)) { + const auto name = scopedEditor->GetName(instance); + if (!name.toQString().endsWith(QLatin1String("_animatable"))) + scopedEditor->SetName(instance, name + "_animatable"); + } + } + + if (changeMaterialFile) { + scopedEditor->setMaterialProperties(instance, Q3DStudio::CString::fromQString( + getBridge()->getDefaultMaterialName()), {}, {}); + + // Select the original instance again since potentially creating a material selects the + // created one + doc->SelectDataModelObject(instance); + + rebuildTree(); // Hack to mimic value changing behavior of the type selector + } + + saveIfMaterial(instance); +} + +void InspectorControlModel::setShaderValue(long instance, int handle, const QVariant &value) +{ + Q_UNUSED(handle) + + const QString typeValue = value.toString(); + QString v = QStringLiteral("Standard Material"); + for (size_t matIdx = 0, end = m_materials.size(); matIdx < end; ++matIdx) { + if (m_materials[matIdx].m_name == typeValue) + v = m_materials[matIdx].m_relativePath; + } + + const auto doc = g_StudioApp.GetCore()->GetDoc(); + + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, QObject::tr("Set Material Type")) + ->SetMaterialType(instance, v); + + const auto dispatch = g_StudioApp.GetCore()->GetDispatch(); + QVector<qt3dsdm::Qt3DSDMInstanceHandle> refMats; + doc->getSceneReferencedMaterials(doc->GetSceneInstance(), refMats); + for (auto &refMat : qAsConst(refMats)) { + const auto origMat = getBridge()->getMaterialReference(refMat); + if (origMat.Valid() && long(origMat) == instance) + dispatch->FireImmediateRefreshInstance(refMat); + } + + saveIfMaterial(instance); +} + +void InspectorControlModel::setMatDataValue(long instance, int handle, const QVariant &value) +{ + Q_UNUSED(handle) + + const QString typeValue = value.toString(); + QString v; + QString name; + Q3DStudio::CString srcPath; + QMap<QString, QString> values; + QMap<QString, QMap<QString, QString>> textureValues; + + const auto doc = g_StudioApp.GetCore()->GetDoc(); + + bool changeMaterialFile = false; + if (typeValue == getDefaultMaterialString()) { + v = QStringLiteral("Referenced Material"); + name = getBridge()->getDefaultMaterialName(); + srcPath = Q3DStudio::CString::fromQString(name); + changeMaterialFile = true; + } else { + const auto sceneEditor = doc->getSceneEditor(); + for (size_t matIdx = 0, end = m_matDatas.size(); matIdx < end; ++matIdx) { + QString shownName = m_matDatas[matIdx].m_name; + int slashIndex = shownName.lastIndexOf(QLatin1Char('/')); + if (slashIndex != -1) + shownName = shownName.mid(slashIndex + 1); + if (QString::compare(shownName + QLatin1String(" (") + + m_matDatas[matIdx].m_relativePath + QLatin1Char(')'), + typeValue, Qt::CaseInsensitive) == 0 + || QString::compare(shownName, typeValue, Qt::CaseInsensitive) == 0) { + v = QStringLiteral("Referenced Material"); + changeMaterialFile = true; + name = m_matDatas[matIdx].m_name; + srcPath = Q3DStudio::CString::fromQString(m_matDatas[matIdx].m_relativePath); + const auto material = sceneEditor->getMaterial(srcPath.toQString()); + if (material.Valid()) { + // Get the correct case source path + const auto absPath = sceneEditor->getFilePathFromMaterialName( + sceneEditor->GetName(material).toQString()); + const auto relPath = QDir(doc->GetDocumentDirectory()) + .relativeFilePath(absPath); + srcPath = Q3DStudio::CString::fromQString(relPath); + } + values = m_matDatas[matIdx].m_values; + textureValues = m_matDatas[matIdx].m_textureValues; + break; + } + } + } + + if (changeMaterialFile) { + { + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, QString()) + ->setMaterialValues(srcPath.toQString(), values, textureValues); + } + // Several aspects of the editor are not updated correctly + // if the data core is changed without a transaction + // The above scope completes the transaction for creating a new material + // Next the added undo has to be popped from the stack + // TODO: Find a way to update the editor fully without a transaction + g_StudioApp.GetCore()->GetCmdStack()->RemoveLastUndo(); + } + + Q3DStudio::ScopedDocumentEditor scopedEditor( + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, tr("Set Material Type"))); + scopedEditor->SetMaterialType(instance, v); + + if (changeMaterialFile) { + scopedEditor->setMaterialSourcePath(instance, srcPath); + scopedEditor->setMaterialReferenceByPath(instance, srcPath.toQString()); + + // Select original instance again since potentially + // creating a material selects the created one + doc->SelectDataModelObject(instance); + + rebuildTree(); // Hack to mimic value changing behavior of the type selector + } + + saveIfMaterial(instance); +} + +void InspectorControlModel::setRenderableValue(long instance, int handle, const QVariant &value) +{ + qt3dsdm::SValue oldValue = currentPropertyValue(instance, handle); + + QString v = value.toString(); + if (v == QObject::tr("No renderable item")) + v = QString(); + + if (v == qt3dsdm::get<QString>(oldValue)) + return; + + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*g_StudioApp.GetCore()->GetDoc(), QObject::tr("Set Property")) + ->SetInstancePropertyValueAsRenderable(instance, handle, + Q3DStudio::CString::fromQString(v)); +} + +void InspectorControlModel::setPropertyValue(long instance, int handle, const QVariant &value, + bool commit) +{ + const auto doc = g_StudioApp.GetCore()->GetDoc(); + const auto studio = doc->GetStudioSystem(); + // Name property needs special handling + if (instance && handle == getBridge()->GetNameProperty()) { + // Ignore preview of name property change + if (!commit) + return; + + m_modifiedProperty.first = 0; + m_modifiedProperty.second = 0; + m_previouslyCommittedValue = {}; + + Q3DStudio::CString currentName = getBridge()->GetName(instance, true); + Q3DStudio::CString newName = Q3DStudio::CString::fromQString(value.toString()); + if (!newName.IsEmpty()) { + if (getBridge()->isInsideMaterialContainer(instance) + && ((newName.Find('/') != Q3DStudio::CString::ENDOFSTRING + || newName.Find('#') != Q3DStudio::CString::ENDOFSTRING + || newName.Find(':') != Q3DStudio::CString::ENDOFSTRING) + || m_suspendMaterialRename)) { + return; + } + qt3dsdm::Qt3DSDMInstanceHandle parentInstance = getBridge() + ->GetParentInstance(instance); + + if (parentInstance == doc->GetSceneInstance() + && newName.toQString() == getBridge()->getMaterialContainerName()) { + QString theTitle = QObject::tr("Rename Object Error"); + QString theString = getBridge()->getMaterialContainerName() + + QObject::tr(" is a reserved name."); + // Display error message box asynchronously so focus loss won't trigger setting + // the name again + g_StudioApp.GetDialogs()->asyncDisplayMessageBox(theTitle, theString, + Qt3DSMessageBox::ICON_ERROR); + return; + } + + Q3DStudio::CString realNewName = newName; + if (getBridge()->isInsideMaterialContainer(instance)) { + Q3DStudio::CString realName = getBridge()->GetName(instance); + int slashIndex = realName.rfind('/'); + if (slashIndex != Q3DStudio::CString::ENDOFSTRING) + realNewName = realName.Left(slashIndex + 1) + newName; + } + + if (!getBridge()->CheckNameUnique(parentInstance, instance, realNewName)) { + QString origNewName = newName.toQString(); + realNewName = getBridge()->GetUniqueChildName(parentInstance, instance, + realNewName); + newName = realNewName; + if (getBridge()->isInsideMaterialContainer(instance)) { + int slashIndex = newName.rfind('/'); + if (slashIndex != Q3DStudio::CString::ENDOFSTRING) + newName = newName.substr(slashIndex + 1); + } + // Display rename message box asynchronously so focus loss won't trigger setting + // the name again + g_StudioApp.GetDialogs()->DisplayObjectRenamed(origNewName, newName.toQString(), + true); + } + + const auto sceneEditor = doc->getSceneEditor(); + + // A materialdef with the same name might exists as a file but not in the container, + // so an additional check is needed for that case + if (getBridge()->isInsideMaterialContainer(instance)) { + int i = 1; + while (QFileInfo(sceneEditor->getFilePathFromMaterialName( + realNewName.toQString())).exists()) { + ++i; + realNewName = Q3DStudio::CString::fromQString( + realNewName.toQString() + QString::number(i)); + if (!getBridge()->CheckNameUnique(parentInstance, instance, realNewName)) { + realNewName = getBridge()->GetUniqueChildName(parentInstance, instance, + realNewName); + } + } + newName = realNewName; + int slashIndex = newName.rfind('/'); + if (slashIndex != Q3DStudio::CString::ENDOFSTRING) + newName = newName.substr(slashIndex + 1); + } + + if (newName != currentName) { + if (getBridge()->isInsideMaterialContainer(instance)) { + const auto properOldName = sceneEditor->GetName(instance).toQString(); + const QString dirPath = doc->GetDocumentDirectory(); + for (size_t matIdx = 0, end = m_matDatas.size(); matIdx < end; ++matIdx) { + if (m_matDatas[matIdx].m_name == properOldName) { + QFileInfo fileInfo(dirPath + QLatin1Char('/') + + m_matDatas[matIdx].m_relativePath); + const QString newFile = fileInfo.absolutePath() + + QLatin1Char('/') + + newName.toQString() + + QStringLiteral(".materialdef"); + const auto properNewName + = sceneEditor->getMaterialNameFromFilePath(newFile); + newName = Q3DStudio::CString::fromQString(properNewName); + doc->queueMaterialRename(properOldName, properNewName); + } + } + } + Q3DStudio::SCOPED_DOCUMENT_EDITOR( + *g_StudioApp.GetCore()->GetDoc(), + QObject::tr("Set Name"))->SetName(instance, newName, false); + } + } + return; + } + + qt3dsdm::SValue oldValue = m_guideInspectable + ? m_guideInspectable->properties()[handleToGuidePropIndex(handle)]->GetInspectableData() + : currentPropertyValue(instance, handle); + qt3dsdm::SValue v = value; + + const bool hasPreview = (m_modifiedProperty.first == instance + && m_modifiedProperty.second == handle); + + // If this set is a commit for property that was previously changed without + // committing, we must let the set go through even if the value hasn't changed + // to finish the transaction. + if (v == oldValue && !(commit && hasPreview)) + return; + + if (!commit && !hasPreview) { + m_previouslyCommittedValue = oldValue; + m_modifiedProperty.first = instance; + m_modifiedProperty.second = handle; + } + + if (instance) { + // If the user enters 0.0 to any (x, y, z) values of camera scale, + // we reset the value back to original, because zero scale factor will crash + // camera-specific inverse matrix math. (Additionally, scale of zero for a camera + // is generally not useful anyway.) We could silently discard zero values also deeper in the + // value setter code, but then the inspector panel value would not be updated as opposed + // to both rejecting invalid and resetting the original value here. + EStudioObjectType theType = getBridge()->GetObjectType(instance); + + if (theType == EStudioObjectType::OBJTYPE_CAMERA && + studio->GetPropertySystem()->GetName(handle) == Q3DStudio::CString("scale")) { + const QVector3D theFloat3 = qt3dsdm::get<QVector3D>(v); + if (theFloat3.x() == 0.0f || theFloat3.y() == 0.0f || theFloat3.z() == 0.0f ) + v = oldValue; + } + + // some properties may initialize OpenGL resources (e.g. loading meshes will + // initialize vertex buffers), so the renderer's OpenGL context must be current + Q3DStudio::IStudioRenderer &theRenderer(g_StudioApp.getRenderer()); + theRenderer.MakeContextCurrent(); + m_UpdatableEditor.EnsureEditor(QObject::tr("Set Property"), __FILE__, __LINE__) + .SetInstancePropertyValue(instance, handle, v); + + theRenderer.ReleaseContext(); + + m_UpdatableEditor.FireImmediateRefresh(instance); + } else if (m_guideInspectable) { + m_guideInspectable->properties()[handleToGuidePropIndex(handle)]->ChangeInspectableData(v); + } + + if (commit) { + m_modifiedProperty.first = 0; + m_modifiedProperty.second = 0; + if (m_previouslyCommittedValue == v) { + if (m_guideInspectable) + m_guideInspectable->Rollback(); + else + m_UpdatableEditor.RollbackEditor(); + } else { + if (m_guideInspectable) { + // If the guide ends up over the matte, destroy it + QSize presSize = g_StudioApp.GetCore()->GetStudioProjectSettings() + ->getPresentationSize(); + bool isInPres = true; + qt3dsdm::SValue posValue = m_guideInspectable->GetPosition(); + float position = qt3dsdm::get<float>(posValue); + if (m_guideInspectable->isHorizontal()) + isInPres = 0.f <= position && float(presSize.height()) >= position; + else + isInPres = 0.f <= position && float(presSize.width()) >= position; + if (isInPres) + m_guideInspectable->Commit(); + else + m_guideInspectable->Destroy(); + } else { + m_UpdatableEditor.CommitEditor(); + } + } + + m_previouslyCommittedValue = {}; + refreshTree(); + + saveIfMaterial(instance); + } +} + +void InspectorControlModel::setSlideSelection(long instance, int handle, int index, + const QStringList &list) +{ + const auto doc = g_StudioApp.GetCore()->GetDoc(); + auto studioSystem = doc->GetStudioSystem(); + const auto metaDataProvider = doc->GetStudioSystem()->GetActionMetaData(); + const auto info = metaDataProvider->GetMetaDataPropertyInfo( + metaDataProvider->GetMetaDataProperty(instance, handle)); + QStringList stringlist = qt3dsdm::get<QStringList>(info->m_MetaDataData); + + auto slideSystem = studioSystem->GetSlideSystem(); + std::pair<bool, bool> slideData( + getSlideCharacteristics(instance, *studioSystem->GetSlideCore(), + *slideSystem)); + bool hasNextSlide(slideData.first); + bool hasPreviousSlide(slideData.second); + qt3dsdm::SStringOrInt newSelectedData; + if (!hasNextSlide) + stringlist.removeAll("Next"); + if (!hasPreviousSlide) + stringlist.removeAll("Previous"); + + auto itemCount = stringlist.count(); + if (index < itemCount) { + newSelectedData = qt3dsdm::SStringOrInt(std::make_shared<qt3dsdm::CDataStr> + (Q3DStudio::CString::fromQString(list[index]).c_str())); + } else { + auto slideHandle = slideSystem->GetSlideByInstance(instance); + auto masterSlide = slideSystem->GetMasterSlide(slideHandle); + long slideIndex = index - itemCount + 1; + auto newSelectedSlide = slideSystem->GetSlideByIndex(masterSlide, slideIndex); + newSelectedData = qt3dsdm::SStringOrInt((long)newSelectedSlide); + } + + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*g_StudioApp.GetCore()->GetDoc(), QObject::tr("Set Property")) + ->SetInstancePropertyValue(instance, handle, newSelectedData); +} + +// temporarily prevent material renaming when opening the colors dialog (fix for QT3DS-3407) +void InspectorControlModel::suspendMaterialRename(bool flag) +{ + m_suspendMaterialRename = flag; +} + +void InspectorControlModel::setPropertyControllerInstance( + long instance,int property, Q3DStudio::CString controllerInstance, bool controlled) +{ + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + IObjectReferenceHelper *objRefHelper = doc->GetDataModelObjectReferenceHelper(); + + Q3DStudio::CString instancepath = Q3DStudio::CString( + objRefHelper->GetObjectReferenceString(doc->GetSceneInstance(), + CRelativePathTools::EPATHTYPE_GUID, instance)); + Q_ASSERT(instancepath.size()); + + doc->SetInstancePropertyControlled(instance, instancepath, property, + controllerInstance, controlled); +} + +void InspectorControlModel::setPropertyControlled(long instance, int property) +{ + const auto signalSender + = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetFullSystemSignalSender(); + + signalSender->SendControlledToggled(instance, property); +} + +bool InspectorControlModel::isLayer(long instance) const +{ + return getBridge()->GetObjectType(instance) == EStudioObjectType::OBJTYPE_LAYER; +} + +QString InspectorControlModel::renderableId(const QString &filePath) const +{ + return g_StudioApp.getRenderableId(filePath); +} + +void InspectorControlModel::setPropertyAnimated(long instance, int handle, bool animated) +{ + CCmd* cmd = nullptr; + auto doc = g_StudioApp.GetCore()->GetDoc(); + if (animated) + cmd = new CCmdDataModelAnimate(doc, instance, handle); + else + cmd = new CCmdDataModelDeanimate(doc, instance, handle); + + g_StudioApp.GetCore()->ExecuteCommand(cmd); +} + +QVariant InspectorControlModel::data(const QModelIndex &index, int role) const +{ + if (!hasIndex(index.row(), index.column(),index.parent())) + return {}; + + const auto row = index.row(); + + switch (role) { + case GroupValuesRole: + return m_groupElements.at(row).controlElements; + case GroupTitleRole: + return m_groupElements.at(row).groupTitle; + case GroupInfoRole: + return m_groupElements.at(row).groupInfo; + } + return {}; +} + +QHash<int, QByteArray> InspectorControlModel::roleNames() const +{ + auto names = QAbstractListModel::roleNames(); + names.insert(GroupValuesRole, "values"); + names.insert(GroupTitleRole, "title"); + names.insert(GroupInfoRole, "info"); + return names; +} + +InspectorControlBase::~InspectorControlBase() +{ +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlModel.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlModel.h new file mode 100644 index 00000000..3063f047 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlModel.h @@ -0,0 +1,258 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef INSPECTORCONTROLMODEL_H +#define INSPECTORCONTROLMODEL_H + +#include "Qt3DSDMValue.h" +#include "Qt3DSDMMetaDataValue.h" +#include "Qt3DSDMMetaDataTypes.h" +#include "IDocumentEditor.h" + +#include <QtCore/qabstractitemmodel.h> +#include <QtCore/qvector.h> + +class CInspectableBase; +class Qt3DSDMInspectable; +class GuideInspectable; +class VariantsGroupModel; +class CClientDataModelBridge; + +namespace qt3dsdm { +class ISignalConnection; +typedef std::shared_ptr<ISignalConnection> TSignalConnectionPtr; +} + +namespace Q3DStudio +{ +class Qt3DSDMInspectorRow; +} + +class InspectorControlBase : public QObject +{ + Q_OBJECT + Q_PROPERTY(qt3dsdm::DataModelDataType::Value dataType MEMBER m_dataType CONSTANT) + Q_PROPERTY(qt3dsdm::AdditionalMetaDataType::Value propertyType MEMBER m_propertyType CONSTANT) + Q_PROPERTY(QVariant value MEMBER m_value NOTIFY valueChanged) + Q_PROPERTY(QVariant values MEMBER m_values NOTIFY valuesChanged) + Q_PROPERTY(QString title MEMBER m_title CONSTANT) + Q_PROPERTY(QString toolTip MEMBER m_tooltip NOTIFY tooltipChanged) + Q_PROPERTY(int instance MEMBER m_instance CONSTANT) + Q_PROPERTY(int handle MEMBER m_property CONSTANT) + + Q_PROPERTY(bool enabled MEMBER m_enabled CONSTANT) + Q_PROPERTY(bool animatable MEMBER m_animatable CONSTANT) + Q_PROPERTY(bool animated MEMBER m_animated NOTIFY animatedChanged) + Q_PROPERTY(bool controlled MEMBER m_controlled NOTIFY controlledChanged) + Q_PROPERTY(bool controllable MEMBER m_controllable CONSTANT) + Q_PROPERTY(QString controller MEMBER m_controller NOTIFY controlledChanged) + +public: + virtual ~InspectorControlBase(); + +Q_SIGNALS: + void valueChanged(); + void valuesChanged(); + void animatedChanged(); + void controlledChanged(); + void tooltipChanged(); + +public: + qt3dsdm::DataModelDataType::Value m_dataType; + qt3dsdm::AdditionalMetaDataType::Value m_propertyType; + qt3dsdm::SMetaDataPropertyInfo m_metaProperty; + QVariant m_value; + QVariant m_values; + QString m_title; + QString m_tooltip; + + qt3dsdm::Qt3DSDMInstanceHandle m_instance; + qt3dsdm::Qt3DSDMPropertyHandle m_property; + + bool m_enabled = true; + bool m_animatable = false; + bool m_animated = false; + bool m_controlled = false; + bool m_controllable = false; + QString m_controller; + std::vector<qt3dsdm::TSignalConnectionPtr> m_connections; +}; + +class InspectorControlModel : public QAbstractListModel +{ + Q_OBJECT +public: + explicit InspectorControlModel(VariantsGroupModel *variantsModel, QObject *parent); + ~InspectorControlModel() override = default; + + enum Roles { + GroupValuesRole = Qt::UserRole + 1, + GroupTitleRole, + GroupInfoRole + }; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + + QHash<int, QByteArray> roleNames() const override; + + void setInspectable(CInspectableBase *inInspectable); + CInspectableBase *inspectable() const; + void setMaterials(std::vector<Q3DStudio::CFilePath> &materials); + void setMatDatas(const std::vector<Q3DStudio::CFilePath> &matdatas); + void updateFontValues(InspectorControlBase *element) const; + void refreshRenderables(); + void refresh(); + void saveIfMaterial(qt3dsdm::Qt3DSDMInstanceHandle instance); + + bool hasInstanceProperty(long instance, int handle); + + qt3dsdm::SValue currentPropertyValue(long instance, int handle) const; + QString currentControllerValue(long instance, int handle) const; + void setPropertyControllerInstance(long instance,int handle, + Q3DStudio::CString controllerInstance, + bool controlled); + void notifyPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance, + qt3dsdm::Qt3DSDMPropertyHandle inProperty); + + Q_INVOKABLE void setMaterialTypeValue(long instance, int handle, const QVariant &value); + Q_INVOKABLE void setShaderValue(long instance, int handle, const QVariant &value); + Q_INVOKABLE void setMatDataValue(long instance, int handle, const QVariant &value); + Q_INVOKABLE void setRenderableValue(long instance, int handle, const QVariant &value); + Q_INVOKABLE void setPropertyValue(long instance, int handle, const QVariant &value, bool commit = true); + Q_INVOKABLE void setSlideSelection(long instance, int handle, int index, + const QStringList &list); + Q_INVOKABLE void suspendMaterialRename(bool flag); + Q_INVOKABLE void setPropertyAnimated(long instance, int handle, bool animated); + Q_INVOKABLE void setPropertyControlled(long instance, int property); + Q_INVOKABLE bool isLayer(long instance) const; + Q_INVOKABLE QString renderableId(const QString &filePath) const; + Q_INVOKABLE bool isMaterial() const; + Q_INVOKABLE bool isDefaultMaterial() const; + Q_INVOKABLE void addMaterial(); + Q_INVOKABLE void duplicateMaterial(); + Q_INVOKABLE bool isGroupCollapsed(int groupIdx) const; + Q_INVOKABLE void updateGroupCollapseState(int groupIdx, bool state); + +private: + void onSlideRearranged(const qt3dsdm::Qt3DSDMSlideHandle &inMaster, int inOldIndex, + int inNewIndex); + + + struct GroupInspectorControl { + QString groupTitle; + QVariantList controlElements; + QString groupInfo; + + ~GroupInspectorControl() { + } + }; + + QVector<GroupInspectorControl> m_groupElements; + CInspectableBase *m_inspectableBase = nullptr; + GuideInspectable *m_guideInspectable = nullptr; + + struct MaterialEntry + { + QString m_name; + QString m_relativePath; + }; + + struct MaterialDataEntry + { + QString m_name; + QString m_relativePath; + QMap<QString, QString> m_values; + QMap<QString, QMap<QString, QString>> m_textureValues; + }; + + std::vector<MaterialEntry> m_materials; + std::vector<MaterialDataEntry> m_matDatas; + std::vector<Q3DStudio::CFilePath> m_cachedMatDatas; + qt3dsdm::Qt3DSDMInstanceHandle m_refMaterial; + + Q3DStudio::CUpdateableDocumentEditor m_UpdatableEditor; + + bool m_suspendMaterialRename = false; + + QPair<long, int> m_modifiedProperty; + + qt3dsdm::SValue m_previouslyCommittedValue; + + QHash<int, QHash<int, bool> > m_collapseMap; + + QString getBasicMaterialString() const; + QString getAnimatableMaterialString() const; + QString getReferencedMaterialString() const; + QString getStandardMaterialString() const; + QString getDefaultMaterialString() const; + bool isInsideMaterialContainer() const; + bool isInsideMaterialContainer(CInspectableBase *inspectable) const; + bool isAnimatableMaterial() const; + bool isAnimatableMaterial(CInspectableBase *inspectable) const; + bool isBasicMaterial() const; + bool isBasicMaterial(CInspectableBase *inspectable) const; + void updateMaterialValues(const QStringList &values, int elementIndex, + bool updatingShaders = false); + qt3dsdm::Qt3DSDMInstanceHandle getReferenceMaterial(CInspectableBase *inspectable) const; + void updateShaderValues(); + void updateMatDataValues(); + void updatePropertyValue(InspectorControlBase *element) const; + void rebuildTree(); + void refreshTree(); + void updateAnimateToggleState(InspectorControlBase *inItem); + void updateControlledToggleState(InspectorControlBase *inItem) const; + + QStringList materialTypeValues() const; + QStringList shaderValues() const; + QStringList matDataValues() const; + InspectorControlBase *createMaterialTypeItem(Qt3DSDMInspectable *inspectable, int groupIndex); + InspectorControlBase *createShaderItem(Qt3DSDMInspectable *inspectable, int groupIndex); + InspectorControlBase *createMatDataItem(Qt3DSDMInspectable *inspectable, int groupIndex); + InspectorControlBase *createItem(Qt3DSDMInspectable *inspectable, + Q3DStudio::Qt3DSDMInspectorRow *row, int groupIndex); + InspectorControlBase *createItem(Qt3DSDMInspectable *inspectable, + const qt3dsdm::SMetaDataPropertyInfo &metaProperty, + int groupIndex); + + QVector<GroupInspectorControl> computeTree(CInspectableBase *inspectBase); + bool isTreeRebuildRequired(CInspectableBase *inspectBase); + + GroupInspectorControl computeGroup(CInspectableBase* inspectBase, + int theIndex, bool disableAnimation = false, + bool isReference = false); + bool isGroupRebuildRequired(CInspectableBase *inspectable, int theIndex) const; + + CClientDataModelBridge *getBridge() const; + + static int handleToGuidePropIndex(int handle) { return handle - 1; } + + VariantsGroupModel *m_variantsModel = nullptr; +}; + +#endif // INSPECTORCONTROLMODEL_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.cpp new file mode 100644 index 00000000..e4e0379f --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.cpp @@ -0,0 +1,930 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "InspectorControlView.h" +#include "Literals.h" +#include "CColor.h" +#include "Qt3DSDMValue.h" +#include "StudioUtils.h" +#include "InspectorControlModel.h" +#include "StudioPreferences.h" +#include "Core.h" +#include "Doc.h" +#include "IDocumentEditor.h" +#include "ImageChooserModel.h" +#include "ImageChooserView.h" +#include "MeshChooserView.h" +#include "TextureChooserView.h" +#include "InspectableBase.h" +#include "StudioApp.h" +#include "ObjectListModel.h" +#include "ObjectBrowserView.h" +#include "IDirectoryWatchingSystem.h" +#include "StandardExtensions.h" +#include "FileChooserView.h" +#include "IObjectReferenceHelper.h" +#include "Qt3DSDMStudioSystem.h" +#include "StudioFullSystem.h" +#include "ClientDataModelBridge.h" +#include "MainFrm.h" +#include "DataInputDlg.h" +#include "Dialogs.h" +#include "ProjectFile.h" +#include "MaterialRefView.h" +#include "BasicObjectsModel.h" +#include "Qt3DSDMSlides.h" +#include "VariantsGroupModel.h" +#include "VariantTagDialog.h" +#include "SlideView.h" +#include "TimelineWidget.h" +#include "SelectedValue.h" +#include "Qt3DSDMInspectable.h" +#include "Qt3DSDMSlides.h" +#include "Qt3DSDMMaterialInspectable.h" +#include "GuideInspectable.h" + +#include <QtCore/qtimer.h> +#include <QtQml/qqmlcontext.h> +#include <QtQml/qqmlengine.h> +#include <QtWidgets/qmenu.h> +#include <QtWidgets/qdesktopwidget.h> +#include <QtWidgets/qlistwidget.h> + +InspectorControlView::InspectorControlView(const QSize &preferredSize, QWidget *parent) + : QQuickWidget(parent), + TabNavigable(), + m_variantsGroupModel(new VariantsGroupModel(this)), + m_inspectorControlModel(new InspectorControlModel(m_variantsGroupModel, this)), + m_meshChooserView(new MeshChooserView(this)), + m_preferredSize(preferredSize) +{ + setResizeMode(QQuickWidget::SizeRootObjectToView); + QTimer::singleShot(0, this, &InspectorControlView::initialize); + auto dispatch = g_StudioApp.GetCore()->GetDispatch(); + dispatch->AddPresentationChangeListener(this); + dispatch->AddDataModelListener(this); + + connect(m_meshChooserView, &MeshChooserView::meshSelected, this, + [this] (int handle, int instance, const QString &name) { + if (name.startsWith(QLatin1Char('#'))) { + if (m_inspectorControlModel) + m_inspectorControlModel->setPropertyValue(instance, handle, name); + } else { + setPropertyValueFromFilename(instance, handle, name); + } + }); +} + +static bool isInList(const wchar_t **list, const Q3DStudio::CString &inStr) +{ + for (const wchar_t **item = list; item && *item; ++item) { + if (inStr.Compare(*item, Q3DStudio::CString::ENDOFSTRING, false)) + return true; + } + return false; +} + +void InspectorControlView::filterMaterials(std::vector<Q3DStudio::CFilePath> &materials) +{ + static const wchar_t *extensions[] = { + L"material", + L"shader", + nullptr + }; + for (size_t i = 0; i < m_fileList.size(); ++i) { + if (isInList(extensions, m_fileList[i].GetExtension())) + materials.push_back(m_fileList[i]); + } +} + +void InspectorControlView::filterMatDatas(std::vector<Q3DStudio::CFilePath> &matDatas) +{ + static const wchar_t *extensions[] = { + L"materialdef", + nullptr + }; + for (size_t i = 0; i < m_fileList.size(); ++i) { + if (isInList(extensions, m_fileList[i].GetExtension())) + matDatas.push_back(m_fileList[i]); + } +} + +void InspectorControlView::OnNewPresentation() +{ + auto core = g_StudioApp.GetCore(); + auto sp = core->GetDoc()->GetStudioSystem()->GetFullSystem()->GetSignalProvider(); + auto assetGraph = core->GetDoc()->GetAssetGraph(); + + m_connections.push_back(core->GetDispatch()->ConnectSelectionChange( + std::bind(&InspectorControlView::OnSelectionSet, this, std::placeholders::_1))); + m_connections.push_back(g_StudioApp.getDirectoryWatchingSystem().AddDirectory( + g_StudioApp.GetCore()->getProjectFile().getProjectPath(), + std::bind(&InspectorControlView::onFilesChanged, this, std::placeholders::_1))); + m_connections.push_back(sp->ConnectInstancePropertyValue( + std::bind(&InspectorControlView::onPropertyChanged, this, std::placeholders::_1, + std::placeholders::_2))); + m_connections.push_back(assetGraph->ConnectChildAdded( + std::bind(&InspectorControlView::onChildAdded, this, std::placeholders::_2))); + m_connections.push_back(assetGraph->ConnectChildRemoved( + std::bind(&InspectorControlView::onChildRemoved, this))); +} + +void InspectorControlView::OnClosingPresentation() +{ + // Image chooser model needs to be deleted, because otherwise it'll try to update the model for + // the new presentation before subpresentations are resolved, corrupting the model. + // The model also has a connection to project file which needs to refreshed if project changes. + delete m_imageChooserView; + m_fileList.clear(); + m_connections.clear(); +} + +void InspectorControlView::onFilesChanged( + const Q3DStudio::TFileModificationList &inFileModificationList) +{ + static const wchar_t *materialExtensions[] = { + L"material", L"shader", L"materialdef", + nullptr + }; + static const wchar_t *fontExtensions[] = { + L"ttf", L"otf", + nullptr + }; + + bool updateFonts = false; + for (size_t idx = 0, end = inFileModificationList.size(); idx < end; ++idx) { + const Q3DStudio::SFileModificationRecord &record(inFileModificationList[idx]); + if (record.m_FileInfo.IsFile()) { + if (isInList(materialExtensions, record.m_File.GetExtension())) { + Q3DStudio::CFilePath relativePath( + Q3DStudio::CFilePath::GetRelativePathFromBase( + g_StudioApp.GetCore()->GetDoc()->GetDocumentDirectory(), + record.m_File)); + + if (record.m_ModificationType == Q3DStudio::FileModificationType::Created) + qt3dsdm::binary_sort_insert_unique(m_fileList, relativePath); + else if (record.m_ModificationType == Q3DStudio::FileModificationType::Destroyed) + qt3dsdm::binary_sort_erase(m_fileList, relativePath); + } else if (isInList(fontExtensions, record.m_File.GetExtension())) { + if (record.m_ModificationType == Q3DStudio::FileModificationType::Created + || record.m_ModificationType == Q3DStudio::FileModificationType::Destroyed) { + updateFonts = true; + } + } else if (record.m_ModificationType == Q3DStudio::FileModificationType::Modified + && record.m_File.toQString() + == g_StudioApp.GetCore()->getProjectFile().getProjectFilePath()) { + g_StudioApp.GetCore()->getProjectFile().loadSubpresentationsAndDatainputs( + g_StudioApp.m_subpresentations, g_StudioApp.m_dataInputDialogItems); + m_inspectorControlModel->refreshRenderables(); + } + } + } + std::vector<Q3DStudio::CFilePath> materials; + filterMaterials(materials); + m_inspectorControlModel->setMaterials(materials); + + std::vector<Q3DStudio::CFilePath> matDatas; + filterMatDatas(matDatas); + m_inspectorControlModel->setMatDatas(matDatas); + + if (updateFonts) { + // The fonts list in doc is not necessarily yet updated, so do update async + QTimer::singleShot(0, this, [this]() { + m_inspectorControlModel->updateFontValues(nullptr); + }); + } +} + +InspectorControlView::~InspectorControlView() +{ + g_StudioApp.GetCore()->GetDispatch()->RemovePresentationChangeListener(this); + delete m_dataInputChooserView; +} + +QSize InspectorControlView::sizeHint() const +{ + return m_preferredSize; +} + +void InspectorControlView::mousePressEvent(QMouseEvent *event) +{ + g_StudioApp.setLastActiveView(this); + QQuickWidget::mousePressEvent(event); +} + +void InspectorControlView::initialize() +{ + CStudioPreferences::setQmlContextProperties(rootContext()); + rootContext()->setContextProperty(QStringLiteral("_parentView"), this); + rootContext()->setContextProperty(QStringLiteral("_inspectorModel"), m_inspectorControlModel); + rootContext()->setContextProperty(QStringLiteral("_variantsGroupModel"), m_variantsGroupModel); + rootContext()->setContextProperty(QStringLiteral("_resDir"), StudioUtils::resourceImageUrl()); + rootContext()->setContextProperty(QStringLiteral("_tabOrderHandler"), tabOrderHandler()); + rootContext()->setContextProperty(QStringLiteral("_mouseHelper"), &m_mouseHelper); + rootContext()->setContextProperty(QStringLiteral("_utils"), &m_qmlUtils); + m_mouseHelper.setWidget(this); + + qmlRegisterUncreatableType<qt3dsdm::DataModelDataType>( + "Qt3DStudio", 1, 0, "DataModelDataType", + QStringLiteral("DataModelDataType is an enum container")); + qmlRegisterUncreatableType<qt3dsdm::AdditionalMetaDataType>( + "Qt3DStudio", 1, 0, "AdditionalMetaDataType", + QStringLiteral("AdditionalMetaDataType is an enum container")); + engine()->addImportPath(StudioUtils::qmlImportPath()); + setSource(QUrl(QStringLiteral("qrc:/Palettes/Inspector/InspectorControlView.qml"))); +} + +QAbstractItemModel *InspectorControlView::inspectorControlModel() const +{ + return m_inspectorControlModel; +} + +QString InspectorControlView::titleText() const +{ + if (m_inspectableBase) { + Q3DStudio::CString theName = m_inspectableBase->getName(); + if (theName == L"PathAnchorPoint") + return tr("Anchor Point"); + else + return theName.toQString(); + } + return tr("No Object Selected"); +} + +bool InspectorControlView::isRefMaterial(int instance) const +{ + auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge(); + return bridge->IsReferencedMaterialInstance(instance); +} + +QString InspectorControlView::noneString() const +{ + return ChooserModelBase::noneString(); +} + +bool InspectorControlView::canLinkProperty(int instance, int handle) const +{ + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + const auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge(); + + if (bridge->isInsideMaterialContainer(instance)) + return false; + + if (bridge->IsMaterialBaseInstance(instance)) // all material types are unlinkable + return false; + + if (handle == bridge->GetSceneAsset().m_Eyeball.m_Property) // eyeball is unlinkable + return false; + + return doc->GetDocumentReader().CanPropertyBeLinked(instance, handle); +} + +bool InspectorControlView::canOpenInInspector(int instance, int handle) const +{ + const auto doc = g_StudioApp.GetCore()->GetDoc(); + qt3dsdm::SValue value; + doc->GetPropertySystem()->GetInstancePropertyValue(instance, handle, value); + if (!value.empty() && value.getType() == qt3dsdm::DataModelDataType::Long4) { + qt3dsdm::SLong4 guid = qt3dsdm::get<qt3dsdm::SLong4>(value); + return guid.Valid(); + } + return false; +} + +void InspectorControlView::openInInspector() +{ + const auto doc = g_StudioApp.GetCore()->GetDoc(); + const auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge(); + qt3dsdm::SValue value; + doc->GetPropertySystem()->GetInstancePropertyValue(m_contextMenuInstance, m_contextMenuHandle, + value); + qt3dsdm::SLong4 guid = qt3dsdm::get<qt3dsdm::SLong4>(value); + const auto instance = bridge->GetInstanceByGUID(guid); + doc->SelectDataModelObject(instance); +} + +void InspectorControlView::onPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance, + qt3dsdm::Qt3DSDMPropertyHandle inProperty) +{ + m_inspectorControlModel->notifyPropertyChanged(inInstance, inProperty); + + auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge(); + // titleChanged implies icon change too, but that will only occur if inspectable type changes, + // which will invalidate the inspectable anyway, so in reality we are only interested in name + // property here + if (inProperty == bridge->GetNameProperty() && m_inspectableBase + && m_inspectableBase->isValid()) { + Q_EMIT titleChanged(); + } +} + +void InspectorControlView::onChildAdded(int inChild) +{ + // Changes to asset graph invalidate the object browser model, so close it if it is open + if (m_activeBrowser.isActive() && m_activeBrowser.m_browser == m_objectReferenceView) + m_activeBrowser.clear(); + + const auto doc = g_StudioApp.GetCore()->GetDoc(); + const auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge(); + if (bridge->IsCustomMaterialInstance(inChild)) { + QVector<qt3dsdm::Qt3DSDMInstanceHandle> refMats; + doc->getSceneReferencedMaterials(doc->GetSceneInstance(), refMats); + for (auto &refMat : qAsConst(refMats)) { + if ((int)bridge->getMaterialReference(refMat) == inChild) + g_StudioApp.GetCore()->GetDispatch()->FireImmediateRefreshInstance(refMat); + } + } +} + +void InspectorControlView::onChildRemoved() +{ + // Changes to asset graph invalidate the object browser model, so close it if it is open + if (m_activeBrowser.isActive() && m_activeBrowser.m_browser == m_objectReferenceView) + m_activeBrowser.clear(); +} + +QColor InspectorControlView::titleColor(int instance, int handle) const +{ + QColor ret = CStudioPreferences::textColor(); + if (instance != 0) { + if (g_StudioApp.GetCore()->GetDoc()->GetDocumentReader() + .IsPropertyLinked(instance, handle)) { + ret = CStudioPreferences::masterColor(); + } + } + return ret; +} + +QString InspectorControlView::titleIcon() const +{ + if (m_inspectableBase) + return CStudioObjectTypes::GetNormalIconName(m_inspectableBase->getObjectType()); + return {}; +} + +bool InspectorControlView::isEditable(int handle) const +{ + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + if (doc->GetStudioSystem()->GetSlideSystem()->IsMasterSlide(doc->GetActiveSlide()) + && doc->GetStudioSystem()->GetPropertySystem()->GetName(handle) == L"eyeball") { + return false; + } + return true; +} + +void InspectorControlView::OnSelectionSet(Q3DStudio::SSelectedValue selectable) +{ + CInspectableBase *inspectable = createInspectableFromSelectable(selectable); + + if (inspectable && !inspectable->isValid()) + inspectable = nullptr; + + setInspectable(inspectable); +} + +CInspectableBase *InspectorControlView::createInspectableFromSelectable( + Q3DStudio::SSelectedValue selectable) +{ + using namespace Q3DStudio; + + CInspectableBase *inspectableBase = nullptr; + if (!selectable.empty()) { + switch (selectable.getType()) { + case SelectedValueTypes::Slide: { + // TODO: seems like slides are not directly selectable, this should be removed. + auto selectableInstance = selectable.getData<SSlideInstanceWrapper>().m_Instance; + inspectableBase = new Qt3DSDMInspectable(selectableInstance); + } break; + + case SelectedValueTypes::MultipleInstances: + case SelectedValueTypes::Instance: { + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + // Note: Inspector doesn't support multiple selection + qt3dsdm::TInstanceHandleList selectedsInstances = selectable.GetSelectedInstances(); + if (selectedsInstances.size() == 1) { + Qt3DSDMInstanceHandle selectedInstance = selectedsInstances[0]; + if (doc->GetDocumentReader().IsInstance(selectedInstance)) { + auto *bridge = doc->GetStudioSystem()->GetClientDataModelBridge(); + qt3dsdm::Qt3DSDMSlideHandle activeSlide = doc->GetActiveSlide(); + + // Scene or Component (when being edited) + if (selectedInstance == bridge->GetOwningComponentInstance(activeSlide)) { + Qt3DSDMInstanceHandle activeSlideInstance = doc->GetStudioSystem() + ->GetSlideSystem()->GetSlideInstance(activeSlide); + inspectableBase = new Qt3DSDMInspectable(selectedInstance, + activeSlideInstance); + } else if (bridge->IsMaterialBaseInstance(selectedInstance)) { + inspectableBase = new Qt3DSDMMaterialInspectable(selectedInstance); + } else { + inspectableBase = new Qt3DSDMInspectable(selectedInstance); + } + } + } + } break; + + case SelectedValueTypes::Guide: { + qt3dsdm::Qt3DSDMGuideHandle guide = selectable.getData<qt3dsdm::Qt3DSDMGuideHandle>(); + inspectableBase = new GuideInspectable(guide); + } break; + + default: + break; // Ignore slide insertion and unknown selectable types + }; + } + + return inspectableBase; +} + +void InspectorControlView::setInspectable(CInspectableBase *inInspectable) +{ + if (m_inspectableBase != inInspectable) { + m_activeBrowser.clear(); + m_inspectableBase = inInspectable; + m_inspectorControlModel->setInspectable(inInspectable); + + Q_EMIT titleChanged(); + + m_variantsGroupModel->refresh(); + } +} + +void InspectorControlView::showContextMenu(int x, int y, int handle, int instance) +{ + m_contextMenuInstance = instance; + m_contextMenuHandle = handle; + + QMenu theContextMenu; + + auto doc = g_StudioApp.GetCore()->GetDoc(); + + if (canOpenInInspector(instance, handle)) { + auto action = theContextMenu.addAction(tr("Open in Inspector")); + connect(action, &QAction::triggered, this, &InspectorControlView::openInInspector); + } + + if (canLinkProperty(instance, handle)) { + bool isLinked = doc->GetDocumentReader().IsPropertyLinked(instance, handle); + auto action = theContextMenu.addAction(isLinked ? tr("Unlink Property from Master Slide") + : tr("Link Property from Master Slide")); + connect(action, &QAction::triggered, this, &InspectorControlView::toggleMasterLink); + } else { + auto action = theContextMenu.addAction(tr("Unable to link from Master Slide")); + action->setEnabled(false); + } + + theContextMenu.exec(mapToGlobal({x, y})); + m_contextMenuInstance = 0; + m_contextMenuHandle = 0; +} + +// context menu for the variants tags +void InspectorControlView::showTagContextMenu(int x, int y, const QString &group, + const QString &tag) +{ + QMenu theContextMenu; + + auto actionRename = theContextMenu.addAction(QObject::tr("Rename Tag")); + connect(actionRename, &QAction::triggered, this, [&]() { + VariantTagDialog dlg(VariantTagDialog::RenameTag, group, tag); + if (dlg.exec() == QDialog::Accepted) { + g_StudioApp.GetCore()->getProjectFile().renameVariantTag(group, dlg.getNames().first, + dlg.getNames().second); + m_variantsGroupModel->refresh(); + + // refresh slide view so the tooltip show the renamed tag immediately, no need to + // refresh the timeline because each row gets the tags directly from the property which + // is always up to date. + g_StudioApp.m_pMainWnd->getSlideView()->refreshVariants(); + } + }); + + auto actionDelete = theContextMenu.addAction(QObject::tr("Delete Tag")); + connect(actionDelete, &QAction::triggered, this, [&]() { + g_StudioApp.GetCore()->getProjectFile().deleteVariantTag(group, tag); + g_StudioApp.m_pMainWnd->getTimelineWidget()->refreshVariants(); + g_StudioApp.m_pMainWnd->getSlideView()->refreshVariants(); + m_variantsGroupModel->refresh(); + if (g_StudioApp.GetCore()->getProjectFile().variantsDef()[group].m_tags.size() == 0) + g_StudioApp.m_pMainWnd->updateActionFilterEnableState(); + }); + + theContextMenu.exec(mapToGlobal({x, y})); +} + +// context menu for the variants groups +void InspectorControlView::showGroupContextMenu(int x, int y, const QString &group) +{ + QMenu theContextMenu; + + ProjectFile &projectFile = g_StudioApp.GetCore()->getProjectFile(); + + auto actionRename = theContextMenu.addAction(QObject::tr("Rename Group")); + connect(actionRename, &QAction::triggered, this, [&]() { + VariantTagDialog dlg(VariantTagDialog::RenameGroup, {}, group); + if (dlg.exec() == QDialog::Accepted) { + projectFile.renameVariantGroup(dlg.getNames().first, dlg.getNames().second); + g_StudioApp.m_pMainWnd->getTimelineWidget()->refreshVariants(); + m_variantsGroupModel->refresh(); + + // refresh slide view so the tooltip show the renamed group immediately, no need to + // refresh the timeline because each row gets the tags directly from the property which + // is always up to date. + g_StudioApp.m_pMainWnd->getSlideView()->refreshVariants(); + } + }); + + auto actionColor = theContextMenu.addAction(QObject::tr("Change Group Color")); + connect(actionColor, &QAction::triggered, this, [&]() { + const auto variantsDef = projectFile.variantsDef(); + QColor newColor = this->showColorDialog(variantsDef[group].m_color); + projectFile.changeVariantGroupColor(group, newColor.name()); + // no need to refresh variants in the timeline widget as it references the group color in + // the project file m_variants, and a redraw is triggered upon color selection dialog close. + g_StudioApp.m_pMainWnd->getSlideView()->refreshVariants(); + m_variantsGroupModel->refresh(); + }); + + auto actionDelete = theContextMenu.addAction(QObject::tr("Delete Group")); + connect(actionDelete, &QAction::triggered, this, [&]() { + projectFile.deleteVariantGroup(group); + g_StudioApp.m_pMainWnd->getTimelineWidget()->refreshVariants(); + g_StudioApp.m_pMainWnd->getSlideView()->refreshVariants(); + g_StudioApp.m_pMainWnd->updateActionFilterEnableState(); + m_variantsGroupModel->refresh(); + }); + + theContextMenu.exec(mapToGlobal({x, y})); +} + +void InspectorControlView::toggleMasterLink() +{ + Q3DStudio::ScopedDocumentEditor editor(*g_StudioApp.GetCore()->GetDoc(), + QObject::tr("Link Property"), __FILE__, __LINE__); + bool wasLinked = editor->IsPropertyLinked(m_contextMenuInstance, m_contextMenuHandle); + + if (wasLinked) + editor->UnlinkProperty(m_contextMenuInstance, m_contextMenuHandle); + else + editor->LinkProperty(m_contextMenuInstance, m_contextMenuHandle); +} + +void InspectorControlView::setPropertyValueFromFilename(long instance, int handle, + const QString &name) +{ + if (m_inspectorControlModel) { + QString value; + if (name != ChooserModelBase::noneString()) { + // Relativize the path to the presentation + const auto doc = g_StudioApp.GetCore()->GetDoc(); + const QDir documentDir(doc->GetDocumentDirectory()); + QString relativeName = documentDir.relativeFilePath(name); + value = relativeName; + } + m_inspectorControlModel->setPropertyValue(instance, handle, value); + } +} + +QObject *InspectorControlView::showImageChooser(int handle, int instance, const QPoint &point) +{ + if (!m_imageChooserView) { + m_imageChooserView = new ImageChooserView(this); + connect(m_imageChooserView, &ImageChooserView::imageSelected, this, + [this] (int handle, int instance, const QString &imageName) { + // To avoid duplicate undo points when setting image property we can't rely + // on regular property duplication checks, as images are not directly stored as + // their paths. Also, there is no check for duplication on renderables. + if (m_imageChooserView->currentDataModelPath() != imageName) { + QString renderableId = g_StudioApp.getRenderableId(imageName); + if (renderableId.isEmpty()) { + setPropertyValueFromFilename(instance, handle, imageName); + } else { + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*g_StudioApp.GetCore()->GetDoc(), + QObject::tr("Set Property")) + ->setInstanceImagePropertyValue( + instance, handle, Q3DStudio::CString::fromQString(renderableId)); + if (m_inspectorControlModel) + m_inspectorControlModel->saveIfMaterial(instance); + } + } + }); + } + + m_imageChooserView->setHandle(handle); + m_imageChooserView->setInstance(instance); + + CDialogs::showWidgetBrowser(this, m_imageChooserView, point); + m_activeBrowser.setData(m_imageChooserView, handle, instance); + + return m_imageChooserView; +} + +QObject *InspectorControlView::showFilesChooser(int handle, int instance, const QPoint &point) +{ + if (!m_fileChooserView) { + m_fileChooserView = new FileChooserView(this); + connect(m_fileChooserView, &FileChooserView::fileSelected, this, + [this] (int handle, int instance, const QString &fileName) { + setPropertyValueFromFilename(instance, handle, fileName); + }); + } + + m_fileChooserView->setHandle(handle); + m_fileChooserView->setInstance(instance); + + CDialogs::showWidgetBrowser(this, m_fileChooserView, point); + m_activeBrowser.setData(m_fileChooserView, handle, instance); + + return m_fileChooserView; +} + +QObject *InspectorControlView::showMeshChooser(int handle, int instance, const QPoint &point) +{ + m_meshChooserView->setHandle(handle); + m_meshChooserView->setInstance(instance); + + m_activeBrowser.setData(m_meshChooserView, handle, instance); + int numPrimitives = BasicObjectsModel::BasicMeshesModel().count(); + bool combo = numPrimitives == m_meshChooserView->numMeshes(); // make a combobox size popup + int comboH = qMin(m_meshChooserView->numMeshes(), 15) // max popup height: 15 items + * CStudioPreferences::controlBaseHeight(); + + CDialogs::showWidgetBrowser(this, m_meshChooserView, point, + CDialogs::WidgetBrowserAlign::ComboBox, + combo ? QSize(CStudioPreferences::valueWidth(), comboH) : QSize()); + + return m_meshChooserView; +} + +QObject *InspectorControlView::showTextureChooser(int handle, int instance, const QPoint &point) +{ + if (!m_textureChooserView) { + m_textureChooserView = new TextureChooserView(this); + connect(m_textureChooserView, &TextureChooserView::textureSelected, this, + [this] (int handle, int instance, const QString &fileName) { + if (m_textureChooserView->currentDataModelPath() != fileName) { + QString renderableId = g_StudioApp.getRenderableId(fileName); + if (renderableId.isEmpty()) + setPropertyValueFromFilename(instance, handle, fileName); + else + m_inspectorControlModel->setPropertyValue(instance, handle, renderableId); + } + }); + } + + m_textureChooserView->setHandle(handle); + m_textureChooserView->setInstance(instance); + + CDialogs::showWidgetBrowser(this, m_textureChooserView, point); + m_activeBrowser.setData(m_textureChooserView, handle, instance); + + return m_textureChooserView; +} + +QObject *InspectorControlView::showObjectReference(int handle, int instance, const QPoint &point) +{ + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + // different base handle than current active root instance means that we have entered/exited + // component after the reference model had been created, and we need to recreate it + if (!m_objectReferenceModel + || (m_objectReferenceModel->baseHandle() != doc->GetActiveRootInstance())) { + if (m_objectReferenceModel) + delete m_objectReferenceModel; + m_objectReferenceModel = new ObjectListModel(g_StudioApp.GetCore(), + doc->GetActiveRootInstance(), this, true); + } + if (!m_objectReferenceView) + m_objectReferenceView = new ObjectBrowserView(this); + m_objectReferenceView->setModel(m_objectReferenceModel); + + if (doc->GetStudioSystem()->GetClientDataModelBridge() + ->GetObjectType(instance) == OBJTYPE_ALIAS) { + QVector<EStudioObjectType> exclude; + exclude << OBJTYPE_ALIAS << OBJTYPE_BEHAVIOR << OBJTYPE_CUSTOMMATERIAL + << OBJTYPE_EFFECT << OBJTYPE_GUIDE << OBJTYPE_IMAGE << OBJTYPE_LAYER + << OBJTYPE_MATERIAL << OBJTYPE_REFERENCEDMATERIAL << OBJTYPE_SCENE; + m_objectReferenceModel->excludeObjectTypes(exclude); + } else { + m_objectReferenceModel->excludeObjectTypes(QVector<EStudioObjectType>()); + } + + disconnect(m_objectReferenceView, nullptr, nullptr, nullptr); + + IObjectReferenceHelper *objRefHelper = doc->GetDataModelObjectReferenceHelper(); + if (objRefHelper) { + qt3dsdm::SValue value = m_inspectorControlModel->currentPropertyValue(instance, handle); + qt3dsdm::Qt3DSDMInstanceHandle refInstance = objRefHelper->Resolve(value, instance); + m_objectReferenceView->selectAndExpand(refInstance, instance); + } + + CDialogs::showWidgetBrowser(this, m_objectReferenceView, point); + m_activeBrowser.setData(m_objectReferenceView, handle, instance); + + connect(m_objectReferenceView, &ObjectBrowserView::selectionChanged, + this, [this, doc, handle, instance] { + auto selectedItem = m_objectReferenceView->selectedHandle(); + qt3dsdm::SObjectRefType objRef = doc->GetDataModelObjectReferenceHelper()->GetAssetRefValue( + selectedItem, instance, + (CRelativePathTools::EPathType)(m_objectReferenceView->pathType())); + qt3dsdm::SValue value = m_inspectorControlModel->currentPropertyValue(instance, handle); + if (!(value.getData<qt3dsdm::SObjectRefType>() == objRef)) { + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, QObject::tr("Set Property")) + ->SetInstancePropertyValue(instance, handle, objRef); + } + }); + + return m_objectReferenceView; +} + +QObject *InspectorControlView::showMaterialReference(int handle, int instance, const QPoint &point) +{ + // create the list widget + if (!m_matRefListWidget) + m_matRefListWidget = new MaterialRefView(this); + + disconnect(m_matRefListWidget, &QListWidget::itemClicked, nullptr, nullptr); + disconnect(m_matRefListWidget, &QListWidget::itemDoubleClicked, nullptr, nullptr); + + const QSize popupSize = m_matRefListWidget->refreshMaterials(instance, handle); + CDialogs::showWidgetBrowser(this, m_matRefListWidget, point, + CDialogs::WidgetBrowserAlign::ComboBox, popupSize); + m_activeBrowser.setData(m_matRefListWidget, handle, instance); + + connect(m_matRefListWidget, &QListWidget::itemClicked, this, + [instance, handle](QListWidgetItem *item) { + auto selectedInstance = item->data(Qt::UserRole).toInt(); + if (selectedInstance > 0) { + qt3dsdm::SValue value; + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem(); + propertySystem->GetInstancePropertyValue(instance, handle, value); + auto refInstance = doc->GetDataModelObjectReferenceHelper()->Resolve(value, instance); + if (selectedInstance != refInstance) { + auto objRef = doc->GetDataModelObjectReferenceHelper()->GetAssetRefValue( + selectedInstance, instance, CRelativePathTools::EPATHTYPE_GUID); + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, QObject::tr("Set Property")) + ->SetInstancePropertyValue(instance, handle, objRef); + } + } + }); + connect(m_matRefListWidget, &QListWidget::itemDoubleClicked, this, [this]() { + m_matRefListWidget->hide(); + }); + + return m_matRefListWidget; +} + +void InspectorControlView::showDataInputChooser(int handle, int instance, const QPoint &point) +{ + if (!m_dataInputChooserView) { + const QVector<EDataType> acceptedTypes; + m_dataInputChooserView = new DataInputSelectView(acceptedTypes, this); + connect(m_dataInputChooserView, &DataInputSelectView::dataInputChanged, this, + [this](int handle, int instance, const QString &controllerName) { + bool controlled = + controllerName == m_dataInputChooserView->getNoneString() ? false : true; + m_inspectorControlModel + ->setPropertyControllerInstance( + instance, handle, + Q3DStudio::CString::fromQString(controllerName), controlled); + m_inspectorControlModel->setPropertyControlled(instance, handle); + }); + } + const auto propertySystem = + g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetPropertySystem(); + const qt3dsdm::DataModelDataType::Value dataType + = propertySystem->GetDataType(handle); + // only add datainputs with matching type for this property + QVector<QPair<QString, int>> dataInputList; + + for (auto &it : qAsConst(g_StudioApp.m_dataInputDialogItems)) + dataInputList.append({it->name, it->type}); + + m_dataInputChooserView->setMatchingTypes(CDataInputDlg::getAcceptedTypes(dataType)); + m_dataInputChooserView-> + setData(dataInputList, + m_inspectorControlModel->currentControllerValue(instance, handle), + handle, instance); + CDialogs::showWidgetBrowser(this, m_dataInputChooserView, point, + CDialogs::WidgetBrowserAlign::ToolButton); + m_activeBrowser.setData(m_dataInputChooserView, handle, instance); +} + +QColor InspectorControlView::showColorDialog(const QColor &color, int instance, int handle) +{ + bool showAlpha = false; + if (instance && handle) { + auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem() + ->GetClientDataModelBridge(); + showAlpha = bridge->getBGColorProperty(instance).GetHandleValue() == handle + || bridge->getTextColorProperty(instance).GetHandleValue() == handle; + } + + m_currentColor = color; + CDialogs *dialogs = g_StudioApp.GetDialogs(); + connect(dialogs, &CDialogs::onColorChanged, + this, &InspectorControlView::dialogCurrentColorChanged); + QColor currentColor = dialogs->displayColorDialog(color, showAlpha); + disconnect(dialogs, &CDialogs::onColorChanged, + this, &InspectorControlView::dialogCurrentColorChanged); + return currentColor; +} + +bool InspectorControlView::toolTipsEnabled() +{ + return CStudioPreferences::ShouldShowTooltips(); +} + +// Converts a path that is relative to the current presentation to be relative to +// the current project root +QString InspectorControlView::convertPathToProjectRoot(const QString &presentationPath) +{ + QDir projDir(g_StudioApp.GetCore()->getProjectFile().getProjectPath()); + QFileInfo presentationFile(g_StudioApp.GetCore()->GetDoc()->GetDocumentPath()); + QDir presentationDir(presentationFile.absolutePath()); + QString absPath = presentationDir.absoluteFilePath(presentationPath); + + return projDir.relativeFilePath(absPath); +} + +void InspectorControlView::OnBeginDataModelNotifications() +{ +} + +void InspectorControlView::OnEndDataModelNotifications() +{ + CInspectableBase *inspectable = m_inspectorControlModel->inspectable(); + if (inspectable && !inspectable->isValid()) + OnSelectionSet(Q3DStudio::SSelectedValue()); + m_inspectorControlModel->refresh(); + + if (m_activeBrowser.isActive()) { + // Check if the instance/handle pair still has an active UI control. If not, close browser. + if (!m_inspectorControlModel->hasInstanceProperty( + m_activeBrowser.m_instance, m_activeBrowser.m_handle)) { + m_activeBrowser.clear(); + } else { + // Update browser selection + if (m_activeBrowser.m_browser == m_imageChooserView) { + m_imageChooserView->updateSelection(); + } else if (m_activeBrowser.m_browser == m_fileChooserView) { + m_fileChooserView->updateSelection(); + } else if (m_activeBrowser.m_browser == m_meshChooserView) { + m_meshChooserView->updateSelection(); + } else if (m_activeBrowser.m_browser == m_textureChooserView) { + m_textureChooserView->updateSelection(); + } else if (m_activeBrowser.m_browser == m_objectReferenceView) { + IObjectReferenceHelper *objRefHelper + = g_StudioApp.GetCore()->GetDoc()->GetDataModelObjectReferenceHelper(); + if (objRefHelper) { + qt3dsdm::SValue value = m_inspectorControlModel->currentPropertyValue( + m_activeBrowser.m_instance, m_activeBrowser.m_handle); + qt3dsdm::Qt3DSDMInstanceHandle refInstance + = objRefHelper->Resolve(value, m_activeBrowser.m_instance); + m_objectReferenceView->selectAndExpand(refInstance, m_activeBrowser.m_instance); + } + } else if (m_activeBrowser.m_browser == m_matRefListWidget) { + m_matRefListWidget->updateSelection(); + } else if (m_activeBrowser.m_browser == m_dataInputChooserView) { + m_dataInputChooserView->setCurrentController( + m_inspectorControlModel->currentControllerValue( + m_dataInputChooserView->instance(), + m_dataInputChooserView->handle())); + } + } + } +} + +void InspectorControlView::OnImmediateRefreshInstanceSingle(qt3dsdm::Qt3DSDMInstanceHandle inInstance) +{ + m_inspectorControlModel->refresh(); +} + +void InspectorControlView::OnImmediateRefreshInstanceMultiple(qt3dsdm::Qt3DSDMInstanceHandle *inInstance, long inInstanceCount) +{ + m_inspectorControlModel->refresh(); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.h new file mode 100644 index 00000000..75d07d73 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.h @@ -0,0 +1,186 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef INSPECTORCONTROLVIEW_H +#define INSPECTORCONTROLVIEW_H + +#include <QtQuickWidgets/qquickwidget.h> +#include <QtCore/qpointer.h> +#include "DispatchListeners.h" +#include "Dispatch.h" +#include "Qt3DSFileTools.h" +#include "TabOrderHandler.h" +#include "MouseHelper.h" +#include "QmlUtils.h" +#include "DataInputSelectView.h" + +class InspectorControlModel; +class VariantsGroupModel; +class CInspectableBase; +class ImageChooserView; +class DataInputSelectView; +class ImageChooserModel; +class MeshChooserView; +class ObjectBrowserView; +class ObjectListModel; +class FileChooserView; +class TextureChooserView; +class MaterialRefView; + +QT_FORWARD_DECLARE_CLASS(QAbstractItemModel) + +class InspectorControlView : public QQuickWidget, + public CPresentationChangeListener, + public IDataModelListener, + public TabNavigable +{ + Q_OBJECT + Q_PROPERTY(QString titleText READ titleText NOTIFY titleChanged FINAL) + Q_PROPERTY(QString titleIcon READ titleIcon NOTIFY titleChanged FINAL) + +public: + explicit InspectorControlView(const QSize &preferredSize, QWidget *parent = nullptr); + ~InspectorControlView() override; + + void OnSelectionSet(Q3DStudio::SSelectedValue inValue); + QAbstractItemModel *inspectorControlModel() const; + + QString titleText() const; + QString titleIcon() const; + VariantsGroupModel *variantsModel() const { return m_variantsGroupModel; } + + Q_INVOKABLE QColor titleColor(int instance = 0, int handle = 0) const; + Q_INVOKABLE QColor showColorDialog(const QColor &color, int instance = 0, int handle = 0); + Q_INVOKABLE void showContextMenu(int x, int y, int handle, int instance); + Q_INVOKABLE void showTagContextMenu(int x, int y, const QString &group, const QString &tag); + Q_INVOKABLE void showDataInputChooser(int handle, int instance, const QPoint &point); + Q_INVOKABLE void showGroupContextMenu(int x, int y, const QString &group); + Q_INVOKABLE QObject *showImageChooser(int handle, int instance, const QPoint &point); + Q_INVOKABLE QObject *showFilesChooser(int handle, int instance, const QPoint &point); + Q_INVOKABLE QObject *showMeshChooser(int handle, int instance, const QPoint &point); + Q_INVOKABLE QObject *showObjectReference(int handle, int instance, const QPoint &point); + Q_INVOKABLE QObject *showMaterialReference(int handle, int instance, const QPoint &point); + Q_INVOKABLE QObject *showTextureChooser(int handle, int instance, const QPoint &point); + Q_INVOKABLE bool toolTipsEnabled(); + Q_INVOKABLE bool isRefMaterial(int instance) const; + Q_INVOKABLE bool isEditable(int handle) const; + Q_INVOKABLE QString convertPathToProjectRoot(const QString &presentationPath); + Q_INVOKABLE QString noneString() const; + + // IDataModelListener + void OnBeginDataModelNotifications() override; + void OnEndDataModelNotifications() override; + void OnImmediateRefreshInstanceSingle(qt3dsdm::Qt3DSDMInstanceHandle inInstance) override; + void OnImmediateRefreshInstanceMultiple(qt3dsdm::Qt3DSDMInstanceHandle *inInstance, + long inInstanceCount) override; + +Q_SIGNALS: + void titleChanged(); + void controlsChanged(); + void imageSelected(const QString &name); + void dialogCurrentColorChanged(const QColor &newColor); + +public Q_SLOTS: + void toggleMasterLink(); + +protected: + QSize sizeHint() const override; + void mousePressEvent(QMouseEvent *event) override; + +private: + void setInspectable(CInspectableBase *inInspectable); + void initialize(); + void onFilesChanged(const Q3DStudio::TFileModificationList &inFileModificationList); + void OnNewPresentation() override; + void OnClosingPresentation() override; + void filterMaterials(std::vector<Q3DStudio::CFilePath> &materials); + void filterMatDatas(std::vector<Q3DStudio::CFilePath> &matDatas); + void setPropertyValueFromFilename(long instance, int handle, const QString &name); + CInspectableBase *createInspectableFromSelectable(Q3DStudio::SSelectedValue selectable); + bool canLinkProperty(int instance, int handle) const; + bool canOpenInInspector(int instance, int handle) const; + void openInInspector(); + void onPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance, + qt3dsdm::Qt3DSDMPropertyHandle inProperty); + void onChildAdded(int inChild); + void onChildRemoved(); + + std::vector<std::shared_ptr<qt3dsdm::ISignalConnection>> m_connections; + QColor m_backgroundColor; + VariantsGroupModel *m_variantsGroupModel = nullptr; + InspectorControlModel *m_inspectorControlModel = nullptr; + CInspectableBase *m_inspectableBase = nullptr; + QPointer<ImageChooserView> m_imageChooserView; + QPointer<MeshChooserView> m_meshChooserView; + QPointer<FileChooserView> m_fileChooserView; + QPointer<TextureChooserView> m_textureChooserView; + QPointer<ObjectBrowserView> m_objectReferenceView; + QPointer<MaterialRefView> m_matRefListWidget; + QPointer<ObjectListModel> m_objectReferenceModel; + QPointer<DataInputSelectView> m_dataInputChooserView; + std::vector<Q3DStudio::CFilePath> m_fileList; + MouseHelper m_mouseHelper; + QmlUtils m_qmlUtils; + + int m_contextMenuInstance = 0; + int m_contextMenuHandle = 0; + + QSize m_preferredSize; + QColor m_currentColor; + + class ActiveBrowserData + { + public: + void setData(QWidget *browser, int handle, int instance) + { + m_browser = browser; + m_handle = handle; + m_instance = instance; + } + void clear() + { + if (isActive()) + m_browser->close(); + m_browser.clear(); + m_handle = -1; + m_instance = -1; + } + bool isActive() const + { + return !m_browser.isNull() && m_browser->isVisible(); + } + + QPointer<QWidget> m_browser = nullptr; + int m_handle = -1; + int m_instance = -1; + }; + + ActiveBrowserData m_activeBrowser; +}; + +#endif // INSPECTORCONTROLVIEW_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.qml new file mode 100644 index 00000000..c291de7b --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorControlView.qml @@ -0,0 +1,1289 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ +import QtQuick 2.8 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Extras 1.4 + +import Qt3DStudio 1.0 +import "../controls" +import "../Action" + +Rectangle { + id: root + color: _backgroundColor + + Connections { + target: _inspectorModel + onModelAboutToBeReset: { + _tabOrderHandler.clear(); + inspectorToolbar.model = null; + if (_inspectorModel.isDefaultMaterial()) + inspectorToolbar.model = defaultMaterialToolbarModel; + else if (_inspectorModel.isMaterial()) + inspectorToolbar.model = materialToolbarModel; + inspectorToolbar.visible = inspectorToolbar.model !== null; + } + } + + MouseArea { + anchors.fill: controlArea + onPressed: { + mouse.accepted = false + focusEater.forceActiveFocus(); + } + } + + ColumnLayout { + id: controlArea + anchors.fill: parent + anchors.topMargin: 4 + spacing: 8 + + Item { + id: focusEater + // Used to eat keyboard focus when user clicks outside any property control + } + + ListModel { + id: materialToolbarModel + + ListElement { + image: "add.png" + name: qsTr("New") + inUse: true + } + + ListElement { + image: "add.png" + name: qsTr("Duplicate") + inUse: true + } + + property var actions: [ + function(){ _inspectorModel.addMaterial(); }, + function(){ _inspectorModel.duplicateMaterial(); } + ] + } + + ListModel { + id: defaultMaterialToolbarModel + + ListElement { + image: "add.png" + name: qsTr("New") + inUse: true + } + + ListElement { + image: "add-disabled.png" + name: qsTr("Duplicate") + inUse: false + } + + property var actions: [ + function(){ _inspectorModel.addMaterial(); } + ] + } + + ListView { + id: inspectorToolbar + model: null + visible: false + + Layout.fillWidth: true + Layout.preferredHeight: 32 + orientation: ListView.Horizontal + + spacing: 4 + + delegate: ToolButton { + id: control + enabled: inUse + + onClicked: { + inspectorToolbar.model.actions[index](); + } + + background: Rectangle { + color: control.pressed ? _selectionColor : (hovered ? _studioColor1 : "transparent") + border.color: _studioColor1 + } + + contentItem: RowLayout { + Image { + source: _resDir + image + } + StyledLabel { + text: name + Layout.preferredWidth: -1 + color: control.enabled ? _textColor : _disabledColor + } + } + } + } + + RowLayout { + height: _controlBaseHeight + 8 + Layout.leftMargin: 4 + + Image { + id: headerImage + source: _parentView.titleIcon !== "" ? _resDir + _parentView.titleIcon : "" + } + + StyledLabel { + text: _parentView.titleText + color: _parentView.titleColor() + } + } + + ListView { + id: groupElements + + Layout.fillWidth: true + Layout.fillHeight: true + Layout.bottomMargin: 10 + spacing: 4 + clip: true + + ScrollBar.vertical: ScrollBar { + visible: size < 1.0 + } + + MouseArea { + anchors.fill: parent + z: -10 + onPressed: { + mouse.accepted = false + focusEater.forceActiveFocus(); + } + } + + model: _inspectorModel + delegate: Rectangle { + id: delegateItem + + property int indexOfThisDelegate: index + + width: parent.width + height: items.height + color: "transparent"; + ListView.delayRemove: true + + readonly property var values: model.values + + Column { + id: items + + x: 10 + width: parent.width - x + spacing: 4 + + Rectangle { // group header + x: -10 + width: delegateItem.width + height: 25 + color: _inspectorGroupHeaderColor + + StyledLabel { + x: 30 + text: model.title + anchors.verticalCenter: parent.verticalCenter + } + + Image { + id: collapseButton + x: 10 + anchors.verticalCenter: parent.verticalCenter + source: { + _resDir + (groupItems.visible ? "arrow_down.png" : "arrow.png") + } + } + + MouseArea { + id: collapseButtonMouseArea + anchors.fill: parent + onClicked: { + if (mouse.button === Qt.LeftButton) { + groupItems.visible = !groupItems.visible; + _inspectorModel.updateGroupCollapseState(indexOfThisDelegate, + !groupItems.visible) + } + } + } + } + + Column { // properties in a group + spacing: 4 + id: groupItems + + visible: !_inspectorModel.isGroupCollapsed(indexOfThisDelegate) + + Repeater { + model: delegateItem.values + + onItemAdded: { + if (index === 0) + _tabOrderHandler.clearGroup(indexOfThisDelegate); + if (item.loadedItem.tabItem1 !== undefined) { + _tabOrderHandler.addItem(indexOfThisDelegate, + item.loadedItem.tabItem1) + if (item.loadedItem.tabItem2 !== undefined) { + _tabOrderHandler.addItem( + indexOfThisDelegate, + item.loadedItem.tabItem2) + if (item.loadedItem.tabItem3 !== undefined) { + _tabOrderHandler.addItem( + indexOfThisDelegate, + item.loadedItem.tabItem3) + } + } + } + } + + RowLayout { // a property row + id: groupDelegateItem + spacing: 0 + enabled: _parentView.isEditable(modelData.handle) + + property alias loadedItem: loader.item + + function showContextMenu(coords) { + _parentView.showContextMenu( + coords.x, coords.y, + model.modelData.handle, + model.modelData.instance); + // Refresh text; title color is wrong after this + propertyRow.color = _parentView.titleColor( + modelData.instance, modelData.handle); + } + + ColumnLayout { // Property row and datainput control + Layout.alignment: Qt.AlignTop + visible: modelData.title !== "variants" + spacing: 0 + RowLayout { // Property row + Layout.alignment: Qt.AlignLeft + Rectangle { // Property animation control button + width: 16 + height: 16 + color: animateButtonMouseArea.containsMouse + ? _studioColor1 : _backgroundColor + + Image { + id: animatedPropertyButton + visible: model.modelData.animatable + + property bool animated: model.modelData.animated + + anchors.fill: parent + fillMode: Image.Pad + + source: { + _resDir + (animated + ? "Inspector-AnimateToggle-Active.png" + : "Inspector-AnimateToggle-Normal.png") + } + + MouseArea { + id: animateButtonMouseArea + anchors.fill: parent + acceptedButtons: Qt.RightButton | Qt.LeftButton + hoverEnabled: true + onClicked: { + if (mouse.button === Qt.LeftButton) { + _inspectorModel.setPropertyAnimated( + model.modelData.instance, + model.modelData.handle, + !model.modelData.animated) + } else { + const coords = mapToItem(root, + mouse.x, + mouse.y); + groupDelegateItem.showContextMenu(coords); + } + } + } + StyledTooltip { + text: qsTr("Enable animation") + enabled: animateButtonMouseArea.containsMouse + } + } + } + + Rectangle { // Datainput control button + width: 16 + height: 16 + color: dataInputButtonMouseArea.containsMouse + ? _studioColor1 : _backgroundColor + + Image { + id: ctrldPropButton + + property bool controlled: model.modelData.controlled + visible: model.modelData.controllable + anchors.fill: parent + fillMode: Image.Pad + + source: { + _resDir + (controlled + ? "Objects-DataInput-Active.png" + : "Objects-DataInput-Inactive.png") + } + + MouseArea { + id: dataInputButtonMouseArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton + hoverEnabled: true + onClicked: { + _parentView.showDataInputChooser( + model.modelData.handle, + model.modelData.instance, + mapToGlobal( + ctrldPropButton.x + + ctrldPropButton.width, + ctrldPropButton.y + + ctrldPropButton.height)); + + } + } + + StyledTooltip { + text: model.modelData.controlled + ? qsTr("Data Input controller:\n") + + model.modelData.controller + : qsTr("Set Data Input controller") + enabled: dataInputButtonMouseArea.containsMouse + } + } + } + + StyledLabel { // Property label + id: propertyRow + + readonly property var modelData: model.modelData + text: model.modelData.title + // Color picked from DataInput icon + color: model.modelData.controlled? + _dataInputColor + : _parentView.titleColor(modelData.instance, + modelData.handle) + + Layout.alignment: Qt.AlignTop + + MouseArea { + id: propertyLabelMouseArea + anchors.fill: parent + acceptedButtons: Qt.RightButton + hoverEnabled: true + onClicked: { + const coords = mapToItem(root, mouse.x, mouse.y); + groupDelegateItem.showContextMenu(coords); + } + } + StyledTooltip { + id: valueToolTip + text: modelData.toolTip + enabled: propertyLabelMouseArea.containsMouse + } + } + } + } + + Loader { + id: loader + readonly property var modelData: propertyRow.modelData + enabled: modelData.enabled + opacity: enabled ? 1 : .5 + Layout.alignment: Qt.AlignTop + sourceComponent: { + if (modelData.title === "variants") + return variantTagsComponent; + + const dataType = modelData.dataType; + switch (dataType) { + case DataModelDataType.Long: + if (modelData.propertyType === + AdditionalMetaDataType.ShadowMapResolution) { + return shadowResolutionComponent; + } + if (modelData.propertyType === AdditionalMetaDataType.Range) + return intSliderComponent; + console.warn("KDAB_TODO: implement handler for type \"Long\", property:", + modelData.propertyType); + return null; + case DataModelDataType.Long4: + if (modelData.propertyType === AdditionalMetaDataType.Image) + return imageChooser; + console.warn("KDAB_TODO: implement handler for type \"long4\" property:", + modelData.propertyType); + return null; + case DataModelDataType.ObjectRef: + if (modelData.propertyType === AdditionalMetaDataType.ObjectRef) { + return _parentView.isRefMaterial(modelData.instance) + ? materialReference + : objectReference; + } + console.warn("KDAB_TODO: implement handler for type: \"objectref\" property:", + modelData.propertyType); + return null; + case DataModelDataType.StringOrInt: + //TODO: Maybe do some further check if the right combo is used + if (modelData.propertyType === AdditionalMetaDataType.StringList) + return slideSelectionDropDown; + console.warn("KDAB_TODO: (String) implement handler for type \"stringOrInt\" property:", + modelData.propertyType); + return null; + case DataModelDataType.String: + if (modelData.propertyType === AdditionalMetaDataType.Import) + return fileChooser; + if (modelData.propertyType === AdditionalMetaDataType.StringList) + return comboDropDown; + if (modelData.propertyType === AdditionalMetaDataType.Renderable) + return renderableDropDown; + if (modelData.propertyType === AdditionalMetaDataType.Mesh) + return meshChooser; + if (modelData.propertyType === AdditionalMetaDataType.MultiLine) + return multiLine; + if (modelData.propertyType === AdditionalMetaDataType.Font) + return fontDropDown; + if (modelData.propertyType === AdditionalMetaDataType.Texture) + return textureChooser; + if (modelData.propertyType === AdditionalMetaDataType.String) + return editLine; + if (modelData.propertyType === AdditionalMetaDataType.None) + return null; + console.warn("KDAB_TODO: (String) implement handler for type \"string\" property:", + modelData.propertyType); + return null; + case DataModelDataType.Bool: + return checkBox; + case DataModelDataType.Float: + if (modelData.propertyType === AdditionalMetaDataType.None) + return valueComponent; + if (modelData.propertyType === AdditionalMetaDataType.Range) + return sliderComponent; + if (modelData.propertyType === AdditionalMetaDataType.FontSize) + return fontSizeComponent; + console.warn("KDAB_TODO: implement handler for type\"float\" property:", + modelData.propertyType); + return null; + case DataModelDataType.Float2: + if (modelData.propertyType === AdditionalMetaDataType.None) + return xyPropertyComponent; + console.warn("TODO: implement handler for type:\"float2\" property:", + modelData.propertyType, "text ", + model.modelData.title); + return null; + case DataModelDataType.Float3: + if (modelData.propertyType === AdditionalMetaDataType.Rotation) + return xyzPropertyComponent; + if (modelData.propertyType === AdditionalMetaDataType.None) + return xyzPropertyComponent; + console.warn("KDAB_TODO: implement handler for type:\"float3\" property:", + modelData.propertyType, "text ", + model.modelData.title); + return null; + case DataModelDataType.Float4: + if (modelData.propertyType === AdditionalMetaDataType.Color) + return colorBox; + return null; + case DataModelDataType.StringRef: + if (modelData.propertyType === AdditionalMetaDataType.None) + return materialTypeDropDown; + if (modelData.propertyType === AdditionalMetaDataType.Renderable) + return shaderDropDown; + if (modelData.propertyType === AdditionalMetaDataType.ObjectRef) + return matDataDropDown; + console.warn("KDAB_TODO: implement handler for type:\"StringRef\" text ", + model.modelData.title); + return null; + default: + console.warn("KDAB_TODO: implement handler for type", + dataType, "property", + modelData.propertyType, "text ", + model.modelData.title); + } + return null; + } + } + } + } + } + + Column { + visible: model.info.length > 0 + StyledLabel { + width: groupElements.width + horizontalAlignment: Text.AlignHCenter + text: model.info; + } + } + } + } + } + } + + Component { + id: editLine + + StyledTextField { + id: textArea + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + property variant value: parent.modelData.value + property Item tabItem1: this + width: _valueWidth + height: _controlBaseHeight + horizontalAlignment: TextInput.AlignLeft + verticalAlignment: TextInput.AlignVCenter + + // Don't just bind text to value, since changing text explicitly in onEditingFinished + // would break binding + onValueChanged: text = value + + onTextChanged: _inspectorModel.setPropertyValue(instance, handle, text, false) + + onEditingFinished: { + _inspectorModel.setPropertyValue(instance, handle, text, true); + // Ensure we update the text to current value also in cases where making name + // unique results in no change to model value + text = value; + } + } + } + + Component { + id: multiLine + + HandlerBaseMultilineText { + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + width: _valueWidth + height: _controlBaseHeight * 3 + value: parent.modelData.value + + onTextChanged: _inspectorModel.setPropertyValue(instance, handle, value, false) + onEditingFinished: _inspectorModel.setPropertyValue(instance, handle, value, true) + } + } + + Component { + id: meshChooser + HandlerFilesChooser { + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + property variant values: parent.modelData.values + value: parent.modelData.value + onShowBrowser: { + activeBrowser = _parentView.showMeshChooser(handle, instance, + mapToGlobal(width, 0)) + } + } + } + + Component { + id: imageChooser + HandlerFilesChooser { + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + property variant values: parent.modelData.values + value: { + var renderableId = _inspectorModel.renderableId(parent.modelData.value); + renderableId === "" ? parent.modelData.value : renderableId; + } + onShowBrowser: { + activeBrowser = _parentView.showImageChooser(handle, instance, + mapToGlobal(width, 0)) + } + } + } + + Component { + id: fileChooser + HandlerFilesChooser { + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + property variant values: parent.modelData.values + value: { + parent.modelData.value === "" ? _parentView.noneString() + : _parentView.convertPathToProjectRoot( + parent.modelData.value) + } + onShowBrowser: { + activeBrowser = _parentView.showFilesChooser(handle, instance, + mapToGlobal(width, 0)) + } + } + } + + Component { + id: textureChooser + HandlerFilesChooser { + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + property variant values: parent.modelData.values + value: parent.modelData.value + onShowBrowser: { + activeBrowser = _parentView.showTextureChooser(handle, instance, + mapToGlobal(width, 0)) + } + } + } + + Component { + id: xyzPropertyComponent + + RowLayout { + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + property variant values: parent.modelData.values + property alias tabItem1: xyzHandler.tabItem1 + property alias tabItem2: xyzHandler.tabItem2 + property alias tabItem3: xyzHandler.tabItem3 + spacing: 0 + + onValuesChanged: { + // FloatTextField can set its text internally, thus breaking the binding, so + // let's set the text value explicitly each time value changes + xyzHandler.valueX = Number(values[0]).toFixed(xyzHandler.numberOfDecimal); + xyzHandler.valueY = Number(values[1]).toFixed(xyzHandler.numberOfDecimal); + xyzHandler.valueZ = Number(values[2]).toFixed(xyzHandler.numberOfDecimal); + } + + HandlerPropertyBaseXYZ { + id: xyzHandler + valueX: Number(values[0]).toFixed(numberOfDecimal) + valueY: Number(values[1]).toFixed(numberOfDecimal) + valueZ: Number(values[2]).toFixed(numberOfDecimal) + onEditingFinished: { + _inspectorModel.setPropertyValue(parent.instance, parent.handle, + Qt.vector3d(valueX, valueY, valueZ), true); + } + onPreviewValueChanged: { + _inspectorModel.setPropertyValue(parent.instance, parent.handle, + Qt.vector3d(valueX, valueY, valueZ), false); + } + } + } + } + + Component { + id: xyPropertyComponent + + RowLayout { + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + property variant values: parent.modelData.values + property alias tabItem1: xyHandler.tabItem1 + property alias tabItem2: xyHandler.tabItem2 + spacing: 0 + + onValuesChanged: { + // FloatTextField can set its text internally, thus breaking the binding, so + // let's set the text value explicitly each time value changes + xyHandler.valueX = Number(values[0]).toFixed(xyHandler.numberOfDecimal); + xyHandler.valueY = Number(values[1]).toFixed(xyHandler.numberOfDecimal); + } + + HandlerPropertyBaseXY { + id: xyHandler + valueX: Number(values[0]).toFixed(numberOfDecimal) + valueY: Number(values[1]).toFixed(numberOfDecimal) + onEditingFinished: { + _inspectorModel.setPropertyValue(parent.instance, parent.handle, + Qt.vector2d(valueX, valueY), true); + } + onPreviewValueChanged: { + _inspectorModel.setPropertyValue(parent.instance, parent.handle, + Qt.vector2d(valueX, valueY), false); + } + } + } + } + + Component { + id: valueComponent + + RowLayout { + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + property real value: Number(parent.modelData.value).toFixed(floatField.decimalValue) + property Item tabItem1: floatField + + onValueChanged: { + // FloatTextField can set its text internally, thus breaking the binding, so + // let's set the text value explicitly each time value changes + floatField.text = Number(value).toFixed(floatField.decimalValue); + } + + spacing: 0 + + FloatTextField { + id: floatField + text: Number(parent.value).toFixed(decimalValue) + implicitHeight: _controlBaseHeight + implicitWidth: _valueWidth / 3 + + onPreviewValueChanged: { + _inspectorModel.setPropertyValue(parent.instance, parent.handle, + Number(text), false); + } + + onEditingFinished: { + _inspectorModel.setPropertyValue(parent.instance, parent.handle, + Number(text), true); + } + } + } + } + + Component { + id: sliderComponent + + HandlerPropertyBaseSlider { + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + property variant values: parent.modelData.values + + value: parent.modelData.value + sliderMin: values[0] + sliderMax: values[1] + sliderDecimals: values[2] + + onCommitValue: _inspectorModel.setPropertyValue(instance, handle, desiredValue, true) + onPreviewValue: _inspectorModel.setPropertyValue(instance, handle, desiredValue, false) + } + } + + Component { + id: comboDropDown + + StyledComboBox { + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + property var values: parent.modelData.values + property var value: parent.modelData.value + + model: values + + implicitWidth: _valueWidth + implicitHeight: _controlBaseHeight + + Component.onCompleted: { + currentIndex = find(value) + } + onCurrentIndexChanged: { + var newValue = textAt(currentIndex) + if (value !== newValue && currentIndex !== -1) + _inspectorModel.setPropertyValue(instance, handle, newValue) + } + onValueChanged: { + currentIndex = find(value) + } + } + } + + Component { + id: fontDropDown + + StyledComboBox { + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + property var values: parent.modelData.values + property var value: parent.modelData.value + property bool blockIndexChange: false + + model: values + + implicitWidth: _valueWidth + implicitHeight: _controlBaseHeight + + Component.onCompleted: { + currentIndex = find(value) + } + onCurrentIndexChanged: { + var newValue = textAt(currentIndex) + if (!blockIndexChange && value !== newValue && currentIndex !== -1) + _inspectorModel.setPropertyValue(instance, handle, newValue) + } + onValueChanged: { + var newNewIndex = find(value); + if (!blockIndexChange || newNewIndex > 0) + currentIndex = newNewIndex; + blockIndexChange = false; + } + onValuesChanged : { + // Changing the values list will reset the currentIndex to zero, so block setting + // the actual font. We'll get the proper index right after. + if (currentIndex > 0) + blockIndexChange = true; + } + } + } + + Component { + id: slideSelectionDropDown + + StyledComboBox { + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + property var values: parent.modelData.values + property var value: parent.modelData.value + + model: values + + implicitWidth: _valueWidth + implicitHeight: _controlBaseHeight + + Component.onCompleted: { + var newIndex = find(value) + if (newIndex === -1) + newIndex = find(value + "|separator") + currentIndex = newIndex + } + onCurrentIndexChanged: { + var newValue = textAt(currentIndex).replace("|separator", "") + if (value !== newValue && currentIndex !== -1) { + _inspectorModel.setSlideSelection(instance, handle, + currentIndex, values) + } + } + onValueChanged: { + var newIndex = find(value) + if (newIndex === -1) + newIndex = find(value + "|separator") + currentIndex = newIndex + } + } + } + + Component { + id: materialTypeDropDown + + MaterialDropDown { + callback: _inspectorModel.setMaterialTypeValue + } + } + + Component { + id: shaderDropDown + + MaterialDropDown { + callback: _inspectorModel.setShaderValue + } + } + + Component { + id: matDataDropDown + + MaterialDropDown { + callback: _inspectorModel.setMatDataValue + } + } + + Component { + id: renderableDropDown + + StyledComboBox { + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + property var values: parent.modelData.values + property var value: parent.modelData.value + model: values + + showArrow: enabled + + implicitWidth: _valueWidth + implicitHeight: _controlBaseHeight + + Component.onCompleted: { + // Disable for non-layer + enabled = _inspectorModel.isLayer(instance); + currentIndex = find(value); + } + + onCurrentIndexChanged: { + var newValue = textAt(currentIndex) + if (value !== newValue && currentIndex !== -1) + _inspectorModel.setRenderableValue(instance, handle, newValue) + } + onValueChanged: { + currentIndex = find(value) + } + } + } + + Component { + id: checkBox + + Item { + id: checkboxItem + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + property bool checked: parent.modelData.value + + width: 16 + height: _controlBaseHeight + Image { + anchors.fill: parent + fillMode: Image.Pad + source: (_resDir + (checked ? "checkbox-checked.png" : "checkbox-unchecked.png")) + + MouseArea { + anchors.fill: parent + onClicked: _inspectorModel.setPropertyValue(checkboxItem.instance, + checkboxItem.handle, + !checkboxItem.checked) + } + } + } + } + + Component { + id: colorBox + + HandlerGenericBaseColor { + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + + color: parent.modelData.value + onColorSelected: _inspectorModel.setPropertyValue(instance, handle, selectedColor); + onPreviewColorSelected: _inspectorModel.setPropertyValue(instance, handle, + selectedColor, false); + } + } + + Component { + id: objectReference + + HandlerGenericChooser { + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + property variant values: parent.modelData.values + value: parent.modelData.value + onShowBrowser: { + activeBrowser = _parentView.showObjectReference(handle, instance, + mapToGlobal(width, 0)) + } + } + } + + Component { + id: materialReference + + HandlerGenericChooser { + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + property variant values: parent.modelData.values + value: parent.modelData.value + onShowBrowser: { + activeBrowser = _parentView.showMaterialReference(handle, instance, + mapToGlobal(width, 0)) + } + } + } + + Component { + id: intSliderComponent + + HandlerPropertyBaseSlider { + intSlider: true; + property int intValue: Math.round(desiredValue) + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + property variant values: parent.modelData.values + value: parent.modelData.value + sliderMin: values[0] + sliderMax: values[1] + + onCommitValue: _inspectorModel.setPropertyValue(instance, handle, intValue, true) + onPreviewValue: _inspectorModel.setPropertyValue(instance, handle, intValue, false) + } + } + + Component { + id: fontSizeComponent + + StyledComboBox { + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + property real value: parent.modelData.value + + editable: true + property bool ready: false + + validator: IntValidator { + bottom: 1 + } + + model: ["8", "9", "10", "11", "12", "14", "16", "18", "20", "22", "24", "26", "28", + "36", "48", "72", "96", "120", "160", "200"] + + implicitWidth: _valueWidth + implicitHeight: _controlBaseHeight + + Component.onCompleted: { + editText = value; + ready = true; + } + + onValueChanged: { + if (ready && !isNaN(value)) + editText = value; + } + + onEditTextChanged: { + if (ready) { + var newvalue = parseInt(editText); + _inspectorModel.setPropertyValue(instance, handle, newvalue, false); + } + } + + onActiveFocusChanged: { + if (!activeFocus) { + var newvalue = parseInt(editText); + _inspectorModel.setPropertyValue(instance, handle, newvalue); + } + } + + } + } + + Component { + id: shadowResolutionComponent + + StyledComboBox { + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + property var value: parent.modelData.value + property int newValue + + model: ["8", "9", "10", "11"] + implicitWidth: _valueWidth + implicitHeight: _controlBaseHeight + + Component.onCompleted: { + currentIndex = find(value) + } + + onCurrentIndexChanged: { + newValue = parseInt(textAt(currentIndex)) + if (value !== newValue && currentIndex !== -1) + _inspectorModel.setPropertyValue(instance, handle, newValue) + } + + onValueChanged: { + currentIndex = find(value) + } + } + } + + Component { + id: variantTagsComponent + + Column { + width: root.width - 10 + spacing: 10 + + Row { + anchors.right: parent.right + anchors.rightMargin: 5 + spacing: 5 + + ToolButton { + id: importButton + text: qsTr("Import...") + font.pixelSize: _fontSize + width: 70 + height: 20 + + onClicked: { + _variantsGroupModel.importVariants() + } + } + + ToolButton { + id: exportButton + text: qsTr("Export...") + font.pixelSize: _fontSize + width: 70 + height: 20 + enabled: !_variantsGroupModel.variantsEmpty + + onClicked: { + _variantsGroupModel.exportVariants() + } + } + } + + Text { + text: qsTr("There are no variant tags yet. Click [+ Group] to add a new tags group and start adding tags.") + font.pixelSize: _fontSize + color: _textColor + visible: _variantsGroupModel.variantsEmpty + width: parent.width + wrapMode: Text.WordWrap + rightPadding: 5 + } + + Repeater { + id: tagsRepeater + model: _variantsGroupModel + property int maxGroupLabelWidth; + + onItemAdded: { + // make all group labels have equal width as the widest one + if (index == 0) + maxGroupLabelWidth = 20; // min group label width + + if (item.groupLabelWidth > maxGroupLabelWidth) { + maxGroupLabelWidth = item.groupLabelWidth; + + if (maxGroupLabelWidth > 150) // max group label width + maxGroupLabelWidth = 150; + } + } + + Row { + id: variantTagsRow + spacing: 5 + + readonly property var tagsModel: model.tags + readonly property var groupModel: model + readonly property int groupLabelWidth: tLabel.implicitWidth + 10 + + Text { + id: tLabel + text: model.group + color: model.color + font.pixelSize: _fontSize + width: tagsRepeater.maxGroupLabelWidth; + elide: Text.ElideRight + anchors.top: parent.top + anchors.topMargin: 5 + + MouseArea { + anchors.fill: parent; + acceptedButtons: Qt.RightButton + onClicked: { + if (mouse.button === Qt.RightButton) { + const coords = mapToItem(root, mouse.x, mouse.y); + _parentView.showGroupContextMenu(coords.x, coords.y, model.group); + } + } + } + } + + Flow { + width: root.width - 110 + spacing: 5 + + Repeater { + model: tagsModel + + Loader { + readonly property var tagsModel: model + readonly property var grpModel: groupModel + sourceComponent: tagComponent + } + } + + ToolButton { + id: addTagButton + text: qsTr("+ Tag") + font.pixelSize: _fontSize + height: 25 + + onClicked: { + _variantsGroupModel.addNewTag(groupModel.group) + } + + } + } + } + } + + Item { width: 1; height: 5 } // vertical spacer + + ToolButton { + id: addGroupButton + text: qsTr("+ Group") + font.pixelSize: _fontSize + width: 65 + height: 25 + onClicked: { + _variantsGroupModel.addNewGroup() + } + } + + Item { width: 1; height: 5 } // vertical spacer + } + } + + Component { + id: tagComponent + + Rectangle { + property bool toggled: tagsModel ? tagsModel.selected : false + property color grpColor: grpModel ? grpModel.color : "" + property bool isBright: grpModel ? _utils.isBright(grpColor) : false + + width: Math.max(tLabel.width + 10, 60) + height: 25 + color: toggled ? grpColor : _backgroundColor + border.color: _studioColor4 + + Text { + id: tLabel + anchors.centerIn: parent + text: tagsModel ? tagsModel.tag : "" + font.pixelSize: _fontSize + color: toggled ? (isBright ? _studioColor1 : _textColor) : _studioColor4 + } + + MouseArea { + anchors.fill: parent; + acceptedButtons: Qt.RightButton | Qt.LeftButton + onClicked: { + if (mouse.button === Qt.LeftButton) { + toggled = !toggled; + _variantsGroupModel.setTagState(grpModel.group, tagsModel.tag, toggled); + } else if (mouse.button === Qt.RightButton) { + const coords = mapToItem(root, mouse.x, mouse.y); + _parentView.showTagContextMenu(coords.x, coords.y, grpModel.group, + tagsModel.tag); + } + } + } + } + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorGroup.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorGroup.cpp new file mode 100644 index 00000000..84967f4c --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorGroup.cpp @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2002 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "InspectorGroup.h" + +CInspectorGroup::CInspectorGroup(const QString &inName) +{ + m_name = inName; +} + +CInspectorGroup::~CInspectorGroup() +{ +} + +QString CInspectorGroup::GetName() const +{ + return m_name; +} + diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorGroup.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorGroup.h new file mode 100644 index 00000000..18f56d79 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/InspectorGroup.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2002 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef INCLUDED_INSPECTOR_GROUP_H +#define INCLUDED_INSPECTOR_GROUP_H + +#include <QString> + +/** + * This is the base class for inspector groups. + * + * Derive from this class in order to create a new group for the inspector palette. + */ +class CInspectorGroup +{ +public: + CInspectorGroup(const QString &inName = {}); + virtual ~CInspectorGroup(); + + QString GetName() const; + +protected: + QString m_name; +}; + +#endif // INCLUDED_INSPECTOR_GROUP_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialDropDown.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialDropDown.qml new file mode 100644 index 00000000..88de98c5 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialDropDown.qml @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ +import QtQuick 2.0 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.2 + +import Qt3DStudio 1.0 +import "../controls" +import "../Action" + +StyledComboBox { + property int instance: parent.modelData.instance + property int handle: parent.modelData.handle + property var values: parent.modelData.values + property var value: parent.modelData.value + property bool blockIndexChange: false + property var callback + + model: values + + implicitWidth: _valueWidth + implicitHeight: _controlBaseHeight + + Component.onCompleted: { + currentIndex = find(value) + } + onCurrentIndexChanged: { + var newValue = textAt(currentIndex) + if (!blockIndexChange && value !== newValue && currentIndex !== -1) + callback(instance, handle, newValue) + } + onValueChanged: { + var newNewIndex = find(value); + if (!blockIndexChange || newNewIndex > 0) + currentIndex = newNewIndex; + blockIndexChange = false; + } + onValuesChanged : { + // Changing the values list will reset the currentIndex to zero, so block setting + // the actual material. We'll get the proper index right after. + if (currentIndex > 0) + blockIndexChange = true; + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialRefView.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialRefView.cpp new file mode 100644 index 00000000..b2d1a595 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialRefView.cpp @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "MaterialRefView.h" +#include "StudioApp.h" +#include "StudioPreferences.h" +#include "Doc.h" +#include "Core.h" +#include "Qt3DSDMStudioSystem.h" +#include "ClientDataModelBridge.h" +#include "IObjectReferenceHelper.h" +#include "IDocumentEditor.h" + +#include <QtCore/qtimer.h> + +MaterialRefView::MaterialRefView(QWidget *parent) +: QListWidget(parent) +{ + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + setResizeMode(QListWidget::Fixed); +} + +/** + * Gather and display the currently used standard and custom material list + * + * @param instance The instance that owns the property handle + * @param handle The property handle this materials list is for + * + * @return necessary size for the dialog + */ +QSize MaterialRefView::refreshMaterials(int instance, int handle) +{ + clear(); // clear old material list + + m_instance = instance; + m_handle = handle; + qt3dsdm::Qt3DSDMInstanceHandle refInstance = getRefInstance(); + + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem(); + auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge(); + static const QPixmap pixMaterialNormal = QPixmap(":/images/Objects-Material-Normal.png"); + + QVector<qt3dsdm::Qt3DSDMInstanceHandle> mats; + doc->getSceneMaterials(doc->GetSceneInstance(), mats); + + // add freshly collected material list + for (auto matInstance : qAsConst(mats)) { + qt3dsdm::SValue v; + propertySystem->GetInstancePropertyValue(matInstance, + bridge->GetObjectDefinitions().m_Named.m_NameProp, v); + QString matName = qt3dsdm::get<QString>(v); + + // get the material's object name (parent) + qt3dsdm::Qt3DSDMInstanceHandle parentInstance = bridge->GetParentInstance(matInstance); + qt3dsdm::SValue vParent; + propertySystem->GetInstancePropertyValue(parentInstance, + bridge->GetObjectDefinitions().m_Named.m_NameProp, vParent); + QString objName = qt3dsdm::get<QString>(vParent); + matName.append(QLatin1String(" (") + objName + QLatin1String(")")); + + QListWidgetItem *matItem = new QListWidgetItem(this); + matItem->setData(Qt::DisplayRole, matName); + matItem->setData(Qt::DecorationRole, pixMaterialNormal); + matItem->setData(Qt::UserRole, QVariant(matInstance)); + + if (matInstance == refInstance) + setCurrentItem(matItem); + } + + if (count() == 0) { + // Show an unselectable dummy item + static const QPixmap pixWarning = QPixmap(":/images/warning.png"); + QListWidgetItem *matItem = new QListWidgetItem(this); + matItem->setData(Qt::DisplayRole, tr("No animatable materials found")); + matItem->setData(Qt::DecorationRole, pixWarning); + matItem->setData(Qt::UserRole, -1); + setSelectionMode(QAbstractItemView::NoSelection); + } else { + setSelectionMode(QAbstractItemView::SingleSelection); + } + + QSize widgetSize(CStudioPreferences::valueWidth(), + qMin(10, count()) * CStudioPreferences::controlBaseHeight()); + + return widgetSize; +} + +void MaterialRefView::updateSelection() +{ + int refInstance = getRefInstance(); + for (int i = 0, itemCount = count(); i < itemCount; ++i) { + int matInstance = item(i)->data(Qt::UserRole).toInt(); + if (matInstance == refInstance) { + setCurrentRow(i); + break; + } + } +} + +bool MaterialRefView::isFocused() const +{ + return hasFocus(); +} + +int MaterialRefView::getRefInstance() const +{ + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem(); + + qt3dsdm::SValue value; + propertySystem->GetInstancePropertyValue(m_instance, m_handle, value); + return doc->GetDataModelObjectReferenceHelper()->Resolve(value, m_instance); +} + +void MaterialRefView::focusInEvent(QFocusEvent *event) +{ + QAbstractItemView::focusInEvent(event); + emit focusChanged(); +} + +void MaterialRefView::focusOutEvent(QFocusEvent *event) +{ + QAbstractItemView::focusOutEvent(event); + emit focusChanged(); + QTimer::singleShot(0, this, &QAbstractItemView::close); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialRefView.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialRefView.h new file mode 100644 index 00000000..d4131cda --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/MaterialRefView.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef MATERIALREFVIEW_H +#define MATERIALREFVIEW_H + +#include <QtWidgets/qlistwidget.h> +#include "Qt3DSDMHandles.h" + +class MaterialRefView : public QListWidget +{ + Q_OBJECT + Q_PROPERTY(bool focused READ isFocused NOTIFY focusChanged) + +public: + explicit MaterialRefView(QWidget *parent = nullptr); + + QSize refreshMaterials(int instance, int handle); + + void updateSelection(); +protected: + void focusInEvent(QFocusEvent *event) override; + void focusOutEvent(QFocusEvent *event) override; + +Q_SIGNALS: + void focusChanged(); + +private: + bool isFocused() const; + int getRefInstance() const; + + int m_instance = -1; + int m_handle = -1; +}; + +#endif // MATERIALREFVIEW_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooser.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooser.qml new file mode 100644 index 00000000..fcaf498c --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooser.qml @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +Rectangle { + id: root + + color: _backgroundColor + border.color: _studioColor3 + + ColumnLayout { + anchors.fill: parent + ListView { + id: listView + + Layout.fillWidth: true + Layout.fillHeight: true + Layout.margins: 4 + + boundsBehavior: Flickable.StopAtBounds + clip: true + + ScrollBar.vertical: ScrollBar {} + + model: _meshChooserModel + + delegate: ChooserDelegate { + onClicked: { + _meshChooserView.setSelectedMeshName(filePath); + } + onDoubleClicked: { + _meshChooserView.setSelectedMeshName(filePath); + _meshChooserView.hide(); + } + } + } + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserModel.cpp new file mode 100644 index 00000000..f87dc89b --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserModel.cpp @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "MeshChooserModel.h" +#include "Core.h" +#include "Doc.h" +#include "IDocumentBufferCache.h" +#include "StudioApp.h" +#include "BasicObjectsModel.h" + +MeshChooserModel::MeshChooserModel(QObject *parent) + : ChooserModelBase(parent) +{ +} + +MeshChooserModel::~MeshChooserModel() +{ + +} + +bool MeshChooserModel::isVisible(const QString &path) const +{ + return getIconType(path) == OBJTYPE_MODEL; +} + +const QVector<ChooserModelBase::FixedItem> MeshChooserModel::getFixedItems() const +{ + static QVector<FixedItem> items; + + if (items.isEmpty()) { + auto primitives = BasicObjectsModel::BasicMeshesModel(); + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + for (int i = 0; i < primitives.size(); i++) { + auto item = primitives.at(i); + const wchar_t *itemName = doc->GetBufferCache().GetPrimitiveName(item.primitiveType()); + if (itemName[0] == L'#') + items.append({ OBJTYPE_MODEL, item.icon(), QString::fromWCharArray(itemName + 1) }); + } + } + + return items; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserModel.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserModel.h new file mode 100644 index 00000000..5aea88a8 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserModel.h @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef MESHCHOOSERMODEL_H +#define MESHCHOOSERMODEL_H + +#include "ChooserModelBase.h" + +class MeshChooserModel : public ChooserModelBase +{ + Q_OBJECT + +public: + explicit MeshChooserModel(QObject *parent = nullptr); + virtual ~MeshChooserModel(); + +private: + bool isVisible(const QString &path) const override; + const QVector<FixedItem> getFixedItems() const override; +}; + +#endif // MESHCHOOSERMODEL_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserView.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserView.cpp new file mode 100644 index 00000000..1aac4731 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserView.cpp @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "MeshChooserView.h" +#include "MeshChooserModel.h" +#include "Literals.h" +#include "StudioUtils.h" +#include "StudioPreferences.h" +#include "IDocumentEditor.h" +#include "Qt3DSDMStudioSystem.h" +#include "Qt3DSDMValue.h" +#include "Core.h" +#include "Doc.h" +#include "StudioApp.h" +#include "IDocumentBufferCache.h" + +#include <QtCore/qtimer.h> +#include <QtQml/qqmlcontext.h> +#include <QtQml/qqmlengine.h> + +MeshChooserView::MeshChooserView(QWidget *parent) + : QQuickWidget(parent) + , m_model(new MeshChooserModel(this)) +{ + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + setResizeMode(QQuickWidget::SizeRootObjectToView); + QTimer::singleShot(0, this, &MeshChooserView::initialize); +} + +void MeshChooserView::initialize() +{ + CStudioPreferences::setQmlContextProperties(rootContext()); + rootContext()->setContextProperty(QStringLiteral("_resDir"), + StudioUtils::resourceImageUrl()); + rootContext()->setContextProperty(QStringLiteral("_meshChooserView"), this); + rootContext()->setContextProperty(QStringLiteral("_meshChooserModel"), m_model); + + engine()->addImportPath(StudioUtils::qmlImportPath()); + setSource(QUrl(QStringLiteral("qrc:/Palettes/Inspector/MeshChooser.qml"))); +} + +int MeshChooserView::numMeshes() const +{ + return m_model->rowCount(); +} + +void MeshChooserView::setSelectedMeshName(const QString &name) +{ + bool resourceName = false; + QString meshName = QLatin1Char('#') + name; + const wchar_t **files = g_StudioApp.GetCore()->GetDoc()->GetBufferCache().GetPrimitiveNames(); + for (const wchar_t **item = files; item && *item; ++item) { + QString primitive = QString::fromWCharArray(*item); + if (primitive == meshName) { + resourceName = true; + break; + } + } + if (!resourceName) + meshName = name; + + Q_EMIT meshSelected(m_handle, m_instance, meshName); +} + +void MeshChooserView::setHandle(int handle) +{ + m_handle = handle; +} + +void MeshChooserView::setInstance(int instance) +{ + m_instance = instance; +} + +void MeshChooserView::updateSelection() +{ + const auto doc = g_StudioApp.GetCore()->GetDoc(); + const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem(); + + qt3dsdm::SValue value; + propertySystem->GetInstancePropertyValue(m_instance, m_handle, value); + + QString currentFile; + const QString meshValue = qt3dsdm::get<QString>(value); + if (meshValue.startsWith(QLatin1Char('#'))) + currentFile = meshValue.mid(1); + else + currentFile = QDir::cleanPath(QDir(doc->GetDocumentDirectory()).filePath(meshValue)); + + m_model->setCurrentFile(currentFile); +} + +bool MeshChooserView::isFocused() const +{ + return hasFocus(); +} + +void MeshChooserView::focusInEvent(QFocusEvent *event) +{ + QQuickWidget::focusInEvent(event); + emit focusChanged(); +} + +void MeshChooserView::focusOutEvent(QFocusEvent *event) +{ + QQuickWidget::focusOutEvent(event); + emit focusChanged(); + QTimer::singleShot(0, this, &QQuickWidget::close); +} + +void MeshChooserView::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Escape) + QTimer::singleShot(0, this, &MeshChooserView::close); + + QQuickWidget::keyPressEvent(event); +} + +void MeshChooserView::showEvent(QShowEvent *event) +{ + updateSelection(); + QQuickWidget::showEvent(event); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserView.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserView.h new file mode 100644 index 00000000..6d12cf14 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/MeshChooserView.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef MESHCHOOSERVIEW_H +#define MESHCHOOSERVIEW_H + +#include <QQuickWidget> + +namespace Q3DStudio { +class CFilePath; +class CString; +} + +class MeshChooserModel; + +QT_FORWARD_DECLARE_CLASS(QAbstractItemModel) + +class MeshChooserView : public QQuickWidget +{ + Q_OBJECT + Q_PROPERTY(bool focused READ isFocused NOTIFY focusChanged) + +public: + explicit MeshChooserView(QWidget *parent = nullptr); + + Q_INVOKABLE void setSelectedMeshName(const QString &name); + + void setHandle(int handle); + void setInstance(int instance); + int numMeshes() const; + + void updateSelection(); + +Q_SIGNALS: + void meshSelected(int handle, int instance, const QString &name); + +protected: + void focusInEvent(QFocusEvent *event) override; + void focusOutEvent(QFocusEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + +Q_SIGNALS: + void focusChanged(); + +private: + void showEvent(QShowEvent *event) override; + void initialize(); + bool isFocused() const; + + int m_handle = -1; + int m_instance = -1; + MeshChooserModel *m_model = nullptr; +}; + +#endif // MESHCHOOSERVIEW_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/MouseHelper.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/MouseHelper.cpp new file mode 100644 index 00000000..24c67fbe --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/MouseHelper.cpp @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "MouseHelper.h" +#include "MainFrm.h" +#include "StudioApp.h" +#include <QtWidgets/qapplication.h> +#include <QtWidgets/qmainwindow.h> +#include <QtWidgets/qwidget.h> +#include <QtGui/qcursor.h> +#include <QtGui/qwindow.h> +#include <QtGui/qscreen.h> +#include <QtCore/qtimer.h> +#include <QtGui/qevent.h> +#include <QtGui/qbitmap.h> + +static void setBlankCursor() +{ + // Qt::BlankCursor gets corrupted in some situations, so use custom bitmap (QTBUG-61678) + static QBitmap *zeroBitmap = nullptr; + if (!zeroBitmap) { + zeroBitmap = new QBitmap(32, 32); + zeroBitmap->clear(); + } + QGuiApplication::setOverrideCursor(QCursor(*zeroBitmap, *zeroBitmap)); +} + +MouseHelper::MouseHelper(QObject *parent) + : QObject(parent) + , m_dragState(StateNotDragging) + , m_maxDelta(50, 50) +{ + // All cursor position modifications are done asynchronously, so we don't get position + // changes in middle of mouse event handling. + m_cursorResetTimer.setInterval(0); + m_cursorResetTimer.setSingleShot(true); + connect(&m_cursorResetTimer, &QTimer::timeout, this, &MouseHelper::resetCursor); +} + +void MouseHelper::startUnboundedDrag() +{ + m_dragState = StateDragging; + setBlankCursor(); + m_startPos = QCursor::pos(); + + QWindow *window = g_StudioApp.m_pMainWnd->windowHandle(); + if (window) + window->installEventFilter(this); // Always install event filter to main window + + if (m_widget) { + // Use the center of the on-screen portion of the parent widget as reference point. + // This ensures cursor restores properly, as the cursor stays on the widget. + m_window = m_widget->window()->windowHandle(); + const QRect screenGeometry = m_window->screen()->geometry(); + const QPoint bottomRight = screenGeometry.bottomRight(); + QSize widgetSize = m_widget->size(); + QPoint widgetPos = m_widget->mapToGlobal(QPoint(0, 0)); + if (widgetPos.x() < 0) { + widgetSize.setWidth(widgetSize.width() + widgetPos.x()); + widgetPos.setX(0); + } + if (widgetPos.y() < 0) { + widgetSize.setHeight(widgetSize.height() + widgetPos.y()); + widgetPos.setY(0); + } + if (widgetPos.x() + widgetSize.width() > bottomRight.x()) + widgetSize.setWidth(bottomRight.x() - widgetPos.x()); + if (widgetPos.y() + widgetSize.height() > bottomRight.y()) + widgetSize.setHeight(bottomRight.y() - widgetPos.y()); + m_maxDelta = QPoint(widgetSize.width() / 2, widgetSize.height() / 2); + m_referencePoint = widgetPos + m_maxDelta; + } else { + // Just assume the screen of the app is at least 400x400 if we don't have widget + m_referencePoint = QPoint(200, 200); + m_window = nullptr; + } + m_previousPoint = m_startPos; + + m_cursorResetTimer.start(); +} + +void MouseHelper::endUnboundedDrag() +{ + QWindow *window = g_StudioApp.m_pMainWnd->windowHandle(); + if (window) + window->removeEventFilter(this); + m_dragState = StateEndingDrag; + m_cursorResetTimer.start(); +} + +QPoint MouseHelper::delta() +{ + QPoint delta(0, 0); + if (m_dragState == StateDragging) { + QPoint currentPoint = QCursor::pos(); + delta = currentPoint - m_previousPoint; + m_previousPoint = currentPoint; + + // Limit delta to even out the maximum possible change rate regardless of widget position + if (delta.x() > m_maxDelta.x()) + delta.setX(m_maxDelta.x()); + else if (delta.x() < -m_maxDelta.x()) + delta.setX(-m_maxDelta.x()); + + if (delta.y() > m_maxDelta.y()) + delta.setY(m_maxDelta.y()); + else if (delta.y() < -m_maxDelta.y()) + delta.setY(-m_maxDelta.y()); + + if (!m_cursorResetTimer.isActive()) + m_cursorResetTimer.start(); + } + return delta; +} + +void MouseHelper::setWidget(QWidget *widget) +{ + m_widget = widget; +} + +void MouseHelper::resetCursor() +{ + switch (m_dragState) { + case StateDragging: + if (m_window) + QCursor::setPos(m_window->screen(), m_referencePoint); + else + QCursor::setPos(m_referencePoint); + m_previousPoint = m_referencePoint; + break; + case StateEndingDrag: + if (m_window) + QCursor::setPos(m_window->screen(), m_startPos); + else + QCursor::setPos(m_startPos); + m_dragState = StateFinalCursorReset; + m_cursorResetTimer.start(); + break; + case StateFinalCursorReset: + // First change to default cursor to avoid any flicker of cursor + qApp->changeOverrideCursor(Qt::ArrowCursor); + qApp->restoreOverrideCursor(); + m_dragState = StateNotDragging; + break; + case StateNotDragging: + default: + break; + } +} + +bool MouseHelper::eventFilter(QObject *obj, QEvent *event) +{ + Q_UNUSED(obj) + + // Eat all mouse button events that are not for left button and all key events + switch (event->type()) { + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: { + QMouseEvent *me = static_cast<QMouseEvent *>(event); + if (me->button() == Qt::LeftButton) + return false; + else + return true; + } + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::ShortcutOverride: + return true; + default: + break; + } + return false; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/MouseHelper.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/MouseHelper.h new file mode 100644 index 00000000..8f343bc4 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/MouseHelper.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ +#ifndef MOUSEHELPER_H +#define MOUSEHELPER_H + +#include <QtCore/qobject.h> +#include <QtCore/qpoint.h> +#include <QtCore/qtimer.h> + +QT_FORWARD_DECLARE_CLASS(QWidget) +QT_FORWARD_DECLARE_CLASS(QWindow) + +class MouseHelper : public QObject +{ + Q_OBJECT + +public: + explicit MouseHelper(QObject *parent = nullptr); + ~MouseHelper() {}; + + Q_INVOKABLE void startUnboundedDrag(); + Q_INVOKABLE void endUnboundedDrag(); + Q_INVOKABLE QPoint delta(); + + void setWidget(QWidget *widget); + +private Q_SLOTS: + void resetCursor(); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + +private: + QPoint m_startPos; + QPoint m_referencePoint; + QPoint m_previousPoint; + QTimer m_cursorResetTimer; + + enum DragState { + StateNotDragging, + StateDragging, + StateEndingDrag, + StateFinalCursorReset + }; + DragState m_dragState; + QWidget *m_widget = nullptr; // Not owned + QWindow *m_window = nullptr; // Not owned + QPoint m_maxDelta; +}; + +#endif // MOUSEHELPER_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowser.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowser.qml new file mode 100644 index 00000000..fc0a2e55 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowser.qml @@ -0,0 +1,196 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import Qt3DStudio 1.0 +import "../controls" + +Rectangle { + id: root + + property alias selectedText: selectionText.text + + color: _backgroundColor + border.color: _studioColor3 + + ColumnLayout { + anchors.fill: parent + + spacing: 10 + + ListView { + id: browserList + + Layout.margins: 10 + Layout.columnSpan: 2 + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: 80 + Layout.preferredHeight: count * 20 + Layout.preferredWidth: root.width + + ScrollBar.vertical: ScrollBar {} + + model: _objectBrowserView.model + boundsBehavior: Flickable.StopAtBounds + clip: true + currentIndex: _objectBrowserView.selection + + delegate: Item { + id: delegateItem + + x: model.depth * 20 + width: parent.width + height: model.parentExpanded ? typeIcon.height + 10 : 0 + + visible: height > 0 + + Behavior on height { + NumberAnimation { + duration: 100 + easing.type: Easing.OutQuad + } + } + + Row { + id: row + + height: typeIcon.height + spacing: 5 + + Image { + source: { + if (!model.hasChildren) + return ""; + model.expanded ? _resDir + "arrow_down.png" + : _resDir + "arrow.png"; + } + + MouseArea { + anchors.fill: parent + onClicked: model.expanded = !model.expanded + } + } + + Rectangle { + height: typeIcon.height + width: typeIcon.width + name.width + 10 + + color: model.index === browserList.currentIndex ? _selectionColor + : "transparent" + + Row { + spacing: 10 + Image { + id: typeIcon + + source: model.icon + } + + StyledLabel { + id: name + anchors.verticalCenter: typeIcon.verticalCenter + color: model.textColor + text: model.name + } + } + + MouseArea { + id: delegateArea + + anchors.fill: parent + onClicked: { + if (_objectBrowserView.selectable(model.index)) { + browserList.currentIndex = model.index; + // Set the selection here, as otherwise we can't set for + // example the same reference material to more than one target + // without selecting something else first + _objectBrowserView.selection = browserList.currentIndex; + } + } + onDoubleClicked: { + if (_objectBrowserView.selectable(model.index)) { + browserList.currentIndex = model.index; + // Set the selection here, as otherwise we can't set for + // example the same reference material to more than one target + // without selecting something else first + _objectBrowserView.selection = browserList.currentIndex; + _objectBrowserView.close(); + } + } + } + } + } + } + + Connections { + target: _objectBrowserView + onSelectionChanged: { + if (browserList.currentIndex !== _objectBrowserView.selection) + browserList.currentIndex = _objectBrowserView.selection; + } + } + } + + StyledMenuSeparator {} + + GridLayout { + columns: 2 + Layout.margins: 10 + + StyledLabel { + text: qsTr("Type") + } + + StyledComboBox { + id: pathCombo + model: [qsTr("Absolute Reference"), qsTr("Path Reference")] + + onActivated: { + if (index === 0) + _objectBrowserView.pathType = ObjectBrowserView.Absolute; + else if (index === 1) + _objectBrowserView.pathType = ObjectBrowserView.Relative; + } + } + + StyledLabel { + text: qsTr("Path") + } + + StyledLabel { + id: selectionText + Layout.preferredWidth: _valueWidth + text: pathCombo.currentIndex === 0 ? _objectBrowserView.absPath(browserList.currentIndex) + : _objectBrowserView.relPath(browserList.currentIndex) + } + } + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowserView.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowserView.cpp new file mode 100644 index 00000000..87238847 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowserView.cpp @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "ObjectBrowserView.h" + +#include "ObjectListModel.h" +#include "StudioPreferences.h" +#include "StudioUtils.h" +#include "StudioApp.h" +#include "Core.h" +#include "Qt3DSDMStudioSystem.h" +#include "ClientDataModelBridge.h" + +#include <QtCore/qtimer.h> +#include <QtQml/qqmlcontext.h> +#include <QtQml/qqmlengine.h> + +ObjectBrowserView::ObjectBrowserView(QWidget *parent) + : QQuickWidget(parent) +{ + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + setResizeMode(QQuickWidget::SizeRootObjectToView); + QTimer::singleShot(0, this, &ObjectBrowserView::initialize); +} + +QAbstractItemModel *ObjectBrowserView::model() const +{ + return m_model; +} + +void ObjectBrowserView::setModel(ObjectListModel *model) +{ + if (!m_model) + m_model = new FlatObjectListModel(model, this); + m_model->setSourceModel(model); + + const auto doc = g_StudioApp.GetCore()->GetDoc(); + const auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge(); + + // Remove "Scene.__Container" and "materials//Default" entries + QModelIndexList list = m_model->match(m_model->index(0, 0), + ObjectListModel::AbsolutePathRole, + QStringLiteral("Scene.") + + bridge->getMaterialContainerName(), 1, + Qt::MatchFlags(Qt::MatchWrap | Qt::MatchExactly + | Qt::MatchRecursive)); + list.append(m_model->match(m_model->index(0, 0), + ObjectListModel::NameRole, + QStringLiteral("materials/") + bridge->getDefaultMaterialName(), 1, + Qt::MatchFlags(Qt::MatchWrap | Qt::MatchExactly + | Qt::MatchRecursive))); + + for (int i = list.size(); i > 0; i--) + m_model->removeRow(list.at(i - 1).row()); + + m_ownerInstance = 0; + m_selection = -1; + + Q_EMIT modelChanged(); +} + +QString ObjectBrowserView::absPath(int index) const +{ + return m_model->index(index, 0).data(ObjectListModel::AbsolutePathRole).toString(); +} + +QString ObjectBrowserView::relPath(int index) const +{ + return m_model->data( + m_model->index(index), + m_model->sourceModel()->indexForHandle(m_ownerInstance), + ObjectListModel::PathReferenceRole).toString(); +} + +bool ObjectBrowserView::selectable(int index) const +{ + auto handleId = m_model->index(index, 0).data(ObjectListModel::HandleRole).toInt(); + auto handle = qt3dsdm::Qt3DSDMInstanceHandle(handleId); + return m_model->sourceModel()->selectable(handle); +} + +void ObjectBrowserView::selectAndExpand(const qt3dsdm::Qt3DSDMInstanceHandle &handle, + const qt3dsdm::Qt3DSDMInstanceHandle &owner) +{ + m_ownerInstance = owner; + QModelIndex index = m_model->sourceIndexForHandle(handle); + if (!index.isValid()) + return; + m_model->expandTo(QModelIndex(), index); + m_blockCommit = true; + setSelection(m_model->rowForSourceIndex(index)); + m_blockCommit = false; +} + +void ObjectBrowserView::setSelection(int index) +{ + if (m_selection != index) { + m_selection = index; + Q_EMIT selectionChanged(); + } +} + +void ObjectBrowserView::setPathType(ObjectBrowserView::PathType type) +{ + if (type != m_pathType) { + m_pathType = type; + Q_EMIT pathTypeChanged(); + } +} + +qt3dsdm::Qt3DSDMInstanceHandle ObjectBrowserView::selectedHandle() const +{ + auto handleId = m_model->index(m_selection, 0).data(ObjectListModel::HandleRole).toInt(); + return qt3dsdm::Qt3DSDMInstanceHandle(handleId); +} + +bool ObjectBrowserView::isFocused() const +{ + return hasFocus(); +} + +void ObjectBrowserView::focusInEvent(QFocusEvent *event) +{ + QQuickWidget::focusInEvent(event); + emit focusChanged(); +} + +void ObjectBrowserView::focusOutEvent(QFocusEvent *event) +{ + QQuickWidget::focusOutEvent(event); + emit focusChanged(); + QTimer::singleShot(0, this, &QQuickWidget::close); +} + +void ObjectBrowserView::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Escape) + QTimer::singleShot(0, this, &ObjectBrowserView::close); + + QQuickWidget::keyPressEvent(event); +} + +void ObjectBrowserView::initialize() +{ + CStudioPreferences::setQmlContextProperties(rootContext()); + rootContext()->setContextProperty(QStringLiteral("_objectBrowserView"), this); + rootContext()->setContextProperty(QStringLiteral("_resDir"), StudioUtils::resourceImageUrl()); + qmlRegisterUncreatableType<ObjectBrowserView>( + "Qt3DStudio", 1, 0, "ObjectBrowserView", + QStringLiteral("Creation of ObjectBrowserView not allowed from QML")); + engine()->addImportPath(StudioUtils::qmlImportPath()); + setSource(QUrl(QStringLiteral("qrc:/Palettes/Inspector/ObjectBrowser.qml"))); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowserView.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowserView.h new file mode 100644 index 00000000..7fb7ec30 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectBrowserView.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ +#ifndef OBJECTBROWSERVIEW_H +#define OBJECTBROWSERVIEW_H + +#include <QQuickWidget> + +#include "RelativePathTools.h" +#include "Qt3DSDMHandles.h" + +#include <QColor> + +class ObjectListModel; +class FlatObjectListModel; + +QT_FORWARD_DECLARE_CLASS(QAbstractItemModel) + +class ObjectBrowserView : public QQuickWidget +{ + Q_OBJECT + Q_PROPERTY(bool focused READ isFocused NOTIFY focusChanged) + Q_PROPERTY(QAbstractItemModel *model READ model NOTIFY modelChanged FINAL) + Q_PROPERTY(int selection READ selection WRITE setSelection NOTIFY selectionChanged FINAL) + Q_PROPERTY(PathType pathType READ pathType WRITE setPathType NOTIFY pathTypeChanged FINAL) + +public: + ObjectBrowserView(QWidget *parent = nullptr); + + + enum PathType { + Absolute = CRelativePathTools::EPATHTYPE_GUID, + Relative = CRelativePathTools::EPATHTYPE_RELATIVE, + }; + Q_ENUM(PathType) + + QAbstractItemModel *model() const; + void setModel(ObjectListModel *model); + + Q_INVOKABLE QString absPath(int index) const; + Q_INVOKABLE QString relPath(int index) const; + Q_INVOKABLE bool selectable(int index) const; + + void selectAndExpand(const qt3dsdm::Qt3DSDMInstanceHandle &handle, + const qt3dsdm::Qt3DSDMInstanceHandle &owner); + + int selection() const { return m_selection; } + void setSelection(int index); + + PathType pathType() const {return m_pathType;} + void setPathType(PathType type); + + qt3dsdm::Qt3DSDMInstanceHandle selectedHandle() const; + + bool canCommit() const { return !m_blockCommit; } + +Q_SIGNALS: + void modelChanged(); + void pathTypeChanged(); + void selectionChanged(); + +protected: + void focusInEvent(QFocusEvent *event) override; + void focusOutEvent(QFocusEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + +Q_SIGNALS: + void focusChanged(); + +private: + void initialize(); + bool isFocused() const; + + FlatObjectListModel *m_model = nullptr; + QHash<int, ObjectListModel *> m_subModels; + QColor m_baseColor = QColor::fromRgb(75, 75, 75); + QColor m_selectColor; + int m_selection = -1; + PathType m_pathType = Absolute; + qt3dsdm::Qt3DSDMInstanceHandle m_ownerInstance = 0; + bool m_blockCommit = false; +}; + +#endif // OBJECTBROWSERVIEW_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectListModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectListModel.cpp new file mode 100644 index 00000000..ad66274a --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectListModel.cpp @@ -0,0 +1,534 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "ObjectListModel.h" + +#include "ClientDataModelBridge.h" +#include "Core.h" +#include "Doc.h" +#include "GraphUtils.h" +#include "IObjectReferenceHelper.h" +#include "StudioUtils.h" +#include "SlideSystem.h" +#include "StudioObjectTypes.h" +#include "StudioPreferences.h" +#include "Qt3DSDMStudioSystem.h" +#include "ClientDataModelBridge.h" + +#include <QCoreApplication> +#include <QColor> + +ObjectListModel::ObjectListModel(CCore *core, + const qt3dsdm::Qt3DSDMInstanceHandle &baseHandle, QObject *parent, + bool isAliasSelectList) + : QAbstractItemModel(parent) + , m_core(core) + , m_baseHandle(baseHandle) + , m_AliasSelectList(isAliasSelectList) +{ + auto doc = m_core->GetDoc(); + m_objRefHelper = doc->GetDataModelObjectReferenceHelper(); + if (!m_AliasSelectList) + m_slideHandle = m_objRefHelper->GetSlideList(m_baseHandle)[0]; + else + m_slideHandle = m_objRefHelper->GetSlideList(m_baseHandle).back(); +} + +QHash<int, QByteArray> ObjectListModel::roleNames() const +{ + auto names = QAbstractItemModel::roleNames(); + names.insert(NameRole, "name"); + names.insert(HandleRole, "handle"); + names.insert(IconRole, "icon"); + names.insert(TextColorRole, "textColor"); + names.insert(AbsolutePathRole, "absolutePath"); + names.insert(PathReferenceRole, "referencePath"); + + return names; +} + +int ObjectListModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) + return 1; + + const auto handle = handleForIndex(parent); + const auto children = childrenList(m_slideHandle, handle); + return int(children.size()); +} + +int ObjectListModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return 1; +} + +QVariant ObjectListModel::data(const QModelIndex &index, int role) const +{ + return data(index, QModelIndex(), role); +} + +QVariant ObjectListModel::data(const QModelIndex &index, + const QModelIndex &startingIndex, + int role) const +{ + if (!hasIndex(index.row(), index.column(), index.parent())) + return {}; + + auto handle = handleForIndex(index); + + auto studioSystem = m_core->GetDoc()->GetStudioSystem(); + auto propertySystem = studioSystem->GetPropertySystem(); + qt3dsdm::SValue typeValue; + propertySystem->GetInstancePropertyValue(handle, + studioSystem->GetClientDataModelBridge() + ->GetTypeProperty(), typeValue); + qt3dsdm::DataModelDataType::Value valueType(qt3dsdm::GetValueType(typeValue)); + if (valueType == qt3dsdm::DataModelDataType::None) + return {}; + + switch (role) { + case NameRole: { + return nameForHandle(handle); + } + case PathReferenceRole: { + Q3DStudio::CString data; + if (startingIndex.isValid()) { + data = m_objRefHelper->GetObjectReferenceString( + handleForIndex(startingIndex), + CRelativePathTools::EPATHTYPE_RELATIVE, + handle); + } else { + data = m_objRefHelper->GetObjectReferenceString( + m_baseHandle, + CRelativePathTools::EPATHTYPE_RELATIVE, + handle); + } + return data.toQString(); + } + case AbsolutePathRole: { + Q3DStudio::CString data(m_objRefHelper->GetObjectReferenceString( + m_baseHandle, CRelativePathTools::EPATHTYPE_GUID, handle)); + return data.toQString(); + } + case HandleRole: { + return (int)handleForIndex(index); + } + case IconRole: { + auto info = m_objRefHelper->GetInfo(handle); + return StudioUtils::resourceImageUrl() + CStudioObjectTypes::GetNormalIconName(info.m_Type); + } + case TextColorRole: { + auto bridge = m_core->GetDoc()->GetStudioSystem()->GetClientDataModelBridge(); + auto objType = bridge->GetObjectType(handle); + auto info = m_objRefHelper->GetInfo(handle); + if (m_excludeTypes.contains(objType)) + return QVariant::fromValue(CStudioPreferences::disabledColor()); + else if (info.m_Master) + return QVariant::fromValue(CStudioPreferences::masterColor()); + else + return QVariant::fromValue(CStudioPreferences::textColor()); + } + default: + return {}; + } + + return {}; +} + +QModelIndex ObjectListModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!parent.isValid()) + return createIndex(row, column, (quintptr)(m_baseHandle)); + + const auto handle = handleForIndex(parent); + const auto children = childrenList(m_slideHandle, handle); + if (row >= children.size()) + return {}; + + auto childHandle = children[row]; + return createIndex(row, column, (quintptr)(childHandle)); +} + +QModelIndex ObjectListModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return {}; + + const auto handle = handleForIndex(index); + qt3dsdm::Qt3DSDMInstanceHandle parentHandle = m_core->GetDoc()->GetAssetGraph()->GetParent(handle); + if (!parentHandle.Valid()) + return {}; + + int row = 0; + qt3dsdm::Qt3DSDMInstanceHandle grandParentHandle = m_core->GetDoc()->GetAssetGraph() + ->GetParent(parentHandle); + if (grandParentHandle.Valid()) { + const auto children = childrenList(m_slideHandle, grandParentHandle); + const auto it = std::find(children.begin(), children.end(), parentHandle); + Q_ASSERT(it != children.end()); + row = it - children.begin(); + } + + return createIndex(row, 0, (quintptr)(parentHandle)); +} + +qt3dsdm::Qt3DSDMInstanceHandle ObjectListModel::handleForIndex(const QModelIndex &index) const +{ + return static_cast<qt3dsdm::Qt3DSDMInstanceHandle>(index.internalId()); +} + +void ObjectListModel::excludeObjectTypes(const QVector<EStudioObjectType> &types) +{ + m_excludeTypes = types; +} + +bool ObjectListModel::selectable(const qt3dsdm::Qt3DSDMInstanceHandle &handle) const +{ + auto bridge = m_core->GetDoc()->GetStudioSystem()->GetClientDataModelBridge(); + auto objType = bridge->GetObjectType(handle); + // disallow aliasing the current active root + bool tryingToAliasParent = (m_core->GetDoc()->GetActiveRootInstance() == handle) + && m_AliasSelectList; + return (!m_excludeTypes.contains(objType) && !tryingToAliasParent); +} + +qt3dsdm::TInstanceHandleList ObjectListModel::childrenList( + const qt3dsdm::Qt3DSDMSlideHandle &slideHandle, + const qt3dsdm::Qt3DSDMInstanceHandle &handle) const +{ + auto studioSystem = m_core->GetDoc()->GetStudioSystem(); + auto slideSystem = studioSystem->GetSlideSystem(); + auto currentMaster = slideSystem->GetMasterSlide(slideHandle); + + qt3dsdm::TInstanceHandleList children; + m_objRefHelper->GetChildInstanceList(handle, children, slideHandle, m_baseHandle, true); + // allow action trigger/target from all objects + if (m_AliasSelectList) { + children.erase( + std::remove_if(children.begin(), children.end(), + [&slideHandle, slideSystem, ¤tMaster](const qt3dsdm::Qt3DSDMInstanceHandle &h) { + const auto childSlide = slideSystem->GetAssociatedSlide(h); + if (!childSlide.Valid()) + return true; + const auto childMaster = slideSystem->GetMasterSlide(childSlide); + if (childMaster == currentMaster) { + return childSlide != childMaster && childSlide != slideHandle; + } else { + return childSlide != childMaster; + } + }), children.end()); + } + return children; +} + +QString ObjectListModel::nameForHandle(const qt3dsdm::Qt3DSDMInstanceHandle &handle) const +{ + const auto data = m_objRefHelper->GetInfo(handle); + return data.m_Name.toQString(); +} + +QModelIndex ObjectListModel::indexForHandle(const qt3dsdm::Qt3DSDMInstanceHandle &handle, + const QModelIndex &startIndex) const +{ + if (handle == m_baseHandle) + return index(0, 0, {}); + + for (int i = 0; i < rowCount(startIndex); i++) { + auto idx = index(i, 0, startIndex); + if (static_cast<qt3dsdm::Qt3DSDMInstanceHandle>(idx.internalId()) == handle) + return idx; + if (rowCount(idx) > 0) { + QModelIndex foundIndex = indexForHandle(handle, idx); + if (foundIndex.isValid()) + return foundIndex; + } + } + return {}; +} + + +FlatObjectListModel::FlatObjectListModel(ObjectListModel *sourceModel, QObject *parent) + : QAbstractListModel(parent) + +{ + Q_ASSERT(sourceModel); + setSourceModel(sourceModel); +} + +QVector<FlatObjectListModel::SourceInfo> FlatObjectListModel::collectSourceIndexes( + const QModelIndex &sourceIndex, int depth) const +{ + QVector<SourceInfo> sourceInfo; + + for (int i = 0; i < m_sourceModel->rowCount(sourceIndex); i++) { + auto idx = m_sourceModel->index(i, 0, sourceIndex); + SourceInfo info; + info.depth = depth; + info.index = idx; + sourceInfo.append(info); + if (m_sourceModel->rowCount(idx) > 0) { + sourceInfo += collectSourceIndexes(idx, depth + 1); + } + } + + return sourceInfo; +} + +QHash<int, QByteArray> FlatObjectListModel::roleNames() const +{ + auto roles = m_sourceModel->roleNames(); + roles.insert(DepthRole, "depth"); + roles.insert(ExpandedRole, "expanded"); + roles.insert(ParentExpandedRole, "parentExpanded"); + roles.insert(HasChildrenRole, "hasChildren"); + return roles; +} + +QModelIndex FlatObjectListModel::mapToSource(const QModelIndex &proxyIndex) const +{ + int row = proxyIndex.row(); + if (row < 0 || row >= m_sourceInfo.count()) + return {}; + return m_sourceInfo[row].index; +} + +QModelIndex FlatObjectListModel::mapFromSource(const QModelIndex &sourceIndex) const +{ + return index(rowForSourceIndex(sourceIndex)); +} + +QVariant FlatObjectListModel::data(const QModelIndex &index, int role) const +{ + return data(index, QModelIndex(), role); +} + +QVariant FlatObjectListModel::data(const QModelIndex &index, + const QModelIndex &startingIndex, + int role) const +{ + const auto row = index.row(); + if (row < 0 || row >= m_sourceInfo.count()) + return {}; + + switch (role) { + case DepthRole: { + auto info = m_sourceInfo[row]; + return info.depth; + } + case ExpandedRole: { + auto info = m_sourceInfo[row]; + return info.expanded; + } + case ParentExpandedRole: { + auto info = m_sourceInfo[row]; + if (info.depth == 0) + return true; + + int depth = info.depth; + for (int i = row - 1; i >= 0; i--) { + const auto prevInfo = m_sourceInfo[i]; + if (prevInfo.depth < depth) { + if (!prevInfo.expanded) { + return false; + } else { + depth = prevInfo.depth; + } + } + } + return true; + } + case HasChildrenRole: { + if (row == m_sourceInfo.count() - 1) + return false; + auto info = m_sourceInfo[row]; + auto nextInfo = m_sourceInfo[row + 1]; + return (nextInfo.depth > info.depth); + } + } + + QModelIndex sourceIndex = mapToSource(index); + if (startingIndex.isValid()) + return m_sourceModel->data(sourceIndex, startingIndex, role); + else + return m_sourceModel->data(sourceIndex, QModelIndex(), role); + +} + +bool FlatObjectListModel::setData(const QModelIndex &index, const QVariant &data, int role) +{ + const auto row = index.row(); + if (row < 0 || row >= m_sourceInfo.count()) + return {}; + + switch (role) { + case ExpandedRole: { + auto info = &m_sourceInfo[index.row()]; + info->expanded = data.toBool(); + Q_EMIT dataChanged(this->index(0, 0), this->index(rowCount() - 1, 0), {}); + return true; + } + } + + QModelIndex sourceIndex = mapToSource(index); + return m_sourceModel->setData(sourceIndex, data, role); +} + +int FlatObjectListModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return m_sourceInfo.count(); +} + +bool FlatObjectListModel::removeRows(int row, int count, const QModelIndex &parent) +{ + beginRemoveRows(parent, row, row + count - 1); + m_sourceInfo.remove(row, count); + endRemoveRows(); + return true; +} + +void FlatObjectListModel::setSourceModel(ObjectListModel *sourceModel) +{ + beginResetModel(); + sourceModel->disconnect(this); + connect(sourceModel, &QAbstractListModel::dataChanged, this, + [this](const QModelIndex &start, const QModelIndex &end, const QVector<int> &roles) { + emit dataChanged(mapFromSource(start), mapFromSource(end), roles); + + }); + connect(sourceModel, &QAbstractListModel::rowsInserted, this, + [this](const QModelIndex &parent, int start, int end) { + const int parentRow = rowForSourceIndex(parent); + const int depth = m_sourceInfo[parentRow].depth + 1; + const int startRow = rowForSourceIndex(parent, start); + Q_ASSERT(startRow != -1); + beginInsertRows({}, startRow, startRow + end - start); + for (int row = end; row >= start; --row) { + SourceInfo info; + info.depth = depth; + info.index = m_sourceModel->index(row, 0, parent); + m_sourceInfo.insert(startRow, info); + } + endInsertRows(); + }); + connect(sourceModel, &QAbstractListModel::rowsRemoved, this, + [this](const QModelIndex &parent, int start, int end) { + const int startRow = rowForSourceIndex(parent, start); + const int endRow = rowForSourceIndex(parent, end); + Q_ASSERT(startRow != -1 && endRow != -1); + beginRemoveRows({}, startRow, endRow); + m_sourceInfo.remove(startRow, endRow - startRow + 1); + endRemoveRows(); + }); + connect(sourceModel, &QAbstractListModel::modelReset, this, + [this]() { + beginResetModel(); + m_sourceInfo = collectSourceIndexes({}, 0); + endResetModel(); + }); + + connect(sourceModel, &ObjectListModel::roleUpdated, this, [this](int role) { + emit dataChanged(index(0,0), index(rowCount() - 1, 0), {role}); + }); + + connect(sourceModel, &ObjectListModel::rolesUpdated, this, + [this](const QVector<int> &roles = QVector<int> ()) { + emit dataChanged(index(0,0), index(rowCount() - 1, 0), roles); + }); + + + m_sourceModel = sourceModel; + m_sourceInfo = collectSourceIndexes({}, 0); + endResetModel(); +} + +// startIndex and searchIndex are source indexes +bool FlatObjectListModel::expandTo(const QModelIndex &startIndex, const QModelIndex &searchIndex) +{ + // Found the index we are looking for. We don't want to expand it, so just return true. + if (startIndex == searchIndex) + return true; + + // Look for the search index in children + const int rowCount = m_sourceModel->rowCount(startIndex); + for (int i = 0; i < rowCount; i++) { + auto idx = m_sourceModel->index(i, 0, startIndex); + if (idx == searchIndex) { + // Expand startIndex as that is the parent + setData(index(rowForSourceIndex(startIndex)), QVariant(true), ExpandedRole); + return true; + } + if (m_sourceModel->rowCount(idx) > 0) { + bool found = expandTo(idx, searchIndex); + if (found) { + // Found by some descendant. Keep expanding parents + setData(index(rowForSourceIndex(startIndex)), QVariant(true), ExpandedRole); + return true; + } + } + } + return false; +} + +int FlatObjectListModel::rowForSourceIndex(const QModelIndex &sourceIndex) const +{ + for (int i = 0; i < m_sourceInfo.size(); i++) { + if (m_sourceInfo[i].index == sourceIndex) + return i; + } + return -1; +} + +int FlatObjectListModel::rowForSourceIndex(const QModelIndex& parentIndex, int row) const +{ + const int parentRow = rowForSourceIndex(parentIndex); + if (parentRow == -1) + return -1; + const int childDepth = m_sourceInfo[parentRow].depth + 1; + int i = parentRow + 1; + while (i < m_sourceInfo.size()) { + const auto& info = m_sourceInfo[i]; + if (info.depth < childDepth) + break; + if (info.depth == childDepth) { + if (row == 0) + break; + --row; + } + ++i; + } + return i; +} + +QModelIndex FlatObjectListModel::sourceIndexForHandle(const qt3dsdm::Qt3DSDMInstanceHandle &handle) +{ + return m_sourceModel->indexForHandle(handle); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectListModel.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectListModel.h new file mode 100644 index 00000000..4013f15c --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/ObjectListModel.h @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef OBJECTLISTMODEL_H +#define OBJECTLISTMODEL_H + +#include <QAbstractItemModel> +#include <QAbstractListModel> + +#include "Qt3DSDMHandles.h" +#include "StudioObjectTypes.h" + +class IObjectReferenceHelper; +class CCore; + +class ObjectListModel : public QAbstractItemModel +{ + Q_OBJECT +public: + ObjectListModel(CCore *core, const qt3dsdm::Qt3DSDMInstanceHandle &baseHandle, + QObject *parent = nullptr, + bool isAliasSelectList = false); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant data(const QModelIndex &index, + const QModelIndex &startingIndex = {}, + int role = Qt::DisplayRole) const; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + + enum Roles { + NameRole = Qt::DisplayRole, + AbsolutePathRole = Qt::UserRole + 1, + PathReferenceRole, + IconRole, + TextColorRole, + HandleRole, + LastRole = HandleRole + }; + + QHash<int, QByteArray> roleNames() const override; + + qt3dsdm::Qt3DSDMInstanceHandle baseHandle() const {return m_baseHandle;} + + QModelIndex indexForHandle(const qt3dsdm::Qt3DSDMInstanceHandle &handle, + const QModelIndex &startIndex = {}) const; + + bool selectable(const qt3dsdm::Qt3DSDMInstanceHandle &handle) const; + + void excludeObjectTypes(const QVector<EStudioObjectType> &types); + +Q_SIGNALS: + void roleUpdated(int role); + void rolesUpdated(const QVector<int> &roles = QVector<int> ()); + +protected: + qt3dsdm::Qt3DSDMInstanceHandle handleForIndex(const QModelIndex &index) const; + + virtual qt3dsdm::TInstanceHandleList childrenList(const qt3dsdm::Qt3DSDMSlideHandle &slideHandle, + const qt3dsdm::Qt3DSDMInstanceHandle &handle) const; + + QString nameForHandle(const qt3dsdm::Qt3DSDMInstanceHandle &handle) const; + + CCore *m_core; + qt3dsdm::Qt3DSDMSlideHandle m_slideHandle; + qt3dsdm::Qt3DSDMInstanceHandle m_baseHandle; + IObjectReferenceHelper *m_objRefHelper; + QVector<EStudioObjectType> m_excludeTypes; + bool m_AliasSelectList; +}; + +class FlatObjectListModel : public QAbstractListModel +{ + Q_OBJECT + +public: + // TODO Make FlatObjectList take a shared pointer + FlatObjectListModel(ObjectListModel *sourceModel, QObject *parent = nullptr); + + enum Roles { + DepthRole = ObjectListModel::LastRole + 1, + ExpandedRole, + ParentExpandedRole, + HasChildrenRole + }; + + QHash<int, QByteArray> roleNames() const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant data(const QModelIndex &index, + const QModelIndex &startingIndex = {}, + int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &data, int role = Qt::EditRole) override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; + + void setSourceModel(ObjectListModel *sourceModel); + ObjectListModel *sourceModel() const { return m_sourceModel; } + bool expandTo(const QModelIndex &rootIndex, const QModelIndex &searchIndex); + int rowForSourceIndex(const QModelIndex &sourceIndex) const; + QModelIndex sourceIndexForHandle(const qt3dsdm::Qt3DSDMInstanceHandle &handle); + +private: + int rowForSourceIndex(const QModelIndex &parentIndex, int row) const; + QModelIndex mapToSource(const QModelIndex &proxyIndex) const; + QModelIndex mapFromSource(const QModelIndex &sourceIndex) const; + + struct SourceInfo { + bool expanded = false; + int depth = 0; + QPersistentModelIndex index; + }; + + QVector<SourceInfo> collectSourceIndexes(const QModelIndex &sourceIndex, int depth) const; + + QVector<SourceInfo> m_sourceInfo; + ObjectListModel *m_sourceModel = nullptr; +}; + + +#endif // OBJECTLISTMODEL_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectable.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectable.cpp new file mode 100644 index 00000000..a82df79c --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectable.cpp @@ -0,0 +1,225 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSDMInspectable.h" +#include "Qt3DSDMInspectorGroup.h" +#include "StudioApp.h" +#include "Core.h" +#include "Doc.h" +#include "Qt3DSDMStudioSystem.h" +#include "ClientDataModelBridge.h" +#include "Qt3DSDMSlides.h" +#include "IDocumentReader.h" + +using namespace qt3dsdm; + +Qt3DSDMInspectable::Qt3DSDMInspectable(qt3dsdm::Qt3DSDMInstanceHandle instance, + qt3dsdm::Qt3DSDMInstanceHandle activeSlideInstance) + : m_instance(instance) + , m_activeSlideInstance(activeSlideInstance) +{ + QT3DS_ASSERT(getDoc()->GetDocumentReader().IsInstance(m_instance)); + + if (m_activeSlideInstance) { + // only active root scene or components set m_activeSlideInstance + auto *bridge = getDoc()->GetStudioSystem()->GetClientDataModelBridge(); + QT3DS_ASSERT(bridge->IsSceneInstance(instance) + || bridge->IsComponentInstance(instance)); + } +} + +// Returns the name of this inspectable +Q3DStudio::CString Qt3DSDMInspectable::getName() +{ + auto *bridge = getDoc()->GetStudioSystem()->GetClientDataModelBridge(); + + if (!m_activeSlideInstance) + return bridge->GetName(m_instance, true); + + Q3DStudio::CString theName = bridge->GetName(m_instance, true); + theName += " ("; + theName += bridge->GetName(m_activeSlideInstance, true); + theName += ")"; + + return theName; +} + +// Returns the number of groups in this inspectable +long Qt3DSDMInspectable::getGroupCount() const +{ + IMetaData &theMetaData = *getDoc()->GetStudioSystem()->GetActionMetaData(); + // In addition to a background group, Scene has a basic properties group (hidden in + // inspector) because it is derived from Asset. Until this is fixed properly, we force the + // Scene groups count to 1 (else an empty group will appear in the inspector). + long count = getObjectType() == OBJTYPE_SCENE ? 1 + : long(theMetaData.GetGroupCountForInstance(m_instance)); + + if (m_activeSlideInstance) + count += long(theMetaData.GetGroupCountForInstance(m_activeSlideInstance)); + + return count; +} + +// Return the property group for display +CInspectorGroup *Qt3DSDMInspectable::getGroup(long inIndex) +{ + Qt3DSDMInspectorGroup *group = new Qt3DSDMInspectorGroup(GetGroupName(inIndex)); + + TMetaDataPropertyHandleList properties = GetGroupProperties(inIndex); + + for (auto &prop : properties) + group->CreateRow(getDoc(), prop); + + return group; +} + +// Return the property handles for display, given the group index +TMetaDataPropertyHandleList Qt3DSDMInspectable::GetGroupProperties(long inIndex) +{ + long activeGroupIdx = activeGroupIndex(inIndex); + TMetaDataPropertyHandleList retval; + IMetaData &theMetaData = *getDoc()->GetStudioSystem()->GetActionMetaData(); + theMetaData.GetMetaDataProperties(GetGroupInstance(inIndex), retval); + qt3dsdm::IPropertySystem &thePropertySystem(*getDoc()->GetStudioSystem()->GetPropertySystem()); + // get name of the current group for filtering + Option<qt3dsdm::TCharStr> theGroupFilterName = + theMetaData.GetGroupFilterNameForInstance(GetGroupInstance(inIndex), activeGroupIdx); + long theGroupCount = getGroupCount(); + + // end is explicitly required + for (size_t idx = 0; idx < retval.size(); ++idx) { + if (theMetaData.GetMetaDataPropertyInfo(retval[idx])->m_IsHidden) { + retval.erase(retval.begin() + idx); + --idx; + } else if (theGroupCount > 1 && theGroupFilterName.hasValue() + && theMetaData.GetMetaDataPropertyInfo(retval[idx])->m_GroupName + != theGroupFilterName) { + retval.erase(retval.begin() + idx); + --idx; + } else { + qt3ds::foundation::NVConstDataRef<SPropertyFilterInfo> theFilters( + theMetaData.GetMetaDataPropertyFilters(retval[idx])); + if (theFilters.size()) { + Option<bool> keepProperty; + // The tests are done in an ambiguous way. Really, show if equal should take + // multiple conditions + // as should hide if equal. They do not, so we need to rigorously define exactly + // how those two interact. + for (QT3DSU32 propIdx = 0, propEnd = theFilters.size(); propIdx < propEnd; + ++propIdx) { + const SPropertyFilterInfo &theFilter(theFilters[propIdx]); + + QT3DS_ASSERT(theFilter.m_FilterType == PropertyFilterTypes::ShowIfEqual + || theFilter.m_FilterType == PropertyFilterTypes::HideIfEqual); + + SValue theValue; + thePropertySystem.GetInstancePropertyValue( + GetGroupInstance(inIndex), theFilter.m_FilterProperty, theValue); + bool resultIfTrue = theFilter.m_FilterType == PropertyFilterTypes::ShowIfEqual; + if (Equals(theValue.toOldSkool(), theFilter.m_Value.toOldSkool())) { + keepProperty = resultIfTrue; + break; + } else { + keepProperty = !resultIfTrue; + } + } + if (keepProperty.hasValue() && *keepProperty == false) { + retval.erase(retval.begin() + idx); + --idx; + } + } + } + } + return retval; +} + +// Return the Group Name, given the group index +QString Qt3DSDMInspectable::GetGroupName(long groupIndex) +{ + std::vector<TCharStr> theGroupNames; + IMetaData &theMetaData = *getDoc()->GetStudioSystem()->GetActionMetaData(); + theMetaData.GetGroupNamesForInstance(GetGroupInstance(groupIndex), theGroupNames); + + long activeGroupIdx = activeGroupIndex(groupIndex); + if (activeGroupIdx < theGroupNames.size()) + return Q3DStudio::CString(theGroupNames[activeGroupIdx].wide_str()).toQString(); + + return QObject::tr("Basic Properties"); +} + +// Return the Inspectable Instance Handle for the Group, given the group index +Qt3DSDMInstanceHandle Qt3DSDMInspectable::GetGroupInstance(long inGroupIndex) +{ + // if active root, return the slide instance at first index + if (m_activeSlideInstance && inGroupIndex == 0) + return m_activeSlideInstance; + + return m_instance; +} + +EStudioObjectType Qt3DSDMInspectable::getObjectType() const +{ + return getDoc()->GetStudioSystem()->GetClientDataModelBridge()->GetObjectType(m_instance); +} + +bool Qt3DSDMInspectable::isValid() const +{ + if (m_activeSlideInstance) { + return getDoc()->GetStudioSystem()->IsInstance(m_instance) + && getDoc()->GetStudioSystem()->IsInstance(m_activeSlideInstance); + } + return getDoc()->GetStudioSystem()->IsInstance(m_instance); +} + +bool Qt3DSDMInspectable::isMaster() const +{ + ISlideSystem *slideSystem = getDoc()->GetStudioSystem()->GetSlideSystem(); + qt3dsdm::Qt3DSDMSlideHandle theSlideHandle = slideSystem->GetAssociatedSlide(m_instance); + if (theSlideHandle.Valid()) + return slideSystem->IsMasterSlide(theSlideHandle); + // Slide handle may not be valid if we are selecting the Scene or if we are inside Component and + // we select the Component root. + return false; +} + +// Returns the group index taking into consideration that for active roots, first index is the slide +// group so need to decrement all index bigger than 1, by 1. For scene we decrement 1 more because +// the first group (Basic properties) is not in use. +long Qt3DSDMInspectable::activeGroupIndex(long groupIndex) const +{ + if (m_activeSlideInstance && groupIndex > 0 && getObjectType() != OBJTYPE_SCENE) + return groupIndex - 1; + + return groupIndex; +} + +CDoc *Qt3DSDMInspectable::getDoc() const +{ + return g_StudioApp.GetCore()->GetDoc(); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectable.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectable.h new file mode 100644 index 00000000..0da2916d --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectable.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef INCLUDED_QT3DSDM_INSPECTABLE_H +#define INCLUDED_QT3DSDM_INSPECTABLE_H + +#include "InspectableBase.h" +#include "Qt3DSDMHandles.h" + +class CDoc; + +// For inspecting data model instances +class Qt3DSDMInspectable : public CInspectableBase +{ +public: + Qt3DSDMInspectable(qt3dsdm::Qt3DSDMInstanceHandle inInstance, + qt3dsdm::Qt3DSDMInstanceHandle activeSlideInstance = 0); + + Q3DStudio::CString getName() override; + long getGroupCount() const override; + CInspectorGroup *getGroup(long) override; + EStudioObjectType getObjectType() const override; + bool isValid() const override; + bool isMaster() const override; + qt3dsdm::Qt3DSDMInstanceHandle getInstance() const override { return m_instance; } + virtual qt3dsdm::TMetaDataPropertyHandleList GetGroupProperties(long inGroupIndex); + virtual qt3dsdm::Qt3DSDMInstanceHandle GetGroupInstance(long inGroupIndex); + +protected: + qt3dsdm::Qt3DSDMInstanceHandle m_instance; + qt3dsdm::Qt3DSDMInstanceHandle m_activeSlideInstance; + + virtual QString GetGroupName(long inGroupIndex); + CDoc *getDoc() const; + long activeGroupIndex(long groupIndex) const; +}; + +#endif diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorGroup.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorGroup.cpp new file mode 100644 index 00000000..f62812ac --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorGroup.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2002 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSDMInspectorGroup.h" +#include "Qt3DSDMInspectorRow.h" +#include "Qt3DSDMInspectable.h" +#include "Qt3DSDMMetaData.h" + +Qt3DSDMInspectorGroup::Qt3DSDMInspectorGroup(const QString &inName) + : CInspectorGroup(inName) +{ +} + +Qt3DSDMInspectorGroup::~Qt3DSDMInspectorGroup() +{ + for (auto it = m_inspectorRows.begin(); it != m_inspectorRows.end(); ++it) + delete (*it); +} + +// Create a new InspectorRowBase. +void Qt3DSDMInspectorGroup::CreateRow(CDoc *inDoc, + qt3dsdm::Qt3DSDMMetaDataPropertyHandle inProperty) +{ + Q3DStudio::Qt3DSDMInspectorRow *theRow = new Q3DStudio::Qt3DSDMInspectorRow(inDoc, inProperty); + m_inspectorRows.push_back(theRow); // this Qt3DSDMInspectorRow is now owned by this class +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorGroup.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorGroup.h new file mode 100644 index 00000000..c2bbc9fc --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorGroup.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2002 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef INCLUDED_QT3DSDM_INSPECTORGROUP_H +#define INCLUDED_QT3DSDM_INSPECTORGROUP_H + +#include "InspectorGroup.h" +#include "Qt3DSDMHandles.h" + +class Qt3DSDMInspectable; +class CDoc; + +namespace Q3DStudio { +class Qt3DSDMInspectorRow; +}; + +class Qt3DSDMInspectorGroup : public CInspectorGroup +{ +public: + Qt3DSDMInspectorGroup(const QString &inName); + ~Qt3DSDMInspectorGroup(); + + void CreateRow(CDoc *inDoc, qt3dsdm::Qt3DSDMMetaDataPropertyHandle inProperty); + + const std::vector<Q3DStudio::Qt3DSDMInspectorRow *> &GetRows() const { return m_inspectorRows; } + +protected: + std::vector<Q3DStudio::Qt3DSDMInspectorRow *> m_inspectorRows; +}; + +#endif diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorRow.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorRow.cpp new file mode 100644 index 00000000..3f629df4 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorRow.cpp @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSDMInspectorRow.h" +#include "Qt3DSDMMetaData.h" +#include "Doc.h" +#include "Qt3DSDMStudioSystem.h" + +using namespace qt3dsdm; + +namespace Q3DStudio { + +Qt3DSDMInspectorRow::Qt3DSDMInspectorRow(CDoc *inDoc, Qt3DSDMMetaDataPropertyHandle inProperty) + : m_MetaProperty(inProperty) +{ + IMetaData *theMetaData = inDoc->GetStudioSystem()->GetActionMetaData(); + SMetaDataPropertyInfo theInfo(theMetaData->GetMetaDataPropertyInfo(inProperty)); + m_MetaDataPropertyInfo = theInfo; +} + +Qt3DSDMInspectorRow::~Qt3DSDMInspectorRow() +{ +} + +} // namespace Q3DStudio diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorRow.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorRow.h new file mode 100644 index 00000000..6c8156c1 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMInspectorRow.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ +#pragma once + +#include "Qt3DSDMHandles.h" +#include "Qt3DSDMMetaDataTypes.h" + +class CDoc; + +namespace Q3DStudio { + +// This is a binding between a DataModelInspectable and an InspectorRow +class Qt3DSDMInspectorRow +{ +public: + explicit Qt3DSDMInspectorRow(CDoc *inDoc, qt3dsdm::Qt3DSDMMetaDataPropertyHandle inProperty); + virtual ~Qt3DSDMInspectorRow(); + + qt3dsdm::Qt3DSDMMetaDataPropertyHandle GetMetaDataProperty() const { return m_MetaProperty; } + + const qt3dsdm::SMetaDataPropertyInfo &GetMetaDataPropertyInfo() const + { + return m_MetaDataPropertyInfo; + } + +protected: + qt3dsdm::Qt3DSDMMetaDataPropertyHandle m_MetaProperty; + qt3dsdm::SMetaDataPropertyInfo m_MetaDataPropertyInfo; +}; + +} // namespace Q3DStudio diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMMaterialInspectable.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMMaterialInspectable.cpp new file mode 100644 index 00000000..69d86623 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMMaterialInspectable.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSDMMaterialInspectable.h" + +using namespace qt3dsdm; + +Qt3DSDMMaterialInspectorGroup::Qt3DSDMMaterialInspectorGroup(const QString &inName) + : Qt3DSDMInspectorGroup(inName) + , m_isMaterialGroup(inName == QLatin1String("Material")) +{ +} + +CInspectorGroup *Qt3DSDMMaterialInspectable::getGroup(long inIndex) +{ + Qt3DSDMInspectorGroup *group = new Qt3DSDMMaterialInspectorGroup(GetGroupName(inIndex)); + + TMetaDataPropertyHandleList properties = GetGroupProperties(inIndex); + + for (auto &prop : properties) + group->CreateRow(getDoc(), prop); + + return group; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMMaterialInspectable.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMMaterialInspectable.h new file mode 100644 index 00000000..0367bb7a --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/Qt3DSDMMaterialInspectable.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef INCLUDED_QT3DSDM_MATERIAL_INSPECTABLE_H +#define INCLUDED_QT3DSDM_MATERIAL_INSPECTABLE_H + +#include "Qt3DSDMInspectable.h" +#include "Qt3DSDMInspectorGroup.h" + +class Qt3DSDMMaterialInspectorGroup : public Qt3DSDMInspectorGroup +{ +public: + Qt3DSDMMaterialInspectorGroup(const QString &inName); + + bool isMaterialGroup() const { return m_isMaterialGroup; } + +private: + bool m_isMaterialGroup = false; +}; + +class Qt3DSDMMaterialInspectable : public Qt3DSDMInspectable +{ +public: + Qt3DSDMMaterialInspectable(qt3dsdm::Qt3DSDMInstanceHandle inInstance) + : Qt3DSDMInspectable(inInstance) + { + } + + CInspectorGroup *getGroup(long) override; +}; + +#endif diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/TabOrderHandler.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/TabOrderHandler.cpp new file mode 100644 index 00000000..1f4b8632 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/TabOrderHandler.cpp @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "TabOrderHandler.h" + +TabOrderHandler::TabOrderHandler(QObject *parent) + : QObject(parent) +{ + +} + +TabOrderHandler::~TabOrderHandler() +{ + +} + +void TabOrderHandler::addItem(int group, QQuickItem *item) +{ + m_itemMap[group].append(item); +} + +void TabOrderHandler::clear() +{ + m_itemMap.clear(); +} + +void TabOrderHandler::clearGroup(int group) +{ + m_itemMap[group].clear(); +} + +void TabOrderHandler::tabNavigate(bool tabForward) +{ + // Find the currently focused control + for (int i = 0; i < m_itemMap.size(); i++) { + const QList<QQuickItem *> items = m_itemMap[i]; + for (int j = 0; j < items.size(); j++) { + if (items[j]->hasActiveFocus()) { + if (tabForward) + nextItem(i, j)->forceActiveFocus(Qt::TabFocusReason); + else + previousItem(i, j)->forceActiveFocus(Qt::BacktabFocusReason); + return; + } + } + } + // Activate the first item if could not find currently focused item + for (int i = 0; i < m_itemMap.size(); i++) { + if (m_itemMap[i].size() > 0) + m_itemMap[i][0]->forceActiveFocus(Qt::TabFocusReason); + } +} + +QQuickItem *TabOrderHandler::nextItem(int group, int index) +{ + if (m_itemMap[group].size() > index + 1) { + // Try next item in group + index++; + } else { + // Get item in next available group + int nextGroup = group + 1; + while (nextGroup != group) { + if (nextGroup >= m_itemMap.size()) + nextGroup = 0; + if (m_itemMap[nextGroup].size() == 0) + nextGroup++; + else + group = nextGroup; + } + index = 0; + } + return m_itemMap[group][index]; +} + +QQuickItem *TabOrderHandler::previousItem(int group, int index) +{ + if (index - 1 >= 0) { + // Try previous item in group + index--; + } else { + // Get last item in previous available group + int nextGroup = group - 1; + while (nextGroup != group) { + if (nextGroup < 0) + nextGroup = m_itemMap.size() - 1; + if (m_itemMap[nextGroup].size() == 0) + nextGroup--; + else + group = nextGroup; + } + index = m_itemMap[group].size() - 1; + } + return m_itemMap[group][index]; +} + diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/TabOrderHandler.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/TabOrderHandler.h new file mode 100644 index 00000000..10c1d400 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/TabOrderHandler.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ +#ifndef TABORDERHANDLER_H +#define TABORDERHANDLER_H + +#include <QtCore/qobject.h> +#include <QtQuick/qquickitem.h> + +class TabOrderHandler : public QObject +{ + Q_OBJECT + +public: + explicit TabOrderHandler(QObject *parent = nullptr); + ~TabOrderHandler(); + + Q_INVOKABLE void addItem(int group, QQuickItem *item); + Q_INVOKABLE void clear(); + Q_INVOKABLE void clearGroup(int group); + + void tabNavigate(bool tabForward); + +private: + QQuickItem *nextItem(int group, int index); + QQuickItem *previousItem(int group, int index); + + QHash<int, QList<QQuickItem *> > m_itemMap; +}; + +class TabNavigable { +public: + TabNavigable() : m_tabOrderHandler(new TabOrderHandler) {} + virtual ~TabNavigable() { delete m_tabOrderHandler; } + TabOrderHandler *tabOrderHandler() const { return m_tabOrderHandler; } + +private: + TabOrderHandler *m_tabOrderHandler; +}; + +#endif // TABORDERHANDLER_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooser.qml b/src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooser.qml new file mode 100644 index 00000000..2a406257 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooser.qml @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +Rectangle { + id: root + + color: _backgroundColor + border.color: _studioColor3 + + ColumnLayout { + anchors.fill: parent + + spacing: 10 + ListView { + id: listView + + Layout.fillWidth: true + Layout.fillHeight: true + Layout.margins: 4 + + boundsBehavior: Flickable.StopAtBounds + spacing: 4 + clip: true + + ScrollBar.vertical: ScrollBar {} + + model: _textureChooserModel + + delegate: ChooserDelegate { + onClicked: { + _textureChooserView.textureSelected(_textureChooserView.handle, + _textureChooserView.instance, filePath); + } + onDoubleClicked: { + _textureChooserView.textureSelected(_textureChooserView.handle, + _textureChooserView.instance, filePath); + _textureChooserView.hide(); + } + } + } + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooserView.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooserView.cpp new file mode 100644 index 00000000..8804e675 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooserView.cpp @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "TextureChooserView.h" +#include "ImageChooserModel.h" +#include "StudioPreferences.h" +#include "Literals.h" +#include "StudioUtils.h" +#include "IDocumentEditor.h" +#include "Qt3DSDMStudioSystem.h" +#include "Qt3DSDMValue.h" +#include "Core.h" +#include "Doc.h" +#include "StudioApp.h" +#include "StudioPreferences.h" + +#include <QtCore/qtimer.h> +#include <QtQml/qqmlcontext.h> +#include <QtQml/qqmlengine.h> + +TextureChooserView::TextureChooserView(QWidget *parent) + : QQuickWidget(parent) + , m_model(new ImageChooserModel(false, this)) +{ + setWindowTitle(tr("Texture")); + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + setResizeMode(QQuickWidget::SizeRootObjectToView); + QTimer::singleShot(0, this, &TextureChooserView::initialize); +} + +void TextureChooserView::initialize() +{ + CStudioPreferences::setQmlContextProperties(rootContext()); + rootContext()->setContextProperty(QStringLiteral("_resDir"), StudioUtils::resourceImageUrl()); + rootContext()->setContextProperty(QStringLiteral("_textureChooserView"), this); + rootContext()->setContextProperty(QStringLiteral("_textureChooserModel"), m_model); + engine()->addImportPath(StudioUtils::qmlImportPath()); + setSource(QUrl(QStringLiteral("qrc:/Palettes/Inspector/TextureChooser.qml"))); +} + +void TextureChooserView::setHandle(int handle) +{ + m_handle = handle; +} + +int TextureChooserView::handle() const +{ + return m_handle; +} + +void TextureChooserView::setInstance(int instance) +{ + m_instance = instance; +} + +int TextureChooserView::instance() const +{ + return m_instance; +} + +QString TextureChooserView::currentDataModelPath() const +{ + QString cleanPath; + const auto doc = g_StudioApp.GetCore()->GetDoc(); + const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem(); + + qt3dsdm::SValue value; + propertySystem->GetInstancePropertyValue(m_instance, m_handle, value); + + const QString path = qt3dsdm::get<QString>(value); + + // An empty value can sometimes be represented by a relative path either to project root or the + // presentation file, such as"./" or "../", so let's just consider all directory paths as empty + if (path.isEmpty() || QFileInfo(path).isDir()) { + cleanPath = ChooserModelBase::noneString(); + } else { + // If path is renderable id, we need to resolve the actual path + const QString renderablePath = g_StudioApp.getRenderableAbsolutePath(path); + + if (renderablePath.isEmpty()) + cleanPath = path; + else + cleanPath = renderablePath; + + cleanPath = QDir::cleanPath(QDir(doc->GetDocumentDirectory()).filePath(cleanPath)); + } + return cleanPath; +} + +void TextureChooserView::updateSelection() +{ + m_model->setCurrentFile(currentDataModelPath()); +} + +void TextureChooserView::focusOutEvent(QFocusEvent *event) +{ + QQuickWidget::focusOutEvent(event); + QTimer::singleShot(0, this, &TextureChooserView::close); +} + +void TextureChooserView::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Escape) + QTimer::singleShot(0, this, &TextureChooserView::close); + + QQuickWidget::keyPressEvent(event); +} + +void TextureChooserView::showEvent(QShowEvent *event) +{ + updateSelection(); + QQuickWidget::showEvent(event); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooserView.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooserView.h new file mode 100644 index 00000000..156b2465 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/TextureChooserView.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef TEXTURECHOOSERVIEW_H +#define TEXTURECHOOSERVIEW_H + +#include <QQuickWidget> + +class ImageChooserModel; + +class TextureChooserView : public QQuickWidget +{ + Q_OBJECT + Q_PROPERTY(int instance READ instance) + Q_PROPERTY(int handle READ handle) + +public: + explicit TextureChooserView(QWidget *parent = nullptr); + + void setHandle(int handle); + int handle() const; + + void setInstance(int instance); + int instance() const; + QString currentDataModelPath() const; + + void updateSelection(); + +Q_SIGNALS: + void textureSelected(int handle, int instance, const QString &name); + +protected: + void focusOutEvent(QFocusEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + +private: + void showEvent(QShowEvent *event) override; + void initialize(); + int m_handle = -1; + int m_instance = -1; + ImageChooserModel *m_model = nullptr; +}; + +#endif // TEXTURECHOOSERVIEW_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.cpp new file mode 100644 index 00000000..05a8fa64 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.cpp @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "VariantTagDialog.h" +#include "ui_VariantTagDialog.h" +#include "Dialogs.h" +#include "StudioApp.h" +#include "Core.h" +#include "ProjectFile.h" + +#include <QtGui/qvalidator.h> + +VariantTagDialog::VariantTagDialog(DialogType type, const QString &group, const QString &name, + QWidget *parent) + : QDialog(parent) + , m_type(type) + , m_group(group) + , m_ui(new Ui::VariantTagDialog) +{ + m_ui->setupUi(this); + + m_names.first = name; + + QRegExpValidator *rgx = new QRegExpValidator(QRegExp("[\\w\\s]+"), this); + m_ui->lineEditTagName->setValidator(rgx); + + if (type == AddGroup) { + setWindowTitle(tr("Add new Group")); + m_ui->label->setText(tr("Group name")); + } else if (type == RenameGroup) { + setWindowTitle(tr("Rename Group")); + m_ui->label->setText(tr("Group name")); + m_ui->lineEditTagName->setText(name); + m_ui->lineEditTagName->selectAll(); + } else if (type == RenameTag) { + m_ui->lineEditTagName->setText(name); + m_ui->lineEditTagName->selectAll(); + } + + window()->setFixedSize(size()); +} + +void VariantTagDialog::accept() +{ + QString name = m_ui->lineEditTagName->text(); + + if (name.isEmpty()) { + displayWarning(EmptyWarning); + } else if (name == m_names.first) { // no change + QDialog::reject(); + } else if (((m_type == AddGroup || m_type == RenameGroup) + && !g_StudioApp.GetCore()->getProjectFile().isVariantGroupUnique(name)) + || (!g_StudioApp.GetCore()->getProjectFile().isVariantTagUnique(m_group, name))) { + displayWarning(UniqueWarning); + } else { + m_names.second = name; + QDialog::accept(); + } +} + +std::pair<QString, QString> VariantTagDialog::getNames() const +{ + return m_names; +} + +void VariantTagDialog::displayWarning(WarningType warningType) +{ + QString warning; + if (warningType == EmptyWarning) { + if (m_type == AddGroup || m_type == RenameGroup) + warning = tr("The group name must not be empty."); + else + warning = tr("The tag name must not be empty."); + } else if (warningType == UniqueWarning) { + if (m_type == AddGroup || m_type == RenameGroup) + warning = tr("The group name must be unique."); + else + warning = tr("The tag name must be unique within the tag group."); + } + + g_StudioApp.GetDialogs()->DisplayMessageBox(tr("Warning"), warning, + Qt3DSMessageBox::ICON_WARNING, false); +} + +VariantTagDialog::~VariantTagDialog() +{ + delete m_ui; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.h new file mode 100644 index 00000000..b5e3989f --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef VARIANTTAGDIALOG_H +#define VARIANTTAGDIALOG_H + +#include <QtWidgets/qdialog.h> + +#ifdef QT_NAMESPACE +using namespace QT_NAMESPACE; +#endif + +QT_BEGIN_NAMESPACE +namespace Ui { +class VariantTagDialog; +} +QT_END_NAMESPACE + +class VariantTagDialog : public QDialog +{ + Q_OBJECT + +public: + enum DialogType { AddTag, RenameTag, AddGroup, RenameGroup }; + + explicit VariantTagDialog(DialogType type, const QString &group = {}, const QString &name = {}, + QWidget *parent = nullptr); + ~VariantTagDialog() override; + +public Q_SLOTS: + void accept() override; + std::pair<QString, QString> getNames() const; + +private: + enum WarningType { + EmptyWarning, + UniqueWarning + }; + + void displayWarning(WarningType warningType); + + DialogType m_type; + QString m_group; + Ui::VariantTagDialog *m_ui; + std::pair<QString, QString> m_names; // holds the tags values before and after rename +}; + +#endif // VARIANTTAGDIALOG_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.ui b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.ui new file mode 100644 index 00000000..2c0b2863 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantTagDialog.ui @@ -0,0 +1,113 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>VariantTagDialog</class> + <widget class="QDialog" name="VariantTagDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>300</width> + <height>100</height> + </rect> + </property> + <property name="windowTitle"> + <string>Add new Tag</string> + </property> + <property name="sizeGripEnabled"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QWidget" name="widget" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QWidget" name="labelEditLayout" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout" stretch="0"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Tag name</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QLineEdit" name="lineEditTagName"/> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>VariantTagDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>VariantTagDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsGroupModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsGroupModel.cpp new file mode 100644 index 00000000..066f912a --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsGroupModel.cpp @@ -0,0 +1,242 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "VariantsGroupModel.h" +#include "VariantsTagModel.h" +#include "StudioApp.h" +#include "Core.h" +#include "MainFrm.h" +#include "Qt3DSDMStudioSystem.h" +#include "ClientDataModelBridge.h" +#include "IDocumentEditor.h" +#include "VariantTagDialog.h" +#include "StudioUtils.h" +#include "Dialogs.h" + +#include <QtCore/qsavefile.h> + +VariantsGroupModel::VariantsGroupModel(QObject *parent) + : QAbstractListModel(parent) +{ + +} + +void VariantsGroupModel::refresh() +{ + int instance = g_StudioApp.GetCore()->GetDoc()->GetSelectedInstance(); + auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge(); + + if (instance == 0 || bridge->GetObjectType(instance) & ~OBJTYPE_IS_VARIANT) { + m_instance = 0; + m_property = 0; + return; + } + + auto propertySystem = g_StudioApp.GetCore()->GetDoc()->GetPropertySystem(); + m_instance = instance; + m_property = bridge->getVariantsProperty(instance); + + qt3dsdm::SValue sValue; + if (propertySystem->GetInstancePropertyValue(m_instance, m_property, sValue)) { + beginResetModel(); + + // delete tag models + for (auto &g : qAsConst(m_data)) + delete g.m_tagsModel; + + m_data.clear(); + + QString propVal = qt3dsdm::get<qt3dsdm::TDataStrPtr>(sValue)->toQString(); + QHash<QString, QStringList> propTags; + if (!propVal.isEmpty()) { + const QStringList propTagsList = propVal.split(QChar(',')); + for (auto &propTag : propTagsList) { + const QStringList propTagPair = propTag.split(QChar(':')); + propTags[propTagPair[0]].append(propTagPair[1]); + } + } + + // build the variants data model + const auto variantsDef = g_StudioApp.GetCore()->getProjectFile().variantsDef(); + const auto keys = g_StudioApp.GetCore()->getProjectFile().variantsDefKeys(); + for (auto &group : keys) { + TagGroupData g; + g.m_title = group; + g.m_color = variantsDef[group].m_color; + + QVector<std::pair<QString, bool> > tags; + for (int i = 0; i < variantsDef[group].m_tags.length(); ++i) { + tags.append({variantsDef[group].m_tags[i], + propTags[group].contains(variantsDef[group].m_tags[i])}); + } + + g.m_tagsModel = new VariantsTagModel(tags); + m_data.push_back(g); + } + + endResetModel(); + + bool isVariantsEmpty = rowCount() == 0; + if (m_variantsEmpty != isVariantsEmpty) { + m_variantsEmpty = isVariantsEmpty; + Q_EMIT varaintsEmptyChanged(); + } + } +} + +int VariantsGroupModel::rowCount(const QModelIndex &parent) const +{ + // For list models only the root node (an invalid parent) should return the list's size. For all + // other (valid) parents, rowCount() should return 0 so that it does not become a tree model. + if (parent.isValid()) + return 0; + + return m_data.size(); +} + +QVariant VariantsGroupModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (role == GroupTitleRole) + return m_data.at(index.row()).m_title; + else if (role == GroupColorRole) + return m_data.at(index.row()).m_color; + else if (role == TagRole) + return QVariant::fromValue(m_data.at(index.row()).m_tagsModel); + + return QVariant(); +} + +void VariantsGroupModel::setTagState(const QString &group, const QString &tag, bool selected) +{ + QString val; + QString tagsStr; + bool skipFirst = false; + for (auto &g : qAsConst(m_data)) { + if (g.m_title == group) + g.m_tagsModel->updateTagState(tag, selected); + + tagsStr = g.m_tagsModel->serialize(g.m_title); + if (!tagsStr.isEmpty()) { + if (skipFirst) + val.append(QChar(',')); + val.append(tagsStr); + skipFirst = true; + } + } + + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*g_StudioApp.GetCore()->GetDoc(), QObject::tr("Set Property")) + ->SetInstancePropertyValue(m_instance, m_property, QVariant(val)); +} + +void VariantsGroupModel::addNewTag(const QString &group) +{ + VariantTagDialog dlg(VariantTagDialog::AddTag, group); + + if (dlg.exec() == QDialog::Accepted) { + g_StudioApp.GetCore()->getProjectFile().addVariantTag(group, dlg.getNames().second); + refresh(); + + if (g_StudioApp.GetCore()->getProjectFile().variantsDef()[group].m_tags.size() == 1) + g_StudioApp.m_pMainWnd->updateActionFilterEnableState(); + } +} + +void VariantsGroupModel::addNewGroup() +{ + VariantTagDialog dlg(VariantTagDialog::AddGroup); + + if (dlg.exec() == QDialog::Accepted) { + g_StudioApp.GetCore()->getProjectFile().addVariantGroup(dlg.getNames().second); + refresh(); + } +} + +void VariantsGroupModel::importVariants() +{ + QString importFilePath = g_StudioApp.GetDialogs()->getImportVariantsDlg(); + + if (!importFilePath.isEmpty()) { + g_StudioApp.GetCore()->getProjectFile().loadVariants(importFilePath); + refresh(); + } +} + +void VariantsGroupModel::exportVariants() +{ + QString exportFilePath = g_StudioApp.GetDialogs()->getExportVariantsDlg(); + + if (exportFilePath.isEmpty()) + return; + + QDomDocument domDoc; + domDoc.appendChild(domDoc.createProcessingInstruction(QStringLiteral("xml"), + QStringLiteral("version=\"1.0\"" + " encoding=\"utf-8\""))); + + const auto variantsDef = g_StudioApp.GetCore()->getProjectFile().variantsDef(); + const auto keys = g_StudioApp.GetCore()->getProjectFile().variantsDefKeys(); + QDomElement vElem = domDoc.createElement(QStringLiteral("variants")); + domDoc.appendChild(vElem); + for (auto &g : keys) { + const auto group = variantsDef[g]; + QDomElement gElem = domDoc.createElement(QStringLiteral("variantgroup")); + gElem.setAttribute(QStringLiteral("id"), g); + gElem.setAttribute(QStringLiteral("color"), group.m_color); + vElem.appendChild(gElem); + + for (auto &t : qAsConst(group.m_tags)) { + QDomElement tElem = domDoc.createElement(QStringLiteral("variant"));; + tElem.setAttribute(QStringLiteral("id"), t); + gElem.appendChild(tElem); + } + } + + QSaveFile file(exportFilePath); + if (StudioUtils::openTextSave(file)) + StudioUtils::commitDomDocumentSave(file, domDoc); +} + +QHash<int, QByteArray> VariantsGroupModel::roleNames() const +{ + auto names = QAbstractListModel::roleNames(); + names.insert(GroupTitleRole, "group"); + names.insert(GroupColorRole, "color"); + names.insert(TagRole, "tags"); + return names; +} + +Qt::ItemFlags VariantsGroupModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + + return Qt::ItemIsEditable; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsGroupModel.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsGroupModel.h new file mode 100644 index 00000000..75a218f7 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsGroupModel.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef VARIANTSGROUPMODEL_H +#define VARIANTSGROUPMODEL_H + +#include <QtCore/qabstractitemmodel.h> + +class VariantsTagModel; + +class VariantsGroupModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(bool variantsEmpty MEMBER m_variantsEmpty NOTIFY varaintsEmptyChanged) + +public: +Q_SIGNALS: + void varaintsEmptyChanged(); + +public: + explicit VariantsGroupModel(QObject *parent = nullptr); + + enum Roles { + GroupTitleRole = Qt::UserRole + 1, + GroupColorRole, + TagRole + }; + + struct TagGroupData + { + QString m_title; + QString m_color; + VariantsTagModel *m_tagsModel = nullptr; + }; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = GroupTitleRole) const override; + + Qt::ItemFlags flags(const QModelIndex& index) const override; + + void refresh(); + + Q_INVOKABLE void setTagState(const QString &group, const QString &tag, bool selected); + Q_INVOKABLE void addNewTag(const QString &group); + Q_INVOKABLE void addNewGroup(); + Q_INVOKABLE void importVariants(); + Q_INVOKABLE void exportVariants(); + + +protected: + QHash<int, QByteArray> roleNames() const override; + +private: + QVector<TagGroupData> m_data; + int m_instance = 0; // selected layer instance + int m_property = 0; // variant tags property handler + bool m_variantsEmpty = true; // no groups (nor tags) +}; + +#endif // VARIANTSGROUPMODEL_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsTagModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsTagModel.cpp new file mode 100644 index 00000000..30f33635 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsTagModel.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "VariantsTagModel.h" + +VariantsTagModel::VariantsTagModel(const QVector<std::pair<QString, bool> > &data, QObject *parent) + : QAbstractListModel(parent) + , m_data(data) +{ + +} + +void VariantsTagModel::updateTagState(const QString &tag, bool selected) +{ + for (auto &t : m_data) { + if (t.first == tag) { + t.second = selected; + break; + } + } +} + +// return the tags in a formatted string to be saved to the property +QString VariantsTagModel::serialize(const QString &groupName) const +{ + QString ret; + bool skipFirst = false; + for (auto &t : qAsConst(m_data)) { + if (t.second) { + if (skipFirst) + ret.append(QLatin1Char(',')); + + ret.append(groupName + QLatin1Char(':') + t.first); + + skipFirst = true; + } + } + + return ret; +} + +int VariantsTagModel::rowCount(const QModelIndex &parent) const +{ + // For list models only the root node (an invalid parent) should return the list's size. For all + // other (valid) parents, rowCount() should return 0 so that it does not become a tree model. + if (parent.isValid()) + return 0; + + return m_data.size(); +} + +QVariant VariantsTagModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (role == TagRole) + return m_data.at(index.row()).first; + else if (role == SelectedRole) + return m_data.at(index.row()).second; + + return QVariant(); +} + +QHash<int, QByteArray> VariantsTagModel::roleNames() const +{ + auto names = QAbstractListModel::roleNames(); + names.insert(TagRole, "tag"); + names.insert(SelectedRole, "selected"); + return names; +} + +Qt::ItemFlags VariantsTagModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + + return Qt::ItemIsEditable; // FIXME: Implement me! +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsTagModel.h b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsTagModel.h new file mode 100644 index 00000000..392de760 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Inspector/VariantsTagModel.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef VARIANTSTAGMODEL_H +#define VARIANTSTAGMODEL_H + +#include <QtCore/qabstractitemmodel.h> + +class VariantsTagModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit VariantsTagModel(const QVector<std::pair<QString, bool> > &data, + QObject *parent = nullptr); + + enum Roles { + TagRole = Qt::UserRole + 1, + SelectedRole, + }; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = TagRole) const override; + + Qt::ItemFlags flags(const QModelIndex& index) const override; + + void updateTagState(const QString &tag, bool selected); + QString serialize(const QString &groupName) const; + +protected: + QHash<int, QByteArray> roleNames() const override; + +private: + QVector<std::pair<QString, bool> > m_data; // [{tagName, selectedState}, ...] +}; + +#endif // VARIANTSTAGMODEL_H diff --git a/src/Authoring/Qt3DStudio/Palettes/PaletteManager.cpp b/src/Authoring/Qt3DStudio/Palettes/PaletteManager.cpp new file mode 100644 index 00000000..f83a5df6 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/PaletteManager.cpp @@ -0,0 +1,294 @@ +/**************************************************************************** +** +** Copyright (C) 2002 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +//============================================================================== +// Includes +//============================================================================== +#include "PaletteManager.h" +#include "StudioApp.h" +#include "MainFrm.h" +#include "TimelineWidget.h" +#include "BasicObjectsView.h" +#include "SlideView.h" +#include "WidgetControl.h" +#include "InspectorControlView.h" +#include "ActionView.h" +#include "IDragable.h" +#include "ProjectView.h" +#include "TabOrderHandler.h" +#include "StudioPreferences.h" +#include "scenecameraview.h" + +#include <QtWidgets/qdockwidget.h> +#include <QtWidgets/qboxlayout.h> + +//============================================================================== +/** + * Constructor + */ +CPaletteManager::CPaletteManager(CMainFrame *inMainFrame, QObject *parent) + : QObject(parent) + , m_MainFrame(inMainFrame) +{ + const int defaultBottomDockHeight = int(inMainFrame->height() * 0.25); + const int defaultRightDockWidth = 435; // To fit all inspector controls + const int defaultProjectHeight = 285; // To fit all new project folders, expanded + + // Position tabs to the right + inMainFrame->setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::East); + inMainFrame->setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); + + m_projectDock = new QDockWidget(QObject::tr("Project"), inMainFrame); + m_projectDock->setObjectName(QStringLiteral("project")); + m_projectDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea + | Qt::BottomDockWidgetArea); + + m_slideDock = new QDockWidget(QObject::tr("Slide"), inMainFrame); + m_slideDock->setObjectName(QStringLiteral("slide")); + m_slideDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + // Slide palette has a fixed size hint + auto slideView = new SlideView(m_slideDock); + slideView->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + m_slideDock->setWidget(slideView); + inMainFrame->addDockWidget(Qt::LeftDockWidgetArea, m_slideDock); + m_ControlList.insert({CONTROLTYPE_SLIDE, m_slideDock}); + QObject::connect(m_slideDock, &QDockWidget::dockLocationChanged, slideView, + &SlideView::onDockLocationChange); + + m_basicObjectsDock = new QDockWidget(QObject::tr("Basic Objects"), inMainFrame); + m_basicObjectsDock->setObjectName(QStringLiteral("basic_objects")); + m_basicObjectsDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea + | Qt::BottomDockWidgetArea); + // Basic objects palette has a fixed size hint + auto basicObjectsView = new BasicObjectsView(m_basicObjectsDock); + basicObjectsView->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + m_basicObjectsDock->setWidget(basicObjectsView); + inMainFrame->addDockWidget(Qt::LeftDockWidgetArea, m_basicObjectsDock); + inMainFrame->tabifyDockWidget(m_basicObjectsDock, m_slideDock); + m_ControlList.insert({CONTROLTYPE_BASICOBJECTS, m_basicObjectsDock}); + + m_timelineDock = new QDockWidget(QObject::tr("Timeline")); + m_timelineDock->setObjectName(QStringLiteral("timeline")); + m_timelineDock->setAllowedAreas(Qt::BottomDockWidgetArea); + + // Give the preferred size as percentages of the mainframe size + m_timelineWidget = new TimelineWidget(QSize(inMainFrame->width() - defaultRightDockWidth, + defaultBottomDockHeight)); + m_timelineWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + WidgetControl *timeLineWidgetControl = new WidgetControl(m_timelineWidget, m_timelineDock); + timeLineWidgetControl->RegisterForDnd(timeLineWidgetControl); + timeLineWidgetControl->AddMainFlavor(QT3DS_FLAVOR_FILE); + timeLineWidgetControl->AddMainFlavor(QT3DS_FLAVOR_ASSET_UICFILE); + timeLineWidgetControl->AddMainFlavor(QT3DS_FLAVOR_ASSET_LIB); + timeLineWidgetControl->AddMainFlavor(QT3DS_FLAVOR_ASSET_TL); + timeLineWidgetControl->AddMainFlavor(QT3DS_FLAVOR_BASIC_OBJECTS); + + m_timelineWidget->setParent(timeLineWidgetControl); + + m_timelineDock->setWidget(timeLineWidgetControl); + inMainFrame->addDockWidget(Qt::BottomDockWidgetArea, m_timelineDock); + m_ControlList.insert({CONTROLTYPE_TIMELINE, m_timelineDock}); + + m_cameraDock = new QDockWidget(QObject::tr("Scene Camera")); + m_cameraDock->setObjectName(QStringLiteral("scenecamera")); + m_cameraDock->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::LeftDockWidgetArea + | Qt::RightDockWidgetArea); + + m_cameraWidget = new SceneCameraView(inMainFrame, m_cameraDock); + m_cameraWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + + m_cameraDock->setWidget(m_cameraWidget); + inMainFrame->addDockWidget(Qt::BottomDockWidgetArea, m_cameraDock); + inMainFrame->tabifyDockWidget(m_timelineDock, m_cameraDock); + m_ControlList.insert({CONTROLTYPE_SCENECAMERA, m_cameraDock}); + + // Give the preferred size as percentages of the mainframe size + m_projectView = new ProjectView(QSize(defaultRightDockWidth, defaultProjectHeight), + m_projectDock); + m_projectView->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + m_projectDock->setWidget(m_projectView); + inMainFrame->addDockWidget(Qt::RightDockWidgetArea, m_projectDock); + m_ControlList.insert({CONTROLTYPE_PROJECT, m_projectDock}); + + m_actionDock = new QDockWidget(QObject::tr("Action"), inMainFrame); + m_actionDock->setObjectName(QStringLiteral("action")); + m_actionDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea + | Qt::BottomDockWidgetArea); + // Give the preferred size as percentages of the mainframe size + auto actionView = new ActionView( + QSize(defaultRightDockWidth, inMainFrame->height() - defaultProjectHeight), + m_actionDock); + actionView->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + m_actionDock->setWidget(actionView); + inMainFrame->addDockWidget(Qt::RightDockWidgetArea, m_actionDock); + m_ControlList.insert({CONTROLTYPE_ACTION, m_actionDock}); + + m_inspectorDock = new QDockWidget(QObject::tr("Inspector"), inMainFrame); + m_inspectorDock->setObjectName(QStringLiteral("inspector_control")); + m_inspectorDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea + | Qt::BottomDockWidgetArea); + // Give the preferred size as percentages of the mainframe size + auto inspectorView = new InspectorControlView( + QSize(defaultRightDockWidth, inMainFrame->height() - defaultProjectHeight), + m_inspectorDock); + inspectorView->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + m_inspectorDock->setWidget(inspectorView); + inMainFrame->addDockWidget(Qt::RightDockWidgetArea, m_inspectorDock); + inMainFrame->tabifyDockWidget(m_inspectorDock, m_actionDock); + m_ControlList.insert({CONTROLTYPE_INSPECTOR, m_inspectorDock}); + + m_inspectorDock->raise(); + + m_basicObjectsDock->setEnabled(false); + m_projectDock->setEnabled(false); + m_slideDock->setEnabled(false); + m_actionDock->setEnabled(false); + m_inspectorDock->setEnabled(false); + m_timelineDock->setEnabled(false); + m_cameraDock->setEnabled(false); +} + +//============================================================================== +/** + * Destructor + */ +CPaletteManager::~CPaletteManager() +{ + TControlMap::iterator theIterator = m_ControlList.begin(); + TControlMap::iterator theEndIterator = m_ControlList.end(); + // Delete all the controls + for (theIterator = m_ControlList.begin(); theIterator != theEndIterator; ++theIterator) + delete theIterator->second; +} + +//============================================================================= +/** + * Force a control to become invisible + */ +void CPaletteManager::HideControl(long inType) +{ + auto dock = GetControl(inType); + + if (dock) { + // Make sure the control is invisible + dock->setVisible(false); + } +} +//============================================================================= +/** + * Detemine if a control is currently visible + */ +bool CPaletteManager::IsControlVisible(long inType) const +{ + auto dock = GetControl(inType); + return dock && dock->isVisible(); +} + +//============================================================================= +/** + * Force a control to become visible + */ +void CPaletteManager::ShowControl(long inType) +{ + auto dock = GetControl(inType); + + if (dock) { + // Make sure the control is visible + dock->setVisible(true); + dock->setFocus(); + } +} + +//============================================================================= +/** + * Flip the visible state of a control + */ +void CPaletteManager::ToggleControl(long inType) +{ + if (IsControlVisible(inType)) + HideControl(inType); + else + ShowControl(inType); +} + +//============================================================================== +/** + * Return the Control (Palette) according to its EControlTypes enum. + * @param inType EControlTypes + */ +QDockWidget *CPaletteManager::GetControl(long inType) const +{ + auto dock = m_ControlList.find(inType); + if (dock != m_ControlList.end() && dock->second) + return dock->second; + else + return nullptr; +} + +QWidget *CPaletteManager::getFocusWidget() const +{ + TControlMap::const_iterator end = m_ControlList.end(); + for (TControlMap::const_iterator iter = m_ControlList.begin(); iter != end; ++iter) { + if (iter->second->widget()->hasFocus()) + return iter->second->widget(); + } + return nullptr; +} + +bool CPaletteManager::tabNavigateFocusedWidget(bool tabForward) +{ + QWidget *palette = getFocusWidget(); + if (palette) { + if (auto inspector = qobject_cast<InspectorControlView *>(palette)) { + inspector->tabOrderHandler()->tabNavigate(tabForward); + return true; + } else if (auto actionview = qobject_cast<ActionView *>(palette)) { + actionview->tabOrderHandler()->tabNavigate(tabForward); + return true; + } + } + return false; +} + +ProjectView *CPaletteManager::projectView() const +{ + return m_projectView; +} + +void CPaletteManager::EnablePalettes() +{ + m_basicObjectsDock->setEnabled(true); + m_projectDock->setEnabled(true); + m_slideDock->setEnabled(true); + m_timelineDock->setEnabled(true); + m_actionDock->setEnabled(true); + m_inspectorDock->setEnabled(true); + m_cameraDock->setEnabled(true); +} + diff --git a/src/Authoring/Qt3DStudio/Palettes/PaletteManager.h b/src/Authoring/Qt3DStudio/Palettes/PaletteManager.h new file mode 100644 index 00000000..0b672216 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/PaletteManager.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2002 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +//============================================================================== +// Prefix +//============================================================================== +#ifndef INCLUDED_VIEW_MANAGER_H +#define INCLUDED_VIEW_MANAGER_H 1 + +//============================================================================== +// Includes +//============================================================================== +#include <QtWidgets/qdockwidget.h> +#include <QtCore/qobject.h> + +//============================================================================== +// Forwards +//============================================================================== +class CMainFrame; +class WidgetControl; +class TimeLineToolbar; +class TimelineView; +class ProjectView; +class TimelineWidget; +class SceneCameraView; + +QT_FORWARD_DECLARE_CLASS(QDockWidget) + +//============================================================================== +/** + * @class CPaletteManager + */ +class CPaletteManager : public QObject +{ + Q_OBJECT +public: + enum EControlTypes { + CONTROLTYPE_NONE = 0, + CONTROLTYPE_ACTION, + CONTROLTYPE_BASICOBJECTS, + CONTROLTYPE_INSPECTOR, + CONTROLTYPE_SLIDE, + CONTROLTYPE_TIMELINE, + CONTROLTYPE_PROJECT, + CONTROLTYPE_SCENECAMERA, + }; + +protected: + typedef std::map<long, QDockWidget *> TControlMap; + +protected: + CMainFrame *m_MainFrame; + TControlMap m_ControlList; + + QDockWidget *m_basicObjectsDock; + QDockWidget *m_projectDock; + QDockWidget *m_slideDock; + QDockWidget *m_timelineQmlDock; + QDockWidget *m_timelineDock; + QDockWidget *m_actionDock; + QDockWidget *m_inspectorDock; + QDockWidget *m_cameraDock; + + TimelineView *m_timelineView; + ProjectView *m_projectView = nullptr; + TimelineWidget *m_timelineWidget; + SceneCameraView *m_cameraWidget; + +public: + CPaletteManager(CMainFrame *inMainFrame, QObject *parent = nullptr); + virtual ~CPaletteManager(); + + // Access + void HideControl(long inType); + bool IsControlVisible(long inType) const; + void ShowControl(long inType); + void ToggleControl(long inType); + QDockWidget *GetControl(long inType) const; + QWidget *getFocusWidget() const; + bool tabNavigateFocusedWidget(bool tabForward); + ProjectView *projectView() const; + + // Commands + void EnablePalettes(); +}; + +#endif // INCLUDED_VIEW_MANAGER_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Progress/ProgressDlg.ui b/src/Authoring/Qt3DStudio/Palettes/Progress/ProgressDlg.ui new file mode 100644 index 00000000..4cb14042 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Progress/ProgressDlg.ui @@ -0,0 +1,131 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ProgressDlg</class> + <widget class="QDialog" name="ProgressDlg"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>360</width> + <height>112</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>Sub-presentations</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QWidget" name="backgroundWidget" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="progressIcon"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../images.qrc">:/images/anim_progress.png</pixmap> + </property> + <property name="alignment"> + <set>Qt::AlignJustify|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>24</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="progressActionText"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Action text goes here</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="progressAdditionalText"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Additional text goes here</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>30</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources> + <include location="../../images.qrc"/> + </resources> + <connections/> +</ui> diff --git a/src/Authoring/Qt3DStudio/Palettes/Progress/ProgressView.cpp b/src/Authoring/Qt3DStudio/Palettes/Progress/ProgressView.cpp new file mode 100644 index 00000000..e6f57687 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Progress/ProgressView.cpp @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2002 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "ProgressView.h" +#include "ui_ProgressDlg.h" + +#include <QtCore/qtimer.h> + +//============================================================================= +/** + * Constructor: Protected because the view is always created dynamically. + * You must call Initialize() before trying to use this class. + */ +CProgressView::CProgressView(QWidget *parent) + : QDialog(parent, Qt::SplashScreen) + , m_ui(new Ui::ProgressDlg) +{ + m_ui->setupUi(this); + m_ui->progressAdditionalText->setWordWrap(true); +} + +//============================================================================= +/** + * Destructor + */ +CProgressView::~CProgressView() +{ + delete m_ui; +} + +void CProgressView::SetActionText(const QString &inActionText) +{ + m_ui->progressActionText->setText(inActionText); +} + +void CProgressView::SetAdditionalText(const QString &inAdditionalText) +{ + m_ui->progressAdditionalText->setText(inAdditionalText); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Progress/ProgressView.h b/src/Authoring/Qt3DStudio/Palettes/Progress/ProgressView.h new file mode 100644 index 00000000..384c72d7 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Progress/ProgressView.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2002 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ +#ifndef INCLUDED_PROGRESS_VIEW_H +#define INCLUDED_PROGRESS_VIEW_H 1 + +#pragma once + +#include <QtWidgets/qdialog.h> + +#include "Qt3DSString.h" + +#ifdef QT_NAMESPACE +using namespace QT_NAMESPACE; +#endif + +QT_BEGIN_NAMESPACE +namespace Ui { + class ProgressDlg; +} +QT_END_NAMESPACE + +//============================================================================= +/** + * Windows view encapsulating the splash screen. + */ +class CProgressView : public QDialog +{ +public: + CProgressView(QWidget *parent = nullptr); + virtual ~CProgressView(); + + void SetActionText(const QString &inActionText); + void SetAdditionalText(const QString &inAdditionalText); + +protected: + Ui::ProgressDlg *m_ui; +}; + +#endif // INCLUDED_PROGRESS_VIEW_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.cpp b/src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.cpp new file mode 100644 index 00000000..de515ad7 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.cpp @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "ChooseImagePropertyDlg.h" +#include "StudioApp.h" +#include "Core.h" +#include "Qt3DSDMDataCore.h" +#include "ui_ChooseImagePropertyDlg.h" + +// This dialog displays all texture properties of an object and allows the user to choose one +ChooseImagePropertyDlg::ChooseImagePropertyDlg(qt3dsdm::Qt3DSDMInstanceHandle inTarget, + bool isRefMaterial, + QWidget *parent) + : QDialog(parent) + , m_instance(inTarget) + , m_ui(new Ui::ChooseImagePropertyDlg) +{ + m_ui->setupUi(this); + + connect(m_ui->listProps, &QListWidget::itemClicked, this, [this](QListWidgetItem *item) { + m_selectedPropHandle = item->isSelected() ? item->data(Qt::UserRole).toInt() : -1; + }); + + connect(m_ui->listProps, &QListWidget::itemDoubleClicked, this, [this](QListWidgetItem *item) { + Q_UNUSED(item) + QDialog::accept(); + }); + + if (!isRefMaterial) + m_ui->cbDetach->setVisible(false); + + fillList(); + + window()->setFixedSize(size()); +} + +ChooseImagePropertyDlg::~ChooseImagePropertyDlg() +{ + delete m_ui; +} + +void ChooseImagePropertyDlg::setTextureTitle() +{ + setWindowTitle(tr("Set texture")); + m_ui->label->setText(tr("Set texture to:")); +} + +bool ChooseImagePropertyDlg::detachMaterial() const +{ + return m_ui->cbDetach->checkState() == Qt::Checked; +} + +// fill the list with all material properties of type image +void ChooseImagePropertyDlg::fillList() +{ + qt3dsdm::IPropertySystem *propertySystem = g_StudioApp.GetCore()->GetDoc()->GetPropertySystem(); + qt3dsdm::TPropertyHandleList props; + propertySystem->GetAggregateInstanceProperties(m_instance, props); + for (auto &p : props) { + auto metaDataType = propertySystem->GetAdditionalMetaDataType(m_instance, p); + if (metaDataType == qt3dsdm::AdditionalMetaDataType::Value::Image) { + QString propName = QString::fromStdWString(propertySystem->GetFormalName(m_instance, p) + .wide_str()); + QListWidgetItem *newItem = new QListWidgetItem(propName); + newItem->setData(Qt::UserRole, QVariant(p)); + m_ui->listProps->addItem(newItem); + } + } + + if (m_ui->listProps->count() > 0) { + // select the first item by default + m_ui->listProps->setCurrentRow(0); + m_selectedPropHandle = m_ui->listProps->currentItem()->data(Qt::UserRole).toInt(); + } +} + +int ChooseImagePropertyDlg::getSelectedPropertyHandle() const +{ + return m_selectedPropHandle; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.h b/src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.h new file mode 100644 index 00000000..3b6153ea --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef CHOOSEIMAGEPROPERTYDLG_H +#define CHOOSEIMAGEPROPERTYDLG_H + +#include "Qt3DSDMHandles.h" +#include <QtWidgets/qdialog.h> + +#ifdef QT_NAMESPACE +using namespace QT_NAMESPACE; +#endif + +QT_BEGIN_NAMESPACE +namespace Ui { +class ChooseImagePropertyDlg; +} +QT_END_NAMESPACE + +class ChooseImagePropertyDlg : public QDialog +{ + Q_OBJECT + +public: + explicit ChooseImagePropertyDlg(qt3dsdm::Qt3DSDMInstanceHandle instance, + bool isRefMaterial = false, + QWidget *parent = 0); + ~ChooseImagePropertyDlg(); + + int getSelectedPropertyHandle() const; + bool detachMaterial() const; + void setTextureTitle(); // make title/label show 'texture' instead of 'sub-presentation' + +private: + Ui::ChooseImagePropertyDlg *m_ui; + qt3dsdm::Qt3DSDMInstanceHandle m_instance; + int m_selectedPropHandle = -1; + + void fillList(); +}; + +#endif // CHOOSEIMAGEPROPERTYDLG_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.ui b/src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.ui new file mode 100644 index 00000000..3e3d3924 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Project/ChooseImagePropertyDlg.ui @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ChooseImagePropertyDlg</class> + <widget class="QDialog" name="ChooseImagePropertyDlg"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>250</width> + <height>250</height> + </rect> + </property> + <property name="minimumSize"> + <size> + <width>200</width> + <height>150</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>1000</width> + <height>1000</height> + </size> + </property> + <property name="windowTitle"> + <string>Set sub-presentation</string> + </property> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <property name="sizeGripEnabled"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QWidget" name="widget" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Set sub-presentation to:</string> + </property> + </widget> + </item> + <item> + <widget class="QListWidget" name="listProps"/> + </item> + <item> + <widget class="QCheckBox" name="cbDetach"> + <property name="toolTip"> + <string>Detach from referenced material</string> + </property> + <property name="text"> + <string>Detach Material</string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ChooseImagePropertyDlg</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>240</x> + <y>240</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ChooseImagePropertyDlg</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>240</x> + <y>240</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.cpp b/src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.cpp new file mode 100644 index 00000000..323a6ed1 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.cpp @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "EditPresentationIdDlg.h" +#include "ui_EditPresentationIdDlg.h" +#include "StudioApp.h" +#include "Core.h" +#include "Dialogs.h" + +EditPresentationIdDlg::EditPresentationIdDlg(const QString &src, DialogType type, QWidget *parent) + : QDialog(parent) + , m_src(src) + , m_ui(new Ui::EditPresentationIdDlg) + , m_dialogType(type) +{ + m_ui->setupUi(this); + + switch (m_dialogType) { + case EditPresentationId: + m_ui->label->setText(tr("Presentation Id")); + setWindowTitle(tr("Edit Presentation Id")); + break; + case EditQmlStreamId: + m_ui->label->setText(tr("Qml Stream Id")); + setWindowTitle(tr("Edit Qml Stream Id")); + break; + case EditPresentationName: + m_ui->label->setText(tr("Presentation Name")); + setWindowTitle(tr("Rename Presentation")); + break; + case EditQmlStreamName: + m_ui->label->setText(tr("Qml Stream Name")); + setWindowTitle(tr("Rename Qml Stream")); + break; + case DuplicatePresentation: + m_ui->label->setText(tr("Presentation Name")); + setWindowTitle(tr("Duplicate Presentation")); + break; + case DuplicateQmlStream: + m_ui->label->setText(tr("Qml Stream Name")); + setWindowTitle(tr("Duplicate Qml Stream")); + break; + default: + break; + } + + if (m_dialogType == EditPresentationId || m_dialogType == EditQmlStreamId) { + m_presentationId = g_StudioApp.GetCore()->getProjectFile().getPresentationId(src); + m_ui->lineEditPresentationId->setText(m_presentationId); + } else { + QFileInfo fi(src); + QString initialText = fi.completeBaseName(); + if (m_dialogType == DuplicatePresentation || m_dialogType == DuplicateQmlStream) + initialText = g_StudioApp.GetCore()->getProjectFile().getUniquePresentationName(src); + m_ui->lineEditPresentationId->setText(initialText); + } + + window()->setFixedSize(size()); +} + +void EditPresentationIdDlg::accept() +{ + QString newValue = m_ui->lineEditPresentationId->text(); + if (newValue.isEmpty()) { + displayWarning(EmptyWarning); + } else if (m_dialogType == EditPresentationId || m_dialogType == EditQmlStreamId) { + if (newValue != m_presentationId) { + if (!g_StudioApp.GetCore()->getProjectFile().isUniquePresentationId(newValue, m_src)) { + displayWarning(UniqueWarning); + } else { + g_StudioApp.GetCore()->getProjectFile().writePresentationId(newValue, m_src); + QDialog::accept(); + } + } else { + QDialog::accept(); + } + } else { // editing name + QFileInfo fi(m_src); + QString suffix = QStringLiteral(".") + fi.suffix(); + if (!newValue.endsWith(suffix)) + newValue.append(suffix); + + if (newValue == suffix) { + // If we are left with just the suffix, treat it as an empty name + displayWarning(EmptyWarning); + } else { + int slashIndex = m_src.lastIndexOf(QLatin1Char('/')); + if (slashIndex >= 0) + newValue.prepend(m_src.left(slashIndex + 1)); + + if (newValue != m_src) { + bool success = false; + if (m_dialogType == DuplicatePresentation || m_dialogType == DuplicateQmlStream) { + success = g_StudioApp.GetCore()->getProjectFile().duplicatePresentation( + m_src, newValue); + if (success) { + m_duplicateFile = g_StudioApp.GetCore()->getProjectFile() + .getAbsoluteFilePathTo(newValue); + } + } else { + success = g_StudioApp.GetCore()->getProjectFile().renamePresentationFile( + m_src, newValue); + } + if (success) + QDialog::accept(); + else + displayWarning(UniqueWarning); + } else { + QDialog::accept(); + } + } + } +} + +void EditPresentationIdDlg::displayWarning(WarningType warningType) +{ + QString warning; + QString uniqueFileNote; + if (warningType == UniqueWarning) + uniqueFileNote = tr("The new name must be unique within its folder and a valid filename."); + + switch (m_dialogType) { + // Presentation Id warnings are also displayed from preferences dialog, so they are handled + // by CStudioApp. + case EditPresentationId: + if (warningType == EmptyWarning) + g_StudioApp.showPresentationIdEmptyWarning(); + else + g_StudioApp.showPresentationIdUniqueWarning(); + return; + case EditQmlStreamId: + if (warningType == EmptyWarning) + warning = tr("Qml stream Id must not be empty."); + else + warning = tr("Qml stream Id must be unique."); + break; + case EditPresentationName: + if (warningType == EmptyWarning) + warning = tr("Presentation name must not be empty."); + else + warning = tr("Renaming presentation failed.\n") + uniqueFileNote; + break; + case EditQmlStreamName: + if (warningType == EmptyWarning) + warning = tr("Qml stream name must not be empty."); + else + warning = tr("Renaming Qml stream failed.\n") + uniqueFileNote; + break; + case DuplicatePresentation: + if (warningType == EmptyWarning) + warning = tr("Presentation name must not be empty."); + else + warning = tr("Duplicating presentation failed.\n") + uniqueFileNote; + break; + case DuplicateQmlStream: + if (warningType == EmptyWarning) + warning = tr("Qml stream name must not be empty."); + else + warning = tr("Duplicating Qml stream failed.\n") + uniqueFileNote; + break; + default: + break; + } + + g_StudioApp.GetDialogs()->DisplayMessageBox(tr("Warning"), warning, + Qt3DSMessageBox::ICON_WARNING, false); +} + +EditPresentationIdDlg::~EditPresentationIdDlg() +{ + delete m_ui; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.h b/src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.h new file mode 100644 index 00000000..30bfccbd --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef EDITPRESENTATIONIDDLG_H +#define EDITPRESENTATIONIDDLG_H + +#include <QtWidgets/qdialog.h> + +#ifdef QT_NAMESPACE +using namespace QT_NAMESPACE; +#endif + +QT_BEGIN_NAMESPACE +namespace Ui { +class EditPresentationIdDlg; +} +QT_END_NAMESPACE + +class EditPresentationIdDlg : public QDialog +{ + Q_OBJECT + +public: + enum DialogType { + EditPresentationId, + EditQmlStreamId, + EditPresentationName, + EditQmlStreamName, + DuplicatePresentation, + DuplicateQmlStream + }; + + explicit EditPresentationIdDlg(const QString &src, DialogType type = EditPresentationId, + QWidget *parent = nullptr); + ~EditPresentationIdDlg(); + + QString getDuplicateFile() const { return m_duplicateFile; } + +public Q_SLOTS: + void accept() override; + +private: + enum WarningType { + EmptyWarning, + UniqueWarning + }; + void displayWarning(WarningType warningType); + + Ui::EditPresentationIdDlg *m_ui; + QString m_src; // src attribute value for the current presentation in the project file + QString m_presentationId; + DialogType m_dialogType; + QString m_duplicateFile; +}; + +#endif // EDITPRESENTATIONIDDLG_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.ui b/src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.ui new file mode 100644 index 00000000..23fbf8e9 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Project/EditPresentationIdDlg.ui @@ -0,0 +1,113 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>EditPresentationIdDlg</class> + <widget class="QDialog" name="EditPresentationIdDlg"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>300</width> + <height>100</height> + </rect> + </property> + <property name="windowTitle"> + <string>Edit Presentation Id</string> + </property> + <property name="sizeGripEnabled"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QWidget" name="widget" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QWidget" name="labelEditLayout" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout" stretch="0"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Presentation Id</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QLineEdit" name="lineEditPresentationId"/> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>EditPresentationIdDlg</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>EditPresentationIdDlg</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/ProjectContextMenu.cpp b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectContextMenu.cpp new file mode 100644 index 00000000..5c985359 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectContextMenu.cpp @@ -0,0 +1,231 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "ProjectContextMenu.h" +#include "ProjectView.h" + +ProjectContextMenu::ProjectContextMenu(ProjectView *parent, int index) + : QMenu(parent) + , m_view(parent) + , m_index(index) +{ + QAction *action = nullptr; + QAction *openFileAction = nullptr; + QAction *deleteFileAction = nullptr; + + if (!m_view->isFolder(m_index)) { + openFileAction = new QAction(tr("Open")); + connect(openFileAction, &QAction::triggered, this, &ProjectContextMenu::handleOpenFile); + addAction(openFileAction); + + deleteFileAction = new QAction(tr("Delete")); + connect(deleteFileAction, &QAction::triggered, this, &ProjectContextMenu::handleDeleteFile); + // Delete file action is added after other file actions later + } + + if (m_view->isPresentation(m_index)) { + const bool currentPresentation = m_view->isCurrentPresentation(m_index); + openFileAction->setText(tr("Open Presentation")); + openFileAction->setEnabled(!currentPresentation); + + deleteFileAction->setEnabled(!currentPresentation); + + action = new QAction(tr("Rename Presentation")); + connect(action, &QAction::triggered, this, &ProjectContextMenu::handleRenamePresentation); + addAction(action); + + action = new QAction(tr("Edit Presentation Id")); + connect(action, &QAction::triggered, this, &ProjectContextMenu::handleEditPresentationId); + addAction(action); + + action = new QAction(tr("Duplicate Presentation")); + connect(action, &QAction::triggered, + this, &ProjectContextMenu::handleDuplicatePresentation); + addAction(action); + + static const QIcon iconInitial = QIcon(QStringLiteral(":/images/initial_notUsed.png")); + + if (m_view->isInitialPresentation(m_index)) { + action = new QAction(iconInitial, tr("Initial Presentation")); + // This action does nothing, it's merely informative, so let's disable it + action->setEnabled(false); + } else { + action = new QAction(tr("Set as Initial Presentation")); + if (m_view->presentationId(m_index).isEmpty()) { + action->setEnabled(false); + } else { + connect(action, &QAction::triggered, + this, &ProjectContextMenu::handleInitialPresentation); + } + } + addAction(action); + } else if (m_view->isQmlStream(m_index)) { + action = new QAction(tr("Rename Qml Stream")); + connect(action, &QAction::triggered, this, &ProjectContextMenu::handleRenameQmlStream); + addAction(action); + + action = new QAction(tr("Edit Qml Stream Id")); + connect(action, &QAction::triggered, this, &ProjectContextMenu::handleEditQmlStreamId); + addAction(action); + + action = new QAction(tr("Duplicate Qml Stream")); + connect(action, &QAction::triggered, + this, &ProjectContextMenu::handleDuplicatePresentation); + addAction(action); + } + + if (m_view->isMaterialData(m_index)) { + openFileAction->setText(tr("Edit")); + + action = new QAction(tr("Duplicate")); + connect(action, &QAction::triggered, this, &ProjectContextMenu::handleDuplicate); + addAction(action); + } + + if (deleteFileAction) { + deleteFileAction->setEnabled(deleteFileAction->isEnabled() + && !m_view->isReferenced(m_index)); + addAction(deleteFileAction); + } + + addSeparator(); + + action = new QAction(tr("Show Containing Folder")); + connect(action, &QAction::triggered, this, &ProjectContextMenu::handleShowContainingFolder); + addAction(action); + + addSeparator(); + + action = new QAction(tr("Copy Path")); + connect(action, &QAction::triggered, this, &ProjectContextMenu::handleCopyPath); + addAction(action); + + action = new QAction(tr("Copy Full Path")); + connect(action, &QAction::triggered, this, &ProjectContextMenu::handleCopyFullPath); + addAction(action); + + addSeparator(); + + action = new QAction(tr("Import Assets...")); + connect(action, &QAction::triggered, this, &ProjectContextMenu::handleImportAssets); + addAction(action); + + if (m_view->isMaterialFolder(m_index) || m_view->isInMaterialFolder(m_index)) { + addSeparator(); + QMenu *createMenu = addMenu(tr("Create")); + action = new QAction(tr("Basic Material")); + connect(action, &QAction::triggered, this, &ProjectContextMenu::handleAddMaterial); + createMenu->addAction(action); + } + + if (m_view->isRefreshable(m_index)) { + addSeparator(); + action = new QAction(tr("Refresh Import...")); + connect(action, &QAction::triggered, this, &ProjectContextMenu::handleRefreshImport); + addAction(action); + } +} + +ProjectContextMenu::~ProjectContextMenu() +{ +} + +void ProjectContextMenu::handleOpenFile() +{ + m_view->openFile(m_index); +} + +void ProjectContextMenu::handleEditPresentationId() +{ + m_view->editPresentationId(m_index, false); +} + +void ProjectContextMenu::handleEditQmlStreamId() +{ + m_view->editPresentationId(m_index, true); +} + +void ProjectContextMenu::handleShowContainingFolder() +{ + m_view->showContainingFolder(m_index); +} + +void ProjectContextMenu::handleCopyPath() +{ + m_view->copyPath(m_index); +} + +void ProjectContextMenu::handleCopyFullPath() +{ + m_view->copyFullPath(m_index); +} + +void ProjectContextMenu::handleRefreshImport() +{ + m_view->refreshImport(m_index); +} + +void ProjectContextMenu::handleImportAssets() +{ + m_view->assetImportInContext(m_index); +} + +void ProjectContextMenu::handleAddMaterial() +{ + m_view->addMaterial(m_index); +} + +void ProjectContextMenu::handleDuplicate() +{ + m_view->duplicate(m_index); +} + +void ProjectContextMenu::handleDuplicatePresentation() +{ + m_view->duplicatePresentation(m_index); +} + +void ProjectContextMenu::handleInitialPresentation() +{ + m_view->setInitialPresentation(m_index); +} + +void ProjectContextMenu::handleRenamePresentation() +{ + m_view->renamePresentation(m_index, false); +} + +void ProjectContextMenu::handleRenameQmlStream() +{ + m_view->renamePresentation(m_index, true); +} + +void ProjectContextMenu::handleDeleteFile() +{ + m_view->deleteFile(m_index); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/ProjectContextMenu.h b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectContextMenu.h new file mode 100644 index 00000000..12cd2367 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectContextMenu.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef PROJECT_CONTEXT_MENU_H +#define PROJECT_CONTEXT_MENU_H + +#include <QtWidgets/qmenu.h> + +class ProjectView; + +class ProjectContextMenu : public QMenu +{ + Q_OBJECT +public: + explicit ProjectContextMenu(ProjectView *parent, int index); + virtual ~ProjectContextMenu(); + +private Q_SLOTS: + void handleOpenFile(); + void handleEditPresentationId(); + void handleEditQmlStreamId(); + void handleShowContainingFolder(); + void handleCopyPath(); + void handleCopyFullPath(); + void handleRefreshImport(); + void handleImportAssets(); + void handleAddMaterial(); + void handleDuplicate(); + void handleDuplicatePresentation(); + void handleInitialPresentation(); + void handleRenamePresentation(); + void handleRenameQmlStream(); + void handleDeleteFile(); + +private: + ProjectView *m_view; + int m_index; +}; +#endif // PROJECT_CONTEXT_MENU_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/ProjectFileSystemModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectFileSystemModel.cpp new file mode 100644 index 00000000..4f62cd59 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectFileSystemModel.cpp @@ -0,0 +1,1372 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "qtAuthoring-config.h" +#include <QtCore/qset.h> +#include <QtCore/qtimer.h> + +#include "PresentationFile.h" +#include "Qt3DSCommonPrecompile.h" +#include "ProjectFileSystemModel.h" +#include "StudioUtils.h" +#include "StudioApp.h" +#include "ClientDataModelBridge.h" +#include "Core.h" +#include "Doc.h" +#include "Qt3DSFileTools.h" +#include "ImportUtils.h" +#include "Dialogs.h" +#include "Qt3DSDMStudioSystem.h" +#include "Qt3DSImportTranslation.h" +#include "Qt3DSMessageBox.h" +#include "IDocumentEditor.h" +#include "IDragable.h" +#include "IObjectReferenceHelper.h" +#include "IDirectoryWatchingSystem.h" + +ProjectFileSystemModel::ProjectFileSystemModel(QObject *parent) : QAbstractListModel(parent) + , m_model(new QFileSystemModel(this)) +{ + connect(m_model, &QAbstractItemModel::rowsInserted, this, &ProjectFileSystemModel::modelRowsInserted); + connect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ProjectFileSystemModel::modelRowsRemoved); + connect(m_model, &QAbstractItemModel::layoutChanged, this, &ProjectFileSystemModel::modelLayoutChanged); + connect(&g_StudioApp.GetCore()->getProjectFile(), &ProjectFile::presentationIdChanged, + this, &ProjectFileSystemModel::handlePresentationIdChange); + connect(&g_StudioApp.GetCore()->getProjectFile(), &ProjectFile::assetNameChanged, + this, &ProjectFileSystemModel::asyncUpdateReferences); + + m_projectReferencesUpdateTimer.setSingleShot(true); + m_projectReferencesUpdateTimer.setInterval(0); + + connect(&m_projectReferencesUpdateTimer, &QTimer::timeout, + this, &ProjectFileSystemModel::updateProjectReferences); +} + +QHash<int, QByteArray> ProjectFileSystemModel::roleNames() const +{ + auto modelRoleNames = m_model->roleNames(); + modelRoleNames.insert(IsExpandableRole, "_isExpandable"); + modelRoleNames.insert(IsDraggableRole, "_isDraggable"); + modelRoleNames.insert(IsReferencedRole, "_isReferenced"); + modelRoleNames.insert(IsProjectReferencedRole, "_isProjectReferenced"); + modelRoleNames.insert(DepthRole, "_depth"); + modelRoleNames.insert(ExpandedRole, "_expanded"); + modelRoleNames.insert(FileIdRole, "_fileId"); + modelRoleNames.insert(ExtraIconRole, "_extraIcon"); + return modelRoleNames; +} + +int ProjectFileSystemModel::rowCount(const QModelIndex &) const +{ + return m_items.count(); +} + +QVariant ProjectFileSystemModel::data(const QModelIndex &index, int role) const +{ + const auto &item = m_items.at(index.row()); + + switch (role) { + case Qt::DecorationRole: { + QString path = item.index.data(QFileSystemModel::FilePathRole).toString(); + return StudioUtils::resourceImageUrl() + getIconName(path); + } + + case IsExpandableRole: { + if (item.index == m_rootIndex) { + return false; + } else { + return hasVisibleChildren(item.index); + } + } + + case IsDraggableRole: + return QFileInfo(item.index.data(QFileSystemModel::FilePathRole).toString()).isFile(); + + case IsReferencedRole: { + const QString path = item.index.data(QFileSystemModel::FilePathRole).toString(); + return m_references.contains(path); + } + + case IsProjectReferencedRole: { + const QString path = item.index.data(QFileSystemModel::FilePathRole).toString(); + return m_projectReferences.contains(path); + } + + case DepthRole: + return item.depth; + + case ExpandedRole: + return item.expanded; + + case FileIdRole: { + const QString path = item.index.data(QFileSystemModel::FilePathRole).toString(); + EStudioObjectType iconType = getIconType(path); + if (iconType == OBJTYPE_PRESENTATION || iconType == OBJTYPE_QML_STREAM) + return presentationId(path); + else + return {}; + } + + case ExtraIconRole: { + const QString path = item.index.data(QFileSystemModel::FilePathRole).toString(); + EStudioObjectType iconType = getIconType(path); + if (iconType == OBJTYPE_PRESENTATION || iconType == OBJTYPE_QML_STREAM) { + if (presentationId(path).isEmpty()) + return QStringLiteral("warning.png"); + else + return {}; + } else { + return {}; + } + } + + default: + return m_model->data(item.index, role); + } +} + +QMimeData *ProjectFileSystemModel::mimeData(const QModelIndexList &indexes) const +{ + const QString path = filePath(indexes.first().row()); // can only drag one item + return CDropSourceFactory::Create(QT3DS_FLAVOR_ASSET_UICFILE, path); +} + +QString ProjectFileSystemModel::filePath(int row) const +{ + if (row < 0 || row >= m_items.size()) + return QString(); + const auto &item = m_items.at(row); + return item.index.data(QFileSystemModel::FilePathRole).toString(); +} + +bool ProjectFileSystemModel::isRefreshable(int row) const +{ + const QString path = filePath(row); + // Import needs to be refreshable even if it is not referenced, as user may drag just individual + // meshes into the scene, and not the whole import. + return path.endsWith(QLatin1String(".import")); +} + +void ProjectFileSystemModel::updateReferences() +{ + m_references.clear(); + const auto doc = g_StudioApp.GetCore()->GetDoc(); + const auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge(); + const auto sourcePathList = bridge->GetSourcePathList(); + const auto fontFileList = bridge->GetFontFileList(); + const auto effectTextureList = bridge->GetDynamicObjectTextureList(); + auto renderableList = bridge->getRenderableList(); + auto subpresentationRecord = g_StudioApp.m_subpresentations; + + const QDir projectDir(doc->GetCore()->getProjectFile().getProjectPath()); + const QString projectPath = QDir::cleanPath(projectDir.absolutePath()); + const QString projectPathSlash = projectPath + QLatin1Char('/'); + + // Add current presentation to renderables list + renderableList.insert(doc->getPresentationId()); + subpresentationRecord.push_back( + SubPresentationRecord({}, doc->getPresentationId(), + projectDir.relativeFilePath(doc->GetDocumentPath()))); + + auto addReferencesPresentation = [this, doc, &projectPath](const QString &str) { + addPathsToReferences(m_references, projectPath, doc->GetResolvedPathToDoc(str)); + }; + auto addReferencesRenderable = [this, &projectPath, &projectPathSlash, &subpresentationRecord] + (const QString &id) { + for (SubPresentationRecord r : qAsConst(subpresentationRecord)) { + if (r.m_id == id) + addPathsToReferences(m_references, projectPath, projectPathSlash + r.m_argsOrSrc); + } + }; + + std::for_each(sourcePathList.begin(), sourcePathList.end(), addReferencesPresentation); + std::for_each(fontFileList.begin(), fontFileList.end(), addReferencesPresentation); + std::for_each(effectTextureList.begin(), effectTextureList.end(), addReferencesPresentation); + std::for_each(renderableList.begin(), renderableList.end(), addReferencesRenderable); + + m_references.insert(projectPath); + + updateRoles({IsReferencedRole, Qt::DecorationRole}); +} + +/** + * Checks if file is already imported and if not, adds it to outImportedFiles + * + * @param importFile The new imported file to check + * @param outImportedFiles List of already imported files + * @return true if importFile was added + */ +bool ProjectFileSystemModel::addUniqueImportFile(const QString &importFile, + QStringList &outImportedFiles) const +{ + const QString cleanPath = QFileInfo(importFile).canonicalFilePath(); + if (outImportedFiles.contains(cleanPath)) { + return false; + } else { + outImportedFiles.append(cleanPath); + return true; + } +} + +/** + * Copy a file with option to override an existing file or skip the override. + * + * @param srcFile The source file to copy. + * @param targetFile The destination file path. + * @param outImportedFiles list of absolute source paths of the dependent assets that are imported + * in the same import context. + * @param outOverrideChoice The copy skip/override choice used in this import context. + */ +void ProjectFileSystemModel::overridableCopyFile(const QString &srcFile, const QString &targetFile, + QStringList &outImportedFiles, + int &outOverrideChoice) const +{ + QFileInfo srcFileInfo(srcFile); + if (srcFileInfo.exists() && addUniqueImportFile(srcFile, outImportedFiles)) { + QFileInfo targetFileInfo(targetFile); + if (srcFileInfo == targetFileInfo) + return; // Autoskip when source and target is the same + if (!targetFileInfo.dir().exists()) + targetFileInfo.dir().mkpath(QStringLiteral(".")); + + if (targetFileInfo.exists()) { // asset exists, show override / skip box + if (outOverrideChoice == QMessageBox::YesToAll) { + QFile::remove(targetFile); + } else if (outOverrideChoice == QMessageBox::NoToAll) { + // QFile::copy() does not override files + } else { + QString pathFromRoot = QDir(g_StudioApp.GetCore()->getProjectFile() + .getProjectPath()) + .relativeFilePath(targetFile); + outOverrideChoice = g_StudioApp.GetDialogs() + ->displayOverrideAssetBox(pathFromRoot); + if (outOverrideChoice & (QMessageBox::Yes | QMessageBox::YesToAll)) + QFile::remove(targetFile); + } + } + QFile::copy(srcFile, targetFile); + } +} + +void ProjectFileSystemModel::updateProjectReferences() +{ + m_projectReferences.clear(); + + const QDir projectDir(g_StudioApp.GetCore()->getProjectFile().getProjectPath()); + const QString projectPath = QDir::cleanPath(projectDir.absolutePath()); + + QHashIterator<QString, bool> updateIt(m_projectReferencesUpdateMap); + while (updateIt.hasNext()) { + updateIt.next(); + m_presentationReferences.remove(updateIt.key()); + if (updateIt.value()) { + QFileInfo fi(updateIt.key()); + QDir fileDir = fi.dir(); + const QString suffix = fi.suffix(); + QHash<QString, QString> importPathMap; + QSet<QString> newReferences; + + const auto addReferencesFromImportMap = [&]() { + QHashIterator<QString, QString> pathIter(importPathMap); + while (pathIter.hasNext()) { + pathIter.next(); + const QString path = pathIter.key(); + QString targetAssetPath; + if (path.startsWith(QLatin1String("./"))) // path from project root + targetAssetPath = projectDir.absoluteFilePath(path); + else // relative path + targetAssetPath = fileDir.absoluteFilePath(path); + newReferences.insert(QDir::cleanPath(targetAssetPath)); + } + }; + + if (CDialogs::presentationExtensions().contains(suffix) + || CDialogs::qmlStreamExtensions().contains(suffix)) { + // Presentation file added/modified, check that it is one of the subpresentations, + // or we don't care about it + const QString relPath = g_StudioApp.GetCore()->getProjectFile() + .getRelativeFilePathTo(updateIt.key()); + for (int i = 0, count = g_StudioApp.m_subpresentations.size(); i < count; ++i) { + SubPresentationRecord &rec = g_StudioApp.m_subpresentations[i]; + if (rec.m_argsOrSrc == relPath) { + if (rec.m_type == QLatin1String("presentation")) { + // Since this is not actual import, source and target uip is the same, + // and we are only interested in the absolute paths of the "imported" + // asset files + QString dummyStr; + QHash<QString, QString> dummyMap; + QSet<QString> dummyDataInputSet; + QSet<QString> dummyDataOutputSet; + PresentationFile::getSourcePaths(fi, fi, importPathMap, + dummyStr, dummyMap, dummyDataInputSet, + dummyDataOutputSet); + addReferencesFromImportMap(); + } else { // qml-stream + QQmlApplicationEngine qmlEngine; + bool isQmlStream = false; + QObject *qmlRoot = getQmlStreamRootNode(qmlEngine, updateIt.key(), + isQmlStream); + if (qmlRoot && isQmlStream) { + QSet<QString> assetPaths; + getQmlAssets(qmlRoot, assetPaths); + QDir qmlDir = fi.dir(); + for (auto &assetSrc : qAsConst(assetPaths)) { + QString targetAssetPath; + targetAssetPath = qmlDir.absoluteFilePath(assetSrc); + newReferences.insert(QDir::cleanPath(targetAssetPath)); + } + } + } + break; + } + } + } else if (CDialogs::materialExtensions().contains(suffix) + || CDialogs::effectExtensions().contains(suffix)) { + // Use dummy set, as we are only interested in values set in material files + QSet<QString> dummySet; + g_StudioApp.GetCore()->GetDoc()->GetDocumentReader() + .ParseSourcePathsOutOfEffectFile( + updateIt.key(), + g_StudioApp.GetCore()->getProjectFile().getProjectPath(), + false, // No need to recurse src mats; those get handled individually + importPathMap, dummySet); + addReferencesFromImportMap(); + } + if (!newReferences.isEmpty()) + m_presentationReferences.insert(updateIt.key(), newReferences); + } + } + + // Update reference cache + QHashIterator<QString, QSet<QString>> presIt(m_presentationReferences); + while (presIt.hasNext()) { + presIt.next(); + const auto &refs = presIt.value(); + for (auto &ref : refs) + addPathsToReferences(m_projectReferences, projectPath, ref); + } + + m_projectReferencesUpdateMap.clear(); + updateRoles({IsProjectReferencedRole}); +} + +void ProjectFileSystemModel::getQmlAssets(const QObject *qmlNode, + QSet<QString> &outAssetPaths) const +{ + QString assetSrc = qmlNode->property("source").toString(); // absolute file path + + if (!assetSrc.isEmpty()) { + // remove file:/// + if (assetSrc.startsWith(QLatin1String("file:///"))) + assetSrc = assetSrc.mid(8); + else if (assetSrc.startsWith(QLatin1String("file://"))) + assetSrc = assetSrc.mid(7); + +#if !defined(Q_OS_WIN) + // Only windows has drive letter in the path, other platforms need to start with / + assetSrc.prepend(QLatin1Char('/')); +#endif + outAssetPaths.insert(assetSrc); + } + + // recursively load child nodes + const QObjectList qmlNodeChildren = qmlNode->children(); + for (auto &node : qmlNodeChildren) + getQmlAssets(node, outAssetPaths); +} + +QObject *ProjectFileSystemModel::getQmlStreamRootNode(QQmlApplicationEngine &qmlEngine, + const QString &filePath, + bool &outIsQmlStream) const +{ + QObject *qmlRoot = nullptr; + outIsQmlStream = false; + + qmlEngine.load(filePath); + if (qmlEngine.rootObjects().size() > 0) { + qmlRoot = qmlEngine.rootObjects().at(0); + const char *rootClassName = qmlEngine.rootObjects().at(0) + ->metaObject()->superClass()->className(); + // The assumption here is that any qml that is not a behavior is a qml stream + if (strcmp(rootClassName, "Q3DStudio::Q3DSQmlBehavior") != 0) + outIsQmlStream = true; + } + + return qmlRoot; +} + +Q3DStudio::DocumentEditorFileType::Enum ProjectFileSystemModel::assetTypeForRow(int row) +{ + if (row <= 0 || row >= m_items.size()) + return Q3DStudio::DocumentEditorFileType::Unknown; + + const QString rootPath = m_items[0].index.data(QFileSystemModel::FilePathRole).toString(); + QString path = m_items[row].index.data(QFileSystemModel::FilePathRole).toString(); + QFileInfo fi(path); + if (!fi.isDir()) + path = fi.absolutePath(); + path = path.mid(rootPath.length() + 1); + const int slash = path.indexOf(QLatin1String("/")); + if (slash >= 0) + path = path.left(slash); + if (path == QLatin1String("effects")) + return Q3DStudio::DocumentEditorFileType::Effect; + else if (path == QLatin1String("fonts")) + return Q3DStudio::DocumentEditorFileType::Font; + else if (path == QLatin1String("maps")) + return Q3DStudio::DocumentEditorFileType::Image; + else if (path == QLatin1String("materials")) + return Q3DStudio::DocumentEditorFileType::Material; + else if (path == QLatin1String("models")) + return Q3DStudio::DocumentEditorFileType::DAE; + else if (path == QLatin1String("scripts")) + return Q3DStudio::DocumentEditorFileType::Behavior; + else if (path == QLatin1String("presentations")) + return Q3DStudio::DocumentEditorFileType::Presentation; + else if (path == QLatin1String("qml")) + return Q3DStudio::DocumentEditorFileType::QmlStream; + + return Q3DStudio::DocumentEditorFileType::Unknown; +} + +void ProjectFileSystemModel::setRootPath(const QString &path) +{ + m_projectReferences.clear(); + m_presentationReferences.clear(); + m_projectReferencesUpdateMap.clear(); + m_projectReferencesUpdateTimer.stop(); + + // Delete the old model. If the new project is in a totally different directory tree, not + // doing this will result in unexplicable crashes when trying to parse something that should + // not be parsed. + disconnect(m_model, &QAbstractItemModel::rowsInserted, + this, &ProjectFileSystemModel::modelRowsInserted); + disconnect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved, + this, &ProjectFileSystemModel::modelRowsRemoved); + disconnect(m_model, &QAbstractItemModel::layoutChanged, + this, &ProjectFileSystemModel::modelLayoutChanged); + delete m_model; + m_model = new QFileSystemModel(this); + connect(m_model, &QAbstractItemModel::rowsInserted, + this, &ProjectFileSystemModel::modelRowsInserted); + connect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved, + this, &ProjectFileSystemModel::modelRowsRemoved); + connect(m_model, &QAbstractItemModel::layoutChanged, + this, &ProjectFileSystemModel::modelLayoutChanged); + + setRootIndex(m_model->setRootPath(path)); + + // Open the presentations folder by default + connect(this, &ProjectFileSystemModel::dataChanged, + this, &ProjectFileSystemModel::asyncExpandPresentations); + + QTimer::singleShot(0, [this]() { + // Watch the project directory for changes to .uip files. + // Note that this initial connection will notify creation for all files, so we call it + // asynchronously to ensure the subpresentations are registered. + m_directoryConnection = g_StudioApp.getDirectoryWatchingSystem().AddDirectory( + g_StudioApp.GetCore()->getProjectFile().getProjectPath(), + std::bind(&ProjectFileSystemModel::onFilesChanged, this, + std::placeholders::_1)); + }); +} + +void ProjectFileSystemModel::setRootIndex(const QModelIndex &rootIndex) +{ + if (rootIndex == m_rootIndex) + return; + + clearModelData(); + + m_rootIndex = rootIndex; + + beginInsertRows({}, 0, 0); + m_items.append({ m_rootIndex, 0, true, nullptr, 0 }); + endInsertRows(); + + showModelTopLevelItems(); +} + +void ProjectFileSystemModel::clearModelData() +{ + beginResetModel(); + m_defaultDirToAbsPathMap.clear(); + m_items.clear(); + endResetModel(); +} + +void ProjectFileSystemModel::showModelTopLevelItems() +{ + int rowCount = m_model->rowCount(m_rootIndex); + + if (rowCount == 0) { + if (m_model->hasChildren(m_rootIndex) && m_model->canFetchMore(m_rootIndex)) + m_model->fetchMore(m_rootIndex); + } else { + showModelChildItems(m_rootIndex, 0, rowCount - 1); + } +} + +void ProjectFileSystemModel::showModelChildItems(const QModelIndex &parentIndex, int start, int end) +{ + const int parentRow = modelIndexRow(parentIndex); + if (parentRow == -1) + return; + + Q_ASSERT(isVisible(parentIndex)); + + QVector<QModelIndex> rowsToInsert; + for (int i = start; i <= end; ++i) { + const auto &childIndex = m_model->index(i, 0, parentIndex); + if (isVisible(childIndex)) + rowsToInsert.append(childIndex); + } + + const int insertCount = rowsToInsert.count(); + if (insertCount == 0) + return; + + auto parent = &m_items[parentRow]; + + const int depth = parent->depth + 1; + const int startRow = parentRow + parent->childCount + 1; + + beginInsertRows({}, startRow, startRow + insertCount - 1); + + for (auto it = rowsToInsert.rbegin(); it != rowsToInsert.rend(); ++it) + m_items.insert(startRow, { *it, depth, false, parent, 0 }); + + for (; parent != nullptr; parent = parent->parent) + parent->childCount += insertCount; + + endInsertRows(); + + // also fetch children so we're notified when files are added or removed in immediate subdirs + for (const auto &childIndex : rowsToInsert) { + if (m_model->hasChildren(childIndex) && m_model->canFetchMore(childIndex)) + m_model->fetchMore(childIndex); + } +} + +void ProjectFileSystemModel::expand(int row) +{ + if (row < 0 || row > m_items.size() - 1 || m_items[row].expanded) + return; + + auto &item = m_items[row]; + const auto &modelIndex = item.index; + + const int rowCount = m_model->rowCount(modelIndex); + if (rowCount == 0) { + if (m_model->hasChildren(modelIndex) && m_model->canFetchMore(modelIndex)) + m_model->fetchMore(modelIndex); + } else { + showModelChildItems(modelIndex, 0, rowCount - 1); + } + + item.expanded = true; + Q_EMIT dataChanged(index(row), index(row)); +} + +bool ProjectFileSystemModel::hasValidUrlsForDropping(const QList<QUrl> &urls) const +{ + for (const auto &url : urls) { + if (url.isLocalFile()) { + const QString path = url.toLocalFile(); + const QFileInfo fileInfo(path); + if (fileInfo.isFile()) { + const QString extension = fileInfo.suffix(); + return extension.compare(QLatin1String(CDialogs::GetDAEFileExtension()), + Qt::CaseInsensitive) == 0 +#ifdef QT_3DSTUDIO_FBX + || extension.compare(QLatin1String(CDialogs::GetFbxFileExtension()), + Qt::CaseInsensitive) == 0 +#endif + || getIconType(path) != OBJTYPE_UNKNOWN; + } + } + } + + return false; +} + +void ProjectFileSystemModel::showInfo(int row) +{ + if (row < 0 || row >= m_items.size()) + row = 0; + + const TreeItem &item = m_items.at(row); + QString path = item.index.data(QFileSystemModel::FilePathRole).toString(); + + QFileInfo fi(path); + + if (fi.suffix() == QLatin1String("materialdef")) { + const auto doc = g_StudioApp.GetCore()->GetDoc(); + bool isDocModified = doc->isModified(); + { // Scope for the ScopedDocumentEditor + Q3DStudio::ScopedDocumentEditor sceneEditor( + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, QString())); + const auto material = sceneEditor->getOrCreateMaterial(path); + QString name; + QMap<QString, QString> values; + QMap<QString, QMap<QString, QString>> textureValues; + sceneEditor->getMaterialInfo(fi.absoluteFilePath(), name, values, textureValues); + sceneEditor->setMaterialValues(fi.absoluteFilePath(), values, textureValues); + if (material.Valid()) + doc->SelectDataModelObject(material); + } + // Several aspects of the editor are not updated correctly + // if the data core is changed without a transaction + // The above scope completes the transaction for creating a new material + // Next the added undo has to be popped from the stack + // and the modified flag has to be restored + // TODO: Find a way to update the editor fully without a transaction + doc->SetModifiedFlag(isDocModified); + g_StudioApp.GetCore()->GetCmdStack()->RemoveLastUndo(); + } +} + +void ProjectFileSystemModel::duplicate(int row) +{ + if (row < 0 || row >= m_items.size()) + row = 0; + + const TreeItem &item = m_items.at(row); + QString path = item.index.data(QFileSystemModel::FilePathRole).toString(); + + QFileInfo srcFile(path); + const QString destPathStart = srcFile.dir().absolutePath() + QLatin1Char('/') + + srcFile.completeBaseName() + QStringLiteral(" Copy"); + const QString destPathEnd = QStringLiteral(".") + srcFile.suffix(); + QString destPath = destPathStart + destPathEnd; + + int i = 1; + while (QFile::exists(destPath)) { + i++; + destPath = destPathStart + QString::number(i) + destPathEnd; + } + + QFile::copy(path, destPath); +} + +void ProjectFileSystemModel::importUrls(const QList<QUrl> &urls, int row, bool autoSort) +{ + if (row < 0 || row >= m_items.size()) + row = 0; // Import to root folder row not specified + + // If importing via buttons or doing in-context import to project root, + // sort imported items to default folders according to their type + const bool sortToDefaults = autoSort || row == 0; + if (sortToDefaults) + updateDefaultDirMap(); + + const TreeItem &item = m_items.at(row); + QString targetPath = item.index.data(QFileSystemModel::FilePathRole).toString(); + + QFileInfo fi(targetPath); + if (!fi.isDir()) + targetPath = fi.absolutePath(); + const QDir targetDir(targetPath); + + QStringList expandPaths; + QHash<QString, QString> presentationNodes; // <relative path to presentation, presentation id> + // List of all files that have been copied by this import. Used to avoid duplicate imports + // due to some of the imported files also being assets used by other imported files. + QStringList importedFiles; + QMap<QString, CDataInputDialogItem *> importedDataInputs; + int overrideChoice = QMessageBox::NoButton; + + for (const auto &url : urls) { + QString sortedPath = targetPath; + QDir sortedDir = targetDir; + + if (sortToDefaults) { + const QString defaultDir = m_defaultDirToAbsPathMap.value( + g_StudioApp.GetDialogs()->defaultDirForUrl(url)); + if (!defaultDir.isEmpty()) { + sortedPath = defaultDir; + sortedDir.setPath(sortedPath); + } + } + + if (sortedDir.exists()) { + importUrl(sortedDir, url, presentationNodes, importedFiles, importedDataInputs, + overrideChoice); + expandPaths << sortedDir.path(); + } + } + + // Batch update all imported presentation nodes + g_StudioApp.GetCore()->getProjectFile().addPresentationNodes(presentationNodes); + + // Add new data inputs that are missing from project's data inputs. Duplicates are ignored, + // even if they are different type. + QMapIterator<QString, CDataInputDialogItem *> diIt(importedDataInputs); + bool addedDi = false; + while (diIt.hasNext()) { + diIt.next(); + if (!g_StudioApp.m_dataInputDialogItems.contains(diIt.key())) { + g_StudioApp.m_dataInputDialogItems.insert(diIt.key(), diIt.value()); + addedDi = true; + } else { + delete diIt.value(); + } + } + if (addedDi) { + g_StudioApp.saveDataInputsToProjectFile(); + g_StudioApp.checkDeletedDatainputs(); // Updates externalPresBoundTypes + } + + for (const QString &expandPath : qAsConst(expandPaths)) { + int expandRow = rowForPath(expandPath); + if (expandRow >= 0 && !m_items[expandRow].expanded) + expand(expandRow); + } +} + +/** + * Imports a single asset and the assets it depends on. + * + * @param targetDir Target path where the asset is imported to + * @param url Source url where the asset is imported from + * @param outPresentationNodes Map where presentation node information is stored for later + * registration. The key is relative path to presentation. The value + * is presentation id. + * @param outImportedFiles List of absolute source paths of the dependent assets that are imported + * in the same import context. + * @param outDataInputs Map of data inputs that are in use in this import context. + * @param outOverrideChoice The copy skip/override choice used in this import context. + */ +void ProjectFileSystemModel::importUrl(QDir &targetDir, const QUrl &url, + QHash<QString, QString> &outPresentationNodes, + QStringList &outImportedFiles, + QMap<QString, CDataInputDialogItem *> &outDataInputs, + int &outOverrideChoice) const +{ + using namespace Q3DStudio; + using namespace qt3dsimp; + // Drag and Drop - From Explorer window to Project Palette + // For all valid Project File Types: + // - This performs a file copy from the source Explorer location to the selected Project Palette + // Folder + // - The destination copy must NOT be read-only even if the source is read-only + // For DAE, it will import the file. + + if (!url.isLocalFile()) + return; + + const QString sourceFile = url.toLocalFile(); + + const QFileInfo fileInfo(sourceFile); + if (!fileInfo.isFile()) + return; + + // Skip importing if the file has already been imported + if (!addUniqueImportFile(sourceFile, outImportedFiles)) + return; + + const auto doc = g_StudioApp.GetCore()->GetDoc(); + + const QString extension = fileInfo.suffix(); + const QString fileStem = fileInfo.baseName(); + const QString outputFileName = QStringLiteral("%1.%2").arg(fileStem).arg(CDialogs::GetImportFileExtension()); + + if (extension.compare(QLatin1String(CDialogs::GetDAEFileExtension()), Qt::CaseInsensitive) == 0) { + SColladaTranslator translator(sourceFile); + const QDir outputDir = SFileTools::FindUniqueDestDirectory(targetDir, fileStem); + const QString fullOutputFile = outputDir.filePath(outputFileName); + const SImportResult importResult = + CPerformImport::TranslateToImportFile(translator, CFilePath(fullOutputFile)); + bool forceError = QFileInfo(fullOutputFile).isFile() == false; + IDocumentEditor::DisplayImportErrors( + sourceFile, importResult.m_Error, doc->GetImportFailedHandler(), + translator.m_TranslationLog, forceError); +#ifdef QT_3DSTUDIO_FBX + } else if (extension.compare(QLatin1String(CDialogs::GetFbxFileExtension()), Qt::CaseInsensitive) == 0) { + SFbxTranslator translator(sourceFile); + const QDir outputDir = SFileTools::FindUniqueDestDirectory(targetDir, fileStem); + const QString fullOutputFile = outputDir.filePath(outputFileName); + const SImportResult importResult = + CPerformImport::TranslateToImportFile(translator, CFilePath(fullOutputFile)); + bool forceError = QFileInfo(fullOutputFile).isFile() == false; + IDocumentEditor::DisplayImportErrors( + sourceFile, importResult.m_Error, doc->GetImportFailedHandler(), + translator.m_TranslationLog, forceError); +#endif + } else { + QQmlApplicationEngine qmlEngine; + QObject *qmlRoot = nullptr; + bool isQmlStream = false; + if (extension == QLatin1String("qml")) { + qmlRoot = getQmlStreamRootNode(qmlEngine, sourceFile, isQmlStream); + if (qmlRoot) { + if (isQmlStream && targetDir.path().endsWith(QLatin1String("/scripts"))) { + const QString path(QStringLiteral("../qml")); + targetDir.mkpath(path); // create the folder if doesn't exist + targetDir.cd(path); + } + } else { + // Invalid qml file, block import + g_StudioApp.GetDialogs()->DisplayKnownErrorDialog( + tr("Failed to parse '%1'\nAborting import.").arg(sourceFile)); + return; + } + } + // Copy the file to target directory + // FindAndCopyDestFile will make sure the file name is unique and make sure it is + // not read only. + QString destPath; // final file path (after copying and renaming) + bool copyResult = SFileTools::FindAndCopyDestFile(targetDir, sourceFile, destPath); + Q_ASSERT(copyResult); + + QString presentationPath; + if (CDialogs::isPresentationFileExtension(extension.toLatin1().data())) { + presentationPath = doc->GetCore()->getProjectFile().getRelativeFilePathTo(destPath); + QSet<QString> dataInputs; + QSet<QString> dataOutputs; + importPresentationAssets(fileInfo, QFileInfo(destPath), outPresentationNodes, + outImportedFiles, dataInputs, dataOutputs, outOverrideChoice); + const QString projFile = PresentationFile::findProjectFile(fileInfo.absoluteFilePath()); + + // #TODO: Handle DataOutputs QT3DS-3510 + QMap<QString, CDataInputDialogItem *> allDataInputs; + ProjectFile::loadDataInputs(projFile, allDataInputs); + for (auto &di : dataInputs) { + if (allDataInputs.contains(di)) + outDataInputs.insert(di, allDataInputs[di]); + } + } else if (qmlRoot && isQmlStream) { // importing a qml stream + presentationPath = doc->GetCore()->getProjectFile().getRelativeFilePathTo(destPath); + importQmlAssets(qmlRoot, fileInfo.dir(), targetDir, outImportedFiles, + outOverrideChoice); + } + + // outPresentationNodes can already contain this presentation in case of multi-importing + // both a presentation and its subpresentation + if (!presentationPath.isEmpty() && !outPresentationNodes.contains(presentationPath)) { + const QString srcProjFile = PresentationFile::findProjectFile(sourceFile); + QString presId; + if (!srcProjFile.isEmpty()) { + QVector<SubPresentationRecord> subpresentations; + ProjectFile::getPresentations(srcProjFile, subpresentations); + QDir srcProjDir(QFileInfo(srcProjFile).path()); + const QString relSrcPresFilePath = srcProjDir.relativeFilePath(sourceFile); + auto *sp = std::find_if( + subpresentations.begin(), subpresentations.end(), + [&relSrcPresFilePath](const SubPresentationRecord &spr) -> bool { + return spr.m_argsOrSrc == relSrcPresFilePath; + }); + // Make sure we are not adding a duplicate id. In that case presId will be empty + // which causes autogeneration of an unique id. + if (sp != subpresentations.end() + && g_StudioApp.GetCore()->getProjectFile().isUniquePresentationId(sp->m_id)) { + presId = sp->m_id; + } + } + outPresentationNodes.insert(presentationPath, presId); + } + + // For effect and custom material files, automatically copy related resources + if (CDialogs::IsEffectFileExtension(extension.toLatin1().data()) + || CDialogs::IsMaterialFileExtension(extension.toLatin1().data())) { + QHash<QString, QString> effectFileSourcePaths; + QString absSrcPath = fileInfo.absoluteFilePath(); + QString projectPath + = QFileInfo(PresentationFile::findProjectFile(absSrcPath)).absolutePath(); + // Since we are importing a bare material/effect, we don't care about possible dynamic + // values of texture properties + QSet<QString> dummyPropertySet; + g_StudioApp.GetCore()->GetDoc()->GetDocumentReader() + .ParseSourcePathsOutOfEffectFile(absSrcPath, projectPath, true, + effectFileSourcePaths, dummyPropertySet); + + QHashIterator<QString, QString> pathIter(effectFileSourcePaths); + while (pathIter.hasNext()) { + pathIter.next(); + overridableCopyFile(pathIter.value(), + QDir(g_StudioApp.GetCore()->getProjectFile().getProjectPath()) + .absoluteFilePath(pathIter.key()), + outImportedFiles, outOverrideChoice); + } + } + } +} + +/** + * Import all assets used in a uip file, this includes materials and effects (and their assets), + * images, fonts, subpresentations (and their recursive assets), models and scripts. Assets are + * imported in the same relative structure in the imported-from folder in order not to break the + * assets paths. + * + * @param uipSrc source file path where the uip is imported from + * @param uipTarget target path where the uip is imported to + * @param outPresentationNodes map where presentation node information is stored for later + * registration. The key is relative path to presentation. The value + * is presentation id. + * @param overrideChoice tracks user choice (yes to all / no to all) to maintain the value through + * recursive calls + * @param outImportedFiles list of absolute source paths of the dependent assets that are imported + * in the same import context. + * @param outDataInputs set of data input identifiers that are in use by this presentation and its + * subpresentations. + * @param outOverrideChoice The copy skip/override choice used in this import context. + */ +void ProjectFileSystemModel::importPresentationAssets( + const QFileInfo &uipSrc, const QFileInfo &uipTarget, + QHash<QString, QString> &outPresentationNodes, QStringList &outImportedFiles, + QSet<QString> &outDataInputs, QSet<QString> &outDataOutputs, int &outOverrideChoice) const +{ + QHash<QString, QString> importPathMap; + QString projPathSrc; // project absolute path for the source uip + PresentationFile::getSourcePaths(uipSrc, uipTarget, importPathMap, projPathSrc, + outPresentationNodes, outDataInputs, outDataOutputs); + const QDir projDir(g_StudioApp.GetCore()->getProjectFile().getProjectPath()); + const QDir uipSrcDir = uipSrc.dir(); + const QDir uipTargetDir = uipTarget.dir(); + + QHashIterator<QString, QString> pathIter(importPathMap); + while (pathIter.hasNext()) { + pathIter.next(); + QString srcAssetPath = pathIter.value(); + const QString path = pathIter.key(); + QString targetAssetPath; + if (srcAssetPath.isEmpty()) + srcAssetPath = uipSrcDir.absoluteFilePath(path); + targetAssetPath = uipTargetDir.absoluteFilePath(path); + + overridableCopyFile(srcAssetPath, targetAssetPath, outImportedFiles, outOverrideChoice); + + if (path.endsWith(QLatin1String(".uip"))) { + // recursively load any uip asset's assets + importPresentationAssets(QFileInfo(srcAssetPath), QFileInfo(targetAssetPath), + outPresentationNodes, outImportedFiles, outDataInputs, + outDataOutputs, outOverrideChoice); + + // update the path in outPresentationNodes to be correctly relative in target project + const QString subId = outPresentationNodes.take(path); + if (!subId.isEmpty()) + outPresentationNodes.insert(projDir.relativeFilePath(targetAssetPath), subId); + } else if (path.endsWith(QLatin1String(".qml"))) { + // recursively load any qml stream assets + QQmlApplicationEngine qmlEngine; + bool isQmlStream = false; + QObject *qmlRoot = getQmlStreamRootNode(qmlEngine, srcAssetPath, isQmlStream); + if (qmlRoot && isQmlStream) { + importQmlAssets(qmlRoot, QFileInfo(srcAssetPath).dir(), + QFileInfo(targetAssetPath).dir(), outImportedFiles, + outOverrideChoice); + // update path in outPresentationNodes to be correctly relative in target project + const QString subId = outPresentationNodes.take(path); + if (!subId.isEmpty()) + outPresentationNodes.insert(projDir.relativeFilePath(targetAssetPath), subId); + } + } + } +} + +/** + * Import all assets specified in "source" properties in a qml file. + * + * @param qmlNode The qml node to checkfor assets. Recursively checks all child nodes, too. + * @param srcDir target dir where the assets are imported to + * @param outImportedFiles list of absolute source paths of the dependent assets that are imported + * in the same import context. + * @param outOverrideChoice The copy skip/override choice used in this import context. + */ +void ProjectFileSystemModel::importQmlAssets(const QObject *qmlNode, const QDir &srcDir, + const QDir &targetDir, + QStringList &outImportedFiles, + int &outOverrideChoice) const +{ + QSet<QString> assetPaths; + getQmlAssets(qmlNode, assetPaths); + + for (auto &assetSrc : qAsConst(assetPaths)) { + overridableCopyFile(srcDir.absoluteFilePath(assetSrc), + targetDir.absoluteFilePath(srcDir.relativeFilePath(assetSrc)), + outImportedFiles, outOverrideChoice); + } +} + +int ProjectFileSystemModel::rowForPath(const QString &path) const +{ + for (int i = m_items.size() - 1; i >= 0 ; --i) { + const QString itemPath = m_items[i].index.data(QFileSystemModel::FilePathRole).toString(); + if (path == itemPath) + return i; + } + return -1; +} + +void ProjectFileSystemModel::updateRoles(const QVector<int> &roles, int startRow, int endRow) +{ + Q_EMIT dataChanged(index(startRow, 0), + index(endRow < 0 ? rowCount() - 1 : endRow, 0), roles); +} + +void ProjectFileSystemModel::collapse(int row) +{ + Q_ASSERT(row >= 0 && row < m_items.size()); + + auto &item = m_items[row]; + Q_ASSERT(item.expanded == true); + + const int childCount = item.childCount; + + if (childCount > 0) { + beginRemoveRows({}, row + 1, row + childCount); + + m_items.erase(std::begin(m_items) + row + 1, std::begin(m_items) + row + 1 + childCount); + + for (auto parent = &item; parent != nullptr; parent = parent->parent) + parent->childCount -= childCount; + + endRemoveRows(); + } + + item.expanded = false; + Q_EMIT dataChanged(index(row), index(row)); +} + +int ProjectFileSystemModel::modelIndexRow(const QModelIndex &modelIndex) const +{ + auto it = std::find_if( + std::begin(m_items), + std::end(m_items), + [&modelIndex](const TreeItem &item) + { + return item.index == modelIndex; + }); + + return it != std::end(m_items) ? std::distance(std::begin(m_items), it) : -1; +} + +bool ProjectFileSystemModel::isExpanded(const QModelIndex &modelIndex) const +{ + if (modelIndex == m_rootIndex) + return true; + const int row = modelIndexRow(modelIndex); + return row != -1 && m_items.at(row).expanded; +} + +EStudioObjectType ProjectFileSystemModel::getIconType(const QString &path) const +{ + return Q3DStudio::ImportUtils::GetObjectFileTypeForFile(path).m_IconType; +} + +QString ProjectFileSystemModel::getIconName(const QString &path) const +{ + QString iconName; + + bool referenced = m_references.contains(path); + + QFileInfo fileInfo(path); + if (fileInfo.isFile()) { + EStudioObjectType type = getIconType(path); + + if (type == OBJTYPE_PRESENTATION) { + const bool isCurrent = isCurrentPresentation(path); + const bool isInitial = isInitialPresentation(path); + if (isInitial) { + iconName = isCurrent ? QStringLiteral("initial_used.png") + : QStringLiteral("initial_notUsed.png"); + } else if (isCurrent) { + iconName = QStringLiteral("presentation_edit.png"); + } + } + + if (iconName.isEmpty()) { + if (type != OBJTYPE_UNKNOWN) { + iconName = referenced ? CStudioObjectTypes::GetNormalIconName(type) + : CStudioObjectTypes::GetDisabledIconName(type); + } else { + iconName = referenced ? QStringLiteral("Objects-Layer-Normal.png") + : QStringLiteral("Objects-Layer-Disabled.png"); + } + } + } else { + iconName = referenced ? QStringLiteral("Objects-Folder-Normal.png") + : QStringLiteral("Objects-Folder-Disabled.png"); + } + + return iconName; +} + +bool ProjectFileSystemModel::hasVisibleChildren(const QModelIndex &modelIndex) const +{ + const QDir dir(modelIndex.data(QFileSystemModel::FilePathRole).toString()); + if (!dir.exists() || dir.isEmpty()) + return false; + + const auto fileInfoList = dir.entryInfoList(QDir::Dirs|QDir::Files|QDir::NoDotAndDotDot); + for (const auto &fileInfo : fileInfoList) { + if (fileInfo.isDir() || getIconType(fileInfo.filePath()) != OBJTYPE_UNKNOWN) + return true; + } + + return false; +} + +bool ProjectFileSystemModel::isVisible(const QModelIndex &modelIndex) const +{ + QString path = modelIndex.data(QFileSystemModel::FilePathRole).toString(); + + if (modelIndex == m_rootIndex || QFileInfo(path).isDir()) + return true; + + if (path.endsWith(QLatin1String("_autosave.uip")) + || path.endsWith(QLatin1String("_@preview@.uip")) + || path.endsWith(QLatin1String(".uia"))) { + return false; + } + + return getIconType(path) != OBJTYPE_UNKNOWN; +} + +void ProjectFileSystemModel::modelRowsInserted(const QModelIndex &parent, int start, int end) +{ + if (!m_rootIndex.isValid()) + return; + + if (isExpanded(parent)) { + showModelChildItems(parent, start, end); + } else { + if (hasVisibleChildren(parent)) { + // show expand arrow + const int row = modelIndexRow(parent); + Q_EMIT dataChanged(index(row), index(row)); + } + } +} + +void ProjectFileSystemModel::modelRowsRemoved(const QModelIndex &parent, int start, int end) +{ + if (!m_rootIndex.isValid()) + return; + + if (isExpanded(parent)) { + for (int i = start; i <= end; ++i) { + const int row = modelIndexRow(m_model->index(i, 0, parent)); + + if (row != -1) { + const auto &item = m_items.at(row); + + beginRemoveRows({}, row, row + item.childCount); + + for (auto parent = item.parent; parent != nullptr; parent = parent->parent) + parent->childCount -= 1 + item.childCount; + + m_items.erase(std::begin(m_items) + row, + std::begin(m_items) + row + item.childCount + 1); + + endRemoveRows(); + } + } + } + + if (!hasVisibleChildren(parent)) { + // collapse the now empty folder + const int row = modelIndexRow(parent); + if (m_items[row].expanded) + collapse(row); + else + Q_EMIT dataChanged(index(row), index(row)); + } +} + +void ProjectFileSystemModel::modelLayoutChanged() +{ + if (!m_rootIndex.isValid()) + return; + + QSet<QPersistentModelIndex> expandedItems; + for (const auto &item : m_items) { + if (item.expanded) + expandedItems.insert(item.index); + } + + const std::function<int(const QModelIndex &, TreeItem *)> insertChildren = [this, &expandedItems, &insertChildren](const QModelIndex &parentIndex, TreeItem *parent) + { + Q_ASSERT(isVisible(parentIndex)); + + const int rowCount = m_model->rowCount(parentIndex); + const int depth = parent->depth + 1; + + int childCount = 0; + + for (int i = 0; i < rowCount; ++i) { + const auto &childIndex = m_model->index(i, 0, parentIndex); + if (isVisible(childIndex)) { + const bool expanded = expandedItems.contains(childIndex); + m_items.append({ childIndex, depth, expanded, parent, 0 }); + auto &item = m_items.last(); + if (expanded) { + item.childCount = insertChildren(childIndex, &item); + childCount += item.childCount; + } + ++childCount; + } + } + + return childCount; + }; + + const int itemCount = m_items.count(); + + m_items.erase(std::begin(m_items) + 1, std::end(m_items)); + m_items.reserve(itemCount); + insertChildren(m_rootIndex, &m_items.first()); + + Q_ASSERT(m_items.count() == itemCount); + + Q_EMIT dataChanged(index(0), index(itemCount - 1)); +} + +void ProjectFileSystemModel::updateDefaultDirMap() +{ + if (m_defaultDirToAbsPathMap.isEmpty()) { + m_defaultDirToAbsPathMap.insert(QStringLiteral("effects"), QString()); + m_defaultDirToAbsPathMap.insert(QStringLiteral("fonts"), QString()); + m_defaultDirToAbsPathMap.insert(QStringLiteral("maps"), QString()); + m_defaultDirToAbsPathMap.insert(QStringLiteral("materials"), QString()); + m_defaultDirToAbsPathMap.insert(QStringLiteral("models"), QString()); + m_defaultDirToAbsPathMap.insert(QStringLiteral("scripts"), QString()); + m_defaultDirToAbsPathMap.insert(QStringLiteral("presentations"), QString()); + m_defaultDirToAbsPathMap.insert(QStringLiteral("qml"), QString()); + } + + const QString rootPath = m_items[0].index.data(QFileSystemModel::FilePathRole).toString(); + const QStringList keys = m_defaultDirToAbsPathMap.keys(); + for (const QString &key : keys) { + QString currentValue = m_defaultDirToAbsPathMap[key]; + if (currentValue.isEmpty()) { + const QString defaultPath = rootPath + QLatin1Char('/') + key; + const QFileInfo fi(defaultPath); + if (fi.exists() && fi.isDir()) + m_defaultDirToAbsPathMap.insert(key, defaultPath); + } else { + const QFileInfo fi(currentValue); + if (!fi.exists()) + m_defaultDirToAbsPathMap.insert(key, QString()); + } + } +} + +void ProjectFileSystemModel::addPathsToReferences(QSet<QString> &references, + const QString &projectPath, + const QString &origPath) +{ + references.insert(origPath); + QString path = origPath; + QString parentPath = QFileInfo(path).path(); + do { + references.insert(path); + path = parentPath; + parentPath = QFileInfo(path).path(); + } while (path != projectPath && parentPath != path); +} + +void ProjectFileSystemModel::handlePresentationIdChange(const QString &path, const QString &id) +{ + const QString cleanPath = QDir::cleanPath( + QDir(g_StudioApp.GetCore()->GetDoc()->GetCore()->getProjectFile() + .getProjectPath()).absoluteFilePath(path)); + int row = rowForPath(cleanPath); + m_projectReferencesUpdateMap.insert(cleanPath, true); + m_projectReferencesUpdateTimer.start(); + updateRoles({FileIdRole, ExtraIconRole}, row, row); +} + +void ProjectFileSystemModel::asyncExpandPresentations() +{ + disconnect(this, &ProjectFileSystemModel::dataChanged, + this, &ProjectFileSystemModel::asyncExpandPresentations); + + // expand presentation folder by default (if it exists). + QTimer::singleShot(0, [this]() { + QString path = g_StudioApp.GetCore()->getProjectFile().getProjectPath() + + QStringLiteral("/presentations"); + expand(rowForPath(path)); + }); +} + +void ProjectFileSystemModel::asyncUpdateReferences() +{ + QTimer::singleShot(0, this, &ProjectFileSystemModel::updateReferences); +} + +void ProjectFileSystemModel::onFilesChanged( + const Q3DStudio::TFileModificationList &inFileModificationList) +{ + // If any presentation file changes, update asset reference caches + for (size_t idx = 0, end = inFileModificationList.size(); idx < end; ++idx) { + const Q3DStudio::SFileModificationRecord &record(inFileModificationList[idx]); + if (record.m_File.isFile()) { + const QString suffix = record.m_File.suffix(); + const bool isQml = CDialogs::qmlStreamExtensions().contains(suffix); + if (isQml || CDialogs::presentationExtensions().contains(suffix) + || CDialogs::materialExtensions().contains(suffix) + || CDialogs::effectExtensions().contains(suffix)) { + const QString filePath = record.m_File.absoluteFilePath(); + if (record.m_ModificationType == Q3DStudio::FileModificationType::Created + || record.m_ModificationType == Q3DStudio::FileModificationType::Modified) { + if (isQml && !g_StudioApp.isQmlStream(filePath)) + continue; // Skip non-stream qml's to match import logic + m_projectReferencesUpdateMap.insert(filePath, true); + } else if (record.m_ModificationType + == Q3DStudio::FileModificationType::Destroyed) { + m_projectReferencesUpdateMap.insert(filePath, false); + } + m_projectReferencesUpdateTimer.start(); + } + } + } +} + +bool ProjectFileSystemModel::isCurrentPresentation(const QString &path) const +{ + return path == g_StudioApp.GetCore()->GetDoc()->GetDocumentPath(); +} + +bool ProjectFileSystemModel::isInitialPresentation(const QString &path) const +{ + QString checkId = presentationId(path); + + return !checkId.isEmpty() + && checkId == g_StudioApp.GetCore()->getProjectFile().initialPresentation(); +} + +QString ProjectFileSystemModel::presentationId(const QString &path) const +{ + QString presId; + if (isCurrentPresentation(path)) + presId = g_StudioApp.GetCore()->GetDoc()->getPresentationId(); + else + presId = g_StudioApp.getRenderableId(QFileInfo(path).absoluteFilePath()); + + return presId; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/ProjectFileSystemModel.h b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectFileSystemModel.h new file mode 100644 index 00000000..8e9cefb8 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectFileSystemModel.h @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ +#ifndef TREEVIEWADAPTOR_H +#define TREEVIEWADAPTOR_H + +#include "StudioObjectTypes.h" +#include "DocumentEditorEnumerations.h" +#include "Qt3DSFileTools.h" +#include "Dispatch.h" + +#include <QtWidgets/qfilesystemmodel.h> +#include <QtWidgets/qmessagebox.h> +#include <QtCore/qabstractitemmodel.h> +#include <QtCore/qlist.h> +#include <QtCore/qurl.h> +#include <QtCore/qtimer.h> +#include <QtQml/qqmlapplicationengine.h> + +QT_FORWARD_DECLARE_CLASS(QFileSystemModel) +class CDataInputDialogItem; + +class ProjectFileSystemModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit ProjectFileSystemModel(QObject *parent = nullptr); + + enum { + IsExpandableRole = QFileSystemModel::FilePermissions + 1, + IsDraggableRole, + IsReferencedRole, + IsProjectReferencedRole, // Means some other presentation in the project uses this file + DepthRole, + ExpandedRole, + FileIdRole, + ExtraIconRole + }; + + void setRootPath(const QString &path); + + QHash<int, QByteArray> roleNames() const override; + int rowCount(const QModelIndex &parent = {}) const override; + QVariant data(const QModelIndex &index, int role) const override; + QMimeData *mimeData(const QModelIndexList &indexes) const override; + + QString filePath(int row) const; + bool isRefreshable(int row) const; + bool isCurrentPresentation(const QString &path) const; + bool isInitialPresentation(const QString &path) const; + QString presentationId(const QString &path) const; + + Q3DStudio::DocumentEditorFileType::Enum assetTypeForRow(int row); + int rowForPath(const QString &path) const; + void updateRoles(const QVector<int> &roles, int startRow = 0, int endRow = -1); + + Q_INVOKABLE void expand(int row); + Q_INVOKABLE void collapse(int row); + + Q_INVOKABLE void importUrls(const QList<QUrl> &urls, int row, bool autoSort = true); + Q_INVOKABLE bool hasValidUrlsForDropping(const QList<QUrl> &urls) const; + Q_INVOKABLE void showInfo(int row); + Q_INVOKABLE void duplicate(int row); + + void asyncUpdateReferences(); + void onFilesChanged(const Q3DStudio::TFileModificationList &inFileModificationList); + +Q_SIGNALS: + void modelChanged(QAbstractItemModel *model); + +private: + void setRootIndex(const QModelIndex &rootIndex); + void clearModelData(); + void showModelTopLevelItems(); + void showModelChildItems(const QModelIndex &parentItem, int start, int end); + int modelIndexRow(const QModelIndex &modelIndex) const; + bool isExpanded(const QModelIndex &modelIndex) const; + QString getIconName(const QString &path) const; + EStudioObjectType getIconType(const QString &path) const; + bool isVisible(const QModelIndex& modelIndex) const; + bool hasVisibleChildren(const QModelIndex &modelIndex) const; + void importUrl(QDir &targetDir, const QUrl &url, + QHash<QString, QString> &outPresentationNodes, + QStringList &outImportedFiles, + QMap<QString, CDataInputDialogItem *> &outDataInputs, + int &outOverrideChoice) const; + void importPresentationAssets(const QFileInfo &uipSrc, const QFileInfo &uipTarget, + QHash<QString, QString> &outPresentationNodes, + QStringList &outImportedFiles, QSet<QString> &outDataInputs, + QSet<QString> &outDataOutputs,int &outOverrideChoice) const; + + void modelRowsInserted(const QModelIndex &parent, int start, int end); + void modelRowsRemoved(const QModelIndex &parent, int start, int end); + void modelRowsMoved(const QModelIndex &parent, int start, int end); + void modelLayoutChanged(); + void importQmlAssets(const QObject *qmlNode, const QDir &srcDir, const QDir &targetDir, + QStringList &outImportedFiles, int &outOverrideChoice) const; + void updateDefaultDirMap(); + void addPathsToReferences(QSet<QString> &references, const QString &projectPath, + const QString &origPath); + void handlePresentationIdChange(const QString &path, const QString &id); + void asyncExpandPresentations(); + void updateReferences(); + bool addUniqueImportFile(const QString &importFile, QStringList &outImportedFiles) const; + void overridableCopyFile(const QString &srcFile, const QString &targetFile, + QStringList &outImportedFiles, int &outOverrideChoice) const; + void updateProjectReferences(); + void getQmlAssets(const QObject *qmlNode, QSet<QString> &outAssetPaths) const; + QObject *getQmlStreamRootNode(QQmlApplicationEngine &qmlEngine, const QString &filePath, + bool &outIsQmlStream) const; + + struct TreeItem { + QPersistentModelIndex index; + int depth; + bool expanded; + TreeItem *parent; + int childCount; + }; + + QFileSystemModel *m_model = nullptr; + QPersistentModelIndex m_rootIndex; + QList<TreeItem> m_items; + QSet<QString> m_references; + QHash<QString, QString> m_defaultDirToAbsPathMap; + + // Cache of assets referred by other presentation files and qml streams in the project + // Key: Absolute presentation file path + // Value: Set of absolute asset file paths referred by the presentation file + QHash<QString, QSet<QString>> m_presentationReferences; + + // Compilation of all m_presentationReferences sets and their parent paths + QSet<QString> m_projectReferences; + + // Key: uip that needs update + // Value: if true, uip was modified or created. If false, it was removed. + QHash<QString, bool> m_projectReferencesUpdateMap; + QTimer m_projectReferencesUpdateTimer; + std::shared_ptr<qt3dsdm::ISignalConnection> m_directoryConnection; +}; + +#endif // TREEVIEWADAPTOR_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.cpp b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.cpp new file mode 100644 index 00000000..dadb5895 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.cpp @@ -0,0 +1,534 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "ProjectView.h" +#include "ProjectFileSystemModel.h" +#include "Core.h" +#include "Dispatch.h" +#include "Doc.h" +#include "Literals.h" +#include "StudioUtils.h" +#include "ImportUtils.h" +#include "StudioApp.h" +#include "StudioClipboard.h" +#include "StudioPreferences.h" +#include "Qt3DSImport.h" +#include "Dialogs.h" +#include "IDocumentEditor.h" +#include "ProjectContextMenu.h" +#include "EditPresentationIdDlg.h" + +#include <QtCore/qprocess.h> +#include <QtCore/qtimer.h> +#include <QtGui/qdrag.h> +#include <QtGui/qdesktopservices.h> +#include <QtQml/qqmlcontext.h> +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlfile.h> +#include <QtQuick/qquickitem.h> + +ProjectView::ProjectView(const QSize &preferredSize, QWidget *parent) : QQuickWidget(parent) + , m_ProjectModel(new ProjectFileSystemModel(this)) + , m_preferredSize(preferredSize) +{ + const QString theApplicationPath = Qt3DSFile::GetApplicationDirectory(); + + m_defaultBehaviorDir = theApplicationPath + QStringLiteral("/Content/Behavior Library"); + m_defaultEffectDir = theApplicationPath + QStringLiteral("/Content/Effect Library"); + m_defaultFontDir = theApplicationPath + QStringLiteral("/Content/Font Library"); + m_defaultImageDir = theApplicationPath + QStringLiteral("/Content/Maps Library"); + m_defaultMaterialDir = theApplicationPath + QStringLiteral("/Content/Material Library"); + m_defaultModelDir = theApplicationPath + QStringLiteral("/Content/Models Library"); + m_defaultPresentationDir = theApplicationPath + QStringLiteral("/Content/Presentations"); + m_defaultQmlStreamDir = theApplicationPath + QStringLiteral("/Content/Qml Streams"); + + m_BehaviorDir = m_defaultBehaviorDir; + m_EffectDir = m_defaultEffectDir; + m_FontDir = m_defaultFontDir; + m_ImageDir = m_defaultImageDir; + m_MaterialDir = m_defaultMaterialDir; + m_ModelDir = m_defaultModelDir; + m_presentationDir = m_defaultPresentationDir; + m_qmlStreamDir = m_defaultQmlStreamDir; + + m_assetImportDir = theApplicationPath + QStringLiteral("/Content"); + + setResizeMode(QQuickWidget::SizeRootObjectToView); + QTimer::singleShot(0, this, &ProjectView::initialize); + + auto dispatch = g_StudioApp.GetCore()->GetDispatch(); + dispatch->AddPresentationChangeListener(this); + dispatch->AddDataModelListener(this); + dispatch->AddFileOpenListener(this); +} + +ProjectView::~ProjectView() +{ +} + +QAbstractItemModel *ProjectView::projectModel() const +{ + return m_ProjectModel; +} + +QSize ProjectView::sizeHint() const +{ + return m_preferredSize; +} + +void ProjectView::initialize() +{ + CStudioPreferences::setQmlContextProperties(rootContext()); + rootContext()->setContextProperty(QStringLiteral("_resDir"), StudioUtils::resourceImageUrl()); + rootContext()->setContextProperty(QStringLiteral("_parentView"), this); + + engine()->addImportPath(StudioUtils::qmlImportPath()); + setSource(QUrl(QStringLiteral("qrc:/Palettes/Project/ProjectView.qml"))); +} + +void ProjectView::effectAction(int row) +{ + m_EffectDir = m_defaultEffectDir; + QList<QUrl> urls = g_StudioApp.GetDialogs()->SelectAssets( + m_EffectDir, Q3DStudio::DocumentEditorFileType::Effect); + m_ProjectModel->importUrls(urls, row); +} + +void ProjectView::fontAction(int row) +{ + m_FontDir = m_defaultFontDir; + QList<QUrl> urls = g_StudioApp.GetDialogs()->SelectAssets( + m_FontDir, Q3DStudio::DocumentEditorFileType::Font); + m_ProjectModel->importUrls(urls, row); +} + +void ProjectView::imageAction(int row) +{ + m_ImageDir = m_defaultImageDir; + QList<QUrl> urls = g_StudioApp.GetDialogs()->SelectAssets( + m_ImageDir, Q3DStudio::DocumentEditorFileType::Image); + m_ProjectModel->importUrls(urls, row); +} + +void ProjectView::materialAction(int row) +{ + m_MaterialDir = m_defaultMaterialDir; + QList<QUrl> urls = g_StudioApp.GetDialogs()->SelectAssets( + m_MaterialDir, Q3DStudio::DocumentEditorFileType::Material); + m_ProjectModel->importUrls(urls, row); +} + +void ProjectView::modelAction(int row) +{ + m_ModelDir = m_defaultModelDir; + QList<QUrl> urls = g_StudioApp.GetDialogs()->SelectAssets( + m_ModelDir, Q3DStudio::DocumentEditorFileType::DAE); + m_ProjectModel->importUrls(urls, row); +} + +void ProjectView::presentationAction(int row) +{ + m_presentationDir = m_defaultPresentationDir; + QList<QUrl> urls = g_StudioApp.GetDialogs()->SelectAssets( + m_presentationDir, Q3DStudio::DocumentEditorFileType::Presentation); + m_ProjectModel->importUrls(urls, row); +} + +void ProjectView::behaviorAction(int row) +{ + m_BehaviorDir = m_defaultBehaviorDir; + QList<QUrl> urls = g_StudioApp.GetDialogs()->SelectAssets( + m_BehaviorDir, Q3DStudio::DocumentEditorFileType::Behavior); + m_ProjectModel->importUrls(urls, row); +} + +void ProjectView::assetImportAction(int row) +{ + QList<QUrl> urls = g_StudioApp.GetDialogs()->SelectAssets( + m_assetImportDir, Q3DStudio::DocumentEditorFileType::Unknown); + m_ProjectModel->importUrls(urls, row); +} + +void ProjectView::assetImportInContext(int row) +{ + // If the context is a default directory, select the correct directory + Q3DStudio::DocumentEditorFileType::Enum assetType = m_ProjectModel->assetTypeForRow(row); + QString *assetDir = &m_assetImportDir; + switch (assetType) { + case Q3DStudio::DocumentEditorFileType::Effect: + assetDir = &m_EffectDir; + break; + case Q3DStudio::DocumentEditorFileType::Font: + assetDir = &m_FontDir; + break; + case Q3DStudio::DocumentEditorFileType::Image: + assetDir = &m_ImageDir; + break; + case Q3DStudio::DocumentEditorFileType::Material: + assetDir = &m_MaterialDir; + break; + case Q3DStudio::DocumentEditorFileType::DAE: + assetDir = &m_ModelDir; + break; + case Q3DStudio::DocumentEditorFileType::Behavior: + assetDir = &m_BehaviorDir; + break; + case Q3DStudio::DocumentEditorFileType::Presentation: + assetDir = &m_presentationDir; + break; + default: + break; + } + + QList<QUrl> urls; + urls = g_StudioApp.GetDialogs()->SelectAssets(*assetDir, assetType); + m_ProjectModel->importUrls(urls, row, false); +} + +void ProjectView::OnNewPresentation() +{ + rebuild(); +} + +void ProjectView::OnOpenDocument(const QString &inFilename, bool inSucceeded) +{ + Q_UNUSED(inFilename) + Q_UNUSED(inSucceeded) +} + +void ProjectView::OnSaveDocument(const QString &inFilename, bool inSucceeded, bool inSaveCopy) +{ + Q_UNUSED(inFilename) + Q_UNUSED(inSucceeded) + Q_UNUSED(inSaveCopy) + m_ProjectModel->asyncUpdateReferences(); +} + +void ProjectView::OnDocumentPathChanged(const QString &inNewPath) +{ + Q_UNUSED(inNewPath) +} + +void ProjectView::OnBeginDataModelNotifications() +{ +} + +void ProjectView::OnEndDataModelNotifications() +{ + m_ProjectModel->asyncUpdateReferences(); +} + +void ProjectView::OnImmediateRefreshInstanceSingle(qt3dsdm::Qt3DSDMInstanceHandle inInstance) +{ + Q_UNUSED(inInstance); +} + +void ProjectView::OnImmediateRefreshInstanceMultiple(qt3dsdm::Qt3DSDMInstanceHandle *inInstance, + long inInstanceCount) +{ + Q_UNUSED(inInstance); + Q_UNUSED(inInstanceCount); +} + +void ProjectView::mousePressEvent(QMouseEvent *event) +{ + g_StudioApp.setLastActiveView(this); + QQuickWidget::mousePressEvent(event); +} + +void ProjectView::startDrag(QQuickItem *item, int row) +{ + item->grabMouse(); // Grab to make sure we can ungrab after the drag + const auto index = m_ProjectModel->index(row); + + QDrag drag(this); + drag.setMimeData(m_ProjectModel->mimeData({index})); + drag.setPixmap(QPixmap(QQmlFile::urlToLocalFileOrQrc(index.data(Qt::DecorationRole).toUrl()))); + Qt::DropAction action = Qt::CopyAction; + // prevent DnD the currently open presentation and presentations with empty id + if (isCurrentPresentation(row) || ((isPresentation(row) || isQmlStream(row)) + && presentationId(row).isEmpty())) { + action = Qt::IgnoreAction; + } + drag.exec(action); + + // Ungrab to trigger mouse release on the originating item + QTimer::singleShot(0, item, &QQuickItem::ungrabMouse); +} + +bool ProjectView::isCurrentPresentation(int row) const +{ + return m_ProjectModel->isCurrentPresentation(m_ProjectModel->filePath(row)); +} + +void ProjectView::editPresentationId(int row, bool qmlStream) +{ + QString relativePresPath = QDir(g_StudioApp.GetCore()->getProjectFile().getProjectPath()) + .relativeFilePath(m_ProjectModel->filePath(row)); + + EditPresentationIdDlg dlg(relativePresPath, + qmlStream ? EditPresentationIdDlg::EditQmlStreamId + : EditPresentationIdDlg::EditPresentationId, this); + dlg.exec(); +} + +void ProjectView::renamePresentation(int row, bool qmlStream) +{ + QString relativePresPath = QDir(g_StudioApp.GetCore()->getProjectFile().getProjectPath()) + .relativeFilePath(m_ProjectModel->filePath(row)); + + EditPresentationIdDlg dlg(relativePresPath, + qmlStream ? EditPresentationIdDlg::EditQmlStreamName + : EditPresentationIdDlg::EditPresentationName, this); + dlg.exec(); +} + +void ProjectView::showContainingFolder(int row) const +{ + if (row == -1) + return; + const auto path = m_ProjectModel->filePath(row); +#if defined(Q_OS_WIN) + QString param = QStringLiteral("explorer "); + if (!QFileInfo(path).isDir()) + param += QLatin1String("/select,"); + param += QDir::toNativeSeparators(path).replace(QLatin1String(" "), QLatin1String("\ ")); + QProcess::startDetached(param); +#elif defined(Q_OS_MACOS) + QProcess::startDetached("/usr/bin/osascript", {"-e", + QStringLiteral("tell application \"Finder\" to reveal POSIX file \"%1\"").arg(path)}); + QProcess::startDetached("/usr/bin/osascript", {"-e", + QStringLiteral("tell application \"Finder\" to activate")}); +#else + // we cannot select a file here, because no file browser really supports it... + QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(path).absolutePath())); +#endif +} + +void ProjectView::copyPath(int row) const +{ + if (row == -1) + return; + + const auto doc = g_StudioApp.GetCore()->GetDoc(); + const QString path = m_ProjectModel->filePath(row); + const QString relativePath = doc->GetRelativePathToDoc(path); + CStudioClipboard::CopyTextToClipboard(relativePath); +} + +void ProjectView::copyFullPath(int row) const +{ + if (row == -1) + return; + const auto path = m_ProjectModel->filePath(row); + CStudioClipboard::CopyTextToClipboard(path); +} + +bool ProjectView::isPresentation(int row) const +{ + return m_ProjectModel->filePath(row).endsWith(QLatin1String(".uip")); +} + +bool ProjectView::isQmlStream(int row) const +{ + return g_StudioApp.isQmlStream(m_ProjectModel->filePath(row)); +} + +bool ProjectView::isMaterialFolder(int row) const +{ + return m_ProjectModel->filePath(row).endsWith(QLatin1String("/materials")); +} + +bool ProjectView::isInMaterialFolder(int row) const +{ + return g_StudioApp.GetCore()->getProjectFile().getRelativeFilePathTo( + m_ProjectModel->filePath(row)).startsWith(QLatin1String("materials/")); +} + +bool ProjectView::isMaterialData(int row) const +{ + return m_ProjectModel->filePath(row).endsWith(QLatin1String(".materialdef")); +} + +bool ProjectView::isInitialPresentation(int row) const +{ + return m_ProjectModel->isInitialPresentation(m_ProjectModel->filePath(row)); +} + +bool ProjectView::isFolder(int row) const +{ + return QFileInfo(m_ProjectModel->filePath(row)).isDir(); +} + +bool ProjectView::isReferenced(int row) const +{ + const auto index = m_ProjectModel->index(row); + return index.data(ProjectFileSystemModel::IsReferencedRole).toBool() + || index.data(ProjectFileSystemModel::IsProjectReferencedRole).toBool(); +} + +QString ProjectView::presentationId(int row) const +{ + return m_ProjectModel->presentationId(m_ProjectModel->filePath(row)); +} + +void ProjectView::setInitialPresentation(int row) +{ + QString setId = presentationId(row); + + // If presentation id is empty, it means .uip is not part of the project. It shouldn't be + // possible to set initial presentation in that case. + Q_ASSERT(!setId.isEmpty()); + + g_StudioApp.GetCore()->getProjectFile().setInitialPresentation(setId); + m_ProjectModel->updateRoles({Qt::DecorationRole}); +} + +bool ProjectView::isRefreshable(int row) const +{ + return m_ProjectModel->isRefreshable(row); +} + +void ProjectView::showContextMenu(int x, int y, int index) +{ + ProjectContextMenu contextMenu(this, index); + contextMenu.exec(mapToGlobal({x, y})); +} + +bool ProjectView::toolTipsEnabled() +{ + return CStudioPreferences::ShouldShowTooltips(); +} + +void ProjectView::openFile(int row) +{ + if (row == -1) + return; + + QFileInfo fi(m_ProjectModel->filePath(row)); + if (fi.isDir() || isCurrentPresentation(row)) + return; + + QString filePath = QDir::cleanPath(fi.absoluteFilePath()); + QTimer::singleShot(0, [filePath, row, this]() { + // .uip files should be opened in this studio instance + if (filePath.endsWith(QLatin1String(".uip"), Qt::CaseInsensitive)) { + if (g_StudioApp.PerformSavePrompt()) + g_StudioApp.OnLoadDocument(filePath); + } else if (filePath.endsWith(QLatin1String(".materialdef"), Qt::CaseInsensitive)) { + editMaterial(row); + } else { + QDesktopServices::openUrl(QUrl::fromLocalFile(filePath)); + } + }); +} + +void ProjectView::refreshImport(int row) const +{ + if (row == -1) + return; + using namespace Q3DStudio; + const auto path = m_ProjectModel->filePath(row); + qt3dsimp::ImportPtrOrError importPtr = qt3dsimp::Import::Load(path.toStdWString().c_str()); + if (importPtr.m_Value) { + const auto destDir = importPtr.m_Value->GetDestDir(); + const auto srcFile = importPtr.m_Value->GetSrcFile(); + const QString fullSrcPath(QDir(destDir).filePath(srcFile)); + const QFileInfo oldFile(fullSrcPath); + const QFileInfo newFile(g_StudioApp.GetDialogs()->ConfirmRefreshModelFile(fullSrcPath)); + if (newFile.exists() && newFile.isFile()) { + // We don't want to create undo point of "Refresh Import", undoing this sort of + // thing is supposed to be done in the DCC tool. + g_StudioApp.GetCore()->GetDoc()->getSceneEditor()->RefreshImport( + oldFile.canonicalFilePath(), newFile.canonicalFilePath()); + } + } +} + +void ProjectView::addMaterial(int row) const +{ + if (row == -1) + return; + + QString path = m_ProjectModel->filePath(row); + QFileInfo info(path); + if (info.isFile()) + path = info.dir().path(); + path += QLatin1String("/Material"); + QString extension = QLatin1String(".materialdef"); + + QFile file(path + extension); + int i = 1; + while (file.exists()) { + i++; + file.setFileName(path + QString::number(i) + extension); + } + + file.open(QIODevice::WriteOnly); + file.write("<MaterialData version=\"1.0\">\n</MaterialData>"); +} + +void ProjectView::editMaterial(int row) const +{ + m_ProjectModel->showInfo(row); +} + +void ProjectView::duplicate(int row) const +{ + m_ProjectModel->duplicate(row); +} + +void ProjectView::duplicatePresentation(int row) const +{ + g_StudioApp.duplicatePresentation(m_ProjectModel->filePath(row)); +} + +void ProjectView::deleteFile(int row) const +{ + if (isReferenced(row)) { + // Execution should never get here, as menu option is disabled, but since reference cache + // updates are asynchronous, it is possible to have situation where menu item is enabled + // but deletion is no longer valid when selected. + qWarning() << __FUNCTION__ << "Tried to delete referenced file"; + return; + } + + const QString &filePath = m_ProjectModel->filePath(row); + + if (isPresentation(row) || isQmlStream(row)) { + // When deleting renderables, project file assets needs to be updated + g_StudioApp.GetCore()->getProjectFile().deletePresentationFile(filePath); + } else { + QFile file(filePath); + file.remove(); + } +} + +void ProjectView::rebuild() +{ + m_ProjectModel->setRootPath(g_StudioApp.GetCore()->getProjectFile().getProjectPath()); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.h b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.h new file mode 100644 index 00000000..7533e677 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.h @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ +#ifndef PROJECTVIEW_H +#define PROJECTVIEW_H + +#include "DispatchListeners.h" +#include "Qt3DSFile.h" +#include "EditPresentationIdDlg.h" + +#include <QQuickWidget> +#include <QModelIndex> + +class ProjectFileSystemModel; +QT_FORWARD_DECLARE_CLASS(QQuickItem) + +class ProjectView : public QQuickWidget, + public CPresentationChangeListener, + public IDataModelListener, + public CFileOpenListener + + +{ + Q_OBJECT + + Q_PROPERTY(QAbstractItemModel *projectModel READ projectModel NOTIFY projectChanged FINAL) + +public: + explicit ProjectView(const QSize &preferredSize, QWidget *parent = nullptr); + ~ProjectView(); + + QSize sizeHint() const override; + + QAbstractItemModel *projectModel() const; + + Q_INVOKABLE void effectAction(int row); + Q_INVOKABLE void fontAction(int row); + Q_INVOKABLE void imageAction(int row); + Q_INVOKABLE void materialAction(int row); + Q_INVOKABLE void modelAction(int row); + Q_INVOKABLE void presentationAction(int row); + Q_INVOKABLE void behaviorAction(int row); + Q_INVOKABLE void assetImportAction(int row); + void assetImportInContext(int row); + + Q_INVOKABLE void startDrag(QQuickItem *item, int row); + Q_INVOKABLE void showContextMenu(int x, int y, int index); + Q_INVOKABLE bool toolTipsEnabled(); + Q_INVOKABLE void openFile(int row); + + void showContainingFolder(int row) const; + void copyPath(int row) const; + void copyFullPath(int row) const; + void refreshImport(int row) const; + void addMaterial(int row) const; + void editMaterial(int row) const; + void duplicate(int row) const; + void duplicatePresentation(int row) const; + void deleteFile(int row) const; + + bool isRefreshable(int row) const; + + Q_INVOKABLE bool isPresentation(int row) const; + Q_INVOKABLE bool isQmlStream(int row) const; + bool isCurrentPresentation(int row) const; + bool isMaterialFolder(int row) const; + bool isInMaterialFolder(int row) const; + bool isMaterialData(int row) const; + bool isInitialPresentation(int row) const; + bool isFolder(int row) const; + bool isReferenced(int row) const; + QString presentationId(int row) const; + void setInitialPresentation(int row); + Q_INVOKABLE void editPresentationId(int row, bool qmlStream); + void renamePresentation(int row, bool qmlStream); + + // CPresentationChangeListener + void OnNewPresentation() override; + // CFileOpenListener + void OnOpenDocument(const QString &inFilename, bool inSucceeded) override; + void OnSaveDocument(const QString &inFilename, bool inSucceeded, bool inSaveCopy) override; + void OnDocumentPathChanged(const QString &inNewPath) override; + // IDataModelListener + void OnBeginDataModelNotifications() override; + void OnEndDataModelNotifications() override; + // These are used during drag operations or during operations which + // require immediate user feedback. So they are unimplemented, effectively, + // we ignore them. + void OnImmediateRefreshInstanceSingle(qt3dsdm::Qt3DSDMInstanceHandle inInstance) override; + void OnImmediateRefreshInstanceMultiple(qt3dsdm::Qt3DSDMInstanceHandle *inInstance, + long inInstanceCount) override; + +Q_SIGNALS: + void projectChanged(); + +protected: + void mousePressEvent(QMouseEvent *event) override; + +private: + void initialize(); + void rebuild(); + + ProjectFileSystemModel *m_ProjectModel = nullptr; + QColor m_BaseColor = QColor::fromRgb(75, 75, 75); + QString m_defaultBehaviorDir; + QString m_defaultEffectDir; + QString m_defaultFontDir; + QString m_defaultImageDir; + QString m_defaultMaterialDir; + QString m_defaultModelDir; + QString m_defaultPresentationDir; + QString m_defaultQmlStreamDir; + QString m_BehaviorDir; + QString m_EffectDir; + QString m_FontDir; + QString m_ImageDir; + QString m_MaterialDir; + QString m_ModelDir; + QString m_presentationDir; + QString m_qmlStreamDir; + QString m_assetImportDir; + QSize m_preferredSize; +}; + +#endif // PROJECTVIEW_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.qml b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.qml new file mode 100644 index 00000000..043d9ba2 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Project/ProjectView.qml @@ -0,0 +1,317 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +Rectangle { + id: root + + color: _backgroundColor + + ColumnLayout { + anchors.fill: parent + spacing: 4 + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: { + _parentView.showContextMenu(mouse.x, mouse.y, projectTree.currentIndex); + } + } + + ListView { + id: projectTree + + anchors.fill: parent + clip: true + + ScrollBar.vertical: ScrollBar {} + + model: _parentView.projectModel + + onCurrentIndexChanged: { + // Try to keep something selected always + if ((currentIndex < 0 || currentIndex >= count) && count > 0) + currentIndex = 0; + } + + delegate: Rectangle { + id: delegateItem + property bool dragging: false + property bool dragStarted: false + property point pressPoint + width: parent.width + height: 20 + color: (index == projectTree.currentIndex || dragging) ? _selectionColor + : "transparent" + function handlePress(mouse, tryDrag) { + projectTree.currentIndex = model.index; + + if (mouse.button === Qt.LeftButton && tryDrag && _isDraggable) { + pressPoint = Qt.point(mouse.x, mouse.y); + dragStarted = false; + } + } + + function handlePositionChange(mouse, item) { + if (_isDraggable && !dragStarted + && (Math.abs(mouse.x - pressPoint.x) > 4 + || Math.abs(mouse.y - pressPoint.y) > 4)) { + dragStarted = true; + _parentView.startDrag(item, index); + } + } + + function handleClick(mouse) { + if (mouse.button === Qt.RightButton) { + var rootPoint = mapToItem(root, mouse.x, mouse.y); + _parentView.showContextMenu(rootPoint.x, rootPoint.y, + projectTree.currentIndex); + } + } + + function handleDoubleClick(mouse) { + if (mouse.button === Qt.LeftButton) { + if (_isExpandable) { + if (_expanded) + projectTree.model.collapse(index); + else + projectTree.model.expand(index); + } else { + _parentView.openFile(index); + } + } + } + + MouseArea { + id: delegateMouseArea + anchors.fill: parent + acceptedButtons: Qt.RightButton | Qt.LeftButton + + onPressed: delegateItem.handlePress(mouse, false) + onClicked: delegateItem.handleClick(mouse) + onDoubleClicked: delegateItem.handleDoubleClick(mouse) + + Row { + x: _depth*28 + anchors.verticalCenter: parent.verticalCenter + + Image { + source: _resDir + (_expanded ? "arrow_down.png" : "arrow.png") + opacity: _isExpandable ? 1 : 0 + + MouseArea { + visible: _isExpandable + anchors.fill: parent + acceptedButtons: Qt.LeftButton + onPressed: delegateItem.handlePress(mouse, false) + onClicked: { + if (_expanded) + projectTree.model.collapse(index) + else + projectTree.model.expand(index) + } + } + } + + Image { + id: fileIconImage + source: fileIcon + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton | Qt.LeftButton + onPressed: delegateItem.handlePress(mouse, true) + onPositionChanged: delegateItem.handlePositionChange( + mouse, fileIconImage) + onClicked: delegateItem.handleClick(mouse) + onDoubleClicked: delegateItem.handleDoubleClick(mouse) + } + } + + StyledLabel { + id: fileNameLabel + text: _fileId ? fileName + " <" + _fileId + ">" : fileName; + color: { + _isReferenced ? _textColor + : _isProjectReferenced ? _projectReferencedColor + : _disabledColor + } + leftPadding: 2 + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton | Qt.LeftButton + onPressed: delegateItem.handlePress(mouse, true) + onPositionChanged: delegateItem.handlePositionChange( + mouse, fileNameLabel) + onClicked: delegateItem.handleClick(mouse) + onDoubleClicked: delegateItem.handleDoubleClick(mouse) + } + } + + Item { + // Spacer item + width: 4 + height: 1 + } + + Image { + source: _extraIcon ? _resDir + _extraIcon : "" + visible: _extraIcon ? true : false + + MouseArea { + id: warningMouseArea + anchors.fill: parent + acceptedButtons: Qt.RightButton | Qt.LeftButton + hoverEnabled: true + onPressed: delegateItem.handlePress(mouse, false) + onClicked: delegateItem.handleClick(mouse) + onDoubleClicked: _parentView.editPresentationId( + index, _parentView.isQmlStream(index)) + } + StyledTooltip { + text: _parentView.isPresentation(index) + ? qsTr("No presentation Id") + : qsTr("No Qml stream Id") + enabled: warningMouseArea.containsMouse + } + } + } + } + + DropArea { + anchors.fill: parent + + onEntered: { + if (drag.hasUrls + && projectTree.model.hasValidUrlsForDropping(drag.urls)) { + dragging = true; + drag.accept(Qt.CopyAction) + } else { + drag.accepted = false; + } + } + + onExited: { + dragging = false; + } + + onDropped: { + if (drop.hasUrls) + projectTree.model.importUrls(drop.urls, index, false); + dragging = false; + } + } + } + DropArea { + // Leftover listview area. Dropping here is equivalent to dropping to root + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + height: parent.height - parent.contentHeight + onEntered: { + if (drag.hasUrls && projectTree.model.hasValidUrlsForDropping(drag.urls)) + drag.accept(Qt.CopyAction) + else + drag.accepted = false; + } + onDropped: { + if (drop.hasUrls) + projectTree.model.importUrls(drop.urls, 0, false) + } + } + } + } + + StyledMenuSeparator { + leftPadding: 12 + rightPadding: 12 + } + + RowLayout { + Layout.fillWidth: true + Layout.margins: 4 + Layout.rightMargin: 12 + Layout.leftMargin: 12 + + StyledToolButton { + enabledImage: "Asset-import-Normal.png"; + onClicked: _parentView.assetImportAction(projectTree.currentIndex); + toolTipText: qsTr("Import Assets"); + } + + Item { + Layout.fillWidth: true + } + + StyledToolButton { + enabledImage: "Objects-Effect-Normal.png"; + onClicked: _parentView.effectAction(projectTree.currentIndex) + toolTipText: qsTr("Open Effect Library") + } + + StyledToolButton { + enabledImage: "Objects-Text-Normal.png"; + onClicked: _parentView.fontAction(projectTree.currentIndex) + toolTipText: qsTr("Open Font Library") + } + + StyledToolButton { + enabledImage: "Objects-Image-Normal.png"; + onClicked: _parentView.imageAction(projectTree.currentIndex) + toolTipText: qsTr("Open Map Library") + } + + StyledToolButton { + enabledImage: "Objects-Material-Normal.png"; + onClicked: _parentView.materialAction(projectTree.currentIndex) + toolTipText: qsTr("Open Material Library") + } + + StyledToolButton { + enabledImage: "Assets-Model.png"; + onClicked: _parentView.modelAction(projectTree.currentIndex) + toolTipText: qsTr("Open Model Library") + } + + StyledToolButton { + enabledImage: "Objects-Behavior-Normal.png"; + onClicked: _parentView.behaviorAction(projectTree.currentIndex) + toolTipText: qsTr("Open Behavior Library") + } + } + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Slide/SlideContextMenu.cpp b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideContextMenu.cpp new file mode 100644 index 00000000..7bd7efef --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideContextMenu.cpp @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "SlideContextMenu.h" +#include "SlideView.h" + +SlideContextMenu::SlideContextMenu(SlideView *parent, int row, int rowCount, bool master) + : QMenu(parent) + , m_view(parent) + , m_row(row) + , m_rowCount(rowCount) +{ + QAction *action = new QAction(tr("New Slide")); + action->setEnabled(!master); + connect(action, &QAction::triggered, this, &SlideContextMenu::handleAddNewSlide); + addAction(action); + + action = new QAction(tr("Delete Slide\tDel")); + action->setEnabled(!master && m_row != -1 && m_rowCount > 1); + connect(action, &QAction::triggered, this, &SlideContextMenu::handleRemoveSlide); + addAction(action); + + QString ctrlKey(QStringLiteral("Ctrl+")); +#ifdef Q_OS_MACOS + ctrlKey = "⌘"; +#endif + action = new QAction(tr("Duplicate Slide\t%1D").arg(ctrlKey)); + action->setEnabled(!master && m_row != -1); + connect(action, &QAction::triggered, this, &SlideContextMenu::handleDuplicateSlide); + addAction(action); +} + +SlideContextMenu::~SlideContextMenu() +{ +} + +void SlideContextMenu::handleAddNewSlide() +{ + m_view->addNewSlide(m_row == -1 ? m_rowCount : m_row + 1); +} + +void SlideContextMenu::handleRemoveSlide() +{ + m_view->removeSlide(m_row); +} + +void SlideContextMenu::handleDuplicateSlide() +{ + m_view->duplicateSlide(m_row); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Slide/SlideContextMenu.h b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideContextMenu.h new file mode 100644 index 00000000..bb4bb864 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideContextMenu.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef SLIDE_CONTEXT_MENU_H +#define SLIDE_CONTEXT_MENU_H + +#include <QtWidgets/qmenu.h> + +class SlideView; + +class SlideContextMenu : public QMenu +{ + Q_OBJECT +public: + explicit SlideContextMenu(SlideView *parent, int row, int rowCount, bool master); + virtual ~SlideContextMenu(); + +private Q_SLOTS: + void handleAddNewSlide(); + void handleRemoveSlide(); + void handleDuplicateSlide(); + +private: + SlideView *m_view; + int m_row; + int m_rowCount; +}; +#endif // SLIDE_CONTEXT_MENU_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Slide/SlideModel.cpp b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideModel.cpp new file mode 100644 index 00000000..c07377c1 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideModel.cpp @@ -0,0 +1,468 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "SlideModel.h" + +#include "CmdActivateSlide.h" +#include "Core.h" +#include "Doc.h" +#include "StudioApp.h" +#include "SlideSystem.h" +#include "IDocumentEditor.h" + +#include "ClientDataModelBridge.h" +#include "Qt3DSDMStudioSystem.h" +#include "Qt3DSDMSlides.h" + +SlideModel::SlideModel(int slideCount, QObject *parent) : QAbstractListModel(parent) + , m_slides(slideCount) +{ +} + +QVariant SlideModel::data(const QModelIndex &index, int role) const +{ + if (!hasIndex(index.row(), index.column(),index.parent())) + return {}; + + const auto row = index.row(); + + switch (role) { + case NameRole: + return slideName(m_slides[row]); + case SelectedRole: + return row == m_selectedRow; + case VariantsRole: + int slideIdx = GetDoc()->GetStudioSystem()->GetSlideSystem()->GetSlideIndex(m_slides[row]); + if (slideIdx < m_variantsModel.size()) { + const auto variantsDef = g_StudioApp.GetCore()->getProjectFile().variantsDef(); + const auto keys = m_variantsModelKeys[slideIdx]; + QString templ = QString::fromWCharArray(L"<font color='%1'>\u25A0</font>"); + QString slideVariants; + for (auto g : keys) // variants groups + slideVariants.append(templ.arg(variantsDef[g].m_color)); + + return slideVariants; + } + } + + return {}; +} + +bool SlideModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!hasIndex(index.row(), index.column(),index.parent())) + return false; + + auto &slideHandle = m_slides[index.row()]; + + switch (role) { + case NameRole: { + setSlideName(slideHandle, value.toString()); + Q_EMIT dataChanged(index, index, {role}); + break; + } + case HandleRole: { + slideHandle = value.value<qt3dsdm::Qt3DSDMSlideHandle>(); + qt3dsdm::Qt3DSDMInstanceHandle instanceHandle + = GetDoc()->GetStudioSystem()->GetSlideSystem()->GetSlideInstance(slideHandle); + m_slideLookupHash.insert(instanceHandle, slideHandle); + Q_EMIT dataChanged(index, index, {HandleRole, NameRole}); + break; + } + case SelectedRole: { + m_selectedRow = value.toBool() ? index.row() : -1; + + if (m_selectedRow != -1) { + CCmdActivateSlide *theCmd = new CCmdActivateSlide(GetDoc(), m_slides[m_selectedRow]); + g_StudioApp.GetCore()->ExecuteCommand(theCmd); + } + + Q_EMIT dataChanged(this->index(0, 0), this->index(rowCount() - 1, 0), {role}); + break; + } + default: + return false; + } + + return true; +} + +int SlideModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return m_slides.count(); +} + +QHash<int, QByteArray> SlideModel::roleNames() const +{ + auto names = QAbstractListModel::roleNames(); + names.insert(NameRole, "name"); + names.insert(VariantsRole, "variants"); + names.insert(SelectedRole, "selected"); + + return names; +} + +bool SlideModel::insertRows(int row, int count, const QModelIndex &parent) +{ + if (row > m_slides.count()) + return false; + + beginInsertRows(parent, row, row + count - 1); + for (int i = 0; i < count; ++i) + m_slides.insert(row, {}); + endInsertRows(); + + setData(index(row + count - 1), true, SelectedRole); + return true; +} + +bool SlideModel::removeRows(int row, int count, const QModelIndex &parent) +{ + if (row + count > m_slides.count()) + return false; + + bool selectionRemoved = false; + beginRemoveRows(parent, row, row + count - 1); + for (int i = 0; i < count; ++i) { + if (m_selectedRow == row + i) + selectionRemoved = true; + m_slides.removeAt(row); + } + endRemoveRows(); + + auto newSelectedRow = -1; + if (selectionRemoved) { + if (row > 0) + newSelectedRow = row - 1; + else + newSelectedRow = 0; + } else if (m_selectedRow > row) { + newSelectedRow = m_selectedRow - count; + } + if (newSelectedRow != -1) + setData(index(newSelectedRow), true, SelectedRole); + + return true; +} + +void SlideModel::duplicateRow(int row) +{ + const auto handle = m_slides[row]; + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*GetDoc(), QObject::tr("Duplicate Slide")) + ->DuplicateSlide(handle); +} + +void SlideModel::startRearrange(int row) +{ + m_rearrangeStartRow = row; + m_rearrangeEndRow = -1; +} + +void SlideModel::move(int fromRow, int toRow) +{ + if (fromRow == toRow) + return; + + onSlideRearranged({}, fromRow + 1, toRow + 1); +} + +void SlideModel::finishRearrange(bool commit) +{ + if (m_rearrangeEndRow != m_rearrangeStartRow + && m_rearrangeEndRow >= 0 && m_rearrangeStartRow >= 0) { + // Restore state before committing the actual change + // +1 added as DocumentEditor uses 1 based indexes for slides + int endRow = m_rearrangeEndRow + 1; + onSlideRearranged({}, endRow, m_rearrangeStartRow + 1); + + if (commit) { + auto handle = m_slides[m_rearrangeStartRow]; + m_rearrangeStartRow = -1; + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*GetDoc(), QObject::tr("Rearrange Slide")) + ->RearrangeSlide(handle, endRow); + } + } + m_rearrangeEndRow = -1; + m_rearrangeStartRow = -1; +} + +void SlideModel::clear() +{ + beginResetModel(); + m_slides.clear(); + m_slideLookupHash.clear(); + endResetModel(); +} + +void SlideModel::addNewSlide(int row) +{ + const auto handle = (row < m_slides.size()) ? m_slides[row] : m_slides.last(); + const auto instanceHandle = GetBridge()->GetOwningComponentInstance(handle); + qt3dsdm::Qt3DSDMSlideHandle theMasterSlide = GetBridge()->GetComponentSlide(instanceHandle, 0); + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*GetDoc(), QObject::tr("Create Slide")) + ->AddSlide(theMasterSlide, row + 1); +} + +void SlideModel::removeSlide(int row) +{ + // Don't allow deleting of the last slide + if (m_slides.size() > 1) { + const auto handle = m_slides[row]; + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*GetDoc(), QObject::tr("Delete Slide"))->DeleteSlide( + handle); + } +} + +void SlideModel::onNewSlide(const qt3dsdm::Qt3DSDMSlideHandle &inSlide) +{ + qt3dsdm::ISlideSystem &theSlideSystem(*GetDoc()->GetStudioSystem()->GetSlideSystem()); + + // Ignore new slides added to random different components + if (m_slides.size() && theSlideSystem.GetMasterSlide(inSlide) + != theSlideSystem.GetMasterSlide(m_slides[0])) { + return; + } + + finishRearrange(false); // Cancel any uncommitted rearrange + + // Find the slide index + int row = int(slideIndex(inSlide)); + + // Slide index zero indicates master slide. We can't add master slides + Q_ASSERT(row > 0); + + --row; + + beginInsertRows({}, row, row); + m_slides.insert(row, inSlide); + qt3dsdm::Qt3DSDMInstanceHandle instanceHandle + = GetDoc()->GetStudioSystem()->GetSlideSystem()->GetSlideInstance(inSlide); + m_slideLookupHash.insert(instanceHandle, inSlide); + endInsertRows(); + + setData(index(row), true, SelectedRole); +} + +void SlideModel::onDeleteSlide(const qt3dsdm::Qt3DSDMSlideHandle &inSlide) +{ + for (int i = 0; i < m_slides.size(); ++i) { + if (m_slides[i] == inSlide) { + if (m_rearrangeStartRow >= 0) { + finishRearrange(false); // Cancel any uncommitted rearrange + // We need to re-resolve the index after rearrange cancel + for (int j = 0; j < m_slides.size(); ++j) { + if (m_slides[j] == inSlide) { + i = j; + break; + } + } + } + QList<qt3dsdm::Qt3DSDMInstanceHandle> keys = m_slideLookupHash.keys(inSlide); + for (auto key : keys) + m_slideLookupHash.remove(key); + removeRows(i, 1); + break; + } + } +} + +void SlideModel::onSlideRearranged(const qt3dsdm::Qt3DSDMSlideHandle &inMaster, int fromRow, + int toRow) +{ + if (inMaster.Valid()) { + // If imMaster is valid, this was triggered by either a rearrange commit or + // undo/redo of a rearrange, so we need to cancel any uncommitted rearrange. + finishRearrange(false); + // Check we are working on correct slide set + qt3dsdm::ISlideSystem &theSlideSystem(*GetDoc()->GetStudioSystem()->GetSlideSystem()); + if (fromRow - 1 >= m_slides.size() + || inMaster != theSlideSystem.GetMasterSlide(m_slides[fromRow - 1])) + return; + } else { + // Do not do uncommitted rearranges if we haven't started a rearrange (or more likely + // an uncommitted rearrange was canceled while in progress by undo/redo operation) + if (m_rearrangeStartRow < 0) + return; + } + + // -1 because internal slide model has 1-based indexing for non-master slides + if (fromRow > toRow) + beginMoveRows({}, fromRow - 1, fromRow - 1, {}, toRow - 1); + else + beginMoveRows({}, fromRow - 1, fromRow - 1, {}, toRow); + m_slides.move(fromRow - 1, toRow - 1); + m_rearrangeEndRow = toRow - 1; + + endMoveRows(); +} + +bool SlideModel::hasSlideWithName(const QString &name) const +{ + for (const auto &slide: m_slides) { + if (slideName(slide) == name) + return true; + } + return false; +} + +QString SlideModel::slideName(const qt3dsdm::Qt3DSDMSlideHandle &handle) const +{ + auto doc = GetDoc(); + if (!doc->isValid()) + return {}; + const auto instanceHandle = doc->GetStudioSystem()->GetSlideSystem()->GetSlideInstance(handle); + return GetBridge()->GetName(instanceHandle).toQString(); +} + +void SlideModel::setSlideName(const qt3dsdm::Qt3DSDMSlideHandle &handle, const QString &name) +{ + const auto oldName = slideName(handle); + if (oldName != name && !name.trimmed().isEmpty()) { + using namespace qt3dsdm; + CDoc *theDoc = GetDoc(); + CClientDataModelBridge *theBridge = GetBridge(); + if (!theBridge) + return; + const auto instanceHandle = GetDoc()->GetStudioSystem()-> + GetSlideSystem()->GetSlideInstance(handle); + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*theDoc, QObject::tr("Set Slide Name")) + ->SetSlideName(instanceHandle, theBridge->GetNameProperty(), + Q3DStudio::CString::fromQString(oldName), + Q3DStudio::CString::fromQString(name)); + } +} + +void SlideModel::refreshVariants(const QVector<QHash<QString, QStringList>> &vModel, + const QVector<QStringList> &vModelKeys) +{ + m_variantsModel.clear(); + m_variantsModelKeys.clear(); + + if (vModel.isEmpty()) { + const auto *slideSystem = GetDoc()->GetStudioSystem()->GetSlideSystem(); + int slideCount = slideSystem->GetSlideCount(slideSystem->GetMasterSlide( + GetDoc()->GetActiveSlide())); + m_variantsModel.resize(slideCount); + m_variantsModelKeys.resize(slideCount); + + const auto propertySystem = GetDoc()->GetPropertySystem(); + const QVector<int> instances = GetDoc()->getVariantInstances(); + for (auto instance : instances) { + int slideIdx = slideIndex(slideSystem->GetAssociatedSlide(instance)); + qt3dsdm::SValue sValue; + if (propertySystem->GetInstancePropertyValue(instance, + GetBridge()->getVariantsProperty(instance), + sValue)) { + QString propVal = qt3dsdm::get<qt3dsdm::TDataStrPtr>(sValue)->toQString(); + if (!propVal.isEmpty()) { + QStringList tagPairs = propVal.split(QLatin1Char(',')); + for (int i = 0; i < tagPairs.size(); ++i) { + QStringList pair = tagPairs[i].split(QLatin1Char(':')); + if (!m_variantsModel[slideIdx][pair[0]].contains(pair[1])) + m_variantsModel[slideIdx][pair[0]].append(pair[1]); + + if (!m_variantsModelKeys[slideIdx].contains(pair[0])) + m_variantsModelKeys[slideIdx].append(pair[0]); + } + } + } + } + + // add master slide variants to other slides + const auto keys = m_variantsModel[0].keys(); + for (int i = 1; i < slideCount; ++i) { + for (auto g : keys) { + for (int j = 0; j < m_variantsModel[0][g].length(); ++j) { + if (!m_variantsModel[i][g].contains(m_variantsModel[0][g][j])) + m_variantsModel[i][g].append(m_variantsModel[0][g][j]); + } + + if (!m_variantsModelKeys[i].contains(g)) + m_variantsModelKeys[i].append(g); + } + } + } else { + m_variantsModel = vModel; + m_variantsModelKeys = vModelKeys; + } + + Q_EMIT dataChanged(this->index(0, 0), this->index(rowCount() - 1, 0), {VariantsRole}); +} + +CDoc *SlideModel::GetDoc() const +{ + return g_StudioApp.GetCore()->GetDoc(); +} + +long SlideModel::slideIndex(const qt3dsdm::Qt3DSDMSlideHandle &handle) const +{ + return GetDoc()->GetStudioSystem()->GetSlideSystem()->GetSlideIndex(handle); +} + +int SlideModel::rowToSlideIndex(int row) const +{ + return GetDoc()->GetStudioSystem()->GetSlideSystem()->GetSlideIndex(m_slides[row]); +} + +CClientDataModelBridge *SlideModel::GetBridge() const +{ + auto doc = GetDoc(); + if (!doc->isValid()) + return nullptr; + return doc->GetStudioSystem()->GetClientDataModelBridge(); +} + +void SlideModel::refreshSlideLabel(qt3dsdm::Qt3DSDMInstanceHandle instanceHandle, + qt3dsdm::Qt3DSDMPropertyHandle propertyHandle) +{ + if (m_slideLookupHash.contains(instanceHandle) + && propertyHandle == GetBridge()->GetNameProperty()) { + qt3dsdm::Qt3DSDMSlideHandle slideHandle = m_slideLookupHash.value(instanceHandle); + for (int i = 0; i < m_slides.size(); ++i) { + if (m_slides[i] == slideHandle) { + setData(index(i, 0), GetBridge()->GetName(instanceHandle).toQString(), + SlideModel::NameRole); + break; + } + } + } +} + +// Set selected slide highlight on UI +void SlideModel::setSelectedSlideIndex(const QModelIndex &index) +{ + if (m_selectedRow == index.row() || + !hasIndex(index.row(), index.column(), index.parent())) + return; + + m_selectedRow = index.row(); + Q_EMIT dataChanged(this->index(0, 0), this->index(rowCount() - 1, 0), {SelectedRole}); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Slide/SlideModel.h b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideModel.h new file mode 100644 index 00000000..0b1deab1 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideModel.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef SLIDEMODEL_H +#define SLIDEMODEL_H + +#include <QtCore/qabstractitemmodel.h> +#include <QtCore/qhash.h> + +#include "Qt3DSDMHandles.h" + +class CClientDataModelBridge; +class CDoc; + +class SlideModel : public QAbstractListModel +{ + Q_OBJECT +public: + enum Roles { + NameRole = Qt::DisplayRole, + HandleRole = Qt::UserRole + 1, + SelectedRole, + VariantsRole + }; + + SlideModel(int slideCount, QObject *parent = nullptr); + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::DisplayRole) override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QHash<int, QByteArray> roleNames() const override; + + bool insertRows(int row, int count, + const QModelIndex &parent = QModelIndex()) override; + bool removeRows(int row, int count, + const QModelIndex &parent = QModelIndex()) override; + void duplicateRow(int row); + void startRearrange(int row); + void move(int fromRow, int toRow); + void finishRearrange(bool commit); + + void clear(); + void addNewSlide(int row); + void removeSlide(int row); + + void onNewSlide(const qt3dsdm::Qt3DSDMSlideHandle &inSlide); + void onDeleteSlide(const qt3dsdm::Qt3DSDMSlideHandle &inSlide); + void onSlideRearranged(const qt3dsdm::Qt3DSDMSlideHandle &inMaster, int inOldIndex, + int toRow); + void refreshSlideLabel(qt3dsdm::Qt3DSDMInstanceHandle instanceHandle, + qt3dsdm::Qt3DSDMPropertyHandle propertyHandle); + void setSelectedSlideIndex(const QModelIndex &index); + void refreshVariants(const QVector<QHash<QString, QStringList>> &vModel = {}, + const QVector<QStringList> &vModelKeys = {}); + int rowToSlideIndex(int row) const; + QVector<QHash<QString, QStringList> > variantsModel() const { return m_variantsModel; } + QVector<QStringList> variantsModelKeys() const { return m_variantsModelKeys; } + +private: + bool hasSlideWithName(const QString &name) const; + QString slideName(const qt3dsdm::Qt3DSDMSlideHandle &handle) const; + void setSlideName(const qt3dsdm::Qt3DSDMSlideHandle &handle, const QString &name); + inline CDoc *GetDoc() const; + inline long slideIndex(const qt3dsdm::Qt3DSDMSlideHandle &handle) const; + inline CClientDataModelBridge *GetBridge() const; + + QVector<qt3dsdm::Qt3DSDMSlideHandle> m_slides; + int m_selectedRow = -1; + int m_rearrangeStartRow = -1; + int m_rearrangeEndRow = -1; + QVector<QHash<QString, QStringList> > m_variantsModel; + QVector<QStringList> m_variantsModelKeys; + QHash<qt3dsdm::Qt3DSDMInstanceHandle, qt3dsdm::Qt3DSDMSlideHandle> m_slideLookupHash; +}; + + +#endif // SLIDEMODEL_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.cpp b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.cpp new file mode 100644 index 00000000..c49f34d2 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.cpp @@ -0,0 +1,621 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "SlideView.h" +#include "Core.h" +#include "Dispatch.h" +#include "Doc.h" +#include "StudioPreferences.h" +#include "SlideModel.h" +#include "StudioApp.h" +#include "StudioUtils.h" +#include "SlideContextMenu.h" +#include "DataInputSelectView.h" +#include "DataInputDlg.h" +#include "IDocumentEditor.h" +#include "ClientDataModelBridge.h" +#include "Qt3DSDMStudioSystem.h" +#include "Qt3DSDMSlides.h" +#include "Dialogs.h" + +#include "QtWidgets/qlabel.h" +#include <QtQml/qqmlcontext.h> +#include <QtQml/qqmlengine.h> + +SlideView::SlideView(QWidget *parent) : QQuickWidget(parent) + , m_MasterSlideModel(new SlideModel(1, this)) + , m_SlidesModel(new SlideModel(0, this)) + , m_CurrentModel(m_SlidesModel) + , m_variantsToolTip(new QLabel(this)) + , m_toolTip(tr("No Controller")) +{ + m_variantsToolTip->setObjectName(QStringLiteral("variantsToolTip")); + m_variantsToolTip->setWindowModality(Qt::NonModal); + m_variantsToolTip->setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip); + m_variantsToolTip->setContentsMargins(2, 2, 2, 2); + + g_StudioApp.GetCore()->GetDispatch()->AddPresentationChangeListener(this); + setResizeMode(QQuickWidget::SizeRootObjectToView); + QTimer::singleShot(0, this, &SlideView::initialize); + + m_variantRefreshTimer.setSingleShot(true); + m_variantRefreshTimer.setInterval(0); + connect(&m_variantRefreshTimer, &QTimer::timeout, [this]() { + m_SlidesModel->refreshVariants(); + m_MasterSlideModel->refreshVariants(m_SlidesModel->variantsModel(), + m_SlidesModel->variantsModelKeys()); + }); +} + +SlideView::~SlideView() +{ + clearSlideList(); + g_StudioApp.GetCore()->GetDispatch()->RemovePresentationChangeListener(this); + delete m_dataInputSelector; +} + +bool SlideView::showMasterSlide() const +{ + return m_CurrentModel == m_MasterSlideModel; +} + +void SlideView::setShowMasterSlide(bool show) +{ + const bool currentIsMaster = m_CurrentModel == m_MasterSlideModel; + if (show == currentIsMaster) + return; + + m_CurrentModel = show ? m_MasterSlideModel : m_SlidesModel; + + // We need to get the first slide in the correct master mode + CDoc *theDoc = GetDoc(); + qt3dsdm::Qt3DSDMInstanceHandle theRoot = theDoc->GetActiveRootInstance(); + CClientDataModelBridge *theBridge = GetBridge(); + qt3dsdm::Qt3DSDMSlideHandle theNewActiveSlide = + theBridge->GetOrCreateGraphRoot(theRoot); // this will return the master slide + qt3dsdm::ISlideSystem *theSlideSystem = theDoc->GetStudioSystem()->GetSlideSystem(); + if (m_CurrentModel != m_MasterSlideModel) { + qt3dsdm::Qt3DSDMSlideHandle masterSlide = theNewActiveSlide; + theNewActiveSlide = m_MasterSlideReturnPointers.value(masterSlide, 0); + if (!theSlideSystem->SlideValid(theNewActiveSlide)) { + theNewActiveSlide = theSlideSystem->GetSlideByIndex( + masterSlide, 1); // activate the first slide; + } + } + + // We have forced a mode change, and so we need to set the current active TC + // to be in the correct mode so our slide palette will show the correct information + if (theNewActiveSlide.Valid()) + theDoc->NotifyActiveSlideChanged(theNewActiveSlide); + + Q_EMIT showMasterSlideChanged(); + Q_EMIT currentModelChanged(); +} + +void SlideView::showControllerDialog(const QPoint &point) +{ + QString currCtr = m_currentController.size() ? + m_currentController : m_dataInputSelector->getNoneString(); + QVector<QPair<QString, int>> dataInputList; + + for (auto &it : qAsConst(g_StudioApp.m_dataInputDialogItems)) + dataInputList.append({it->name, it->type}); + + m_dataInputSelector->setData(dataInputList, currCtr); + CDialogs::showWidgetBrowser(this, m_dataInputSelector, point, + CDialogs::WidgetBrowserAlign::ToolButton); +} + +bool SlideView::toolTipsEnabled() +{ + return CStudioPreferences::ShouldShowTooltips(); +} + +QSize SlideView::sizeHint() const +{ + return {150, 500}; +} + +QSize SlideView::minimumSizeHint() const +{ + // prevent datainput control indicator from overlapping + // with slide name too much when panel is minimised + return {100, 0}; +} + +void SlideView::deselectAll() +{ + g_StudioApp.GetCore()->GetDoc()->DeselectAllItems(); +} + +void SlideView::addNewSlide(int row) +{ + m_SlidesModel->addNewSlide(row); +} + +void SlideView::removeSlide(int row) +{ + m_SlidesModel->removeSlide(row); +} + +void SlideView::duplicateSlide(int row) +{ + m_SlidesModel->duplicateRow(row); +} + +void SlideView::startSlideRearrange(int row) +{ + m_SlidesModel->startRearrange(row); +} + +void SlideView::moveSlide(int from, int to) +{ + m_SlidesModel->move(from, to); +} + +void SlideView::finishSlideRearrange(bool commit) +{ + m_SlidesModel->finishRearrange(commit); +} + +void SlideView::showContextMenu(int x, int y, int row) +{ + SlideContextMenu contextMenu(this, row, m_SlidesModel->rowCount(), + m_CurrentModel == m_MasterSlideModel); + contextMenu.exec(mapToGlobal({x, y})); +} + +void SlideView::showVariantsTooltip(int row, const QPoint &point) +{ + QString templ = QStringLiteral("<font color='%1'>%2</font>"); + QString tooltipStr("<table>"); + const auto variantsDef = g_StudioApp.GetCore()->getProjectFile().variantsDef(); + const auto slideIndex = m_CurrentModel->rowToSlideIndex(row); + const auto variantsModel = m_CurrentModel->variantsModel()[slideIndex]; + const auto variantsModelKeys = m_CurrentModel->variantsModelKeys()[slideIndex]; + for (auto &g : variantsModelKeys) { + tooltipStr.append("<tr><td>"); + tooltipStr.append(templ.arg(variantsDef[g].m_color).arg(g + ": ")); + tooltipStr.append("</td><td>"); + const auto tags = variantsModel[g]; + for (auto &t : tags) + tooltipStr.append(t + ", "); + tooltipStr.chop(2); + tooltipStr.append("</td></tr>"); + } + tooltipStr.append("</table>"); + + m_variantsToolTip->setText(tooltipStr); + m_variantsToolTip->adjustSize(); + m_variantsToolTip->move(point); + m_variantsToolTip->raise(); + m_variantsToolTip->show(); +} + +void SlideView::hideVariantsTooltip() +{ + m_variantsToolTip->hide(); +} + +void SlideView::OnNewPresentation() +{ + // Register callbacks + qt3dsdm::IStudioFullSystemSignalProvider *theSignalProvider = + g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetFullSystemSignalProvider(); + m_MasterSlideReturnPointers.clear(); + + m_Connections.push_back(theSignalProvider->ConnectActiveSlide( + std::bind(&SlideView::OnActiveSlide, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3))); + + // Needed for undo/redo functionality to work properly + m_Connections.push_back(theSignalProvider->ConnectSlideCreated( + std::bind(&SlideView::OnNewSlide, this, std::placeholders::_1))); + m_Connections.push_back(theSignalProvider->ConnectSlideDeleted( + std::bind(&SlideView::OnDeleteSlide, this, std::placeholders::_1))); + m_Connections.push_back(theSignalProvider->ConnectSlideRearranged( + std::bind(&SlideView::OnSlideRearranged, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3))); + + // Set up listener for the name changes to slide + m_Connections.push_back(theSignalProvider->ConnectInstancePropertyValue( + std::bind(&SlideView::onPropertyChanged, this, + std::placeholders::_1, std::placeholders::_2))); + + // object created/deleted + m_Connections.push_back(theSignalProvider->ConnectInstanceCreated( + std::bind(&SlideView::onAssetCreated, this, std::placeholders::_1))); + m_Connections.push_back(theSignalProvider->ConnectInstanceDeleted( + std::bind(&SlideView::onAssetDeleted, this, std::placeholders::_1))); + + // Set up listener for undo/redo changes in order to update + // slide datainput control + CDispatch *theDispatch = g_StudioApp.GetCore()->GetDispatch(); + theDispatch->AddDataModelListener(this); + + refreshVariants(); +} + +void SlideView::OnClosingPresentation() +{ + m_Connections.clear(); + clearSlideList(); +} + +void SlideView::mousePressEvent(QMouseEvent *event) +{ + g_StudioApp.setLastActiveView(this); + QQuickWidget::mousePressEvent(event); +} + +void SlideView::OnActiveSlide(const qt3dsdm::Qt3DSDMSlideHandle &inMaster, int inIndex, + const qt3dsdm::Qt3DSDMSlideHandle &inSlide) +{ + // Don't use inIndex because inIndex might have been changed due to deletion + Q_UNUSED(inIndex); + Q_UNUSED(inMaster); + + qt3dsdm::ISlideSystem &theSlideSystem(*GetDoc()->GetStudioSystem()->GetSlideSystem()); + int currentSlideIndex = theSlideSystem.GetSlideIndex(inSlide); + setShowMasterSlide(currentSlideIndex == 0); + setActiveSlide(inSlide); + + // Update slide highlight to match active slide + // -1 because first slide is masterslide + auto index = m_SlidesModel->index(currentSlideIndex - 1, 0); + m_SlidesModel->setSelectedSlideIndex(index); + + if (currentSlideIndex != 0) + m_MasterSlideReturnPointers[inMaster] = inSlide; +} + +void SlideView::OnNewSlide(const qt3dsdm::Qt3DSDMSlideHandle &inSlide) +{ + m_SlidesModel->onNewSlide(inSlide); +} + +void SlideView::OnDeleteSlide(const qt3dsdm::Qt3DSDMSlideHandle &inSlide) +{ + m_SlidesModel->onDeleteSlide(inSlide); +} + +void SlideView::OnSlideRearranged(const qt3dsdm::Qt3DSDMSlideHandle &inMaster, int inOldIndex, + int inNewIndex) +{ + m_SlidesModel->onSlideRearranged(inMaster, inOldIndex, inNewIndex); +} + +void SlideView::onDataInputChange(int handle, int instance, const QString &dataInputName) +{ + Q_UNUSED(handle) + Q_UNUSED(instance) + + if (dataInputName == m_currentController) + return; + + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + CClientDataModelBridge *bridge = doc->GetStudioSystem()->GetClientDataModelBridge(); + qt3dsdm::Qt3DSDMInstanceHandle slideRoot = doc->GetActiveRootInstance(); + QString fullSlideControlStr; + + if (dataInputName != m_dataInputSelector->getNoneString()) { + fullSlideControlStr = "$" + dataInputName + " @slide"; + m_controlled = true; + m_currentController = dataInputName; + m_toolTip = tr("Slide Controller:\n") + m_currentController; + } else { + m_controlled = false; + m_currentController.clear(); + m_toolTip = tr("No Controller"); + } + qt3dsdm::Qt3DSDMPropertyHandle ctrldProp; + if (bridge->GetObjectType(slideRoot) == EStudioObjectType::OBJTYPE_SCENE) + ctrldProp = bridge->GetObjectDefinitions().m_Scene.m_ControlledProperty; + else if (bridge->GetObjectType(slideRoot) == EStudioObjectType::OBJTYPE_COMPONENT) + ctrldProp = bridge->GetObjectDefinitions().m_Component.m_ControlledProperty; + else + Q_ASSERT(false); + + qt3dsdm::SValue controlledPropertyVal; + doc->GetStudioSystem()->GetPropertySystem()->GetInstancePropertyValue(slideRoot, ctrldProp, + controlledPropertyVal); + + // To indicate that slide transitions are controlled by data input, + // we set "controlled property" of this scene to contain the name of + // controller followed by special indicator "@slide". + // If we have existing slide control in this root element, replace it. + // Otherwise just append slide control string to controlledproperty + // (it might already contain timeline control information) + QString existingCtrl = qt3dsdm::get<QString>(controlledPropertyVal); + if (existingCtrl.contains(QLatin1String("@slide"))) { + int slideStrPos = existingCtrl.indexOf(QLatin1String("@slide")); + // find the controlling datainput name and build the string to replace + int ctrStrPos = existingCtrl.lastIndexOf(QLatin1Char('$'), slideStrPos - 2); + QString prevCtrler = existingCtrl.mid(ctrStrPos, slideStrPos - ctrStrPos - 1); + existingCtrl.replace(prevCtrler + QLatin1String(" @slide"), fullSlideControlStr); + } else { + if (!existingCtrl.isEmpty() && m_controlled) + existingCtrl.append(QLatin1Char(' ')); + existingCtrl.append(fullSlideControlStr); + } + + if (existingCtrl.endsWith(QLatin1Char(' '))) + existingCtrl.chop(1); + + if (existingCtrl.startsWith(QLatin1Char(' '))) + existingCtrl.remove(0, 1); + + qt3dsdm::SValue fullCtrlPropVal + = std::make_shared<qt3dsdm::CDataStr>(Q3DStudio::CString::fromQString(existingCtrl)); + + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, QObject::tr("Set Slide control")) + ->SetInstancePropertyValue(slideRoot, ctrldProp, fullCtrlPropVal); + + UpdateSlideViewTitleColor(); + Q_EMIT controlledChanged(); +} + +void SlideView::onAssetCreated(qt3dsdm::Qt3DSDMInstanceHandle inInstance) +{ + // refresh the variants model if the created asset has a variants property set. + if (GetBridge()->GetObjectType(inInstance) & OBJTYPE_IS_VARIANT) { + const auto propertySystem = GetDoc()->GetPropertySystem(); + qt3dsdm::SValue sValue; + if (propertySystem->GetInstancePropertyValue(inInstance, + GetBridge()->getVariantsProperty(inInstance), + sValue)) { + if (qt3dsdm::get<qt3dsdm::TDataStrPtr>(sValue)->GetLength() != 0) + refreshVariants(); + } + } +} + +void SlideView::onAssetDeleted(qt3dsdm::Qt3DSDMInstanceHandle inInstance) +{ + Q_UNUSED(inInstance) + + refreshVariants(); +} + +void SlideView::onPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance, + qt3dsdm::Qt3DSDMPropertyHandle inProperty) +{ + // refresh slide name + m_SlidesModel->refreshSlideLabel(inInstance, inProperty); + + // refresh variants + if (inProperty == GetBridge()->getVariantsProperty(inInstance)) + refreshVariants(); +} + +void SlideView::onDockLocationChange(Qt::DockWidgetArea area) +{ + m_dockArea = area; + Q_EMIT dockAreaChanged(); +} + +// Set the state of slide control based on scene or component +// controlledproperty +void SlideView::updateDataInputStatus() +{ + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + CClientDataModelBridge *bridge = doc->GetStudioSystem()->GetClientDataModelBridge(); + qt3dsdm::Qt3DSDMInstanceHandle slideRoot = doc->GetActiveRootInstance(); + + qt3dsdm::Qt3DSDMPropertyHandle ctrldProp; + if (bridge->GetObjectType(slideRoot) == EStudioObjectType::OBJTYPE_SCENE) + ctrldProp = bridge->GetObjectDefinitions().m_Scene.m_ControlledProperty; + else if (bridge->GetObjectType(slideRoot) == EStudioObjectType::OBJTYPE_COMPONENT) + ctrldProp = bridge->GetObjectDefinitions().m_Component.m_ControlledProperty; + else + Q_ASSERT(false); + + qt3dsdm::SValue controlledPropertyVal; + doc->GetStudioSystem()->GetPropertySystem()->GetInstancePropertyValue(slideRoot, ctrldProp, + controlledPropertyVal); + QString existingCtrl = qt3dsdm::get<QString>(controlledPropertyVal); + + QString newController; + int slideStrPos = existingCtrl.indexOf(QLatin1String("@slide")); + if (slideStrPos != -1) { + int ctrStrPos = existingCtrl.lastIndexOf(QLatin1Char('$'), slideStrPos - 2); + newController = existingCtrl.mid(ctrStrPos + 1, slideStrPos - ctrStrPos - 2); + } + if (newController != m_currentController) { + m_currentController = newController; + if (!m_currentController.isEmpty()) { + m_toolTip = tr("Slide Controller:\n") + m_currentController; + m_controlled = true; + } else { + m_currentController.clear(); + m_toolTip = tr("No Controller"); + m_controlled = false; + } + // update UI + UpdateSlideViewTitleColor(); + Q_EMIT controlledChanged(); + if (m_dataInputSelector && m_dataInputSelector->isVisible()) + m_dataInputSelector->setCurrentController(m_currentController); + } +} +void SlideView::initialize() +{ + CStudioPreferences::setQmlContextProperties(rootContext()); + rootContext()->setContextProperty(QStringLiteral("_parentView"), this); + rootContext()->setContextProperty(QStringLiteral("_resDir"), StudioUtils::resourceImageUrl()); + + engine()->addImportPath(StudioUtils::qmlImportPath()); + setSource(QUrl(QStringLiteral("qrc:/Palettes/Slide/SlideView.qml"))); + + const QVector<EDataType> acceptedTypes = { EDataType::DataTypeString }; + m_dataInputSelector = new DataInputSelectView(acceptedTypes, this); + connect(m_dataInputSelector, &DataInputSelectView::dataInputChanged, + this, &SlideView::onDataInputChange); +} + +void SlideView::clearSlideList() +{ + m_ActiveRoot = 0; + m_SlidesModel->clear(); +} + +void SlideView::setActiveSlide(const qt3dsdm::Qt3DSDMSlideHandle &inActiveSlideHandle) +{ + // Make sure we are in the correct master mode based on the inActiveSlideHandle + // If we changed mode, then we need to force a rebuild + bool theRebuildFlag = isMaster(inActiveSlideHandle) && (m_CurrentModel != m_MasterSlideModel); + + // Check to see if the incoming slide is a sibling of the current active slide + // If it is, then we may be able to update without rebuilding everything + if (!theRebuildFlag + && m_ActiveRoot == GetBridge()->GetOwningComponentInstance(inActiveSlideHandle)) { + // If this is a new active slide, but the same root parent + if (m_ActiveSlideHandle != inActiveSlideHandle) { + m_ActiveSlideHandle = inActiveSlideHandle; + } + } else { + // We have a new parent or a new slide that makes us rebuild the entire list + rebuildSlideList(inActiveSlideHandle); + } +} + +void SlideView::rebuildSlideList(const qt3dsdm::Qt3DSDMSlideHandle &inActiveSlideHandle) +{ + // Clear out the existing slides + clearSlideList(); + + // Add new slide controls as required + if (inActiveSlideHandle.Valid()) { + m_ActiveSlideHandle = inActiveSlideHandle; + m_ActiveRoot = GetBridge()->GetOwningComponentInstance(inActiveSlideHandle); + + // Get the Master Slide handle and the slide count + qt3dsdm::ISlideSystem *theSlideSystem = GetSlideSystem(); + qt3dsdm::Qt3DSDMSlideHandle theMasterSlide = + theSlideSystem->GetMasterSlide(inActiveSlideHandle); + + // update handle for master slide + qt3dsdm::Qt3DSDMSlideHandle theMasterSlideHandle = + theSlideSystem->GetSlideByIndex(theMasterSlide, 0); + m_MasterSlideModel->setData(m_MasterSlideModel->index(0, 0), + QVariant::fromValue(theMasterSlideHandle), + SlideModel::HandleRole); + + long theSlideCount = (long)theSlideSystem->GetSlideCount(theMasterSlide); + + // Iterate through, creating the new slide controls + m_SlidesModel->clear(); + m_SlidesModel->insertRows(0, theSlideCount - 1, {}); + int row = 0; + for (long theSlideIndex = 1; theSlideIndex < theSlideCount; ++theSlideIndex) { + qt3dsdm::Qt3DSDMSlideHandle theSlideHandle = + theSlideSystem->GetSlideByIndex(theMasterSlide, theSlideIndex); + auto index = m_SlidesModel->index(row, 0); + m_SlidesModel->setData(index, + QVariant::fromValue(theSlideHandle), + SlideModel::HandleRole); + const auto instanceHandle = + GetDoc()->GetStudioSystem()->GetSlideSystem()->GetSlideInstance(theSlideHandle); + m_SlidesModel->setData(index, + GetBridge()->GetName(instanceHandle).toQString(), + SlideModel::NameRole); + // This slide is the active slide + if (theSlideHandle == m_ActiveSlideHandle) { + m_SlidesModel->setData(index, true, SlideModel::SelectedRole); + } + row++; + } + } +} + +CDoc *SlideView::GetDoc() const +{ + return g_StudioApp.GetCore()->GetDoc(); +} + +CClientDataModelBridge *SlideView::GetBridge() const +{ + return GetDoc()->GetStudioSystem()->GetClientDataModelBridge(); +} + +qt3dsdm::ISlideSystem *SlideView::GetSlideSystem() const +{ + return GetDoc()->GetStudioSystem()->GetSlideSystem(); +} + +long SlideView::GetSlideIndex(const qt3dsdm::Qt3DSDMSlideHandle &inSlideHandle) const +{ + return GetSlideSystem()->GetSlideIndex(inSlideHandle); +} + +bool SlideView::isMaster(const qt3dsdm::Qt3DSDMSlideHandle &inSlideHandle) const +{ + return (0 == GetSlideIndex(inSlideHandle)); +} + +void SlideView::refreshVariants() +{ + if (!m_variantRefreshTimer.isActive()) + m_variantRefreshTimer.start(); +} + +void SlideView::OnBeginDataModelNotifications() +{ +} + +void SlideView::OnEndDataModelNotifications() +{ + updateDataInputStatus(); +} + +void SlideView::OnImmediateRefreshInstanceSingle(qt3dsdm::Qt3DSDMInstanceHandle inInstance) +{ + Q_UNUSED(inInstance) +} + +void SlideView::OnImmediateRefreshInstanceMultiple( + qt3dsdm::Qt3DSDMInstanceHandle *inInstance, long inInstanceCount) +{ + Q_UNUSED(inInstance) + Q_UNUSED(inInstanceCount) +} + +// Notify the user about control state change also with slide view +// title color change. +void SlideView::UpdateSlideViewTitleColor() { + QString styleString; + if (m_controlled) { + styleString = "QDockWidget#slide { color: " + + QString(CStudioPreferences::dataInputColor().name()) + "; }"; + } else { + styleString = "QDockWidget#slide { color: " + + QString(CStudioPreferences::textColor().name()) + "; }"; + } + + parentWidget()->setStyleSheet(styleString); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.h b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.h new file mode 100644 index 00000000..4a15e3a1 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.h @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef SLIDEVIEW_H +#define SLIDEVIEW_H + +#include <QtQuickWidgets/qquickwidget.h> +#include <QtCore/qtimer.h> + +#include "SlideModel.h" +#include "Qt3DSDMSignals.h" +#include "DispatchListeners.h" + +class CClientDataModelBridge; +class CDoc; +class DataInputSelectView; + +QT_FORWARD_DECLARE_CLASS(QLabel); + +namespace qt3dsdm { +class ISlideSystem; +} + +class SlideView : public QQuickWidget, + public CPresentationChangeListener, + public IDataModelListener +{ + Q_OBJECT + + Q_PROPERTY(QAbstractItemModel *currentModel READ currentModel NOTIFY currentModelChanged FINAL) + Q_PROPERTY(bool showMasterSlide READ showMasterSlide WRITE setShowMasterSlide NOTIFY showMasterSlideChanged FINAL) + Q_PROPERTY(bool controlled MEMBER m_controlled NOTIFY controlledChanged) + Q_PROPERTY(QString currController MEMBER m_currentController NOTIFY controlledChanged) + Q_PROPERTY(QString toolTip MEMBER m_toolTip NOTIFY controlledChanged) + Q_PROPERTY(Qt::DockWidgetArea dockArea MEMBER m_dockArea NOTIFY dockAreaChanged) +public: + SlideView(QWidget *parent = nullptr); + ~SlideView(); + + bool showMasterSlide() const; + void setShowMasterSlide(bool show); + QAbstractItemModel *currentModel() { return m_CurrentModel; } + QSize sizeHint() const override; + QSize minimumSizeHint() const override; + void onDataInputChange(int handle, int instance, const QString &dataInputName); + void onDockLocationChange(Qt::DockWidgetArea area); + void refreshVariants(); + + Q_INVOKABLE void deselectAll(); + Q_INVOKABLE void addNewSlide(int row); + Q_INVOKABLE void removeSlide(int row); + Q_INVOKABLE void duplicateSlide(int row); + Q_INVOKABLE void startSlideRearrange(int row); + Q_INVOKABLE void moveSlide(int from, int to); + Q_INVOKABLE void finishSlideRearrange(bool commit); + Q_INVOKABLE void showContextMenu(int x, int y, int row); + Q_INVOKABLE void showControllerDialog(const QPoint &point); + Q_INVOKABLE void showVariantsTooltip(int row, const QPoint &point); + Q_INVOKABLE void hideVariantsTooltip(); + Q_INVOKABLE bool toolTipsEnabled(); + + // Presentation Change Listener + void OnNewPresentation() override; + void OnClosingPresentation() override; + + // IDataModelListener + void OnBeginDataModelNotifications() override; + void OnEndDataModelNotifications() override; + void OnImmediateRefreshInstanceSingle(qt3dsdm::Qt3DSDMInstanceHandle inInstance) override; + void OnImmediateRefreshInstanceMultiple(qt3dsdm::Qt3DSDMInstanceHandle *inInstance, + long inInstanceCount) override; + +Q_SIGNALS: + void currentModelChanged(); + void showMasterSlideChanged(); + void controlledChanged(); + void dockAreaChanged(); + +protected: + void mousePressEvent(QMouseEvent *event) override; + + // DataModel callbacks + virtual void OnActiveSlide(const qt3dsdm::Qt3DSDMSlideHandle &inMaster, int inIndex, + const qt3dsdm::Qt3DSDMSlideHandle &inSlide); + virtual void OnNewSlide(const qt3dsdm::Qt3DSDMSlideHandle &inSlide); + virtual void OnDeleteSlide(const qt3dsdm::Qt3DSDMSlideHandle &inSlide); + virtual void OnSlideRearranged(const qt3dsdm::Qt3DSDMSlideHandle &inMaster, int inOldIndex, + int inNewIndex); + + void updateDataInputStatus(); + void UpdateSlideViewTitleColor(); + +private: + void initialize(); + void clearSlideList(); + void setActiveSlide(const qt3dsdm::Qt3DSDMSlideHandle &inActiveSlideHandle); + inline CDoc *GetDoc() const; + inline CClientDataModelBridge *GetBridge() const; + inline qt3dsdm::ISlideSystem *GetSlideSystem() const; + long GetSlideIndex(const qt3dsdm::Qt3DSDMSlideHandle &inSlideHandle) const; + bool isMaster(const qt3dsdm::Qt3DSDMSlideHandle &inSlideHandle) const; + void rebuildSlideList(const qt3dsdm::Qt3DSDMSlideHandle &inActiveSlideHandle); + void onAssetCreated(qt3dsdm::Qt3DSDMInstanceHandle inInstance); + void onAssetDeleted(qt3dsdm::Qt3DSDMInstanceHandle inInstance); + void onPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance, + qt3dsdm::Qt3DSDMPropertyHandle inProperty); + + SlideModel *m_MasterSlideModel = nullptr; + SlideModel *m_SlidesModel = nullptr; + SlideModel *m_CurrentModel = nullptr; + DataInputSelectView *m_dataInputSelector = nullptr; + QLabel *m_variantsToolTip = nullptr; + std::vector<std::shared_ptr<qt3dsdm::ISignalConnection>> m_Connections; + // We need to remember which slide we were on when we entered the master slide. + // Then, when the users leave the master slide we can go back to roughly the same + // state. + QHash<int, int> m_MasterSlideReturnPointers; + + // the object containing the slides to be inspected. + qt3dsdm::Qt3DSDMInstanceHandle m_ActiveRoot = 0; + qt3dsdm::Qt3DSDMSlideHandle m_ActiveSlideHandle; // the active slide handle + bool m_controlled = false; // Are slides in this slide set controlled by datainput? + QString m_currentController; + QString m_toolTip; + Qt::DockWidgetArea m_dockArea; + QTimer m_variantRefreshTimer; +}; + +#endif // SLIDEVIEW_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.qml b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.qml new file mode 100644 index 00000000..7100ed8a --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Slide/SlideView.qml @@ -0,0 +1,416 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../controls" + +Rectangle { + + id: root + + readonly property bool masterSlide: _parentView.showMasterSlide + + function handleMouseClicks(mouse, mappedCoords) { + if (mouse.button === Qt.RightButton) { + _parentView.showContextMenu(mappedCoords.x, mappedCoords.y, -1); + } else { + root.focus = true; + //Unselect All element when we click outside slider item in listView. + //It worked as it in old version. + _parentView.deselectAll(); + mouse.accepted = false + } + } + + Connections { + target: _parentView + onDockAreaChanged: diIndicator.reAnchor(); + } + + color: _backgroundColor + + Column { + anchors { + top: parent.top + topMargin: 5 + horizontalCenter: parent.horizontalCenter + } + + spacing: 5 + width: parent.width + + MouseArea { + id: masterMouseArea + + width: parent.width + height: childrenRect.height + + propagateComposedEvents: true + acceptedButtons: Qt.AllButtons + onClicked: { + const coords = mapToItem(root, mouse.x, mouse.y); + root.handleMouseClicks(mouse, coords); + } + + Column { + id: masterButtonColumn + spacing: -4 + anchors.horizontalCenter: parent.horizontalCenter + Button { + id: masterEditButton + anchors.horizontalCenter: parent.horizontalCenter + + onClicked: _parentView.showMasterSlide = !_parentView.showMasterSlide + + background: Rectangle { + color: "transparent" + } + contentItem: Image { + source: _parentView.showMasterSlide ? _resDir + "Slide-Normal.png" + : _resDir + "Slide-Master-Active.png" + } + } + + StyledLabel { + id: masterEditLabel + text: _parentView.showMasterSlide ? qsTr("Leave Master") : qsTr("Edit Master") + font.pixelSize: _fontSize + color: _masterColor + verticalAlignment: Text.AlignVCenter + anchors.horizontalCenter: parent.horizontalCenter + } + } + } + StyledMenuSeparator { + id: separator + leftPadding: 12 + rightPadding: 12 + } + + ListView { + id: slideList + + ScrollBar.vertical: ScrollBar {} + + width: root.width + property int listItemHeight: root.height - masterButtonColumn.height + - separator.height - separator2.height + - parent.spacing * 2 - 14 - slideControlButton.height + - slideControlButton.spacing * 2 + // DockWidgetArea is enum; value 0x2 denotes right edge + property int area: _parentView.dockArea + height: listItemHeight > 0 ? listItemHeight : 0 + + anchors.horizontalCenter: parent.horizontalCenter + boundsBehavior: Flickable.StopAtBounds + clip: true + + model: _parentView.currentModel + spacing: 10 + + Rectangle { + id: diIndicator + height: slideList.listItemHeight + width: dataInputImage2.height + + function reAnchor() { + // reset anchors before setting new value + anchors.right = undefined + anchors.left = undefined + // default position for indicator is right edge + // except when slide panel is attached to window right side + if (parent.area === 2) + anchors.left = parent.left + else + anchors.right = parent.right + } + + color: _parentView.controlled ? _dataInputColor : "transparent" + Row { + rotation: 90 + anchors.centerIn: parent + spacing: 5 + Image { + id: dataInputImage2 + fillMode: Image.Pad + visible: _parentView.controlled + source: _resDir + "Objects-DataInput-White.png" + + } + StyledLabel { + text: _parentView.currController + anchors.margins: 16 + color: "#ffffff" + } + } + } + + MouseArea { + // mouse handling for the area not covered by the delegates + propagateComposedEvents: true + anchors.fill: parent + z: -1 // Only reached when clicking outside delegates + acceptedButtons: Qt.AllButtons + onClicked: { + if (slideList.indexAt(mouse.x, mouse.y) === -1) { + const coords = mapToItem(root, mouse.x, mouse.y); + root.handleMouseClicks(mouse, coords); + } else { + mouse.accepted = false; + } + } + onPressed: { + if (slideList.indexAt(mouse.x, mouse.y) !== -1) + mouse.accepted = false; + } + } + + delegate: MouseArea { + id: delegateArea + + property int dragIndex + property bool held : false + + anchors.horizontalCenter: parent.horizontalCenter + height: delegateItem.height + width: parent.width + + acceptedButtons: Qt.RightButton | Qt.LeftButton + drag.target: held ? delegateItem : null + drag.axis: Drag.YAxis + + onPressed: { + dragIndex = model.index; + _parentView.startSlideRearrange(model.index); + if (mouse.x > delegateItem.x && mouse.x < delegateItem.x + delegateItem.width) + held = true; + } + + onReleased: { + held = false; + _parentView.finishSlideRearrange(true); + } + + onCanceled: { + held = false; + _parentView.finishSlideRearrange(false); + } + + onClicked: { + _parentView.deselectAll(); + if (mouse.button === Qt.LeftButton) { + root.focus = true; + model.selected = true; + } + if (mouse.button === Qt.RightButton) { + const coords = mapToItem(root, mouse.x, mouse.y); + _parentView.showContextMenu(coords.x, coords.y, model.index); + } + } + + Item { + id: delegateItem + + anchors.centerIn: parent + height: column.implicitHeight + width: 100 + + Drag.keys: "application/x-slide" + Drag.active: delegateArea.held + Drag.hotSpot.x: width / 2 + Drag.hotSpot.y: height / 2 + Drag.source: delegateArea + + Column { + id: column + spacing: 2 + anchors.fill: parent + Image { + id: slideImage + + source: { + if (masterSlide) + return _resDir + "Slide-Master-Active.png" + return model.selected ? _resDir + "Slide-Active.png" + : _resDir + "Slide-Normal.png"; + } + } + + Label { // variants + width: slideImage.width + font.pixelSize: 14 + font.letterSpacing: 2 + leftPadding: 3 + topPadding: -3 + bottomPadding: 8 + background: Rectangle { color: _variantsSlideViewBGColor } + wrapMode: Text.WrapAnywhere + lineHeight: .6 + visible: model.variants !== undefined && model.variants !== "" + text: model.variants ? model.variants : "" + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: { + _parentView.showVariantsTooltip( + model.index, mapToGlobal(x + width + 2, y)); + } + onExited : { + _parentView.hideVariantsTooltip(); + } + } + } + + Item { + anchors.horizontalCenter: slideImage.horizontalCenter + + height: childrenRect.height + width: childrenRect.width + Row { + StyledLabel { + visible: !masterSlide + text: model.index + 1 + ": " + } + + TextInput { + id: slideName + + property bool ignoreHotkeys: true + + readOnly: masterSlide + selectByMouse: !readOnly + color: _textColor + text: model.name + font.pixelSize: _fontSize + + onFocusChanged: { + if (focus && !readOnly) + selectAll(); + } + + onEditingFinished: { + model.name = text; + slideName.focus = false; + } + + Keys.onEscapePressed: { + slideName.undo(); + slideName.focus = false; + } + } + } + } + } + } + + DropArea { + anchors.fill: parent + keys: "application/x-slide" + onEntered: { + var oldIndex = drag.source.dragIndex + var newIndex = model.index + _parentView.moveSlide(oldIndex, newIndex) + drag.source.dragIndex = newIndex + } + } + + states: State { + when: held + + ParentChange { + target: delegateItem + parent: slideList + } + + PropertyChanges { + target: delegateItem + anchors.centerIn: null + } + } + } + } + + StyledMenuSeparator { + id: separator2 + leftPadding: 12 + rightPadding: 12 + } + // RowLayout for possible addition and positioning of label + // showing the controller name + RowLayout { + Layout.rightMargin: 12 + Layout.leftMargin: 12 + anchors.left: parent.left + Button { + id: slideControlButton + width: dataInputImage.sourceSize.width + height: dataInputImage.sourceSize.height + Layout.leftMargin: 12 + property bool controlled: _parentView.controlled + property string currentController: _parentView.currController + property string toolTip: _parentView.toolTip + background: Rectangle { + color: controlButtonArea.containsMouse ? _studioColor1 : _backgroundColor + } + MouseArea { + id: controlButtonArea + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton + onClicked: { + _parentView.showControllerDialog(mapToGlobal(x + width, y + height)); + } + } + Image { + id: dataInputImage + anchors.fill: parent + fillMode: Image.Pad + property bool controlled: parent.controlled + source: { + _resDir + (controlled + ? "Objects-DataInput-Active.png" + : "Objects-DataInput-Inactive.png") + } + } + StyledTooltip { + id: tooltip + enabled: controlButtonArea.containsMouse + text: parent.toolTip + } + } + StyledLabel { + id: dataInputName + text: _parentView.currController + color: _parentView.controlled ? _dataInputColor : "transparent" + } + } + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/BehaviorTimelineItemBinding.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/BehaviorTimelineItemBinding.cpp new file mode 100644 index 00000000..a1e9ae40 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/BehaviorTimelineItemBinding.cpp @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSCommonPrecompile.h" +#include "BehaviorTimelineItemBinding.h" +#include "TimelineTranslationManager.h" +#include "StudioApp.h" +#include "Qt3DSDMStudioSystem.h" +#include "ClientDataModelBridge.h" +#include "Doc.h" + +using namespace qt3dsdm; + +CBehaviorTimelineItemBinding::CBehaviorTimelineItemBinding(CTimelineTranslationManager *inMgr, + Qt3DSDMInstanceHandle inDataHandle) + : Qt3DSDMTimelineItemBinding(inMgr, inDataHandle) +{ +} + +EStudioObjectType CBehaviorTimelineItemBinding::GetObjectType() const +{ + return OBJTYPE_BEHAVIOR; +} + +//============================================================================= +/** + * Open the associated item as though it was double-clicked in explorer + */ +bool CBehaviorTimelineItemBinding::OpenAssociatedEditor() +{ + return OpenSourcePathFile(); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/BehaviorTimelineItemBinding.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/BehaviorTimelineItemBinding.h new file mode 100644 index 00000000..d8f809d1 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/BehaviorTimelineItemBinding.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +//============================================================================== +// Prefix +//============================================================================== +#ifndef INCLUDED_BEHAVIOR_TIMELINEITEM_BINDING_H +#define INCLUDED_BEHAVIOR_TIMELINEITEM_BINDING_H 1 + +#pragma once + +#include "Qt3DSDMTimelineItemBinding.h" + +//============================================================================== +// Classes +//============================================================================== +class CTimelineTranslationManager; + +//============================================================================= +/** + * Binding to a DataModel object of Behavior type + */ +class CBehaviorTimelineItemBinding : public Qt3DSDMTimelineItemBinding +{ +public: + CBehaviorTimelineItemBinding(CTimelineTranslationManager *inMgr, + qt3dsdm::Qt3DSDMInstanceHandle inDataHandle); + ~CBehaviorTimelineItemBinding() {} + + // Qt3DSDMTimelineItemBinding + EStudioObjectType GetObjectType() const override; + bool OpenAssociatedEditor() override; +}; + +#endif // INCLUDED_BEHAVIOR_TIMELINEITEM_BINDING_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/EmptyTimelineTimebar.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/EmptyTimelineTimebar.cpp new file mode 100644 index 00000000..b135e2c0 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/EmptyTimelineTimebar.cpp @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSCommonPrecompile.h" +#include "EmptyTimelineTimebar.h" +#include "StudioPreferences.h" + +CEmptyTimelineTimebar::CEmptyTimelineTimebar() +{ +} + +CEmptyTimelineTimebar::~CEmptyTimelineTimebar() +{ +} + +long CEmptyTimelineTimebar::GetStartTime() const +{ + return 0; +} + +long CEmptyTimelineTimebar::GetEndTime() const +{ + return 0; +} + +long CEmptyTimelineTimebar::GetDuration() const +{ + return 0; +} + +bool CEmptyTimelineTimebar::ShowHandleBars() const +{ // makes no sense to show handle bars, when this does not have start/end times. + return false; +} + +void CEmptyTimelineTimebar::OnBeginDrag() +{ +} + +void CEmptyTimelineTimebar::OffsetTime(long inDiff) +{ + Q_UNUSED(inDiff); +} + +void CEmptyTimelineTimebar::ChangeTime(long inTime, bool inSetStart) +{ + Q_UNUSED(inTime); + Q_UNUSED(inSetStart); +} + +void CEmptyTimelineTimebar::CommitTimeChange() +{ +} + +void CEmptyTimelineTimebar::RollbackTimeChange() +{ +} + +::CColor CEmptyTimelineTimebar::GetTimebarColor() +{ + return CStudioPreferences::GetObjectTimebarColor(); +} + +QString CEmptyTimelineTimebar::GetTimebarComment() const +{ + return {}; +} + +void CEmptyTimelineTimebar::SetTimebarComment(const QString &inComment) +{ + Q_UNUSED(inComment); +} + +void CEmptyTimelineTimebar::SetTimebarTime(ITimeChangeCallback *inCallback /*= nullptr*/) +{ + Q_UNUSED(inCallback); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/EmptyTimelineTimebar.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/EmptyTimelineTimebar.h new file mode 100644 index 00000000..09c00582 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/EmptyTimelineTimebar.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +//============================================================================== +// Prefix +//============================================================================== +#pragma once + +#include "ITimelineTimebar.h" + +//============================================================================= +/** + * The current timeline UI design is such that even when no timebar shows up ( with the exception of + * the top row, ie the time context ) + * there is a timebar control to store the keyframes for the animated properties. + * Hence, instead of return nullptr for GetTimebar for ITimelineItem, this class will ensure the UI + * classes still work. + */ +class CEmptyTimelineTimebar : public ITimelineTimebar +{ +public: + CEmptyTimelineTimebar(); + virtual ~CEmptyTimelineTimebar(); + + // ITimelineTimebar + long GetStartTime() const override; + long GetEndTime() const override; + long GetDuration() const override; + bool ShowHandleBars() const override; + void OnBeginDrag() override; + void OffsetTime(long inDiff) override; + void ChangeTime(long inTime, bool inSetStart) override; + void CommitTimeChange() override; + void RollbackTimeChange() override; + ::CColor GetTimebarColor() override; + QString GetTimebarComment() const override; + void SetTimebarComment(const QString &inComment) override; + void SetTimebarTime(ITimeChangeCallback *inCallback = nullptr) override; +}; diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/GroupTimelineItemBinding.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/GroupTimelineItemBinding.cpp new file mode 100644 index 00000000..a860b24c --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/GroupTimelineItemBinding.cpp @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSCommonPrecompile.h" +#include "GroupTimelineItemBinding.h" +#include "TimelineTranslationManager.h" +#include "StudioApp.h" +#include "Core.h" +#include "Dialogs.h" + +// Data model specific +#include "Doc.h" +#include "CmdGeneric.h" + +#include "Qt3DSDMStudioSystem.h" +#include "ClientDataModelBridge.h" +#include "Qt3DSDMSlides.h" +#include "Qt3DSDMDataCore.h" +#include "Qt3DSFileTools.h" + +using namespace qt3dsdm; + +CGroupTimelineItemBinding::CGroupTimelineItemBinding(CTimelineTranslationManager *inMgr, + Qt3DSDMInstanceHandle inDataHandle) + : Qt3DSDMTimelineItemBinding(inMgr, inDataHandle) +{ +} + +//============================================================================= +/** + * Ideally we like to be able to edit the component in a different editor ( we've been hoping for + * that feature ) BUT we don't have that, + * and it has always been we 'dive' into the component within Studio. + */ +bool CGroupTimelineItemBinding::OpenAssociatedEditor() +{ + if (GetObjectType() == OBJTYPE_COMPONENT) { + ISlideSystem *theSlideSystem = m_StudioSystem->GetSlideSystem(); + + qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance(); + Q3DStudio::CId theId = m_StudioSystem->GetClientDataModelBridge()->GetGUID(theInstance); + qt3dsdm::Qt3DSDMSlideHandle theMasterSlide = + theSlideSystem->GetMasterSlideByComponentGuid(GuidtoSLong4(theId)); + + if (theMasterSlide.Valid()) { + Qt3DSDMSlideHandle theActiveSlide = theSlideSystem->GetActiveSlide(theMasterSlide); + + CCmd *theCmd = new CCmdGeneric<CDoc, Qt3DSDMSlideHandle>( + m_TransMgr->GetDoc(), &CDoc::NotifyActiveSlideChanged, + &CDoc::NotifyActiveSlideChanged, theActiveSlide, NULL, ""); + theCmd->SetUndoable(false); + theCmd->SetModifiedFlag(false); + m_TransMgr->GetDoc()->GetCore()->ExecuteCommand(theCmd, false); + } + return true; + } + return false; +} + +bool CGroupTimelineItemBinding::IsImported() const +{ + + qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance(); + qt3dsdm::IPropertySystem *thePropertySystem = + m_TransMgr->GetDoc()->GetStudioSystem()->GetPropertySystem(); + qt3dsdm::SValue theValue; + if (thePropertySystem->GetInstancePropertyValue(theInstance, m_TransMgr->GetDoc() + ->GetStudioSystem() + ->GetClientDataModelBridge() + ->GetSourcePathProperty(), + theValue)) { + qt3dsdm::TDataStrPtr theSrcPath(qt3dsdm::get<qt3dsdm::TDataStrPtr>(theValue)); + Q3DStudio::CFilePath theFilePath(theSrcPath->GetData()); + if (theFilePath.GetExtension() == CDialogs::GetWideImportFileExtension()) + return true; + } + // If it is, check to be sure that + // we can get to the import file. + // If we can, then we are imported. + return false; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/GroupTimelineItemBinding.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/GroupTimelineItemBinding.h new file mode 100644 index 00000000..cd7f9dad --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/GroupTimelineItemBinding.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +//============================================================================== +// Prefix +//============================================================================== +#ifndef INCLUDED_GROUP_TIMELINEITEM_BINDING_H +#define INCLUDED_GROUP_TIMELINEITEM_BINDING_H 1 + +#pragma once + +#include "Qt3DSDMTimelineItemBinding.h" + +//============================================================================== +// Classes +//============================================================================== +class ITimelineItem; +class CTimelineTranslationManager; + +//============================================================================= +/** + * Binding to a DataModel object of Group type + */ +class CGroupTimelineItemBinding : public Qt3DSDMTimelineItemBinding +{ +public: + CGroupTimelineItemBinding(CTimelineTranslationManager *inMgr, + qt3dsdm::Qt3DSDMInstanceHandle inDataHandle); + ~CGroupTimelineItemBinding() {} + + // Qt3DSDMTimelineItemBinding + bool OpenAssociatedEditor() override; + bool IsImported() const override; +}; + +#endif // INCLUDED_GROUP_TIMELINEITEM_BINDING_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/IBreadCrumbProvider.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/IBreadCrumbProvider.h new file mode 100644 index 00000000..1a8beb0a --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/IBreadCrumbProvider.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef INCLUDED_IBREADCRUMBPROVIDER_H +#define INCLUDED_IBREADCRUMBPROVIDER_H 1 + +#pragma once + +#include <QColor> +#include <QString> +#include <QObject> + +QT_FORWARD_DECLARE_CLASS(QPixmap) + +struct SBreadCrumb +{ + QColor m_Color; /// Color for text of the bread crumb + QString m_String; /// Text to be displayed for the bread crumb +}; + +//============================================================================= +/** + * A interface class for the breadcrumb control, to walk down the breadcrumb trail, without having + * to know any underlying implementations. + */ +class IBreadCrumbProvider : public QObject +{ + Q_OBJECT +public: + typedef std::vector<SBreadCrumb> TTrailList; + +public: + virtual ~IBreadCrumbProvider() {} + + virtual TTrailList GetTrail(bool inRefresh = true) = 0; + virtual void OnBreadCrumbClicked(long inTrailIndex) = 0; + + virtual QPixmap GetRootImage() const = 0; + virtual QPixmap GetBreadCrumbImage() const = 0; + virtual QPixmap GetSeparatorImage() const = 0; + virtual QPixmap GetActiveBreadCrumbImage() const = 0; +Q_SIGNALS: + void SigBreadCrumbUpdate(); +}; + +#endif // INCLUDED_IBREADCRUMBPROVIDER_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItem.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItem.h new file mode 100644 index 00000000..4308fb2b --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItem.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef INCLUDED_ITIMELINE_ITEM_H +#define INCLUDED_ITIMELINE_ITEM_H 1 + +#pragma once + +#include "INamable.h" +#include "StudioObjectTypes.h" + +class ITimelineTimebar; + +//============================================================================= +/** + * Abstraction of a data model item in the Scene. This might end up deriving from a more generic + * interface, so that common + * functions can be generalized for items in the different palettes. + */ +//============================================================================= +class ITimelineItem : public INamable +{ +public: + virtual ~ITimelineItem() {} + + virtual EStudioObjectType GetObjectType() const = 0; + virtual bool IsMaster() const = 0; + + virtual bool IsShy() const = 0; + virtual void SetShy(bool) = 0; + virtual bool IsLocked() const = 0; + virtual void SetLocked(bool) = 0; + virtual bool IsVisible() const = 0; + virtual void SetVisible(bool) = 0; + virtual bool IsImported() const { return false; } + virtual bool IsVisibilityControlled() const = 0; + + // Actions + virtual bool HasAction(bool inMaster) = 0; + virtual bool ChildrenHasAction(bool inMaster) = 0; + virtual bool ComponentHasAction(bool inMaster) = 0; + + // subpresentations + virtual bool hasSubpresentation() const = 0; + + virtual ITimelineTimebar *GetTimebar() = 0; +}; + +#endif // INCLUDED_ITIMELINE_ITEM_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItemBinding.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItemBinding.h new file mode 100644 index 00000000..cebc46d1 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItemBinding.h @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ +#ifndef INCLUDED_ITIMELINE_ITEM_BINDINGS_H +#define INCLUDED_ITIMELINE_ITEM_BINDINGS_H 1 + +#pragma once + +#include "ITimelineItem.h" +#include "ITimelineItemProperty.h" +#include "SIterator.h" + +class RowTree; +class CControlWindowListener; + +// Data model specific ?? +class CDropTarget; + +class ITimelineItemKeyframesHolder +{ +public: + virtual ~ITimelineItemKeyframesHolder() {} + + virtual void InsertKeyframe() = 0; + virtual void DeleteAllChannelKeyframes() = 0; + virtual IKeyframe *GetKeyframeByTime(long inTime) const = 0; +}; + +//============================================================================= +/** + * Interface to encapsulate data model specific functions, that Timeline UI objects can talk to. + */ +//============================================================================= +class ITimelineItemBinding : public ITimelineItemKeyframesHolder +{ +public: + // List of possible transactions that requires querying the data model if they are valid + enum EUserTransaction { + EUserTransaction_None, + EUserTransaction_Rename, + EUserTransaction_Duplicate, + EUserTransaction_Cut, + EUserTransaction_Copy, + EUserTransaction_Paste, + EUserTransaction_Delete, + EUserTransaction_MakeComponent, + EUserTransaction_EditComponent, + EUserTransaction_MakeAnimatable, + EUserTransaction_Group, + EUserTransaction_Ungroup, + EUserTransaction_AddLayer, + }; + +public: + virtual ~ITimelineItemBinding() {} + + virtual ITimelineItem *GetTimelineItem() = 0; + virtual RowTree *getRowTree() const = 0; // UI + virtual void setRowTree(RowTree *row) = 0; + + // Events + virtual void SetSelected(bool multiSelect) = 0; + virtual void OnCollapsed() = 0; + virtual bool OpenAssociatedEditor() = 0; + virtual void SetDropTarget(CDropTarget *inTarget) = 0; + + // Hierarchy + virtual long GetChildrenCount() = 0; + virtual ITimelineItemBinding *GetChild(long inIndex) = 0; + virtual QList<ITimelineItemBinding *> GetChildren() = 0; + virtual ITimelineItemBinding *GetParent() = 0; + virtual void SetParent(ITimelineItemBinding *parent) = 0; + // Properties + virtual long GetPropertyCount() = 0; + virtual ITimelineItemProperty *GetProperty(long inIndex) = 0; + + // Eye/Lock toggles + virtual bool ShowToggleControls() const = 0; + virtual bool IsLockedEnabled() const = 0; + virtual bool IsVisibleEnabled() const = 0; + + // ContextMenu + virtual bool IsValidTransaction(EUserTransaction inTransaction) = 0; + virtual void PerformTransaction(EUserTransaction inTransaction) = 0; + virtual Q3DStudio::CString GetObjectPath() = 0; + + virtual bool IsExternalizeable() { return false; } + virtual void Externalize() {} + virtual bool IsInternalizeable() { return false; } + virtual void Internalize() {} + + void setCreateUIRow(bool create) { m_createUIRow = create; } + +protected: + bool m_createUIRow = true; // control creation of UI row for old style timeline UI +}; + +//============================================================================= +/** + * Helper iterator class that iterates over a ITimeline's children in a ordered (priority) list. + */ +//============================================================================= +class CTimelineItemOrderedIterator : public CSIterator<ITimelineItemBinding *> +{ +public: + CTimelineItemOrderedIterator(ITimelineItemBinding *inRootTimelineItem) + { + m_RootTimelineItem = inRootTimelineItem; + Reset(); + } + bool IsDone() override { return (m_Index >= m_Total); } + void operator++() override { m_Index++; } + void operator+=(const long inNumToInc) override { m_Index += inNumToInc; } + ITimelineItemBinding *GetCurrent() override { return m_RootTimelineItem->GetChild(m_Index); } + virtual void Reset() + { + m_Index = 0; + m_Total = m_RootTimelineItem->GetChildrenCount(); + } + +protected: + ITimelineItemBinding *m_RootTimelineItem; + long m_Index; + long m_Total; +}; + +//============================================================================= +/** + * Helper iterator class that iterates over a ITimeline's properties + */ +//============================================================================= +class CTimelineItemPropertyIterator : public CSIterator<ITimelineItemProperty *> +{ +public: + CTimelineItemPropertyIterator(ITimelineItemBinding *inTimelineItem) + { + m_TimelineItem = inTimelineItem; + Reset(); + } + bool IsDone() override { return (m_Index >= m_Total); } + void operator++() override { m_Index++; } + void operator+=(const long inNumToInc) override { m_Index += inNumToInc; } + ITimelineItemProperty *GetCurrent() override { return m_TimelineItem->GetProperty(m_Index); } + virtual void Reset() + { + m_Index = 0; + m_Total = m_TimelineItem->GetPropertyCount(); + } + +protected: + ITimelineItemBinding *m_TimelineItem; + long m_Index; + long m_Total; +}; + +#endif // INCLUDED_ITIMELINE_ITEM_BINDINGS_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItemProperty.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItemProperty.h new file mode 100644 index 00000000..72488480 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineItemProperty.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2016 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef INCLUDED_ITIMELINE_ITEM_PROPERTY_H +#define INCLUDED_ITIMELINE_ITEM_PROPERTY_H 1 + +#pragma once + +#include "Qt3DSDMMetaData.h" +#include "Qt3DSString.h" + +class RowTree; +class IKeyframe; + +//============================================================================= +/** + * Abstraction of a data model item's property that is displayed in the Timeline. + */ +//============================================================================= +class ITimelineItemProperty +{ +public: + virtual ~ITimelineItemProperty() {} + + virtual Q3DStudio::CString GetName() const = 0; + virtual bool IsMaster() const = 0; + virtual qt3dsdm::TDataTypePair GetType() const = 0; + virtual float GetMaximumValue() const = 0; + virtual float GetMinimumValue() const = 0; + + virtual void SetSelected() = 0; + virtual void DeleteAllKeys() = 0; + + virtual void setRowTree(RowTree *row) = 0; + virtual RowTree *getRowTree() const = 0; + + // Keyframes + virtual IKeyframe *GetKeyframeByTime(long inTime) const = 0; + virtual IKeyframe *GetKeyframeByIndex(long inIndex) const = 0; + virtual long GetKeyframeCount() const = 0; + virtual long GetChannelCount() const = 0; + virtual float GetChannelValueAtTime(long inChannelIndex, long inTime) = 0; + virtual void SetChannelValueAtTime(long inChannelIndex, long inTime, float inValue) = 0; + virtual bool IsDynamicAnimation() = 0; +}; + +#endif // INCLUDED_ITIMELINE_ITEM_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineTimebar.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineTimebar.h new file mode 100644 index 00000000..2a5ef827 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ITimelineTimebar.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ +#ifndef INCLUDED_ITIMELINE_TIMEBAR_H +#define INCLUDED_ITIMELINE_TIMEBAR_H 1 + +#pragma once + +#include "Qt3DSString.h" + +class ITimeChangeCallback; + +class CColor; + +//============================================================================= +/** + * Interface for a Timebar + */ +//============================================================================= +class ITimelineTimebar +{ +public: + virtual ~ITimelineTimebar() {} + + virtual long GetStartTime() const = 0; + virtual long GetEndTime() const = 0; + virtual long GetDuration() const = 0; + virtual bool ShowHandleBars() const = 0; + //============================================================================= + /** + * TODO: consider refactor. drag&drop specfics should not be in the Data Model. + */ + virtual void OnBeginDrag() = 0; + // + virtual void OffsetTime(long inDiff) = 0; + // Change the start time or the end time of the timebar. inTime: time to change to, inSetStart: + // true to set start time, false to set end time. + virtual void ChangeTime(long inTime, bool inSetStart) = 0; + virtual void CommitTimeChange() = 0; + virtual void RollbackTimeChange() = 0; + // + virtual CColor GetTimebarColor() = 0; + virtual QString GetTimebarComment() const = 0; + virtual void SetTimebarComment(const QString &inComment) = 0; + virtual void SetTimebarTime(ITimeChangeCallback *inCallback = nullptr) = 0; +}; + +#endif // INCLUDED_ITIMELINE_TIMEBAR_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ImageTimelineItemBinding.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ImageTimelineItemBinding.cpp new file mode 100644 index 00000000..ac0fa169 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ImageTimelineItemBinding.cpp @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "ImageTimelineItemBinding.h" +#include "TimelineTranslationManager.h" +#include "EmptyTimelineTimebar.h" + +using namespace qt3dsdm; + +CImageTimelineItemBinding::CImageTimelineItemBinding(CTimelineTranslationManager *inMgr, + Qt3DSDMInstanceHandle inDataHandle) + : Qt3DSDMTimelineItemBinding(inMgr, inDataHandle) +{ +} + +CImageTimelineItemBinding::~CImageTimelineItemBinding() +{ +} + +ITimelineTimebar *CImageTimelineItemBinding::GetTimebar() +{ // No timebars on images + return new CEmptyTimelineTimebar(); +} + +Q3DStudio::CString CImageTimelineItemBinding::GetName() const +{ + return m_Name; +} + +void CImageTimelineItemBinding::SetName(const Q3DStudio::CString &inName) +{ + m_Name = inName; +} + +EStudioObjectType CImageTimelineItemBinding::GetObjectType() const +{ + return OBJTYPE_IMAGE; +} + +bool CImageTimelineItemBinding::ShowToggleControls() const +{ + // no toggle controls, by design + return false; +} + +//============================================================================= +/** + * Open the associated item as though it was double-clicked in explorer + */ +bool CImageTimelineItemBinding::OpenAssociatedEditor() +{ + return OpenSourcePathFile(); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ImageTimelineItemBinding.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ImageTimelineItemBinding.h new file mode 100644 index 00000000..b9343872 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/ImageTimelineItemBinding.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +//============================================================================== +// Prefix +//============================================================================== +#ifndef INCLUDED_IMAGE_TIMELINEITEM_BINDING_H +#define INCLUDED_IMAGE_TIMELINEITEM_BINDING_H 1 + +#pragma once + +#include "Qt3DSDMTimelineItemBinding.h" + +//============================================================================== +// Classes +//============================================================================== +class CTimelineTranslationManager; +class ITimelineTimebar; + +//============================================================================= +/** + * Binding to a DataModel object of Image type + */ +class CImageTimelineItemBinding : public Qt3DSDMTimelineItemBinding +{ +public: + CImageTimelineItemBinding(CTimelineTranslationManager *inMgr, + qt3dsdm::Qt3DSDMInstanceHandle inDataHandle); + virtual ~CImageTimelineItemBinding(); + + // Qt3DSDMTimelineItemBinding + ITimelineTimebar *GetTimebar() override; + Q3DStudio::CString GetName() const override; + void SetName(const Q3DStudio::CString &inName) override; + EStudioObjectType GetObjectType() const override; + bool ShowToggleControls() const override; + bool OpenAssociatedEditor() override; + + void SetPropertyHandle(qt3dsdm::Qt3DSDMPropertyHandle inProperty) + { + m_PropertyHandle = inProperty; + } + qt3dsdm::Qt3DSDMPropertyHandle GetPropertyHandle() const { return m_PropertyHandle; } + +protected: + Q3DStudio::CString m_Name; + qt3dsdm::Qt3DSDMPropertyHandle m_PropertyHandle; +}; + +#endif // INCLUDED_IMAGE_TIMELINEITEM_BINDING_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/LayerTimelineItemBinding.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/LayerTimelineItemBinding.cpp new file mode 100644 index 00000000..79d5da54 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/LayerTimelineItemBinding.cpp @@ -0,0 +1,254 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSCommonPrecompile.h" +#include "LayerTimelineItemBinding.h" +#include "TimelineTranslationManager.h" +#include "ImageTimelineItemBinding.h" +#include "EmptyTimelineTimebar.h" + +// Data model specific +#include "IDoc.h" +#include "ClientDataModelBridge.h" +#include "DropSource.h" +#include "Doc.h" + +#include "Qt3DSDMHandles.h" +#include "Qt3DSDMStudioSystem.h" + +#include "Qt3DSDMMetaData.h" +#include "Qt3DSDMDataCore.h" +#include "StudioFullSystem.h" +#include "StudioCoreSystem.h" +#include "Qt3DSDMSlides.h" + +using namespace qt3dsdm; + +namespace { + +bool ImageSlotIsFilled(qt3dsdm::IPropertySystem *inPropertySystem, Qt3DSDMInstanceHandle inInstance, + const TCharStr &inStr) +{ + Qt3DSDMPropertyHandle theProperty = + inPropertySystem->GetAggregateInstancePropertyByName(inInstance, inStr); + SValue theValue; + inPropertySystem->GetInstancePropertyValue(inInstance, theProperty, theValue); + + SLong4 theLong4 = qt3dsdm::get<SLong4>(theValue); + bool theReturn = theLong4.m_Longs[0] != 0 || theLong4.m_Longs[1] != 0 + || theLong4.m_Longs[2] != 0 || theLong4.m_Longs[3] != 0; + + return theReturn; +} +} + +CLayerTimelineItemBinding::CLayerTimelineItemBinding(CTimelineTranslationManager *inMgr, + qt3dsdm::Qt3DSDMInstanceHandle inDataHandle) + : Qt3DSDMTimelineItemBinding(inMgr, inDataHandle) +{ + qt3dsdm::IPropertySystem *thePropertySystem = m_TransMgr->GetStudioSystem()->GetPropertySystem(); + TPropertyHandleList theProperties; + thePropertySystem->GetAggregateInstanceProperties(inDataHandle, theProperties); + + size_t thePropertyCount = theProperties.size(); + for (size_t thePropertyIndex = 0; thePropertyIndex < thePropertyCount; ++thePropertyIndex) { + Qt3DSDMPropertyHandle theProperty = theProperties[thePropertyIndex]; + + AdditionalMetaDataType::Value theAdditionalMetaDataType = + thePropertySystem->GetAdditionalMetaDataType(inDataHandle, theProperty); + + if (theAdditionalMetaDataType == AdditionalMetaDataType::Image) { + TCharStr theName(thePropertySystem->GetName(theProperty)); + TCharStr theFormalName(thePropertySystem->GetFormalName(inDataHandle, theProperty)); + TNameFormalNamePair thePair = + std::make_tuple(theName, theFormalName, theProperty); + m_ImageNameFormalNamePairs.push_back(thePair); + } + } +} + +CLayerTimelineItemBinding::~CLayerTimelineItemBinding() +{ +} + +EStudioObjectType CLayerTimelineItemBinding::GetObjectType() const +{ + return OBJTYPE_LAYER; +} + +ITimelineItemBinding *CLayerTimelineItemBinding::GetChild(long inIndex) +{ + qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance(); + if (theInstance.Valid()) { + Q3DStudio::CGraphIterator theChildren; + Qt3DSDMSlideHandle theActiveSlide = m_TransMgr->GetDoc()->GetActiveSlide(); + GetAssetChildrenInTimeParent(theInstance, m_TransMgr->GetDoc(), AmITimeParent(), + theChildren, theActiveSlide); + theChildren += inIndex; + return GetOrCreateBinding(theChildren.GetCurrent()); + } + return nullptr; +} + +QList<ITimelineItemBinding *> CLayerTimelineItemBinding::GetChildren() +{ + QList<ITimelineItemBinding *> retlist; + qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance(); + if (theInstance.Valid()) { + Q3DStudio::CGraphIterator theChildren; + Qt3DSDMSlideHandle theActiveSlide = m_TransMgr->GetDoc()->GetActiveSlide(); + GetAssetChildrenInTimeParent(theInstance, m_TransMgr->GetDoc(), AmITimeParent(), + theChildren, theActiveSlide); + int childCount = int(theChildren.GetCount()); + retlist.reserve(childCount); + for (int i = 0; i < childCount; ++i) { + retlist.append(GetOrCreateBinding(theChildren.GetCurrent())); + ++theChildren; + } + } + + return retlist; +} + +void CLayerTimelineItemBinding::OnAddChild(qt3dsdm::Qt3DSDMInstanceHandle inInstance) +{ + using namespace qt3dsdm; + CClientDataModelBridge *theBridge = m_TransMgr->GetStudioSystem()->GetClientDataModelBridge(); + // This is handled via the OnPropertyChanged call below + if (theBridge->IsImageInstance(inInstance)) + return; + else + Qt3DSDMTimelineItemBinding::OnAddChild(inInstance); +} + +void CLayerTimelineItemBinding::OnPropertyChanged(Qt3DSDMPropertyHandle inPropertyHandle) +{ + Qt3DSDMTimelineItemBinding::OnPropertyChanged(inPropertyHandle); +} + +qt3dsdm::Qt3DSDMInstanceHandle +CLayerTimelineItemBinding::GetImage(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle) +{ + qt3dsdm::IPropertySystem *thePropertySystem = m_TransMgr->GetStudioSystem()->GetPropertySystem(); + SValue theImageValue; + thePropertySystem->GetInstancePropertyValue(m_DataHandle, inPropertyHandle, theImageValue); + SLong4 theImageLong4 = qt3dsdm::get<SLong4>(theImageValue); + return m_TransMgr->GetStudioSystem()->GetClientDataModelBridge()->GetImageInstanceByGUID( + theImageLong4); +} + +ITimelineItemBinding * +CLayerTimelineItemBinding::GetOrCreateImageBinding(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle, + const wchar_t *inName) +{ + qt3dsdm::Qt3DSDMInstanceHandle theImageInstance = GetImage(inPropertyHandle); + if (!theImageInstance.Valid()) + return nullptr; + ITimelineItemBinding *theImageTimelineRow = m_TransMgr->GetBinding(theImageInstance); + if (!theImageTimelineRow) // create + { + theImageTimelineRow = m_TransMgr->GetOrCreate(theImageInstance); + // Set the name, by spec: the nice name. + theImageTimelineRow->GetTimelineItem()->SetName(inName); + CImageTimelineItemBinding *theImageBinding = + dynamic_cast<CImageTimelineItemBinding *>(theImageTimelineRow); + if (theImageBinding) + theImageBinding->SetPropertyHandle(inPropertyHandle); + } + return theImageTimelineRow; +} + +ITimelineItemBinding *CLayerTimelineItemBinding::GetOrCreateBinding(Qt3DSDMInstanceHandle instance) +{ + if (instance.Valid()) { + qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance(); + qt3dsdm::IPropertySystem *thePropertySystem = + m_TransMgr->GetStudioSystem()->GetPropertySystem(); + std::shared_ptr<IDataCore> theDataCore = + m_TransMgr->GetStudioSystem()->GetFullSystem()->GetCoreSystem()->GetDataCore(); + ISlideSystem *theSlideSystem = m_TransMgr->GetStudioSystem()->GetSlideSystem(); + ISlideCore *theSlideCore = m_TransMgr->GetStudioSystem()->GetSlideCore(); + + size_t theSlotCursor = (size_t)-1; + { + qt3dsdm::IPropertySystem *thePropertySystem = + m_TransMgr->GetStudioSystem()->GetPropertySystem(); + qt3dsdm::SLong4 theGuid; + { + Qt3DSDMPropertyHandle theTypeProperty = + thePropertySystem->GetAggregateInstancePropertyByName(instance, L"id"); + SValue theIdValue; + thePropertySystem->GetInstancePropertyValue(instance, theTypeProperty, theIdValue); + theGuid = qt3dsdm::get<qt3dsdm::SLong4>(theIdValue); + } + for (size_t theSlotIndex = 0, theSlotCount = m_ImageNameFormalNamePairs.size(); + theSlotIndex < theSlotCount; ++theSlotIndex) { + bool theIsMatch = false; + qt3dsdm::Qt3DSDMPropertyHandle theProperty = + std::get<2>(m_ImageNameFormalNamePairs[theSlotIndex]); + SValue theValue; + const Qt3DSDMPropertyDefinition &theDefinition( + theDataCore->GetProperty(theProperty)); + if (theDefinition.m_Type == DataModelDataType::Long4) { + SValue theDCValue; + if (theDataCore->GetInstancePropertyValue(theInstance, theProperty, + theDCValue)) { + SLong4 thePropGuid = get<SLong4>(theDCValue); + if (thePropGuid == theGuid) + theIsMatch = true; + } + Qt3DSDMSlideHandle theSlide = + theSlideSystem->GetAssociatedSlide(instance); + Qt3DSDMSlideHandle theMasterSlide = theSlideSystem->GetMasterSlide(theSlide); + if (theIsMatch == false && theSlide.Valid() + && theSlideCore->GetSpecificInstancePropertyValue( + theSlide, theInstance, theProperty, theValue)) { + SLong4 thePropGuid = get<SLong4>(theValue); + if (thePropGuid == theGuid) + theIsMatch = true; + } + } + if (theIsMatch) { + theSlotCursor = theSlotIndex; + break; + } + } + } + if (theSlotCursor != (size_t)-1) { + Qt3DSDMPropertyHandle theImageProperty = + thePropertySystem->GetAggregateInstancePropertyByName( + m_DataHandle, std::get<0>(m_ImageNameFormalNamePairs[theSlotCursor])); + return GetOrCreateImageBinding( + theImageProperty, + std::get<1>(m_ImageNameFormalNamePairs[theSlotCursor]).wide_str()); + } else + return m_TransMgr->GetOrCreate(instance); + } + return nullptr; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/LayerTimelineItemBinding.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/LayerTimelineItemBinding.h new file mode 100644 index 00000000..74630a64 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/LayerTimelineItemBinding.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +//============================================================================== +// Prefix +//============================================================================== +#ifndef INCLUDED_LAYER_TIMELINEITEM_BINDING_H +#define INCLUDED_LAYER_TIMELINEITEM_BINDING_H 1 + +#pragma once + +#include "Qt3DSDMTimelineItemBinding.h" + +namespace qt3dsdm { +class CStudioSystem; +} + +//============================================================================= +/** + * Binding to generic DataModel object + */ +class CLayerTimelineItemBinding : public Qt3DSDMTimelineItemBinding +{ +public: // Types + typedef std::tuple<qt3dsdm::TCharStr, qt3dsdm::TCharStr, qt3dsdm::Qt3DSDMPropertyHandle> + TNameFormalNamePair; + typedef std::vector<TNameFormalNamePair> TNameFormalNamePairList; + +protected: // Members + TNameFormalNamePairList m_ImageNameFormalNamePairs; + +public: // Construction + CLayerTimelineItemBinding(CTimelineTranslationManager *inMgr, + qt3dsdm::Qt3DSDMInstanceHandle inDataHandle); + virtual ~CLayerTimelineItemBinding(); + +public: // Qt3DSDMTimelineItemBinding + EStudioObjectType GetObjectType() const override; + // Hierarchy + ITimelineItemBinding *GetChild(long inIndex) override; + QList<ITimelineItemBinding *> GetChildren() override; + void OnAddChild(qt3dsdm::Qt3DSDMInstanceHandle inInstance) override; + // Event callback + void OnPropertyChanged(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle) override; + +protected: + qt3dsdm::Qt3DSDMInstanceHandle GetImage(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle); + ITimelineItemBinding *GetOrCreateImageBinding(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle, + const wchar_t *inName); + ITimelineItemBinding *GetOrCreateBinding(qt3dsdm::Qt3DSDMInstanceHandle instance); +}; + +#endif // INCLUDED_LAYER_TIMELINEITEM_BINDING_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/MaterialTimelineItemBinding.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/MaterialTimelineItemBinding.cpp new file mode 100644 index 00000000..e3483969 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/MaterialTimelineItemBinding.cpp @@ -0,0 +1,210 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSCommonPrecompile.h" +#include "MaterialTimelineItemBinding.h" +#include "TimelineTranslationManager.h" +#include "ImageTimelineItemBinding.h" +#include "EmptyTimelineTimebar.h" + +// Data model specific +#include "IDoc.h" +#include "ClientDataModelBridge.h" +#include "DropSource.h" + +#include "Qt3DSDMHandles.h" +#include "Qt3DSDMStudioSystem.h" + +#include "Qt3DSDMMetaData.h" +#include "Qt3DSDMDataCore.h" +#include "StudioFullSystem.h" +#include "StudioCoreSystem.h" + +using namespace qt3dsdm; + +CMaterialTimelineItemBinding::CMaterialTimelineItemBinding(CTimelineTranslationManager *inMgr, + Qt3DSDMInstanceHandle inDataHandle) + : Qt3DSDMTimelineItemBinding(inMgr, inDataHandle) +{ + qt3dsdm::IPropertySystem *thePropertySystem = m_TransMgr->GetStudioSystem()->GetPropertySystem(); + TPropertyHandleList theProperties; + thePropertySystem->GetAggregateInstanceProperties(inDataHandle, theProperties); + + size_t thePropertyCount = theProperties.size(); + for (size_t thePropertyIndex = 0; thePropertyIndex < thePropertyCount; ++thePropertyIndex) { + Qt3DSDMPropertyHandle theProperty = theProperties[thePropertyIndex]; + + AdditionalMetaDataType::Value theAdditionalMetaDataType = + thePropertySystem->GetAdditionalMetaDataType(inDataHandle, theProperty); + + if (theAdditionalMetaDataType == AdditionalMetaDataType::Image) { + TCharStr theName(thePropertySystem->GetName(theProperty)); + TCharStr theFormalName(thePropertySystem->GetFormalName(inDataHandle, theProperty)); + TNameFormalNamePair thePair = std::make_tuple(theName, theFormalName); + m_ImageNameFormalNamePairs.push_back(thePair); + } + } +} + +CMaterialTimelineItemBinding::~CMaterialTimelineItemBinding() +{ +} + +ITimelineTimebar *CMaterialTimelineItemBinding::GetTimebar() +{ // No timebars on materials + return new CEmptyTimelineTimebar(); +} + +bool CMaterialTimelineItemBinding::ShowToggleControls() const +{ + // Materials have no toggle controls, by design + return false; +} + +bool ImageSlotIsFilled(qt3dsdm::IPropertySystem *inPropertySystem, Qt3DSDMInstanceHandle inInstance, + const TCharStr &inStr) +{ + Qt3DSDMPropertyHandle theProperty = + inPropertySystem->GetAggregateInstancePropertyByName(inInstance, inStr); + SValue theValue; + inPropertySystem->GetInstancePropertyValue(inInstance, theProperty, theValue); + + // Prevent assertion down the path when changing from edited standard material to reference material + if (qt3dsdm::GetValueType(theValue) == DataModelDataType::None) + return false; + + SLong4 theLong4 = qt3dsdm::get<SLong4>(theValue); + bool theReturn = theLong4.m_Longs[0] != 0 || theLong4.m_Longs[1] != 0 + || theLong4.m_Longs[2] != 0 || theLong4.m_Longs[3] != 0; + + return theReturn; +} + +long CMaterialTimelineItemBinding::GetChildrenCount() +{ + long theReturnCount = 0; + if (m_TransMgr->GetStudioSystem()->IsInstance(m_DataHandle)) { + qt3dsdm::IPropertySystem *thePropertySystem = + m_TransMgr->GetStudioSystem()->GetPropertySystem(); + size_t theSlotCount = m_ImageNameFormalNamePairs.size(); + for (size_t theSlotIndex = 0; theSlotIndex < theSlotCount; ++theSlotIndex) { + if (ImageSlotIsFilled(thePropertySystem, m_DataHandle, + std::get<0>(m_ImageNameFormalNamePairs[theSlotIndex]))) { + ++theReturnCount; + } + } + } + + return theReturnCount; +} + +ITimelineItemBinding *CMaterialTimelineItemBinding::GetChild(long inIndex) +{ + qt3dsdm::IPropertySystem *thePropertySystem = m_TransMgr->GetStudioSystem()->GetPropertySystem(); + + size_t theSlotCursor = 0; + size_t theSlotCount = m_ImageNameFormalNamePairs.size(); + for (size_t theSlotIndex = 0; theSlotIndex < theSlotCount; ++theSlotIndex) { + if (ImageSlotIsFilled(thePropertySystem, m_DataHandle, + std::get<0>(m_ImageNameFormalNamePairs[theSlotIndex]))) { + inIndex--; + + if (inIndex < 0) { + theSlotCursor = theSlotIndex; + break; + } + } + } + Qt3DSDMPropertyHandle theImageProperty = thePropertySystem->GetAggregateInstancePropertyByName( + m_DataHandle, std::get<0>(m_ImageNameFormalNamePairs[theSlotCursor])); + return GetOrCreateImageBinding( + theImageProperty, std::get<1>(m_ImageNameFormalNamePairs[theSlotCursor]).wide_str()); +} + +QList<ITimelineItemBinding *> CMaterialTimelineItemBinding::GetChildren() +{ + int childCount = GetChildrenCount(); + QList<ITimelineItemBinding *> retlist; + retlist.reserve(childCount); + for (int i = 0; i < childCount; ++i) + retlist.append(GetChild(i)); + + return retlist; +} + +void CMaterialTimelineItemBinding::OnAddChild(qt3dsdm::Qt3DSDMInstanceHandle inInstance) +{ + using namespace qt3dsdm; + CClientDataModelBridge *theBridge = m_TransMgr->GetStudioSystem()->GetClientDataModelBridge(); + // This is handled via the OnPropertyChanged call below + if (theBridge->IsImageInstance(inInstance)) + return; + else + Qt3DSDMTimelineItemBinding::OnAddChild(inInstance); +} + +void CMaterialTimelineItemBinding::OnPropertyChanged(Qt3DSDMPropertyHandle inPropertyHandle) +{ + Qt3DSDMTimelineItemBinding::OnPropertyChanged(inPropertyHandle); +} + +void CMaterialTimelineItemBinding::OnPropertyLinked(Qt3DSDMPropertyHandle inPropertyHandle) +{ + Qt3DSDMTimelineItemBinding::OnPropertyLinked(inPropertyHandle); +} + +qt3dsdm::Qt3DSDMInstanceHandle +CMaterialTimelineItemBinding::GetImage(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle) +{ + qt3dsdm::IPropertySystem *thePropertySystem = m_TransMgr->GetStudioSystem()->GetPropertySystem(); + SValue theImageValue; + thePropertySystem->GetInstancePropertyValue(m_DataHandle, inPropertyHandle, theImageValue); + SLong4 theImageLong4 = qt3dsdm::get<SLong4>(theImageValue); + return m_TransMgr->GetStudioSystem()->GetClientDataModelBridge()->GetImageInstanceByGUID( + theImageLong4); +} + +ITimelineItemBinding * +CMaterialTimelineItemBinding::GetOrCreateImageBinding(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle, + const wchar_t *inName) +{ + qt3dsdm::Qt3DSDMInstanceHandle theImageInstance = GetImage(inPropertyHandle); + ITimelineItemBinding *theImageTimelineRow = m_TransMgr->GetBinding(theImageInstance); + if (!theImageTimelineRow) // create + { + theImageTimelineRow = m_TransMgr->GetOrCreate(theImageInstance); + // Set the name, by spec: the nice name. + theImageTimelineRow->GetTimelineItem()->SetName(inName); + CImageTimelineItemBinding *theImageBinding = + dynamic_cast<CImageTimelineItemBinding *>(theImageTimelineRow); + if (theImageBinding) + theImageBinding->SetPropertyHandle(inPropertyHandle); + } + return theImageTimelineRow; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/MaterialTimelineItemBinding.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/MaterialTimelineItemBinding.h new file mode 100644 index 00000000..8e188a52 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/MaterialTimelineItemBinding.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +//============================================================================== +// Prefix +//============================================================================== +#ifndef INCLUDED_MATERIAL_TIMELINEITEM_BINDING_H +#define INCLUDED_MATERIAL_TIMELINEITEM_BINDING_H 1 + +#pragma once + +#include "Qt3DSDMTimelineItemBinding.h" +#include "Qt3DSDMDataTypes.h" + +//============================================================================== +// Classes +//============================================================================== +class CTimelineTranslationManager; + +//============================================================================= +/** + * Binding to a DataModel object of Material type + */ +class CMaterialTimelineItemBinding : public Qt3DSDMTimelineItemBinding +{ +public: // Types + typedef std::tuple<qt3dsdm::TCharStr, qt3dsdm::TCharStr> TNameFormalNamePair; + typedef std::vector<TNameFormalNamePair> TNameFormalNamePairList; + +protected: // Members + TNameFormalNamePairList m_ImageNameFormalNamePairs; + +public: // Construction + CMaterialTimelineItemBinding(CTimelineTranslationManager *inMgr, + qt3dsdm::Qt3DSDMInstanceHandle inDataHandle); + virtual ~CMaterialTimelineItemBinding(); + +public: // Qt3DSDMTimelineItemBinding + ITimelineTimebar *GetTimebar() override; + bool ShowToggleControls() const override; + // Hierarchy + long GetChildrenCount() override; + ITimelineItemBinding *GetChild(long inIndex) override; + QList<ITimelineItemBinding *> GetChildren() override; + void OnAddChild(qt3dsdm::Qt3DSDMInstanceHandle inInstance) override; + // Event callback + void OnPropertyChanged(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle) override; + void OnPropertyLinked(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle) override; + +protected: + qt3dsdm::Qt3DSDMInstanceHandle GetImage(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle); + ITimelineItemBinding *GetOrCreateImageBinding(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle, + const wchar_t *inName); +}; + +#endif // INCLUDED_MATERIAL_TIMELINEITEM_BINDING_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/OffsetKeyframesCommandHelper.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/OffsetKeyframesCommandHelper.cpp new file mode 100644 index 00000000..a0c4ee99 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/OffsetKeyframesCommandHelper.cpp @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSCommonPrecompile.h" +#include "OffsetKeyframesCommandHelper.h" +#include "Core.h" + +// Data model specific +#include "IDoc.h" +#include "CmdDataModelChangeKeyframe.h" +#include "IDocumentEditor.h" + +#include "Qt3DSDMTimelineKeyframe.h" //TODO: remove once we resolve the precision issue + +using namespace qt3dsdm; + +COffsetKeyframesCommandHelper::COffsetKeyframesCommandHelper(CDoc &inDoc) + : Q3DStudio::CUpdateableDocumentEditor(inDoc) + , m_Doc(inDoc) +{ +} + +COffsetKeyframesCommandHelper::~COffsetKeyframesCommandHelper() +{ + Finalize(); +} + +//@param inTime time in millisecs +void COffsetKeyframesCommandHelper::SetCommandTime(qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe, + long inTime) +{ + // The DataModel system will take care of merging these under the hood. + ENSURE_EDITOR(QObject::tr("Set Keyframe Time")).SetKeyframeTime(inKeyframe, inTime); +} + +// equivalent to commit (onmouseup) +void COffsetKeyframesCommandHelper::Finalize() +{ + CommitEditor(); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/OffsetKeyframesCommandHelper.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/OffsetKeyframesCommandHelper.h new file mode 100644 index 00000000..0e57773e --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/OffsetKeyframesCommandHelper.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +//============================================================================== +// Prefix +//============================================================================== +#ifndef INCLUDED_OFFSET_KEYFRAMES_COMMAND_HELPER_H +#define INCLUDED_OFFSET_KEYFRAMES_COMMAND_HELPER_H 1 + +#pragma once + +// Data model +#include "Qt3DSDMHandles.h" +#include "IDocumentEditor.h" + +namespace Q3DStudio { +class IDocumentEditor; +} + +//============================================================================== +// Classes +//============================================================================== +class CCmdDataModelSetKeyframeTime; + +class COffsetKeyframesCommandHelper : public Q3DStudio::CUpdateableDocumentEditor +{ +protected: + CDoc &m_Doc; + +public: + COffsetKeyframesCommandHelper(CDoc &inDoc); + ~COffsetKeyframesCommandHelper(); + + void SetCommandTime(qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe, long inTime); + void Finalize(); + void Rollback() { RollbackEditor(); } +}; + +#endif
\ No newline at end of file diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PasteKeyframesCommandHelper.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PasteKeyframesCommandHelper.h new file mode 100644 index 00000000..ead15d2e --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PasteKeyframesCommandHelper.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2016 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef INCLUDED_PASTE_KEYFRAME_COMMAND_HELPER_H +#define INCLUDED_PASTE_KEYFRAME_COMMAND_HELPER_H 1 + +#pragma once + +//============================================================================== +// Include +//============================================================================== +#include "CmdDataModelInsertKeyframe.h" +#include "Qt3DSDMPropertyDefinition.h" +#include "Qt3DSDMDataCore.h" + +// This caches all copied keyframes' time and data, for a paste action. +// This has to deal with the actual data and not keyframe handles, because a prior Cut can +// invalidate those handles. +class CPasteKeyframeCommandHelper +{ +protected: + typedef std::vector<CCmdDataModelInsertKeyframe::STimeKeyframeData> TCopiedKeyframeList; + TCopiedKeyframeList m_CopiedKeyframeList; + +public: // Construction + CPasteKeyframeCommandHelper() {} + ~CPasteKeyframeCommandHelper() {} + + // inTime should be relative to the earliest keyframe time in this list + void AddKeyframeData(qt3dsdm::Qt3DSDMPropertyHandle inProperty, float inKeyframeTime, + qt3dsdm::SGetOrSetKeyframeInfo *inInfos, size_t inInfoCount) + { + m_CopiedKeyframeList.push_back(CCmdDataModelInsertKeyframe::STimeKeyframeData( + inProperty, inKeyframeTime, inInfos, inInfoCount)); + } + + bool HasCopiedKeyframes() const { return !m_CopiedKeyframeList.empty(); } + + // Triggered by a "Paste Keyframe" action + // Note: The logic is based on what the Animation Manager in the old system used to do. + // 1. The condition for paste to occur is that the property name matches. + // The old data model has a limitation that if the destination property is a linked property, + // the source has to come from the same instance, most likely a easy way out than to deal with + // with having to 'sync' all linked animation tracks. + // but that is not an issue in the new data model. + // + // 2. The first pasted keyframe is at current view time and the rest are offset accordingly. + CCmdDataModelInsertKeyframe *GetCommand(CDoc *inDoc, long inTimeOffsetInMilliseconds, + qt3dsdm::Qt3DSDMInstanceHandle inTargetInstance) + { + using namespace qt3dsdm; + + CCmdDataModelInsertKeyframe *theInsertKeyframesCommand = nullptr; + TCopiedKeyframeList::iterator theIter = m_CopiedKeyframeList.begin(); + qt3dsdm::IPropertySystem *thePropertySystem = inDoc->GetStudioSystem()->GetPropertySystem(); + CClientDataModelBridge *theBridge = inDoc->GetStudioSystem()->GetClientDataModelBridge(); + + for (; theIter != m_CopiedKeyframeList.end(); ++theIter) { + TCharStr thePropertyName = thePropertySystem->GetName(theIter->m_Property); + DataModelDataType::Value thePropertyType = + thePropertySystem->GetDataType(theIter->m_Property); + Qt3DSDMPropertyHandle theTargetPropertyHandle = + theBridge->GetAggregateInstancePropertyByName(inTargetInstance, thePropertyName); + if (theTargetPropertyHandle.Valid()) // property exists on target + { + // sanity check for type match + DataModelDataType::Value theTargetPropertyType = + thePropertySystem->GetDataType(theTargetPropertyHandle); + if (theTargetPropertyType == thePropertyType) { + // 2. Offset keyframe time by current view time + double milliseconds = theIter->m_KeyframeTime * 1000.0; + double theTimeInMilliseconds = milliseconds + inTimeOffsetInMilliseconds; + float theTimeInSeconds = static_cast<float>(theTimeInMilliseconds / 1000.0); + + if (!theInsertKeyframesCommand) + theInsertKeyframesCommand = new CCmdDataModelInsertKeyframe( + inDoc, inTargetInstance, theTargetPropertyHandle, theTimeInSeconds, + theIter->m_Infos, theIter->m_ValidInfoCount); + else + theInsertKeyframesCommand->AddKeyframeData( + theTargetPropertyHandle, theTimeInSeconds, theIter->m_Infos, + theIter->m_ValidInfoCount); + } + } + } + return theInsertKeyframesCommand; + } + + void Clear() { m_CopiedKeyframeList.clear(); } +}; + +#endif diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathAnchorPointTimelineItemBinding.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathAnchorPointTimelineItemBinding.cpp new file mode 100644 index 00000000..192c4414 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathAnchorPointTimelineItemBinding.cpp @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSCommonPrecompile.h" +#include "PathAnchorPointTimelineItemBinding.h" +#include "EmptyTimelineTimebar.h" + +using namespace qt3dsdm; + +CPathAnchorPointTimelineItemBinding::CPathAnchorPointTimelineItemBinding( + CTimelineTranslationManager *inMgr, Qt3DSDMInstanceHandle inDataHandle) + : Qt3DSDMTimelineItemBinding(inMgr, inDataHandle) +{ +} + +ITimelineTimebar *CPathAnchorPointTimelineItemBinding::GetTimebar() +{ + return new CEmptyTimelineTimebar(); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathAnchorPointTimelineItemBinding.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathAnchorPointTimelineItemBinding.h new file mode 100644 index 00000000..b806093d --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathAnchorPointTimelineItemBinding.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +//============================================================================== +// Prefix +//============================================================================== +#ifndef PATH_ANCHOR_POINT_TIMELINE_ITEM_BINDING +#define PATH_ANCHOR_POINT_TIMELINE_ITEM_BINDING +#pragma once + +#include "Qt3DSDMTimelineItemBinding.h" +#include "Qt3DSDMDataTypes.h" + +//============================================================================== +// Classes +//============================================================================== +class CTimelineTranslationManager; + +//============================================================================= +/** + * Binding to a DataModel object of Material type + */ +class CPathAnchorPointTimelineItemBinding : public Qt3DSDMTimelineItemBinding +{ +public: // Construction + CPathAnchorPointTimelineItemBinding(CTimelineTranslationManager *inMgr, + qt3dsdm::Qt3DSDMInstanceHandle inDataHandle); + + bool ShowToggleControls() const override { return false; } + bool IsVisible() const override { return true; } + void SetVisible(bool) override {} + ITimelineTimebar *GetTimebar() override; + bool IsLocked() const override { return false; } + void SetLocked(bool) override {} + bool IsShy() const override { return false; } + void SetShy(bool) override {} + bool IsVisibilityControlled() const override { return false; } + Q3DStudio::CString GetName() const override { return L"Anchor Point"; } + void SetName(const Q3DStudio::CString &) override {} +}; + +#endif diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathTimelineItemBinding.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathTimelineItemBinding.cpp new file mode 100644 index 00000000..f646415d --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathTimelineItemBinding.cpp @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSCommonPrecompile.h" +#include "PathTimelineItemBinding.h" +#include "TimelineTranslationManager.h" +#include "Doc.h" + +bool CPathTimelineItemBinding::IsExternalizeable() +{ + // If this path has subpath children, then it is externalizeable. + return m_TransMgr->GetDoc()->GetDocumentReader().IsPathExternalizeable(GetInstance()); +} + +void CPathTimelineItemBinding::Externalize() +{ + Q3DStudio::ScopedDocumentEditor(*m_TransMgr->GetDoc(), QObject::tr("Externalize Path Buffer"), + __FILE__, __LINE__) + ->ExternalizePath(GetInstance()); +} + +bool CPathTimelineItemBinding::IsInternalizeable() +{ + // If this path has a sourcepath, then it might be internalizeable + return m_TransMgr->GetDoc()->GetDocumentReader().IsPathInternalizeable(GetInstance()); +} + +void CPathTimelineItemBinding::Internalize() +{ + Q3DStudio::ScopedDocumentEditor(*m_TransMgr->GetDoc(), QObject::tr("Internalize Path Buffer"), + __FILE__, __LINE__) + ->InternalizePath(GetInstance()); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathTimelineItemBinding.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathTimelineItemBinding.h new file mode 100644 index 00000000..ea006044 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/PathTimelineItemBinding.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +//============================================================================== +// Prefix +//============================================================================== +#ifndef PATH_TIMELINE_ITEM_BINDING +#define PATH_TIMELINE_ITEM_BINDING +#pragma once +#include "Qt3DSDMTimelineItemBinding.h" +#include "Qt3DSDMDataTypes.h" + +//============================================================================== +// Classes +//============================================================================== +class CTimelineTranslationManager; + +//============================================================================= +/** + * Binding to a DataModel object of Material type + */ +class CPathTimelineItemBinding : public Qt3DSDMTimelineItemBinding +{ +public: // Construction + CPathTimelineItemBinding(CTimelineTranslationManager *inMgr, + qt3dsdm::Qt3DSDMInstanceHandle inDataHandle) + : Qt3DSDMTimelineItemBinding(inMgr, inDataHandle) + { + } + + bool IsExternalizeable() override; + void Externalize() override; + bool IsInternalizeable() override; + void Internalize() override; +}; + +#endif diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMAssetTimelineKeyframe.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMAssetTimelineKeyframe.cpp new file mode 100644 index 00000000..65b0d852 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMAssetTimelineKeyframe.cpp @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSCommonPrecompile.h" +#include "Qt3DSDMAssetTimelineKeyframe.h" +#include "Qt3DSDMTimelineItemBinding.h" + +using namespace qt3dsdm; + +Qt3DSDMAssetTimelineKeyframe::Qt3DSDMAssetTimelineKeyframe(Qt3DSDMTimelineItemBinding *inOwningBinding, + long inTime) + : m_OwningBinding(inOwningBinding) + , m_Time(inTime) + , m_Selected(false) +{ +} + +Qt3DSDMAssetTimelineKeyframe::~Qt3DSDMAssetTimelineKeyframe() +{ +} + +Keyframe *Qt3DSDMAssetTimelineKeyframe::getUI() +{ + return m_ui; +} + +void Qt3DSDMAssetTimelineKeyframe::setUI(Keyframe *kfUI) +{ + m_ui = kfUI; +} + +bool Qt3DSDMAssetTimelineKeyframe::IsSelected() const +{ + return m_Selected; +} + +long Qt3DSDMAssetTimelineKeyframe::GetTime() const +{ + return m_Time; +} + +void Qt3DSDMAssetTimelineKeyframe::SetTime(const long inNewTime) +{ + Q_UNUSED(inNewTime); + // note: this is not used. because setting time is currently only done through offsetting by + // moving keyframes OR using the edit time dialog. + Q_ASSERT(0); +} + +void Qt3DSDMAssetTimelineKeyframe::SetDynamic(bool inIsDynamic) +{ + m_OwningBinding->SetDynamicKeyframes(m_Time, inIsDynamic); +} + +bool Qt3DSDMAssetTimelineKeyframe::IsDynamic() const +{ + // return true if any of its property keyframes is dynamic + return m_OwningBinding->HasDynamicKeyframes(m_Time); +} + +void Qt3DSDMAssetTimelineKeyframe::SetSelected(bool inSelected) +{ + m_Selected = inSelected; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMAssetTimelineKeyframe.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMAssetTimelineKeyframe.h new file mode 100644 index 00000000..cf99cf8c --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMAssetTimelineKeyframe.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ +#ifndef QT3DSDM_ASSET_KEYFRAME_H +#define QT3DSDM_ASSET_KEYFRAME_H 1 + +#pragma once + +#include "IKeyframe.h" + +// Data model specific +#include "Qt3DSDMHandles.h" + +class Qt3DSDMTimelineItemBinding; + +//============================================================================== +/** + * Represents a keyframe displayed for a Asset( e.g. material ), for all keyframes (of the + *animated properties) at time t. + */ +//============================================================================== +class Qt3DSDMAssetTimelineKeyframe : public IKeyframe +{ +protected: + Qt3DSDMTimelineItemBinding *m_OwningBinding; + long m_Time; + bool m_Selected; + +public: + Qt3DSDMAssetTimelineKeyframe(Qt3DSDMTimelineItemBinding *inOwningBinding, long inTime); + virtual ~Qt3DSDMAssetTimelineKeyframe(); + + // IKeyframe + bool IsSelected() const override; + long GetTime() const override; + void SetTime(const long inNewTime) override; + void SetDynamic(bool inIsDynamic) override; + Keyframe *getUI() override; + void setUI(Keyframe *kfUI) override; + bool IsDynamic() const override; + + void SetSelected(bool inSelected); + void UpdateTime(const long inTime) { m_Time = inTime; } + +private: + Keyframe *m_ui; +}; + +#endif // QT3DSDM_ASSET_KEYFRAME_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimeline.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimeline.h new file mode 100644 index 00000000..18624897 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimeline.h @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** Copyright (C) 2016 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef QT3DSDM_TIMELINE_H +#define QT3DSDM_TIMELINE_H 1 + +#pragma once + +enum ETimelineKeyframeTransaction { + ETimelineKeyframeTransaction_Add, + ETimelineKeyframeTransaction_Delete, + ETimelineKeyframeTransaction_Update, + ETimelineKeyframeTransaction_DynamicChanged, +}; + +#endif diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemBinding.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemBinding.cpp new file mode 100644 index 00000000..d71ef582 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemBinding.cpp @@ -0,0 +1,1164 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSCommonPrecompile.h" +#include "Qt3DSDMTimelineItemBinding.h" +#include "TimelineTranslationManager.h" +#include "TimeEditDlg.h" +#include "EmptyTimelineTimebar.h" +#include "Qt3DSDMTimelineTimebar.h" +#include "StudioApp.h" +#include "Core.h" +#include "Dialogs.h" +#include "GraphUtils.h" +#include "Qt3DSDMDataCore.h" +#include "DropTarget.h" + +// Data model specific +#include "IDoc.h" +#include "ClientDataModelBridge.h" +#include "Dispatch.h" +#include "DropSource.h" +#include "Qt3DSDMTimelineItemProperty.h" +#include "Qt3DSDMSlides.h" +#include "Qt3DSDMStudioSystem.h" +#include "Qt3DSDMSlideGraphCore.h" +#include "Qt3DSDMActionCore.h" +#include "Qt3DSDMAnimation.h" +#include "CmdDataModelChangeKeyframe.h" +#include "RelativePathTools.h" +#include "IDocumentEditor.h" +#include "Qt3DSFileTools.h" +#include "ImportUtils.h" + +#include <QtWidgets/qmessagebox.h> + +using namespace qt3dsdm; + +Qt3DSDMTimelineItemBinding::Qt3DSDMTimelineItemBinding(CTimelineTranslationManager *inMgr, + Qt3DSDMInstanceHandle inDataHandle) + : m_TransMgr(inMgr) + , m_DataHandle(inDataHandle) + , m_Parent(nullptr) + , m_TimelineTimebar(nullptr) + +{ + m_StudioSystem = m_TransMgr->GetStudioSystem(); +} + +Qt3DSDMTimelineItemBinding::Qt3DSDMTimelineItemBinding(CTimelineTranslationManager *inMgr) + : m_TransMgr(inMgr) + , m_DataHandle(0) + , m_Parent(nullptr) + , m_TimelineTimebar(nullptr) +{ + m_StudioSystem = m_TransMgr->GetStudioSystem(); +} + +Qt3DSDMTimelineItemBinding::~Qt3DSDMTimelineItemBinding() +{ + RemoveAllPropertyBindings(); + delete m_TimelineTimebar; +} + +// helpers +bool Qt3DSDMTimelineItemBinding::GetBoolean(qt3dsdm::Qt3DSDMPropertyHandle inProperty) const +{ + qt3dsdm::IPropertySystem *thePropertySystem = m_StudioSystem->GetPropertySystem(); + SValue theValue; + thePropertySystem->GetInstancePropertyValue(m_DataHandle, inProperty, theValue); + return qt3dsdm::get<bool>(theValue); +} + +void Qt3DSDMTimelineItemBinding::SetBoolean(qt3dsdm::Qt3DSDMPropertyHandle inProperty, + bool inValue, const QString &inNiceText) const +{ + CDoc *theDoc = dynamic_cast<CDoc *>(g_StudioApp.GetCore()->GetDoc()); + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*theDoc, inNiceText) + ->SetInstancePropertyValue(m_DataHandle, inProperty, inValue); +} + +void Qt3DSDMTimelineItemBinding::SetInstanceHandle(qt3dsdm::Qt3DSDMInstanceHandle inDataHandle) +{ + m_DataHandle = inDataHandle; +} + +EStudioObjectType Qt3DSDMTimelineItemBinding::GetObjectType() const +{ + return m_StudioSystem->GetClientDataModelBridge()->GetObjectType(m_DataHandle); +} + +bool Qt3DSDMTimelineItemBinding::IsMaster() const +{ + CDoc *theDoc = dynamic_cast<CDoc *>(g_StudioApp.GetCore()->GetDoc()); + Q3DStudio::IDocumentReader &theReader(theDoc->GetDocumentReader()); + if (GetObjectType() == OBJTYPE_IMAGE) { + CClientDataModelBridge *theBridge = m_StudioSystem->GetClientDataModelBridge(); + Qt3DSDMInstanceHandle theParent; + Qt3DSDMPropertyHandle theProperty; + bool isPropertyLinked; + + theBridge->GetMaterialFromImageInstance(GetInstance(), theParent, theProperty); + isPropertyLinked = theReader.IsPropertyLinked(theParent, theProperty); + + // Also check light probe + if (!isPropertyLinked) { + theBridge->GetLayerFromImageProbeInstance(GetInstance(), theParent, theProperty); + isPropertyLinked = theReader.IsPropertyLinked(theParent, theProperty); + } + + return isPropertyLinked; + } + Qt3DSDMInstanceHandle theQueryHandle(m_DataHandle); + if (GetObjectType() == OBJTYPE_PATHANCHORPOINT) + theQueryHandle = theReader.GetParent(m_DataHandle); + + // logic: you can't unlink name, so if name is linked then, this is master. + Qt3DSDMPropertyHandle theNamePropHandle = + m_StudioSystem->GetPropertySystem()->GetAggregateInstancePropertyByName(theQueryHandle, + L"name"); + return theReader.IsPropertyLinked(theQueryHandle, theNamePropHandle); +} + +bool Qt3DSDMTimelineItemBinding::IsShy() const +{ + return GetBoolean(m_StudioSystem->GetClientDataModelBridge()->GetSceneAsset().m_Shy); +} +void Qt3DSDMTimelineItemBinding::SetShy(bool inShy) +{ + SetBoolean(m_StudioSystem->GetClientDataModelBridge()->GetSceneAsset().m_Shy, inShy, + QObject::tr("Shy Toggle")); +} +bool Qt3DSDMTimelineItemBinding::IsLocked() const +{ + return GetBoolean(m_StudioSystem->GetClientDataModelBridge()->GetSceneAsset().m_Locked); +} + +bool Qt3DSDMTimelineItemBinding::IsVisibilityControlled() const +{ + if (!m_StudioSystem->IsInstance(m_DataHandle)) + return false; + + Qt3DSDMPropertyHandle theNamePropHandle = + m_StudioSystem->GetPropertySystem()->GetAggregateInstancePropertyByName( + m_DataHandle, L"controlledproperty"); + + if (!theNamePropHandle) + return false; + SValue theNameValue; + m_StudioSystem->GetPropertySystem()->GetInstancePropertyValue(m_DataHandle, theNamePropHandle, + theNameValue); + TDataStrPtr theName = qt3dsdm::get<TDataStrPtr>(theNameValue); + + return (wcsstr(theName->GetData(), L"eyeball")); +} + +void ToggleChildrenLock(Q3DStudio::ScopedDocumentEditor &scopedDocEditor, + Qt3DSDMTimelineItemBinding *inTimelineItemBinding, + SDataModelSceneAsset inSceneAsset, bool inLocked) +{ + scopedDocEditor->SetInstancePropertyValue(inTimelineItemBinding->GetInstanceHandle(), + inSceneAsset.m_Locked, inLocked); + const QList<ITimelineItemBinding *> children = inTimelineItemBinding->GetChildren(); + for (auto child : children) { + ToggleChildrenLock(scopedDocEditor, static_cast<Qt3DSDMTimelineItemBinding *>(child), + inSceneAsset, inLocked); + } +} + +void Qt3DSDMTimelineItemBinding::SetLocked(bool inLocked) +{ + CDoc *theDoc = dynamic_cast<CDoc *>(g_StudioApp.GetCore()->GetDoc()); + Q3DStudio::ScopedDocumentEditor scopedDocEditor(*theDoc, QObject::tr("SetLock"), __FILE__, + __LINE__); + + SDataModelSceneAsset sceneAsset = m_StudioSystem->GetClientDataModelBridge()->GetSceneAsset(); + ToggleChildrenLock(scopedDocEditor, this, sceneAsset, inLocked); + + if (inLocked) + g_StudioApp.GetCore()->GetDoc()->NotifySelectionChanged(); +} + +bool Qt3DSDMTimelineItemBinding::IsVisible() const +{ + return GetBoolean(m_StudioSystem->GetClientDataModelBridge()->GetSceneAsset().m_Eyeball); +} + +void Qt3DSDMTimelineItemBinding::SetVisible(bool inVisible) +{ + SetBoolean(m_StudioSystem->GetClientDataModelBridge()->GetSceneAsset().m_Eyeball, + inVisible, QObject::tr("Visibility Toggle")); +} + +bool Qt3DSDMTimelineItemBinding::HasAction(bool inMaster) +{ + TActionHandleList theActions; + CDoc *theDoc = g_StudioApp.GetCore()->GetDoc(); + + Qt3DSDMSlideHandle theSlide = theDoc->GetActiveSlide(); + qt3dsdm::ISlideCore &theSlideCore(*m_StudioSystem->GetSlideCore()); + if (theSlideCore.IsSlide(theSlide)) { + if (inMaster) { + theSlide = + m_StudioSystem->GetSlideSystem()->GetMasterSlide(theSlide); // use the master slide + } + + m_StudioSystem->GetActionCore()->GetActions(theSlide, m_DataHandle, theActions); + } + return theActions.size() > 0; +} + +bool Qt3DSDMTimelineItemBinding::ChildrenHasAction(bool inMaster) +{ + // Get all the instances in this slidegraph + // check whehter it's an action instance and is in the slide of interst + // check also it's owner is a descendent of the viewed instances + CDoc *theDoc = g_StudioApp.GetCore()->GetDoc(); + IActionCore *theActionCore(m_StudioSystem->GetActionCore()); + CClientDataModelBridge *theBridge(m_StudioSystem->GetClientDataModelBridge()); + + Qt3DSDMSlideHandle theSlide = theDoc->GetActiveSlide(); + qt3dsdm::ISlideCore &theSlideCore(*m_StudioSystem->GetSlideCore()); + if (theSlideCore.IsSlide(theSlide)) { + if (inMaster) { + theSlide = + m_StudioSystem->GetSlideSystem()->GetMasterSlide(theSlide); // use the master slide + } + + TSlideInstancePairList theGraphInstances; + m_StudioSystem->GetSlideSystem()->GetAssociatedInstances(theSlide, theGraphInstances); + + qt3dsdm::Qt3DSDMInstanceHandle theObservedInstance = GetInstance(); + if (theObservedInstance.Valid()) { + for (TSlideInstancePairList::const_iterator theIter = theGraphInstances.begin(); + theIter != theGraphInstances.end(); ++theIter) { + if (theIter->first == theSlide && theBridge->IsActionInstance(theIter->second)) { + Qt3DSDMActionHandle theAction = + theActionCore->GetActionByInstance(theIter->second); + SActionInfo theActionInfo = theActionCore->GetActionInfo(theAction); + Qt3DSDMInstanceHandle theAcionOwner = theActionInfo.m_Owner; + if (theAcionOwner.Valid() + && IsAscendant(theAcionOwner, theObservedInstance, theDoc->GetAssetGraph())) + return true; + } + } + } + } + + return false; +} + +bool Qt3DSDMTimelineItemBinding::ComponentHasAction(bool inMaster) +{ + // Get all the instances in this component slidegraph + // check whether the instance is an action instance + // if inMaster is true, we only interest with those that are in the master slide, else we want + // those that are not in the master slide + CClientDataModelBridge *theBridge(m_StudioSystem->GetClientDataModelBridge()); + if (!theBridge->IsComponentInstance(m_DataHandle)) + return false; + + Q3DStudio::CId theAssetId = theBridge->GetGUID(m_DataHandle); + Qt3DSDMSlideHandle theMasterSlide = + m_StudioSystem->GetSlideSystem()->GetMasterSlideByComponentGuid(GuidtoSLong4(theAssetId)); + + TSlideInstancePairList theGraphInstances; + m_StudioSystem->GetSlideSystem()->GetAssociatedInstances(theMasterSlide, theGraphInstances); + + for (TSlideInstancePairList::const_iterator theIter = theGraphInstances.begin(); + theIter != theGraphInstances.end(); ++theIter) { + if (((inMaster && theIter->first == theMasterSlide) + || (!inMaster && theIter->first != theMasterSlide)) + && theBridge->IsActionInstance(theIter->second)) { + return true; + } + } + return false; +} + +bool Qt3DSDMTimelineItemBinding::hasSubpresentation() const +{ + CClientDataModelBridge *bridge(m_StudioSystem->GetClientDataModelBridge()); + IPropertySystem *propSystem = m_StudioSystem->GetPropertySystem(); + EStudioObjectType objType = GetObjectType(); + + if (objType == OBJTYPE_LAYER) { + SValue sourcePathValue; + propSystem->GetInstancePropertyValue(m_DataHandle, bridge->GetSourcePathProperty(), + sourcePathValue); + return get<TDataStrPtr>(sourcePathValue)->GetLength() > 0; + } else if (objType == OBJTYPE_IMAGE) { + SValue subPresValue; + propSystem->GetInstancePropertyValue(m_DataHandle, + bridge->GetSceneImage().m_SubPresentation, + subPresValue); + return get<TDataStrPtr>(subPresValue)->GetLength() > 0; + } + + return false; +} + +ITimelineTimebar *Qt3DSDMTimelineItemBinding::GetTimebar() +{ + if (!m_TimelineTimebar) + m_TimelineTimebar = CreateTimelineTimebar(); + return m_TimelineTimebar; +} + +Q3DStudio::CString Qt3DSDMTimelineItemBinding::GetName() const +{ + if (m_StudioSystem->IsInstance(m_DataHandle) == false) + return L""; + Qt3DSDMPropertyHandle theNamePropHandle = + m_StudioSystem->GetPropertySystem()->GetAggregateInstancePropertyByName(m_DataHandle, + L"name"); + SValue theNameValue; + m_StudioSystem->GetPropertySystem()->GetInstancePropertyValue(m_DataHandle, theNamePropHandle, + theNameValue); + TDataStrPtr theName = qt3dsdm::get<TDataStrPtr>(theNameValue); + + return (theName) ? Q3DStudio::CString(theName->GetData()) : ""; +} + +void Qt3DSDMTimelineItemBinding::SetName(const Q3DStudio::CString &inName) +{ + // Ignore if setting the name to what it currently is to avoid duplicate undo points + if (inName == GetName()) + return; + + // Display warning dialog if user tried to enter an empty string + if (inName.IsEmpty()) { + QString theTitle = QObject::tr("Rename Object Error"); + QString theString = QObject::tr("Object name cannot be an empty string."); + g_StudioApp.GetDialogs()->DisplayMessageBox(theTitle, theString, + Qt3DSMessageBox::ICON_ERROR, false); + + return; + } + + CClientDataModelBridge *theBridge = m_StudioSystem->GetClientDataModelBridge(); + const auto doc = g_StudioApp.GetCore()->GetDoc(); + + // Display warning if the name and path are the same as the material container + if (theBridge->GetParentInstance(m_DataHandle) == doc->GetSceneInstance() + && inName.toQString() == theBridge->getMaterialContainerName()) { + QString theTitle = QObject::tr("Rename Object Error"); + QString theString = theBridge->getMaterialContainerName() + + QObject::tr(" is a reserved name."); + g_StudioApp.GetDialogs()->DisplayMessageBox(theTitle, theString, + Qt3DSMessageBox::ICON_ERROR, false); + // The timeline still shows the new name so refresh the name property + m_StudioSystem->GetFullSystemSignalSender()->SendInstancePropertyValue( + m_DataHandle, theBridge->GetNameProperty()); + return; + } + + // Display warning if we had to modify the user-given name to make it unique + if (!theBridge->CheckNameUnique(theBridge->GetParentInstance(m_DataHandle), + m_DataHandle, inName)) { + // Find unique name based on the input string + Q3DStudio::SCOPED_DOCUMENT_EDITOR( + *m_TransMgr->GetDoc(), QObject::tr("Set Name"))->SetName(m_DataHandle, inName, true); + + g_StudioApp.GetDialogs()->DisplayObjectRenamed( + inName.toQString(), theBridge->GetName(m_DataHandle).toQString()); + return; + } + + Q3DStudio::SCOPED_DOCUMENT_EDITOR( + *m_TransMgr->GetDoc(), QObject::tr("Set Name"))->SetName(m_DataHandle, inName, true); +} + +ITimelineItem *Qt3DSDMTimelineItemBinding::GetTimelineItem() +{ + return this; +} + +RowTree *Qt3DSDMTimelineItemBinding::getRowTree() const +{ + return m_rowTree; +} + +void Qt3DSDMTimelineItemBinding::setRowTree(RowTree *row) +{ + m_rowTree = row; +} + +void Qt3DSDMTimelineItemBinding::SetSelected(bool inMultiSelect) +{ + if (!inMultiSelect) + g_StudioApp.GetCore()->GetDoc()->SelectDataModelObject(m_DataHandle); + else + g_StudioApp.GetCore()->GetDoc()->ToggleDataModelObjectToSelection(m_DataHandle); +} + +void Qt3DSDMTimelineItemBinding::OnCollapsed() +{ + // Preserves legacy behavior where collapsing a tree will select that root, if any of its + // descendant was selected + // TODO: This won't work for Image (because Image is Material's property, not child) + qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance(); + if (theInstance.Valid()) { + CDoc *theDoc = m_TransMgr->GetDoc(); + qt3dsdm::Qt3DSDMInstanceHandle theSelectedInstance = theDoc->GetSelectedInstance(); + if (theSelectedInstance.Valid() + && IsAscendant(theSelectedInstance, theInstance, theDoc->GetAssetGraph())) + SetSelected(false); + } +} + +bool Qt3DSDMTimelineItemBinding::OpenAssociatedEditor() +{ + return false; // nothing to do by default +} + +inline qt3dsdm::Qt3DSDMInstanceHandle Qt3DSDMTimelineItemBinding::GetInstance() const +{ + return m_DataHandle; +} + +void Qt3DSDMTimelineItemBinding::SetDropTarget(CDropTarget *inTarget) +{ + qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance(); + inTarget->SetInstance(theInstance); +} + +long Qt3DSDMTimelineItemBinding::GetChildrenCount() +{ + qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance(); + if (theInstance.Valid()) { + Q3DStudio::CGraphIterator theChildren; + Qt3DSDMSlideHandle theActiveSlide = m_TransMgr->GetDoc()->GetActiveSlide(); + GetAssetChildrenInTimeParent(theInstance, m_TransMgr->GetDoc(), AmITimeParent(), + theChildren, theActiveSlide); + return (long)theChildren.GetCount(); + } + return 0; +} + +ITimelineItemBinding *Qt3DSDMTimelineItemBinding::GetChild(long inIndex) +{ + qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance(); + if (theInstance.Valid()) { + Q3DStudio::CGraphIterator theChildren; + Qt3DSDMSlideHandle theActiveSlide = m_TransMgr->GetDoc()->GetActiveSlide(); + GetAssetChildrenInTimeParent(theInstance, m_TransMgr->GetDoc(), AmITimeParent(), + theChildren, theActiveSlide); + theChildren += inIndex; + + qt3dsdm::Qt3DSDMInstanceHandle theChildInstance = theChildren.GetCurrent(); + if (theChildInstance.Valid()) + return m_TransMgr->GetOrCreate(theChildInstance); + } + return nullptr; +} + +QList<ITimelineItemBinding *> Qt3DSDMTimelineItemBinding::GetChildren() +{ + QList<ITimelineItemBinding *> retlist; + qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance(); + if (theInstance.Valid()) { + Q3DStudio::CGraphIterator theChildren; + Qt3DSDMSlideHandle theActiveSlide = m_TransMgr->GetDoc()->GetActiveSlide(); + GetAssetChildrenInTimeParent(theInstance, m_TransMgr->GetDoc(), AmITimeParent(), + theChildren, theActiveSlide); + int childCount = int(theChildren.GetCount()); + retlist.reserve(childCount); + + for (int i = 0; i < childCount; ++i) { + qt3dsdm::Qt3DSDMInstanceHandle theChildInstance = theChildren.GetCurrent(); + if (theChildInstance.Valid()) + retlist.append(m_TransMgr->GetOrCreate(theChildInstance)); + ++theChildren; + } + } + + return retlist; +} + +ITimelineItemBinding *Qt3DSDMTimelineItemBinding::GetParent() +{ + return m_Parent; +} +void Qt3DSDMTimelineItemBinding::SetParent(ITimelineItemBinding *parent) +{ + if (parent != m_Parent) { + ASSERT(parent == nullptr || m_Parent == nullptr); + m_Parent = parent; + } +} + +long Qt3DSDMTimelineItemBinding::GetPropertyCount() +{ + long theCount = 0; + if (m_StudioSystem->IsInstance(m_DataHandle)) { + TPropertyHandleList theProperties; + m_StudioSystem->GetPropertySystem()->GetAggregateInstanceProperties(m_DataHandle, + theProperties); + for (size_t thePropertyIndex = 0; thePropertyIndex < theProperties.size(); + ++thePropertyIndex) { + if (m_StudioSystem->GetAnimationSystem()->IsPropertyAnimated( + m_DataHandle, theProperties[thePropertyIndex])) { + ++theCount; + } + } + } + return theCount; +} + +ITimelineItemProperty *Qt3DSDMTimelineItemBinding::GetProperty(long inIndex) +{ + TPropertyHandleList theProperties; + m_StudioSystem->GetPropertySystem()->GetAggregateInstanceProperties(m_DataHandle, + theProperties); + long theIndex = -1; + size_t thePropertyIndex = 0; + for (; thePropertyIndex < theProperties.size(); ++thePropertyIndex) { + if (m_StudioSystem->GetAnimationSystem()->IsPropertyAnimated( + m_DataHandle, theProperties[thePropertyIndex])) { + ++theIndex; + if (theIndex == inIndex) + break; + } + } + ASSERT(thePropertyIndex < theProperties.size()); // no reason why this would be out of range!! + return GetOrCreatePropertyBinding(theProperties[thePropertyIndex]); +} + +bool Qt3DSDMTimelineItemBinding::ShowToggleControls() const +{ + return true; +} + +bool Qt3DSDMTimelineItemBinding::IsLockedEnabled() const +{ + return IsLocked(); +} + +bool Qt3DSDMTimelineItemBinding::IsVisibleEnabled() const +{ + // You can only toggle visible if you aren't on the master slide. + return m_StudioSystem->GetSlideSystem()->GetSlideIndex(m_TransMgr->GetDoc()->GetActiveSlide()) + != 0; +} + +bool Qt3DSDMTimelineItemBinding::IsValidTransaction(EUserTransaction inTransaction) +{ + qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance(); + const auto bridge = m_StudioSystem->GetClientDataModelBridge(); + switch (inTransaction) { + case EUserTransaction_Rename: + return (GetObjectType() != OBJTYPE_SCENE && GetObjectType() != OBJTYPE_IMAGE + && (bridge->GetObjectType(theInstance) != OBJTYPE_REFERENCEDMATERIAL + || bridge->GetSourcePath(theInstance).isEmpty())); + + case EUserTransaction_Duplicate: + if (theInstance.Valid()) + return m_StudioSystem->GetClientDataModelBridge()->IsDuplicateable(theInstance); + break; + + case EUserTransaction_Cut: + return g_StudioApp.CanCut(); + + case EUserTransaction_Copy: + return g_StudioApp.CanCopy(); + + case EUserTransaction_Paste: + return m_TransMgr->GetDoc()->canPasteObjects(); + + case EUserTransaction_Delete: + if (theInstance.Valid()) + return m_StudioSystem->GetClientDataModelBridge()->CanDelete(theInstance); + break; + + case EUserTransaction_MakeComponent: { + bool theCanMakeFlag = false; + if (theInstance.Valid()) { + CClientDataModelBridge *theBridge = m_StudioSystem->GetClientDataModelBridge(); + EStudioObjectType theObjectType = theBridge->GetObjectType(theInstance); + + if (!IsLocked()) { + // Any assets that are attached to the Scene directly must not be wrapped in a + // component. + // This may include behavior assets which may be directly attached to the Scene. + // This is because by principal, components cannot exist on the Scene directly. + qt3dsdm::Qt3DSDMInstanceHandle theParentInstance = + theBridge->GetParentInstance(theInstance); + if (theObjectType != OBJTYPE_LAYER && theObjectType != OBJTYPE_SCENE + && theObjectType != OBJTYPE_MATERIAL && theObjectType != OBJTYPE_IMAGE + && theObjectType != OBJTYPE_EFFECT && theObjectType != OBJTYPE_COMPONENT + && (theParentInstance.Valid() + && theBridge->GetObjectType(theParentInstance) + != OBJTYPE_SCENE)) // This checks if the object is + // AttachedToSceneDirectly + { + theCanMakeFlag = true; + } + } + } + return theCanMakeFlag; + } + + case EUserTransaction_EditComponent: + return (GetObjectType() == OBJTYPE_COMPONENT); + + case EUserTransaction_MakeAnimatable: + if (theInstance.Valid()) { + CClientDataModelBridge *bridge = m_StudioSystem->GetClientDataModelBridge(); + EStudioObjectType type = bridge->GetObjectType(theInstance); + return !IsLocked() && type == OBJTYPE_REFERENCEDMATERIAL; + } + return false; + + case EUserTransaction_Group: + return g_StudioApp.canGroupSelectedObjects(); + + case EUserTransaction_Ungroup: + return g_StudioApp.canUngroupSelectedObjects(); + + case EUserTransaction_AddLayer: + return (GetObjectType() == OBJTYPE_SCENE); + + default: // not handled + break; + } + + return false; +} + +using namespace Q3DStudio; + +inline void DoCut(CDoc &inDoc, const qt3dsdm::TInstanceHandleList &inInstances) +{ + inDoc.DeselectAllKeyframes(); + inDoc.CutObject(inInstances); +} + +inline void DoDelete(CDoc &inDoc, const qt3dsdm::TInstanceHandleList &inInstances) +{ + inDoc.DeselectAllKeyframes(); + inDoc.DeleteObject(inInstances); +} + +inline void DoMakeComponent(CDoc &inDoc, const qt3dsdm::TInstanceHandleList &inInstances) +{ + SCOPED_DOCUMENT_EDITOR(inDoc, QObject::tr("Make Component"))->MakeComponent(inInstances); +} + +inline void doMakeAnimatable(CDoc &doc, const qt3dsdm::TInstanceHandleList &instances) +{ + SCOPED_DOCUMENT_EDITOR(doc, QObject::tr("Make Animatable"))->makeAnimatable(instances); +} + +inline void DoGroupObjects(CDoc &inDoc, const qt3dsdm::TInstanceHandleList &inInstances) +{ + g_StudioApp.groupSelectedObjects(); +} + +inline void DoUngroupObjects(CDoc &inDoc, const qt3dsdm::TInstanceHandleList &inInstances) +{ + g_StudioApp.ungroupSelectedObjects(); +} + +inline void doAddLayer(CDoc &inDoc, const qt3dsdm::TInstanceHandleList &inInstances) +{ + qt3dsdm::Qt3DSDMSlideHandle slide = inDoc.GetActiveSlide(); + qt3dsdm::Qt3DSDMInstanceHandle parent = inDoc.GetActiveLayer(); + + SCOPED_DOCUMENT_EDITOR(inDoc, QObject::tr("Add Layer")) + ->CreateSceneGraphInstance(qt3dsdm::ComposerObjectTypes::Layer, parent, slide, + DocumentEditorInsertType::PreviousSibling, + CPt(), PRIMITIVETYPE_UNKNOWN, -1); +} + +void Qt3DSDMTimelineItemBinding::PerformTransaction(EUserTransaction inTransaction) +{ + CDoc *theDoc = m_TransMgr->GetDoc(); + qt3dsdm::TInstanceHandleList theInstances = theDoc->GetSelectedValue().GetSelectedInstances(); + if (theInstances.empty()) + return; + CDispatch &theDispatch(*theDoc->GetCore()->GetDispatch()); + + // Transactions that could result in *this* object being deleted need to be executed + // via postmessage, not in this context because it could result in the currently + // active timeline row being deleted while in its own mouse handler. + switch (inTransaction) { + case EUserTransaction_Duplicate: { + theDoc->DeselectAllKeyframes(); + SCOPED_DOCUMENT_EDITOR(*theDoc, + QObject::tr("Duplicate Object"))->DuplicateInstances(theInstances); + } break; + case EUserTransaction_Cut: { + theDispatch.FireOnAsynchronousCommand( + std::bind(DoCut, std::ref(*theDoc), theInstances)); + } break; + case EUserTransaction_Copy: { + theDoc->DeselectAllKeyframes(); + theDoc->CopyObject(theInstances); + } break; + case EUserTransaction_Paste: { + theDoc->DeselectAllKeyframes(); + theDoc->PasteObject(theDoc->getPasteTarget(GetInstance())); + } break; + case EUserTransaction_Delete: { + theDispatch.FireOnAsynchronousCommand( + std::bind(DoDelete, std::ref(*theDoc), theInstances)); + } break; + case EUserTransaction_MakeComponent: { + theDispatch.FireOnAsynchronousCommand( + std::bind(DoMakeComponent, std::ref(*theDoc), theInstances)); + } break; + case EUserTransaction_MakeAnimatable: { + theDispatch.FireOnAsynchronousCommand( + std::bind(doMakeAnimatable, std::ref(*theDoc), theInstances)); + } break; + case EUserTransaction_Group: { + theDispatch.FireOnAsynchronousCommand( + std::bind(DoGroupObjects, std::ref(*theDoc), theInstances)); + } break; + case EUserTransaction_Ungroup: { + theDispatch.FireOnAsynchronousCommand( + std::bind(DoUngroupObjects, std::ref(*theDoc), theInstances)); + } break; + case EUserTransaction_AddLayer: { + theDispatch.FireOnAsynchronousCommand( + std::bind(doAddLayer, std::ref(*theDoc), theInstances)); + } break; + default: // not handled + break; + } +} + +Q3DStudio::CString Qt3DSDMTimelineItemBinding::GetObjectPath() +{ + CDoc *theDoc = m_TransMgr->GetDoc(); + // Because we are getting absolute path, the base id doesn't matter. + return CRelativePathTools::BuildAbsoluteReferenceString(m_DataHandle, theDoc); +} + +int Qt3DSDMTimelineItemBinding::getAnimatedPropertyIndex(int propertyHandle) const +{ + TPropertyHandleList theProperties; + m_StudioSystem->GetPropertySystem()->GetAggregateInstanceProperties(m_DataHandle, + theProperties); + int index = -1; + for (size_t i = 0; i < theProperties.size(); ++i) { + if (m_StudioSystem->GetAnimationSystem()->IsPropertyAnimated( + m_DataHandle, theProperties[i])) { + index++; + } + if (theProperties[i].GetHandleValue() == propertyHandle) + return index; + } + + return -1; +} + +void Qt3DSDMTimelineItemBinding::getTimeContextIndices(const QSet<int> &children, + QMap<int, int> &indexMap) +{ + qt3dsdm::Qt3DSDMInstanceHandle instance = GetInstance(); + if (instance.Valid()) { + Q3DStudio::CGraphIterator graphChildren; + Qt3DSDMSlideHandle activeSlide = m_TransMgr->GetDoc()->GetActiveSlide(); + GetAssetChildrenInTimeParent(instance, m_TransMgr->GetDoc(), AmITimeParent(), + graphChildren, activeSlide); + const size_t count = graphChildren.GetCount(); + for (size_t current = 0; current < count; ++current) { + auto handle = graphChildren.GetResult(current); + if (children.contains(handle)) + indexMap.insert(int(current), int(handle)); + } + } +} + +void Qt3DSDMTimelineItemBinding::InsertKeyframe() +{ + if (m_PropertyBindingMap.empty()) + return; + + TPropertyBindingMap::const_iterator theIter = m_PropertyBindingMap.begin(); + ScopedDocumentEditor editor(*g_StudioApp.GetCore()->GetDoc(), QObject::tr("Insert Keyframe"), + __FILE__, __LINE__); + for (; theIter != m_PropertyBindingMap.end(); ++theIter) + editor->KeyframeProperty(m_DataHandle, theIter->first, false); +} + +void Qt3DSDMTimelineItemBinding::DeleteAllChannelKeyframes() +{ + if (m_PropertyBindingMap.empty()) + return; + + CDoc *theDoc = m_TransMgr->GetDoc(); + Q3DStudio::ScopedDocumentEditor editor(*theDoc, QObject::tr("Delete Channel Keyframes"), + __FILE__, __LINE__); + for (auto &kv : m_PropertyBindingMap) + kv.second->DeleteAllKeys(); +} + +IKeyframe *Qt3DSDMTimelineItemBinding::GetKeyframeByTime(long inTime) const +{ + TAssetKeyframeList::const_iterator theIter = m_Keyframes.begin(); + for (; theIter != m_Keyframes.end(); ++theIter) { + if ((*theIter).GetTime() == inTime) + return const_cast<Qt3DSDMAssetTimelineKeyframe *>(&(*theIter)); + } + return nullptr; +} + +Qt3DSDMInstanceHandle Qt3DSDMTimelineItemBinding::GetInstanceHandle() const +{ + return m_DataHandle; +} + +long Qt3DSDMTimelineItemBinding::GetFlavor() const +{ + return QT3DS_FLAVOR_ASSET_TL; +} + +ITimelineTimebar *Qt3DSDMTimelineItemBinding::CreateTimelineTimebar() +{ + return new Qt3DSDMTimelineTimebar(m_TransMgr, m_DataHandle); +} + +ITimelineItemProperty * +Qt3DSDMTimelineItemBinding::GetPropertyBinding(Qt3DSDMPropertyHandle inPropertyHandle) +{ + TPropertyBindingMap::iterator theIter = m_PropertyBindingMap.find(inPropertyHandle); + // check if it already exists + if (theIter != m_PropertyBindingMap.end()) + return theIter->second; + return nullptr; +} + +bool Qt3DSDMTimelineItemBinding::isRootComponent() const +{ + auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge(); + return bridge->IsActiveComponent(m_DataHandle); +} + +bool Qt3DSDMTimelineItemBinding::isDefaultMaterial() const +{ + auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge(); + return bridge->isDefaultMaterial(m_DataHandle); +} + +ITimelineItemProperty * +Qt3DSDMTimelineItemBinding::GetOrCreatePropertyBinding(Qt3DSDMPropertyHandle inPropertyHandle) +{ + ITimelineItemProperty *theProperty = GetPropertyBinding(inPropertyHandle); + // check if it already exists + if (theProperty) + return theProperty; + + // Create + Qt3DSDMTimelineItemProperty *theTimelineProperty = + new Qt3DSDMTimelineItemProperty(m_TransMgr, inPropertyHandle, m_DataHandle); + m_PropertyBindingMap.insert(std::make_pair(inPropertyHandle, theTimelineProperty)); + + return theTimelineProperty; +} + +//============================================================================= +/** + * Add a new property row for this property. + * @param inAppend true to skip the check to find where to insert. ( true if this is a + * loading/initializing step, where the call is already done in order ) + */ +void Qt3DSDMTimelineItemBinding::AddPropertyRow(Qt3DSDMPropertyHandle inPropertyHandle, + bool inAppend /*= false */) +{ + ITimelineItemProperty *theTimelineProperty = GetPropertyBinding(inPropertyHandle); + if (theTimelineProperty) // if created, bail + return; + + if (!theTimelineProperty) + theTimelineProperty = GetOrCreatePropertyBinding(inPropertyHandle); + + // Find the row to insert this new property, if any, this preserves the order the property rows + // is displayed in the timeline. + ITimelineItemProperty *theNextProperty = nullptr; + if (!inAppend) { + TPropertyHandleList theProperties; + m_StudioSystem->GetPropertySystem()->GetAggregateInstanceProperties(m_DataHandle, + theProperties); + size_t thePropertyIndex = 0; + size_t thePropertyCount = theProperties.size(); + for (; thePropertyIndex < thePropertyCount; ++thePropertyIndex) { + if (theProperties[thePropertyIndex] == inPropertyHandle) { + ++thePropertyIndex; + break; + } + } + // Not all properties are displayed, so another loop to search for the first one that maps + // to a existing propertyrow + for (; thePropertyIndex < thePropertyCount; ++thePropertyIndex) { + TPropertyBindingMap::iterator theNextPropIter = + m_PropertyBindingMap.find(theProperties[thePropertyIndex]); + if (theNextPropIter != m_PropertyBindingMap.end()) { + theNextProperty = theNextPropIter->second; + break; + } + } + } + + // Update keyframes + AddKeyframes(theTimelineProperty); +} + +void Qt3DSDMTimelineItemBinding::RemovePropertyRow(Qt3DSDMPropertyHandle inPropertyHandle) +{ + TPropertyBindingMap::iterator theIter = m_PropertyBindingMap.find(inPropertyHandle); + if (theIter != m_PropertyBindingMap.end()) { + ITimelineItemProperty *thePropertyBinding = theIter->second; + + DeleteAssetKeyframesWhereApplicable(thePropertyBinding); + m_PropertyBindingMap.erase(theIter); + } +} + +// called when a keyframe is inserted, deleted or updated in the data model +void Qt3DSDMTimelineItemBinding::RefreshPropertyKeyframe( + qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle, qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe, + ETimelineKeyframeTransaction inTransaction) +{ + TPropertyBindingMap::iterator theIter = m_PropertyBindingMap.find(inPropertyHandle); + if (theIter != m_PropertyBindingMap.end()) { + Qt3DSDMTimelineItemProperty *theProperty = theIter->second; + if (theProperty) { + if (theProperty->RefreshKeyframe(inKeyframe, inTransaction)) { + // Update asset keyframes + UpdateKeyframe(theProperty->GetKeyframeByHandle(inKeyframe), inTransaction); + } + } + } +} + +void Qt3DSDMTimelineItemBinding::OnPropertyChanged(Qt3DSDMPropertyHandle inPropertyHandle) +{ +} + +void Qt3DSDMTimelineItemBinding::OnPropertyLinked(Qt3DSDMPropertyHandle inPropertyHandle) +{ + if (m_StudioSystem->GetAnimationSystem()->IsPropertyAnimated(m_DataHandle, inPropertyHandle)) { + // Refresh property row by delete and recreate + RemovePropertyRow(inPropertyHandle); + AddPropertyRow(inPropertyHandle); + } +} + +bool Qt3DSDMTimelineItemBinding::HasDynamicKeyframes(long inTime) +{ + if (inTime == -1) { + if (GetPropertyCount() == 0) + return false; + + for (long i = 0; i < GetPropertyCount(); ++i) { + ITimelineItemProperty *theTimelineItemProperty = GetProperty(i); + if (!theTimelineItemProperty->IsDynamicAnimation()) + return false; + } + return true; + } else { + TPropertyBindingMap::const_iterator theIter = m_PropertyBindingMap.begin(); + for (; theIter != m_PropertyBindingMap.end(); ++theIter) { + IKeyframe *theKeyframe = theIter->second->GetKeyframeByTime(inTime); + if (theKeyframe && theKeyframe->IsDynamic()) + return true; + } + } + return false; +} + +void Qt3DSDMTimelineItemBinding::SetDynamicKeyframes(long inTime, bool inDynamic) +{ + TPropertyBindingMap::const_iterator theIter = m_PropertyBindingMap.begin(); + for (; theIter != m_PropertyBindingMap.end(); ++theIter) { + IKeyframe *theKeyframe = theIter->second->GetKeyframeByTime(inTime); + if (theKeyframe) + theKeyframe->SetDynamic(inDynamic); // TODO: we want this in 1 batch command + } +} + +Q3DStudio::CId Qt3DSDMTimelineItemBinding::GetGuid() const +{ + CClientDataModelBridge *theClientBridge = m_StudioSystem->GetClientDataModelBridge(); + qt3dsdm::IPropertySystem *thePropertySystem = m_StudioSystem->GetPropertySystem(); + SValue theValue; + if (thePropertySystem->GetInstancePropertyValue(m_DataHandle, theClientBridge->GetIdProperty(), + theValue)) { + SLong4 theLong4 = qt3dsdm::get<SLong4>(theValue); + return Q3DStudio::CId(theLong4.m_Longs[0], theLong4.m_Longs[1], theLong4.m_Longs[2], + theLong4.m_Longs[3]); + } + return Q3DStudio::CId(); +} + +// Delete asset keyframes at time t if no property keyframes exist at time t +//@param inSkipPropertyBinding property that to skip, e.g. in cases where property is deleted +//@return true if there are asset keyframes deleted. +bool Qt3DSDMTimelineItemBinding::DeleteAssetKeyframesWhereApplicable( + ITimelineItemProperty *inSkipPropertyBinding /*= nullptr */) +{ + // iterate through m_Keyframes because we cannot obtain time information from the Animation + // keyframes anymore, since they are deleted. + std::vector<long> theDeleteIndicesList; + for (size_t theIndex = 0; theIndex < m_Keyframes.size(); ++theIndex) { + TPropertyBindingMap::iterator theIter = m_PropertyBindingMap.begin(); + for (; theIter != m_PropertyBindingMap.end(); ++theIter) { + if ((!inSkipPropertyBinding || theIter->second != inSkipPropertyBinding) + && theIter->second->GetKeyframeByTime(m_Keyframes[theIndex].GetTime())) { + // done! + break; + } + } + if (theIter == m_PropertyBindingMap.end()) + theDeleteIndicesList.push_back((long)theIndex); + } + // start with the last item, so that the indices remain valid. + for (long i = (long)theDeleteIndicesList.size() - 1; i >= 0; --i) { + TAssetKeyframeList::iterator theKeyIter = m_Keyframes.begin(); + std::advance(theKeyIter, theDeleteIndicesList[i]); + m_Keyframes.erase(theKeyIter); + } + + return !theDeleteIndicesList.empty(); +} + +void Qt3DSDMTimelineItemBinding::RemoveAllPropertyBindings() +{ + TPropertyBindingMap::iterator theIter = m_PropertyBindingMap.begin(); + for (; theIter != m_PropertyBindingMap.end(); ++theIter) + delete theIter->second; + m_PropertyBindingMap.clear(); +} + +void Qt3DSDMTimelineItemBinding::AddKeyframes(ITimelineItemProperty *inPropertyBinding) +{ + for (long i = 0; i < inPropertyBinding->GetKeyframeCount(); ++i) + UpdateKeyframe(inPropertyBinding->GetKeyframeByIndex(i), ETimelineKeyframeTransaction_Add); +} + +// Update the asset keyframes based on the properties' keyframes. +void Qt3DSDMTimelineItemBinding::UpdateKeyframe(IKeyframe *inKeyframe, + ETimelineKeyframeTransaction inTransaction) +{ + bool theDoAddFlag = (inTransaction == ETimelineKeyframeTransaction_Add); + bool theDoDeleteFlag = (inTransaction == ETimelineKeyframeTransaction_Delete); + + // For update, if there isn't already a asset keyframe at the associated time, create one + if (inTransaction == ETimelineKeyframeTransaction_Update) { + theDoAddFlag = inKeyframe && !GetKeyframeByTime(inKeyframe->GetTime()); + theDoDeleteFlag = true; // plus, since we don't keep track of indiviual property keyframes + // here, iterate and make sure list is correct. + } + + if (theDoDeleteFlag) + DeleteAssetKeyframesWhereApplicable(); + + // Add when a new keyframe is added or MAYBE when a keyframe is moved + if (theDoAddFlag && inKeyframe) { + long theKeyframeTime = inKeyframe->GetTime(); + if (theKeyframeTime >= 0) { + bool theAppend = true; + // insert this in the order that it should be. and we trust the + TAssetKeyframeList::iterator theIter = m_Keyframes.begin(); + for (; theIter != m_Keyframes.end(); ++theIter) { + long theTime = (*theIter).GetTime(); + if (theTime == theKeyframeTime) { + theAppend = false; + break; // already exists, we are done. Because we only need 1 to represent ALL + // properties + } + } + if (theAppend) + m_Keyframes.push_back(Qt3DSDMAssetTimelineKeyframe(this, theKeyframeTime)); + } + } +} + +void Qt3DSDMTimelineItemBinding::OnAddChild(Qt3DSDMInstanceHandle inInstance) +{ + CDoc *theDoc = m_TransMgr->GetDoc(); + CClientDataModelBridge *theBridge = m_StudioSystem->GetClientDataModelBridge(); + ISlideSystem *theSlideSystem = m_StudioSystem->GetSlideSystem(); + + qt3dsdm::Qt3DSDMSlideHandle theSlide = theSlideSystem->GetAssociatedSlide(inInstance); + if (theBridge->IsInActiveComponent(inInstance) + && (theSlideSystem->IsMasterSlide(theSlide) || theSlide == theDoc->GetActiveSlide())) { + // Only add if the asset is in the current active component, and it's a master asset or in + // the current slide + ITimelineItemBinding *theNextItem = nullptr; + qt3dsdm::Qt3DSDMInstanceHandle theParentInstance = GetInstance(); + // Figure out where to insert this row, if applicable. + // CAsset has a list of children, and not necessarily all are active in this slide (e.g. + // non-master children) + Q3DStudio::TIdentifier theNextChild = 0; + if (theParentInstance.Valid()) { + // Get the next prioritized child in the same slide + Q3DStudio::CGraphIterator theChildren; + GetAssetChildrenInSlide(theDoc, theParentInstance, theDoc->GetActiveSlide(), + theChildren); + theNextChild = GetSibling(inInstance, true, theChildren); + } + + if (theNextChild != 0) + theNextItem = m_TransMgr->GetOrCreate(theNextChild); + } +} + +void Qt3DSDMTimelineItemBinding::OnDeleteChild(Qt3DSDMInstanceHandle inInstance) +{ +} + +void Qt3DSDMTimelineItemBinding::UpdateActionStatus() +{ +} + +//============================================================================= +/** + * Open the associated item as though it was double-clicked in explorer + * Respective subclasses (for example Image and Behavior) can call this function + */ +bool Qt3DSDMTimelineItemBinding::OpenSourcePathFile() +{ + // Get source path property value + CClientDataModelBridge *theClientBridge = m_StudioSystem->GetClientDataModelBridge(); + qt3dsdm::IPropertySystem *thePropertySystem = m_StudioSystem->GetPropertySystem(); + SValue theValue; + if (thePropertySystem->GetInstancePropertyValue( + m_DataHandle, theClientBridge->GetSourcePathProperty(), theValue)) { + // Open the respective file + Q3DStudio::CFilePath theSourcePath(qt3dsdm::get<qt3dsdm::TDataStrPtr>(theValue)->GetData()); + Qt3DSFile theFile(m_TransMgr->GetDoc()->GetResolvedPathToDoc(theSourcePath)); + theFile.Execute(); + return true; + } + return false; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemBinding.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemBinding.h new file mode 100644 index 00000000..39c81c6d --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemBinding.h @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +//============================================================================== +// Prefix +//============================================================================== +#ifndef INCLUDED_QT3DSDM_TIMELINEITEM_BINDING_H +#define INCLUDED_QT3DSDM_TIMELINEITEM_BINDING_H 1 + +#pragma once + +#include "ITimelineItemBinding.h" +#include "ITimelineItem.h" + +// Data model +#include "Qt3DSDMHandles.h" +#include "IDragable.h" +#include "Qt3DSDMAssetTimelineKeyframe.h" +#include "OffsetKeyframesCommandHelper.h" +#include "Qt3DSDMTimeline.h" +#include "Qt3DSDMSignals.h" +#include "DispatchListeners.h" + +//============================================================================== +// Classes +//============================================================================== +class CTimelineTranslationManager; +class Qt3DSDMTimelineItemProperty; +class CCmdDataModelSetKeyframeTime; +class RowTree; + +namespace qt3dsdm { +class CStudioSystem; +} + +//============================================================================= +/** + * Binding to generic DataModel object + */ +class Qt3DSDMTimelineItemBinding : public ITimelineItemBinding, + public ITimelineItem, + public IDragable + +{ +protected: // Typedef + typedef std::map<qt3dsdm::Qt3DSDMPropertyHandle, Qt3DSDMTimelineItemProperty *> TPropertyBindingMap; + typedef std::vector<Qt3DSDMAssetTimelineKeyframe> TAssetKeyframeList; + +protected: + RowTree *m_rowTree = nullptr; + CTimelineTranslationManager *m_TransMgr; + qt3dsdm::Qt3DSDMInstanceHandle m_DataHandle; + ITimelineItemBinding *m_Parent; + ITimelineTimebar *m_TimelineTimebar; + TPropertyBindingMap m_PropertyBindingMap; + TAssetKeyframeList m_Keyframes; /// Sorted (by time) list of keyframes + qt3dsdm::CStudioSystem *m_StudioSystem; + + qt3dsdm::TSignalConnectionPtr m_StartTimeConnection; + qt3dsdm::TSignalConnectionPtr m_EndTimeConnection; + +public: + Qt3DSDMTimelineItemBinding(CTimelineTranslationManager *inMgr, + qt3dsdm::Qt3DSDMInstanceHandle inDataHandle); + Qt3DSDMTimelineItemBinding(CTimelineTranslationManager *inMgr); + virtual ~Qt3DSDMTimelineItemBinding(); + +protected: + bool GetBoolean(qt3dsdm::Qt3DSDMPropertyHandle inProperty) const; + void SetBoolean(qt3dsdm::Qt3DSDMPropertyHandle inProperty, bool inValue, + const QString &inNiceText) const; + void SetInstanceHandle(qt3dsdm::Qt3DSDMInstanceHandle inDataHandle); + +public: + // ITimelineItem + EStudioObjectType GetObjectType() const override; + bool IsMaster() const override; + bool IsShy() const override; + void SetShy(bool) override; + bool IsLocked() const override; + void SetLocked(bool) override; + bool IsVisible() const override; + void SetVisible(bool) override; + bool HasAction(bool inMaster) override; + bool IsVisibilityControlled() const override; + bool ChildrenHasAction(bool inMaster) override; + bool ComponentHasAction(bool inMaster) override; + bool hasSubpresentation() const override; + ITimelineTimebar *GetTimebar() override; + + // INamable + Q3DStudio::CString GetName() const override; + void SetName(const Q3DStudio::CString &inName) override; + + // ITimelineItemBinding + ITimelineItem *GetTimelineItem() override; + RowTree *getRowTree() const override; + void setRowTree(RowTree *row) override; + void SetSelected(bool inMultiSelect) override; + void OnCollapsed() override; + bool OpenAssociatedEditor() override; + void SetDropTarget(CDropTarget *inTarget) override; + // Hierarchy + long GetChildrenCount() override; + ITimelineItemBinding *GetChild(long inIndex) override; + QList<ITimelineItemBinding *> GetChildren() override; + ITimelineItemBinding *GetParent() override; + void SetParent(ITimelineItemBinding *parent) override; + // Properties + long GetPropertyCount() override; + ITimelineItemProperty *GetProperty(long inIndex) override; + // Eye/Lock toggles + bool ShowToggleControls() const override; + bool IsLockedEnabled() const override; + bool IsVisibleEnabled() const override; + // ContextMenu + bool IsValidTransaction(EUserTransaction inTransaction) override; + void PerformTransaction(EUserTransaction inTransaction) override; + Q3DStudio::CString GetObjectPath() override; + + // ITimelineItemKeyframesHolder + void InsertKeyframe() override; + void DeleteAllChannelKeyframes() override; + IKeyframe *GetKeyframeByTime(long inTime) const override; + + // IUICDMSelectable + virtual qt3dsdm::Qt3DSDMInstanceHandle GetInstanceHandle() const; + + // IDragable + long GetFlavor() const override; + + virtual void AddPropertyRow(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle, + bool inAppend = false); + virtual void RemovePropertyRow(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle); + virtual void RefreshPropertyKeyframe(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle, + qt3dsdm::Qt3DSDMKeyframeHandle, + ETimelineKeyframeTransaction inTransaction); + virtual void OnPropertyChanged(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle); + virtual void OnPropertyLinked(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle); + + // Keyframe manipulation + virtual bool HasDynamicKeyframes(long inTime); + virtual void SetDynamicKeyframes(long inTime, bool inDynamic); + + virtual void OnAddChild(qt3dsdm::Qt3DSDMInstanceHandle inInstance); + virtual void OnDeleteChild(qt3dsdm::Qt3DSDMInstanceHandle inInstance); + + void UpdateActionStatus(); + + Q3DStudio::CId GetGuid() const; + + // Bridge between asset & DataModel. Ideally we should be fully DataModel + virtual qt3dsdm::Qt3DSDMInstanceHandle GetInstance() const; + + int getAnimatedPropertyIndex(int propertyHandle) const; + void getTimeContextIndices(const QSet<int> &children, QMap<int ,int> &indexMap); + + ITimelineItemProperty *GetOrCreatePropertyBinding(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle); + ITimelineItemProperty *GetPropertyBinding(qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle); + + bool isRootComponent() const; + bool isDefaultMaterial() const; + +protected: + virtual ITimelineTimebar *CreateTimelineTimebar(); + void RemoveAllPropertyBindings(); + void AddKeyframes(ITimelineItemProperty *inPropertyBinding); + bool + DeleteAssetKeyframesWhereApplicable(ITimelineItemProperty *inTriggerPropertyBinding = nullptr); + void UpdateKeyframe(IKeyframe *inKeyframe, ETimelineKeyframeTransaction inTransaction); + + // For iterating through children + virtual bool AmITimeParent() const { return false; } + + // subclasses can call this method to open referenced files + virtual bool OpenSourcePathFile(); +}; + +#endif // INCLUDED_QT3DSDM_TIMELINEITEM_BINDING_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemProperty.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemProperty.cpp new file mode 100644 index 00000000..d89fcc98 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemProperty.cpp @@ -0,0 +1,470 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSCommonPrecompile.h" +#include "Qt3DSDMTimelineItemProperty.h" +#include "TimelineTranslationManager.h" +#include "ITimelineItemBinding.h" +#include "Qt3DSDMTimelineItemBinding.h" +#include "Qt3DSDMTimelineKeyframe.h" +#include "CmdDataModelChangeKeyframe.h" +#include "CmdDataModelRemoveKeyframe.h" +#include "StudioApp.h" +#include "Core.h" +#include "RowTree.h" + +// Link to data model +#include "TimeEditDlg.h" +#include "ClientDataModelBridge.h" +#include "Qt3DSDMSlides.h" +#include "Qt3DSDMStudioSystem.h" +#include "Qt3DSDMAnimation.h" +#include "Qt3DSDMMetaData.h" +#include "Qt3DSDMPropertyDefinition.h" +#include "Qt3DSDMDataCore.h" +#include "StudioFullSystem.h" +using namespace qt3dsdm; + +bool SortKeyframeByTime(const Qt3DSDMTimelineKeyframe *inLHS, const Qt3DSDMTimelineKeyframe *inRHS) +{ + return inLHS->GetTime() < inRHS->GetTime(); +} + +// DataModel stores it from 0..1, UI expects 0..255 +inline float DataModelToColor(float inValue) +{ + return inValue * 255; +} + +Qt3DSDMTimelineItemProperty::Qt3DSDMTimelineItemProperty(CTimelineTranslationManager *inTransMgr, + Qt3DSDMPropertyHandle inPropertyHandle, + Qt3DSDMInstanceHandle inInstance) + : m_InstanceHandle(inInstance) + , m_PropertyHandle(inPropertyHandle) + , m_TransMgr(inTransMgr) + , m_SetKeyframeValueCommand(nullptr) +{ + // Cache all the animation handles because we need them for any keyframes manipulation. + // the assumption is that all associated handles are created all at once (i.e. we do not need to + // add or delete from this list ) + CreateKeyframes(); + InitializeCachedVariables(inInstance); + m_Signals.push_back( + m_TransMgr->GetStudioSystem()->GetFullSystem()->GetSignalProvider()->ConnectPropertyLinked( + std::bind(&Qt3DSDMTimelineItemProperty::OnPropertyLinkStatusChanged, this, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))); + + m_Signals.push_back( + m_TransMgr->GetStudioSystem() + ->GetFullSystem() + ->GetSignalProvider() + ->ConnectPropertyUnlinked(std::bind( + &Qt3DSDMTimelineItemProperty::OnPropertyLinkStatusChanged, this, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))); +} + +Qt3DSDMTimelineItemProperty::~Qt3DSDMTimelineItemProperty() +{ + ReleaseKeyframes(); +} + +void Qt3DSDMTimelineItemProperty::CreateKeyframes() +{ + // Cache all the animation handles because we need them for any keyframes manipulation. + // the assumption is that all associated handles are created all at once (i.e. we do not need to + // add or delete from this list ) + qt3dsdm::IPropertySystem *thePropertySystem = m_TransMgr->GetStudioSystem()->GetPropertySystem(); + DataModelDataType::Value theDataType = thePropertySystem->GetDataType(m_PropertyHandle); + IStudioAnimationSystem *theAnimationSystem = + m_TransMgr->GetStudioSystem()->GetAnimationSystem(); + std::tuple<bool, size_t> theArity = GetDatatypeAnimatableAndArity(theDataType); + for (size_t i = 0; i < std::get<1>(theArity); ++i) { + Qt3DSDMAnimationHandle theAnimationHandle = + theAnimationSystem->GetControllingAnimation(m_InstanceHandle, m_PropertyHandle, i); + if (theAnimationHandle.Valid()) + m_AnimationHandles.push_back(theAnimationHandle); + } + if (!m_AnimationHandles.empty()) { // update wrappers for keyframes + IAnimationCore *theAnimationCore = m_TransMgr->GetStudioSystem()->GetAnimationCore(); + TKeyframeHandleList theKeyframes; + // all channels have keyframes at the same time + theAnimationCore->GetKeyframes(m_AnimationHandles[0], theKeyframes); + for (size_t i = 0; i < theKeyframes.size(); ++i) + CreateKeyframeIfNonExistent(theKeyframes[i], m_AnimationHandles[0]); + } +} + +void Qt3DSDMTimelineItemProperty::ReleaseKeyframes() +{ + m_Keyframes.clear(); + m_AnimationHandles.clear(); +} + +qt3dsdm::Qt3DSDMPropertyHandle Qt3DSDMTimelineItemProperty::getPropertyHandle() const +{ + return m_PropertyHandle; +} + +// Type doesn't change and due to the logic required to figure this out, cache it. +void Qt3DSDMTimelineItemProperty::InitializeCachedVariables(qt3dsdm::Qt3DSDMInstanceHandle inInstance) +{ + using namespace Q3DStudio; + qt3dsdm::IPropertySystem *thePropertySystem = m_TransMgr->GetStudioSystem()->GetPropertySystem(); + + m_Type.first = thePropertySystem->GetDataType(m_PropertyHandle); + m_Type.second = thePropertySystem->GetAdditionalMetaDataType(inInstance, m_PropertyHandle); + + // Name doesn't change either. + TCharStr theFormalName = thePropertySystem->GetFormalName(inInstance, m_PropertyHandle); + + if (theFormalName.empty()) // fallback on property name + theFormalName = thePropertySystem->GetName(m_PropertyHandle); + m_Name = theFormalName.c_str(); +} + +Q3DStudio::CString Qt3DSDMTimelineItemProperty::GetName() const +{ + return m_Name; +} + +// Helper function to retrieve the parent binding class. +inline ITimelineItemBinding *GetParentBinding(RowTree *inRow) +{ + ITimelineItemBinding *theParentBinding = nullptr; + if (inRow) { + RowTree *theParentRow = inRow->parentRow(); + if (theParentRow) { + theParentBinding = theParentRow->getBinding(); + Q_ASSERT(theParentBinding); + } + } + return theParentBinding; +} + +bool Qt3DSDMTimelineItemProperty::IsMaster() const +{ + if (m_rowTree) { + if (Qt3DSDMTimelineItemBinding *theParentBinding = + static_cast<Qt3DSDMTimelineItemBinding *>(GetParentBinding(m_rowTree))) + return m_TransMgr->GetDoc()->GetDocumentReader().IsPropertyLinked( + theParentBinding->GetInstanceHandle(), m_PropertyHandle); + } + return false; +} + +qt3dsdm::TDataTypePair Qt3DSDMTimelineItemProperty::GetType() const +{ + return m_Type; +} + +void CompareAndSet(const Qt3DSDMTimelineKeyframe *inKeyframe, float &outRetValue, bool inGreaterThan) +{ + float theValue = (inGreaterThan) ? inKeyframe->GetMaxValue() : inKeyframe->GetMinValue(); + if ((inGreaterThan && theValue > outRetValue) || (!inGreaterThan && theValue < outRetValue)) + outRetValue = theValue; +} + +// return the max value of the current set of keyframes +float Qt3DSDMTimelineItemProperty::GetMaximumValue() const +{ + float theRetVal = FLT_MIN; + do_all(m_Keyframes, std::bind(CompareAndSet, std::placeholders::_1, std::ref(theRetVal), true)); + if (m_Type.first == DataModelDataType::Float4 && m_Type.second == AdditionalMetaDataType::Color) + theRetVal = DataModelToColor(theRetVal); + return theRetVal; +} + +// return the min value of the current set of keyframes +float Qt3DSDMTimelineItemProperty::GetMinimumValue() const +{ + float theRetVal = FLT_MAX; + do_all(m_Keyframes, std::bind(CompareAndSet, std::placeholders::_1, std::ref(theRetVal), false)); + if (m_Type.first == DataModelDataType::Float4 && m_Type.second == AdditionalMetaDataType::Color) + theRetVal = DataModelToColor(theRetVal); + return theRetVal; +} + +RowTree *Qt3DSDMTimelineItemProperty::getRowTree() const +{ + return m_rowTree; +} + +// Ensures the object that owns this property is selected. +void Qt3DSDMTimelineItemProperty::SetSelected() +{ + if (m_rowTree) { + ITimelineItemBinding *theParentBinding = GetParentBinding(m_rowTree); + if (theParentBinding) + theParentBinding->SetSelected(false); + } +} + +void Qt3DSDMTimelineItemProperty::DeleteAllKeys() +{ + if (m_Keyframes.empty()) + return; + + using namespace Q3DStudio; + + ScopedDocumentEditor editor(*m_TransMgr->GetDoc(), QObject::tr("Delete All Keyframes"), + __FILE__, __LINE__); + for (size_t idx = 0, end = m_AnimationHandles.size(); idx < end; ++idx) + editor->DeleteAllKeyframes(m_AnimationHandles[idx]); +} + +IKeyframe *Qt3DSDMTimelineItemProperty::GetKeyframeByTime(long inTime) const +{ + std::vector<long> theTest; + TKeyframeList::const_iterator theIter = m_Keyframes.begin(); + for (; theIter != m_Keyframes.end(); ++theIter) { + if ((*theIter)->GetTime() == inTime) + return (*theIter); + + theTest.push_back((*theIter)->GetTime()); + } + // if key had been deleted, this returns nullptr + return nullptr; +} + +IKeyframe *Qt3DSDMTimelineItemProperty::GetKeyframeByIndex(long inIndex) const +{ + if (inIndex >= 0 && inIndex < (long)m_Keyframes.size()) + return m_Keyframes[inIndex]; + + Q_ASSERT(0); // should not happen + return nullptr; +} + +long Qt3DSDMTimelineItemProperty::GetKeyframeCount() const +{ + // this list is updated in constructor and when keyframes are added or deleted. + return (long)m_Keyframes.size(); +} + +long Qt3DSDMTimelineItemProperty::GetChannelCount() const +{ + return (long)m_AnimationHandles.size(); +} + +float Qt3DSDMTimelineItemProperty::GetChannelValueAtTime(long inChannelIndex, long inTime) +{ + // if no keyframes, get current property value. + if (m_Keyframes.empty()) { + Qt3DSDMTimelineItemBinding *theParentBinding = + static_cast<Qt3DSDMTimelineItemBinding *>(GetParentBinding(m_rowTree)); + if (theParentBinding) { + + SValue theValue; + qt3dsdm::IPropertySystem *thePropertySystem = + m_TransMgr->GetStudioSystem()->GetPropertySystem(); + thePropertySystem->GetInstancePropertyValue(theParentBinding->GetInstanceHandle(), + m_PropertyHandle, theValue); + switch (m_Type.first) { + case DataModelDataType::Float4: { + if (m_Type.second == AdditionalMetaDataType::Color) { + SFloat4 theFloat4 = qt3dsdm::get<SFloat4>(theValue); + if (inChannelIndex >= 0 && inChannelIndex < 4) + return DataModelToColor(theFloat4[inChannelIndex]); + } + break; + } + case DataModelDataType::Float3: { + + SFloat3 theFloat3 = qt3dsdm::get<SFloat3>(theValue); + if (inChannelIndex >= 0 && inChannelIndex < 3) + return theFloat3[inChannelIndex]; + break; + } + case DataModelDataType::Float2: { + SFloat2 theFloat2 = qt3dsdm::get<SFloat2>(theValue); + if (inChannelIndex >= 0 && inChannelIndex < 2) + return theFloat2[inChannelIndex]; + break; + } + case DataModelDataType::Float: + return qt3dsdm::get<float>(theValue); + break; + default: // TODO: handle other types + break; + } + } + } + IAnimationCore *theAnimationCore = m_TransMgr->GetStudioSystem()->GetAnimationCore(); + if (!m_AnimationHandles.empty() && inChannelIndex >= 0 + && inChannelIndex < (long)m_AnimationHandles.size()) { + float theValue = theAnimationCore->EvaluateAnimation( + m_AnimationHandles[inChannelIndex], Qt3DSDMTimelineKeyframe::GetTimeInSecs(inTime)); + if (m_Type.first == DataModelDataType::Float4 + && m_Type.second == AdditionalMetaDataType::Color) + theValue = DataModelToColor(theValue); + + return theValue; + } + return 0.f; +} + +void Qt3DSDMTimelineItemProperty::SetChannelValueAtTime(long inChannelIndex, long inTime, + float inValue) +{ + Qt3DSDMTimelineKeyframe *theKeyframeWrapper = + dynamic_cast<Qt3DSDMTimelineKeyframe *>(GetKeyframeByTime(inTime)); + if (theKeyframeWrapper) { + Qt3DSDMTimelineKeyframe::TKeyframeHandleList theKeyframes; + theKeyframeWrapper->GetKeyframeHandles(theKeyframes); + if (!theKeyframes.empty() && inChannelIndex < (long)theKeyframes.size()) { + inValue /= 255; + if (!m_SetKeyframeValueCommand) + m_SetKeyframeValueCommand = new CCmdDataModelSetKeyframeValue( + g_StudioApp.GetCore()->GetDoc(), theKeyframes[inChannelIndex], inValue); + m_SetKeyframeValueCommand->Update(inValue); + } + } +} + +void Qt3DSDMTimelineItemProperty::setRowTree(RowTree *rowTree) +{ + m_rowTree = rowTree; +} + +bool Qt3DSDMTimelineItemProperty::IsDynamicAnimation() +{ + return m_Keyframes.size() > 0 && m_Keyframes[0]->IsDynamic(); +} + +//============================================================================= +/** + * For updating the UI when keyframes are added/updated/deleted. + */ +bool Qt3DSDMTimelineItemProperty::RefreshKeyframe(qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe, + ETimelineKeyframeTransaction inTransaction) +{ + bool theHandled = false; + switch (inTransaction) { + case ETimelineKeyframeTransaction_Delete: { + TKeyframeList::iterator theIter = m_Keyframes.begin(); + for (; theIter != m_Keyframes.end(); ++theIter) { + Qt3DSDMTimelineKeyframe *theKeyframe = *theIter; + if (theKeyframe->HasKeyframeHandle(inKeyframe)) { + m_Keyframes.erase(theIter); + theHandled = true; + break; + } + } + } break; + case ETimelineKeyframeTransaction_Add: { + Q_ASSERT(!m_AnimationHandles.empty()); + IAnimationCore *theAnimationCore = m_TransMgr->GetStudioSystem()->GetAnimationCore(); + Qt3DSDMAnimationHandle theAnimationHandle = + theAnimationCore->GetAnimationForKeyframe(inKeyframe); + // only create for the first animation handle. + if (theAnimationHandle == m_AnimationHandles[0]) { // for undo/redo, the keyframes can be + // added in reverse, hence the need to + // sort + if (CreateKeyframeIfNonExistent(inKeyframe, theAnimationHandle)) + std::stable_sort(m_Keyframes.begin(), m_Keyframes.end(), SortKeyframeByTime); + theHandled = true; + } + } break; + case ETimelineKeyframeTransaction_Update: + case ETimelineKeyframeTransaction_DynamicChanged: + theHandled = true; + break; + default: + return false; + } + + return theHandled; +} + +IKeyframe *Qt3DSDMTimelineItemProperty::GetKeyframeByHandle(qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe) +{ + TKeyframeList::iterator theIter = m_Keyframes.begin(); + for (; theIter != m_Keyframes.end(); ++theIter) { + Qt3DSDMTimelineKeyframe *theKeyframe = *theIter; + if (theKeyframe->HasKeyframeHandle(inKeyframe)) + return *theIter; + } + return nullptr; +} + +/** + * Create a wrapper for this keyframe if doesn't exists. + * @return true if created, false if already exists. + */ +bool Qt3DSDMTimelineItemProperty::CreateKeyframeIfNonExistent( + qt3dsdm::Qt3DSDMKeyframeHandle inKeyframeHandle, Qt3DSDMAnimationHandle inOwningAnimation) +{ + TKeyframeList::iterator theIter = m_Keyframes.begin(); + for (; theIter != m_Keyframes.end(); ++theIter) { + Qt3DSDMTimelineKeyframe *theKeyframe = *theIter; + if (theKeyframe->HasKeyframeHandle(inKeyframeHandle)) + return false; + } + // check for multiple channels => only create 1 Qt3DSDMTimelineKeyframe + Qt3DSDMTimelineKeyframe *theNewKeyframe = + new Qt3DSDMTimelineKeyframe(g_StudioApp.GetCore()->GetDoc()); + theNewKeyframe->AddKeyframeHandle(inKeyframeHandle); + if (m_AnimationHandles.size() + > 1) { // assert assumption that is only called for the first handle + Q_ASSERT(m_AnimationHandles[0] == inOwningAnimation); + IAnimationCore *theAnimationCore = m_TransMgr->GetStudioSystem()->GetAnimationCore(); + float theKeyframeTime = KeyframeTime(theAnimationCore->GetKeyframeData(inKeyframeHandle)); + for (size_t i = 1; i < m_AnimationHandles.size(); ++i) { + TKeyframeHandleList theKeyframes; + theAnimationCore->GetKeyframes(m_AnimationHandles[i], theKeyframes); + // the data model ensures that there is only 1 keyframe created for a given time + for (size_t theKeyIndex = 0; theKeyIndex < theKeyframes.size(); ++theKeyIndex) { + float theValue = + KeyframeTime(theAnimationCore->GetKeyframeData(theKeyframes[theKeyIndex])); + if (theValue == theKeyframeTime) { + theNewKeyframe->AddKeyframeHandle(theKeyframes[theKeyIndex]); + break; + } + } + } + } + m_Keyframes.push_back(theNewKeyframe); + return true; +} + +void Qt3DSDMTimelineItemProperty::OnPropertyLinkStatusChanged(qt3dsdm::Qt3DSDMSlideHandle inSlide, + qt3dsdm::Qt3DSDMInstanceHandle inInstance, + qt3dsdm::Qt3DSDMPropertyHandle inProperty) +{ + if (inInstance == m_InstanceHandle && inProperty == m_PropertyHandle) { + // Re-bind to keyframes because the ones we should be pointing to will have changed. + ReleaseKeyframes(); + CreateKeyframes(); + } +} + +void Qt3DSDMTimelineItemProperty::RefreshKeyFrames(void) +{ + std::stable_sort(m_Keyframes.begin(), m_Keyframes.end(), SortKeyframeByTime); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemProperty.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemProperty.h new file mode 100644 index 00000000..1b1524e4 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineItemProperty.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2016 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef QT3DSDM_TIMELINE_ITEM_PROPERTY_H +#define QT3DSDM_TIMELINE_ITEM_PROPERTY_H 1 + +#pragma once + +#include "ITimelineItemProperty.h" +#include "Qt3DSDMTimelineKeyframe.h" +#include "Qt3DSDMTimeline.h" +#include "Qt3DSDMPropertyDefinition.h" + +class RowTree; +class CTimelineTranslationManager; +class CCmdDataModelSetKeyframeValue; +class Qt3DSDMTimelineItemBinding; + +//============================================================================= +/** + * A data model item's property. + * Typically only animated properties show up in the Timeline. + */ +//============================================================================= +class Qt3DSDMTimelineItemProperty : public ITimelineItemProperty +{ +public: + Qt3DSDMTimelineItemProperty(CTimelineTranslationManager *inTransMgr, + qt3dsdm::Qt3DSDMPropertyHandle inPropertyHandle, + qt3dsdm::Qt3DSDMInstanceHandle inInstance); + virtual ~Qt3DSDMTimelineItemProperty(); + + // ITimelineProperty + Q3DStudio::CString GetName() const override; + bool IsMaster() const override; + qt3dsdm::TDataTypePair GetType() const override; + float GetMaximumValue() const override; + float GetMinimumValue() const override; + void SetSelected() override; + void DeleteAllKeys() override; + IKeyframe *GetKeyframeByTime(long inTime) const override; + IKeyframe *GetKeyframeByIndex(long inIndex) const override; + long GetKeyframeCount() const override; + long GetChannelCount() const override; + float GetChannelValueAtTime(long inChannelIndex, long inTime) override; + void SetChannelValueAtTime(long inChannelIndex, long inTime, float inValue) override; + bool IsDynamicAnimation() override; + + void setRowTree(RowTree *rowTree) override; + RowTree *getRowTree() const override; + + bool RefreshKeyframe(qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe, + ETimelineKeyframeTransaction inTransaction); + IKeyframe *GetKeyframeByHandle(qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe); + + void RefreshKeyFrames(void); + + qt3dsdm::Qt3DSDMPropertyHandle getPropertyHandle() const; + +protected: + void InitializeCachedVariables(qt3dsdm::Qt3DSDMInstanceHandle inInstance); + bool CreateKeyframeIfNonExistent(qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe, + qt3dsdm::Qt3DSDMAnimationHandle inOwningAnimation); + void OnPropertyLinkStatusChanged(qt3dsdm::Qt3DSDMSlideHandle inSlide, + qt3dsdm::Qt3DSDMInstanceHandle inInstance, + qt3dsdm::Qt3DSDMPropertyHandle inProperty); + void CreateKeyframes(); + void ReleaseKeyframes(); + +protected: + typedef std::vector<Qt3DSDMTimelineKeyframe *> TKeyframeList; + + qt3dsdm::Qt3DSDMInstanceHandle m_InstanceHandle; + qt3dsdm::Qt3DSDMPropertyHandle m_PropertyHandle; + CTimelineTranslationManager *m_TransMgr; + std::vector<qt3dsdm::Qt3DSDMAnimationHandle> m_AnimationHandles; + TKeyframeList m_Keyframes; + CCmdDataModelSetKeyframeValue + *m_SetKeyframeValueCommand; // for merging modifying keyframe values via graph + qt3dsdm::TDataTypePair m_Type; + Q3DStudio::CString m_Name; + std::vector<std::shared_ptr<qt3dsdm::ISignalConnection>> m_Signals; + +private: + RowTree *m_rowTree = nullptr; +}; + +#endif // QT3DSDM_TIMELINE_ITEM_PROPERTY_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineKeyframe.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineKeyframe.cpp new file mode 100644 index 00000000..ae7e2035 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineKeyframe.cpp @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSCommonPrecompile.h" +#include "Qt3DSDMTimelineKeyframe.h" +#include "Qt3DSDMAnimation.h" +#include "CmdDataModelChangeKeyframe.h" +#include "CmdBatch.h" +#include "Qt3DSDMStudioSystem.h" +#include "OffsetKeyframesCommandHelper.h" + +#include "Doc.h" +#include "StudioApp.h" +#include "Core.h" + +using namespace qt3dsdm; + +// TODO: figure out if we can just use IDoc instead of CDoc +Qt3DSDMTimelineKeyframe::Qt3DSDMTimelineKeyframe(IDoc *inDoc) + : m_Doc(dynamic_cast<CDoc *>(inDoc)) + , m_Selected(false) +{ +} + +Qt3DSDMTimelineKeyframe::~Qt3DSDMTimelineKeyframe() +{ +} + +bool Qt3DSDMTimelineKeyframe::IsSelected() const +{ + return m_Selected; +} + +float my_roundf(float r) +{ + return (r > 0.0f) ? floorf(r + 0.5f) : ceilf(r - 0.5f); +} + +long Qt3DSDMTimelineKeyframe::GetTime() const +{ + if (!m_KeyframeHandles.empty()) { + IAnimationCore *theAnimationCore = m_Doc->GetStudioSystem()->GetAnimationCore(); + Qt3DSDMKeyframeHandle theKeyframeHandle = *m_KeyframeHandles.begin(); + if (theAnimationCore->KeyframeValid(theKeyframeHandle)) { + float theTimeinSecs = + KeyframeTime(theAnimationCore->GetKeyframeData(theKeyframeHandle)); + // We always convert back and forth between between long and float. + // This causes especially issues when we do comparisons + return (long)my_roundf(theTimeinSecs * 1000); + } + } + return -1; // keyframe was deleted, and data cannot be retrieved. +} + +float Qt3DSDMTimelineKeyframe::GetTimeInSecs(long inTime) +{ + float theTimeinSecs = static_cast<float>(inTime) / 1000.f; + // round off to 4 decimal place to workaround precision issues + // TODO: fix this, either all talk float OR long. choose one. + theTimeinSecs = (float)(((theTimeinSecs + 0.00005) * 10000.0) / 10000.0f); + return theTimeinSecs; +} + +void Qt3DSDMTimelineKeyframe::SetTime(const long inNewTime) +{ + float theTimeinSecs = GetTimeInSecs(inNewTime); + CCmd *theCmd = nullptr; + if (m_KeyframeHandles.size() == 1) { + theCmd = new CCmdDataModelSetKeyframeTime(m_Doc, m_KeyframeHandles.front(), theTimeinSecs); + } else { // more than 1 channel + CCmdBatch *theBatch = new CCmdBatch(m_Doc); + TKeyframeHandleList::iterator theIter = m_KeyframeHandles.begin(); + for (; theIter != m_KeyframeHandles.end(); ++theIter) + theBatch->AddCommand(new CCmdDataModelSetKeyframeTime(m_Doc, *theIter, theTimeinSecs)); + theCmd = theBatch; + } + if (theCmd) + m_Doc->GetCore()->ExecuteCommand(theCmd); + +#ifdef _DEBUG + // we have a precision issue from converting from long to float.. + IAnimationCore *theAnimationCore = m_Doc->GetStudioSystem()->GetAnimationCore(); + long theTest = static_cast<long>( + KeyframeTime(theAnimationCore->GetKeyframeData(*m_KeyframeHandles.begin())) * 1000); + Q_ASSERT(inNewTime == theTest); +#endif +} + +inline Qt3DSDMAnimationHandle GetAnimationHandle(qt3dsdm::IAnimationCore *inAnimationCore, + const TKeyframeHandleList &inKeyframes) +{ + if (!inKeyframes.empty()) + return inAnimationCore->GetAnimationForKeyframe(inKeyframes[0]); + return 0; +} + +void Qt3DSDMTimelineKeyframe::SetDynamic(bool inIsDynamic) +{ + if (!m_KeyframeHandles.empty()) { + Qt3DSDMAnimationHandle theAnimation = + GetAnimationHandle(m_Doc->GetStudioSystem()->GetAnimationCore(), m_KeyframeHandles); + if (theAnimation.Valid()) + m_Doc->GetCore()->ExecuteCommand( + new CCmdDataModelChangeDynamicKeyframe(m_Doc, theAnimation, inIsDynamic)); + } +} + +Keyframe *Qt3DSDMTimelineKeyframe::getUI() +{ + return m_ui; +} + +void Qt3DSDMTimelineKeyframe::setUI(Keyframe *kfUI) +{ + m_ui = kfUI; +} + +// Only the first key of a track can be dynamic. +bool Qt3DSDMTimelineKeyframe::IsDynamic() const +{ + qt3dsdm::IAnimationCore *theAnimationCore = m_Doc->GetStudioSystem()->GetAnimationCore(); + Qt3DSDMAnimationHandle theAnimation = GetAnimationHandle(theAnimationCore, m_KeyframeHandles); + if (theAnimation.Valid()) { + SAnimationInfo theInfo = theAnimationCore->GetAnimationInfo(theAnimation); + if (theInfo.m_DynamicFirstKeyframe) { + TKeyframeHandleList theKeyframes; + theAnimationCore->GetKeyframes(theAnimation, theKeyframes); + if (!theKeyframes.empty()) // only true if track is dynamic and this is the first + // keyframe. Might have to optimize because this is so + // clunky. + return (theKeyframes[0] == m_KeyframeHandles[0]); + } + } + return false; +} + +void Qt3DSDMTimelineKeyframe::AddKeyframeHandle(qt3dsdm::Qt3DSDMKeyframeHandle inHandle) +{ + m_KeyframeHandles.push_back(inHandle); +} + +bool Qt3DSDMTimelineKeyframe::HasKeyframeHandle(qt3dsdm::Qt3DSDMKeyframeHandle inHandle) const +{ + TKeyframeHandleList::const_iterator theIter = m_KeyframeHandles.begin(); + for (; theIter != m_KeyframeHandles.end(); ++theIter) { + if (*theIter == inHandle) + return true; + } + return false; +} + +void Qt3DSDMTimelineKeyframe::SetSelected(bool inSelected) +{ + m_Selected = inSelected; +} + +// For colors, there would be 3 keyframe handles +void Qt3DSDMTimelineKeyframe::UpdateKeyframesTime(COffsetKeyframesCommandHelper *inCommandHelper, + long inTime) +{ + for (size_t i = 0; i < m_KeyframeHandles.size(); ++i) + inCommandHelper->SetCommandTime(m_KeyframeHandles[i], inTime); +} + +void Qt3DSDMTimelineKeyframe::GetKeyframeHandles(TKeyframeHandleList &outList) const +{ + outList = m_KeyframeHandles; +} + +void CompareAndSet(Qt3DSDMKeyframeHandle inKeyframe, IAnimationCore *inAnimationCore, + float &outRetValue, bool inGreaterThan) +{ + TKeyframe theKeyframeData = inAnimationCore->GetKeyframeData(inKeyframe); + float theValue = KeyframeValueValue(theKeyframeData); + if ((inGreaterThan && theValue > outRetValue) || (!inGreaterThan && theValue < outRetValue)) + outRetValue = theValue; +} + +float Qt3DSDMTimelineKeyframe::GetMaxValue() const +{ + IAnimationCore *theAnimationCore = m_Doc->GetStudioSystem()->GetAnimationCore(); + float theRetVal = FLT_MIN; + do_all(m_KeyframeHandles, + std::bind(CompareAndSet, std::placeholders::_1, theAnimationCore, + std::ref(theRetVal), true)); + return theRetVal; +} + +float Qt3DSDMTimelineKeyframe::GetMinValue() const +{ + IAnimationCore *theAnimationCore = m_Doc->GetStudioSystem()->GetAnimationCore(); + float theRetVal = FLT_MAX; + do_all(m_KeyframeHandles, + std::bind(CompareAndSet, std::placeholders::_1, theAnimationCore, + std::ref(theRetVal), false)); + return theRetVal; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineKeyframe.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineKeyframe.h new file mode 100644 index 00000000..7799cc0d --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineKeyframe.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ +#ifndef QT3DSDM_KEYFRAME_H +#define QT3DSDM_KEYFRAME_H 1 + +#pragma once + +#include "IKeyframe.h" + +// Data model specific +#include "Qt3DSDMHandles.h" + +class IDoc; +class CDoc; +class CCmdBatch; +class COffsetKeyframesCommandHelper; +struct Keyframe; + +//============================================================================== +/** + * Wrapper for a keyframe in DataModel. + */ +//============================================================================== +class Qt3DSDMTimelineKeyframe : public IKeyframe +{ +public: + typedef std::vector<qt3dsdm::Qt3DSDMKeyframeHandle> TKeyframeHandleList; + +protected: + TKeyframeHandleList + m_KeyframeHandles; ///< no. corresponds to the channels the animated property has. + CDoc *m_Doc; + bool m_Selected; + Keyframe *m_ui = nullptr; + +public: + Qt3DSDMTimelineKeyframe(IDoc *inDoc); + virtual ~Qt3DSDMTimelineKeyframe(); + + // IKeyframe + bool IsSelected() const override; + long GetTime() const override; + void SetTime(const long inNewTime) override; + void SetDynamic(bool inIsDynamic) override; + Keyframe *getUI() override; + void setUI(Keyframe *kfUI) override; + bool IsDynamic() const override; + + void AddKeyframeHandle(qt3dsdm::Qt3DSDMKeyframeHandle inHandle); + bool HasKeyframeHandle(qt3dsdm::Qt3DSDMKeyframeHandle inHandle) const; + void SetSelected(bool inSelected); + void UpdateKeyframesTime(COffsetKeyframesCommandHelper *inCommandHelper, long inTime); + void GetKeyframeHandles(TKeyframeHandleList &outList) const; + + // support drawing graphs + float GetMaxValue() const; + float GetMinValue() const; + + static float GetTimeInSecs(long inTime); +}; + +#endif // QT3DSDM_KEYFRAME_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineTimebar.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineTimebar.cpp new file mode 100644 index 00000000..0ecefefd --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineTimebar.cpp @@ -0,0 +1,222 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSCommonPrecompile.h" +#include "Qt3DSDMTimelineTimebar.h" +#include "Qt3DSDMStudioSystem.h" +#include "Qt3DSDMDataCore.h" +#include "Qt3DSDMDataTypes.h" +#include "ClientDataModelBridge.h" +#include "TimelineTranslationManager.h" +#include "Doc.h" +#include "Dispatch.h" +#include "Core.h" +#include "IDocumentEditor.h" +#include "StudioFullSystem.h" +#include "StudioPreferences.h" +#include "ITimelineItemBinding.h" +#include "RowTree.h" +#include "RowTimeline.h" +#include "StudioApp.h" +#include "Dialogs.h" + +Qt3DSDMTimelineTimebar::Qt3DSDMTimelineTimebar( + CTimelineTranslationManager *inTimelineTranslationManager, + qt3dsdm::Qt3DSDMInstanceHandle inDataHandle) + : Q3DStudio::CUpdateableDocumentEditor(*inTimelineTranslationManager->GetDoc()) + , m_TimelineTranslationManager(inTimelineTranslationManager) + , m_PropertySystem(inTimelineTranslationManager->GetStudioSystem()->GetPropertySystem()) + , m_DataHandle(inDataHandle) +{ + CClientDataModelBridge *theClientDataModelBridge = + inTimelineTranslationManager->GetStudioSystem()->GetClientDataModelBridge(); + m_StartTime = theClientDataModelBridge->GetSceneAsset().m_StartTime; + m_EndTime = theClientDataModelBridge->GetSceneAsset().m_EndTime; + qt3dsdm::SValue theValue; + if (m_PropertySystem->GetInstancePropertyValue( + m_DataHandle, theClientDataModelBridge->GetSceneAsset().m_TimebarColor, theValue)) { + qt3dsdm::SFloat4 theTimebarColor = qt3dsdm::get<qt3dsdm::SFloat4>(theValue); + + m_Color.SetRGB(static_cast<int>(theTimebarColor.m_Floats[0] * 255.0f), + static_cast<int>(theTimebarColor.m_Floats[1] * 255.0f), + static_cast<int>(theTimebarColor.m_Floats[2] * 255.0f)); + } + qt3dsdm::IStudioFullSystemSignalProvider *theProvider = + inTimelineTranslationManager->GetStudioSystem()->GetFullSystem()->GetSignalProvider(); + m_PropertyChangedSignal = theProvider->ConnectInstancePropertyValue( + std::bind(&Qt3DSDMTimelineTimebar::OnPropertyChanged, this, + std::placeholders::_1, std::placeholders::_2)); + + OnPropertyChanged(m_DataHandle, theClientDataModelBridge->GetSceneAsset().m_TimebarColor); + OnPropertyChanged(m_DataHandle, theClientDataModelBridge->GetSceneAsset().m_TimebarText); +} + +void Qt3DSDMTimelineTimebar::OnPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance, + qt3dsdm::Qt3DSDMPropertyHandle inProperty) +{ + if (m_DataHandle == inInstance) { + bool needsInvalidate = false; + qt3dsdm::SValue theValue; + CClientDataModelBridge *theClientDataModelBridge = + m_TimelineTranslationManager->GetStudioSystem()->GetClientDataModelBridge(); + if (inProperty == theClientDataModelBridge->GetSceneAsset().m_TimebarColor) { + + if (m_PropertySystem->GetInstancePropertyValue( + m_DataHandle, theClientDataModelBridge->GetSceneAsset().m_TimebarColor, + theValue)) { + qt3dsdm::SFloat4 theTimebarColor = qt3dsdm::get<qt3dsdm::SFloat4>(theValue); + + m_Color.SetRGB(static_cast<int>(theTimebarColor.m_Floats[0] * 255.0f), + static_cast<int>(theTimebarColor.m_Floats[1] * 255.0f), + static_cast<int>(theTimebarColor.m_Floats[2] * 255.0f)); + } else { + switch (theClientDataModelBridge->GetObjectType(inInstance)) { + case OBJTYPE_LAYER: + m_Color = CStudioPreferences::GetLayerTimebarColor(); + break; + default: + m_Color = CStudioPreferences::GetObjectTimebarColor(); + break; + } + } + needsInvalidate = true; + } else if (inProperty == theClientDataModelBridge->GetSceneAsset().m_TimebarText) { + if (m_PropertySystem->GetInstancePropertyValue( + m_DataHandle, theClientDataModelBridge->GetSceneAsset().m_TimebarText, + theValue)) { + m_Comment = qt3dsdm::get<qt3dsdm::TDataStrPtr>(theValue)->toQString(); + } else { + m_Comment.clear(); + } + needsInvalidate = true; + } + if (needsInvalidate) { + ITimelineItemBinding *theBinding = + m_TimelineTranslationManager->GetOrCreate(inInstance); + if (theBinding) { + RowTree *rowTree = theBinding->getRowTree(); + if (rowTree) + rowTree->rowTimeline()->setBarColor(m_Color); + } + } + } +} + +Qt3DSDMTimelineTimebar::~Qt3DSDMTimelineTimebar() +{ +} + +// TODO: Can we put this on IInstancePropertyCore? +template <typename T> +T GetInstancePropertyValue(qt3dsdm::IPropertySystem *inPropertySystem, + qt3dsdm::Qt3DSDMInstanceHandle inInstanceHandle, + qt3dsdm::Qt3DSDMPropertyHandle inProperty) +{ + qt3dsdm::SValue theValue; + inPropertySystem->GetInstancePropertyValue(inInstanceHandle, inProperty, theValue); + return qt3dsdm::get<T>(theValue); +} + +long Qt3DSDMTimelineTimebar::GetStartTime() const +{ + return GetInstancePropertyValue<qt3ds::QT3DSI32>(m_PropertySystem, m_DataHandle, m_StartTime); +} + +long Qt3DSDMTimelineTimebar::GetEndTime() const +{ + return GetInstancePropertyValue<qt3ds::QT3DSI32>(m_PropertySystem, m_DataHandle, m_EndTime); +} + +long Qt3DSDMTimelineTimebar::GetDuration() const +{ + auto theStartTime = GetInstancePropertyValue<qt3ds::QT3DSI32>(m_PropertySystem, m_DataHandle, m_StartTime); + auto theEndTime = GetInstancePropertyValue<qt3ds::QT3DSI32>(m_PropertySystem, m_DataHandle, m_EndTime); + + return theEndTime - theStartTime; +} + +bool Qt3DSDMTimelineTimebar::ShowHandleBars() const +{ + return true; +} + +void Qt3DSDMTimelineTimebar::OnBeginDrag() +{ // Really? TODO: Figure out why this is here. + // ASSERT(0); +} + +void Qt3DSDMTimelineTimebar::OffsetTime(long inDiff) +{ + if (m_DataHandle.Valid()) { + ENSURE_EDITOR(QObject::tr("Time Bar Move")).OffsetTimeRange(m_DataHandle, inDiff); + m_TimelineTranslationManager->GetDoc() + ->GetCore() + ->GetDispatch() + ->FireImmediateRefreshInstance(m_DataHandle); + } +} + +void Qt3DSDMTimelineTimebar::ChangeTime(long inTime, bool inSetStart) +{ + if (m_DataHandle.Valid()) { + ENSURE_EDITOR(QObject::tr("Time Bar Resize")).ResizeTimeRange(m_DataHandle, inTime, + inSetStart); + m_TimelineTranslationManager->GetDoc() + ->GetCore() + ->GetDispatch() + ->FireImmediateRefreshInstance(m_DataHandle); + } +} + +void Qt3DSDMTimelineTimebar::CommitTimeChange() +{ + CommitEditor(); +} + +void Qt3DSDMTimelineTimebar::RollbackTimeChange() +{ + RollbackEditor(); +} + +void Qt3DSDMTimelineTimebar::SetTimebarComment(const QString &inComment) +{ + using namespace Q3DStudio; + if (inComment != m_Comment) { + qt3dsdm::Qt3DSDMInstanceHandle theHandle = m_DataHandle; + SCOPED_DOCUMENT_EDITOR(*m_TimelineTranslationManager->GetDoc(), + QObject::tr("Set Time Bar Text")) + ->SetTimebarText(theHandle, inComment); + } +} + +void Qt3DSDMTimelineTimebar::SetTimebarTime(ITimeChangeCallback *inCallback /*= nullptr*/) +{ + g_StudioApp.GetDialogs()->asyncDisplayDurationEditDialog(GetStartTime(), GetEndTime(), + inCallback); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineTimebar.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineTimebar.h new file mode 100644 index 00000000..33e3f22d --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/Qt3DSDMTimelineTimebar.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#pragma once + +/////////////////////////////////////////////////////////////////////////////// +// Includes +#include "ITimelineTimebar.h" +#include "Qt3DSDMHandles.h" +#include "IDocumentEditor.h" + +/////////////////////////////////////////////////////////////////////////////// +// Forwards +class CTimelineTranslationManager; + +namespace Q3DStudio { +class IDocumentEditor; +} + +namespace qt3dsdm { +class IPropertySystem; +class ISignalConnection; +} + +//============================================================================= +/** + * General timebar implementation for DataModel objects + */ +class Qt3DSDMTimelineTimebar : public ITimelineTimebar, public Q3DStudio::CUpdateableDocumentEditor +{ +public: + Qt3DSDMTimelineTimebar(CTimelineTranslationManager *inTimelineTranslationManager, + qt3dsdm::Qt3DSDMInstanceHandle inDataHandle); + virtual ~Qt3DSDMTimelineTimebar(); + +protected: + CTimelineTranslationManager *m_TimelineTranslationManager; + qt3dsdm::IPropertySystem *m_PropertySystem; + qt3dsdm::Qt3DSDMInstanceHandle m_DataHandle; // The Instance Handle for this Timeline Timeber. + qt3dsdm::Qt3DSDMPropertyHandle m_StartTime; + qt3dsdm::Qt3DSDMPropertyHandle m_EndTime; + ::CColor m_Color; // Timebar color + + QString m_Comment; // Timebar comment text + std::shared_ptr<qt3dsdm::ISignalConnection> m_PropertyChangedSignal; + void OnPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance, + qt3dsdm::Qt3DSDMPropertyHandle inProperty); + +public: + // ITimelineTimebar + long GetStartTime() const override; + long GetEndTime() const override; + long GetDuration() const override; + bool ShowHandleBars() const override; + void OnBeginDrag() override; + void OffsetTime(long inDiff) override; + void ChangeTime(long inTime, bool inSetStart) override; + void CommitTimeChange() override; + void RollbackTimeChange() override; + ::CColor GetTimebarColor() override { return m_Color; } + QString GetTimebarComment() const override { return m_Comment; } + void SetTimebarComment(const QString &inComment) override; + void SetTimebarTime(ITimeChangeCallback *inCallback = nullptr) override; +}; diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/SlideTimelineItemBinding.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/SlideTimelineItemBinding.cpp new file mode 100644 index 00000000..6707f717 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/SlideTimelineItemBinding.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSCommonPrecompile.h" +#include "SlideTimelineItemBinding.h" + +// Data model specific +#include "Doc.h" +#include "CmdGeneric.h" +#include "EmptyTimelineTimebar.h" + +#include "Qt3DSDMStudioSystem.h" +#include "Qt3DSDMSlides.h" +#include "ClientDataModelBridge.h" + +using namespace qt3dsdm; + +CSlideTimelineItemBinding::CSlideTimelineItemBinding(CTimelineTranslationManager *inMgr, + Qt3DSDMInstanceHandle inDataHandle) + : Qt3DSDMTimelineItemBinding(inMgr) +{ + qt3dsdm::Qt3DSDMSlideHandle theSlideHandle = + m_StudioSystem->GetSlideSystem()->GetSlideByInstance(inDataHandle); + + // Get the owning component of m_SlideHandle. + // This should return CAsset OBJTYPE_SCENE or OBJTYPE_COMPONENT. + qt3dsdm::Qt3DSDMInstanceHandle theInstance = + m_StudioSystem->GetClientDataModelBridge()->GetOwningComponentInstance(theSlideHandle); + SetInstanceHandle(theInstance); + + // Listen to change on Asset name + IStudioFullSystemSignalProvider *theEngine = m_StudioSystem->GetFullSystemSignalProvider(); + std::function<void(Qt3DSDMInstanceHandle, Qt3DSDMPropertyHandle)> theSetter( + std::bind(&CSlideTimelineItemBinding::OnPropertyChanged, this, std::placeholders::_2)); + m_Connection = theEngine->ConnectInstancePropertyValue( + std::bind(qt3dsdm::MaybackCallbackInstancePropertyValue<std::function<void( + Qt3DSDMInstanceHandle, Qt3DSDMPropertyHandle)>>, + std::placeholders::_1, std::placeholders::_2, theInstance, + m_StudioSystem->GetClientDataModelBridge()->GetNameProperty(), theSetter)); +} + +ITimelineTimebar *CSlideTimelineItemBinding::GetTimebar() +{ // No timebars on slides + return new CEmptyTimelineTimebar(); +} + +void CSlideTimelineItemBinding::SetName(const Q3DStudio::CString & /*inName*/) +{ + // Do nothing because name is read only +} + +bool CSlideTimelineItemBinding::IsValidTransaction(EUserTransaction inTransaction) +{ + qt3dsdm::Qt3DSDMInstanceHandle theInstance = GetInstance(); + switch (inTransaction) { + // Disable the following context menus + case EUserTransaction_Rename: + case EUserTransaction_MakeComponent: + case EUserTransaction_EditComponent: + return false; + default: + break; + } + + return Qt3DSDMTimelineItemBinding::IsValidTransaction(inTransaction); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/SlideTimelineItemBinding.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/SlideTimelineItemBinding.h new file mode 100644 index 00000000..54f01ce5 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/SlideTimelineItemBinding.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +//============================================================================== +// Prefix +//============================================================================== +#ifndef INCLUDED_SLIDE_TIMELINEITEM_BINDING_H +#define INCLUDED_SLIDE_TIMELINEITEM_BINDING_H 1 + +#pragma once + +#include "Qt3DSDMTimelineItemBinding.h" + +//============================================================================== +// Classes +//============================================================================== +class ITimelineItem; +class CTimelineTranslationManager; + +//============================================================================= +/** + * Binding to a DataModel object of Slide type + */ +class CSlideTimelineItemBinding : public Qt3DSDMTimelineItemBinding +{ +public: + CSlideTimelineItemBinding(CTimelineTranslationManager *inMgr, + qt3dsdm::Qt3DSDMInstanceHandle inDataHandle); + ~CSlideTimelineItemBinding() {} + + // Qt3DSDMTimelineItemBinding + ITimelineTimebar *GetTimebar() override; + void SetName(const Q3DStudio::CString &inName) override; + bool IsValidTransaction(EUserTransaction inTransaction) override; + + // No properties + long GetPropertyCount() override { return 0; } + ITimelineItemProperty *GetProperty(long) override { return nullptr; } + + // Eye/Lock toggles are not applicable + bool ShowToggleControls() const override { return false; } + bool IsLockedEnabled() const override { return false; } + bool IsVisibleEnabled() const override { return false; } + + // Shy, Locked, Visible are not applicable + bool IsShy() const override { return false; } + void SetShy(bool) override {} + bool IsLocked() const override { return false; } + void SetLocked(bool) override {} + bool IsVisible() const override { return true; } + void SetVisible(bool) override {} + bool IsVisibilityControlled() const override { return false; } + + // Keyframes, not applicable to a Slide + void InsertKeyframe() override {} + void DeleteAllChannelKeyframes() override {} + IKeyframe *GetKeyframeByTime(long) const override { return nullptr; } + + // Keyframe manipulation, not applicable + bool HasDynamicKeyframes(long inTime) override + { + Q_UNUSED(inTime); + return false; + } + void SetDynamicKeyframes(long inTime, bool inDynamic) override + { + Q_UNUSED(inTime); + Q_UNUSED(inDynamic); + } + +protected: + std::shared_ptr<qt3dsdm::ISignalConnection> + m_Connection; // Callback when the Asset name changes + + bool AmITimeParent() const override { return true; } +}; + +#endif // INCLUDED_SLIDE_TIMELINEITEM_BINDING_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineBreadCrumbProvider.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineBreadCrumbProvider.cpp new file mode 100644 index 00000000..ff7077aa --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineBreadCrumbProvider.cpp @@ -0,0 +1,241 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSCommonPrecompile.h" +#include "TimelineBreadCrumbProvider.h" +#include "Core.h" + +// Link to data model +#include "Doc.h" +#include "StudioApp.h" +#include "Cmd.h" +#include "ResourceCache.h" +#include "CColor.h" + +#include "ClientDataModelBridge.h" +#include "Qt3DSDMStudioSystem.h" +#include "Qt3DSDMSlides.h" +#include "CmdActivateSlide.h" + +using namespace qt3dsdm; + +//============================================================================= +/** + * Constructor + */ +CTimelineBreadCrumbProvider::CTimelineBreadCrumbProvider(CDoc *inDoc) + : m_Doc(inDoc) +{ +} + +//============================================================================= +/** + */ +CTimelineBreadCrumbProvider::~CTimelineBreadCrumbProvider() +{ +} + +//============================================================================= +/** + * determine the color and text string for this breadcrumb + */ +static inline void FillBreadCrumb(SBreadCrumb &outBreadCrumb, + qt3dsdm::Qt3DSDMInstanceHandle inInstance, CDoc *inDoc) +{ + // Get the MasterSlide Handle associated with inAsset + CClientDataModelBridge *theBridge = inDoc->GetStudioSystem()->GetClientDataModelBridge(); + ISlideSystem *theSlideSystem = inDoc->GetStudioSystem()->GetSlideSystem(); + Q3DStudio::CId theId = theBridge->GetGUID(inInstance); + qt3dsdm::Qt3DSDMSlideHandle theMasterSlide = + theSlideSystem->GetMasterSlideByComponentGuid(GuidtoSLong4(theId)); + Q_ASSERT(theMasterSlide.Valid()); // it should be valid because inAsset should be OBJTYPE_SCENE or + // non-library OBJTYPE_COMPONENT + + // Get the active slide index of the master slide. Master Slide always has index 0 + long theActiveIndex = theSlideSystem->GetActiveSlideIndex(theMasterSlide); + bool theIsMaster = (theActiveIndex == 0); + + // Determine the color + outBreadCrumb.m_Color = + theIsMaster ? CColor(0, 0, 255) : CColor(0, 0, 0); // blue for master, black otherwise + + // Determine the text string + outBreadCrumb.m_String = theBridge->GetName(inInstance).toQString(); + outBreadCrumb.m_String += " ("; + if (theIsMaster) { + outBreadCrumb.m_String += QObject::tr("Master"); + } else { + Qt3DSDMSlideHandle theActiveSlide = + theSlideSystem->GetSlideByIndex(theMasterSlide, theActiveIndex); + Qt3DSDMInstanceHandle theInstanceHandle = theSlideSystem->GetSlideInstance(theActiveSlide); + Q_ASSERT(theInstanceHandle.Valid()); + outBreadCrumb.m_String += theBridge->GetName(theInstanceHandle).toQString(); + } + outBreadCrumb.m_String += ")"; +} + +//============================================================================= +/** + * return the trail of breadcrumb. + * This constructs a list of the "time context tree" from Scene down to the current active time + * context. + * @param inRefresh true to refresh the list, false to get existing. + */ +CTimelineBreadCrumbProvider::TTrailList +CTimelineBreadCrumbProvider::GetTrail(bool inRefresh /*= true */) +{ + if (inRefresh) + RefreshSlideList(); + + TTrailList theList; + for (size_t theIndex = 0; theIndex < m_BreadCrumbList.size(); ++theIndex) { + SBreadCrumb theBreadCrumb; + FillBreadCrumb(theBreadCrumb, m_BreadCrumbList[theIndex], m_Doc); + theList.push_back(theBreadCrumb); + } + return theList; +} + +//============================================================================= +/** + * switch current time context to the one 'represented' by the breadcrumbs. + * @param inTrailIndex index into the trail list + */ +void CTimelineBreadCrumbProvider::OnBreadCrumbClicked(long inTrailIndex) +{ + if (inTrailIndex >= 0 && inTrailIndex < (long)m_BreadCrumbList.size()) { + CCmdActivateSlide *theCmd = new CCmdActivateSlide(m_Doc, m_BreadCrumbList[inTrailIndex]); + theCmd->SetForceRefresh(false); + m_Doc->GetCore()->ExecuteCommand(theCmd, false); + } +} + +QPixmap CTimelineBreadCrumbProvider::GetRootImage() const +{ + return CResourceCache::GetInstance()->GetBitmap("breadcrumb_component_scene.png"); +} + +QPixmap CTimelineBreadCrumbProvider::GetBreadCrumbImage() const +{ + return CResourceCache::GetInstance()->GetBitmap("breadcrumb_component_button.png"); +} + +QPixmap CTimelineBreadCrumbProvider::GetSeparatorImage() const +{ + return CResourceCache::GetInstance()->GetBitmap("breadcrumb_component_colon_button.png"); +} + +QPixmap CTimelineBreadCrumbProvider::GetActiveBreadCrumbImage() const +{ + return CResourceCache::GetInstance()->GetBitmap("breadcrumb_component_grey_button.png"); +} + +//============================================================================= +/** + * Called when active time context is changed. + */ +void CTimelineBreadCrumbProvider::RefreshSlideList() +{ + ClearSlideList(); + + qt3dsdm::Qt3DSDMInstanceHandle theActiveRoot = m_Doc->GetActiveRootInstance(); + if (!theActiveRoot.Valid()) + return; + FillSlideList(theActiveRoot); +} + +//============================================================================= +/** + * Callback that inAsset has its name changed, fire off a signal to the UI control. + * All the assets' signals are connected to this object and we'll let the UI control check iterate + * through the list for changes and refresh. + * Alternative we can set up additional classes that listens to specific assets and only the asset + * affected refreshed. the former is easier for now. + */ +void CTimelineBreadCrumbProvider::OnNameDirty() +{ + Q_EMIT SigBreadCrumbUpdate(); +} + +void CTimelineBreadCrumbProvider::ClearSlideList() +{ + m_Connections.clear(); + m_BreadCrumbList.clear(); +} + +//============================================================================= +/** + * This will recurse up the time context tree, so that we can fill the list in a top-down (i.e + * Scene) first manner + */ +void CTimelineBreadCrumbProvider::FillSlideList(qt3dsdm::Qt3DSDMInstanceHandle inInstance) +{ + if (!inInstance.Valid()) + return; + + CClientDataModelBridge *theBridge = m_Doc->GetStudioSystem()->GetClientDataModelBridge(); + ISlideSystem *theSlideSystem = m_Doc->GetStudioSystem()->GetSlideSystem(); + Q3DStudio::CId theId = theBridge->GetGUID(inInstance); + + // Recurse + FillSlideList(theBridge->GetParentComponent(inInstance)); + + m_BreadCrumbList.push_back(inInstance); + + Qt3DSDMPropertyHandle theNameProp = + m_Doc->GetStudioSystem()->GetClientDataModelBridge()->GetNameProperty(); + IStudioFullSystemSignalProvider *theEngine = + m_Doc->GetStudioSystem()->GetFullSystemSignalProvider(); + std::function<void(Qt3DSDMInstanceHandle, Qt3DSDMPropertyHandle)> theSetter( + std::bind(&CTimelineBreadCrumbProvider::OnNameDirty, this)); + + // Listen to name changes on the Asset + m_Connections.push_back( + theEngine->ConnectInstancePropertyValue( + std::bind(qt3dsdm::MaybackCallbackInstancePropertyValue<std::function<void( + Qt3DSDMInstanceHandle, Qt3DSDMPropertyHandle)>>, + std::placeholders::_1, std::placeholders::_2, inInstance, + theNameProp, theSetter))); + + // Listen to name changes on the non-master Slides + qt3dsdm::Qt3DSDMSlideHandle theMasterSlide = + theSlideSystem->GetMasterSlideByComponentGuid(GuidtoSLong4(theId)); + long theSlideCount = (long)theSlideSystem->GetSlideCount(theMasterSlide); + + for (long theIndex = 1; theIndex < theSlideCount; ++theIndex) { + Qt3DSDMSlideHandle theSlide = theSlideSystem->GetSlideByIndex(theMasterSlide, theIndex); + Qt3DSDMInstanceHandle theSlideInstance = theSlideSystem->GetSlideInstance(theSlide); + m_Connections.push_back( + theEngine->ConnectInstancePropertyValue( + std::bind(qt3dsdm::MaybackCallbackInstancePropertyValue<std::function<void( + Qt3DSDMInstanceHandle, Qt3DSDMPropertyHandle)>>, + std::placeholders::_1, std::placeholders::_2, theSlideInstance, + theNameProp, theSetter))); + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineBreadCrumbProvider.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineBreadCrumbProvider.h new file mode 100644 index 00000000..acc7be07 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineBreadCrumbProvider.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef INCLUDED_BREADCRUMBPROVIDER_H +#define INCLUDED_BREADCRUMBPROVIDER_H 1 + +#pragma once + +#include "IBreadCrumbProvider.h" +#include "Qt3DSDMSignals.h" + +// Link to data model +class CDoc; +class CTimelineBreadCrumbProvider; + +//============================================================================= +/** + * Bread crumb provider for displaying a trail of time contexts + */ +class CTimelineBreadCrumbProvider : public IBreadCrumbProvider +{ +public: + CTimelineBreadCrumbProvider(CDoc *inDoc); + virtual ~CTimelineBreadCrumbProvider(); + + TTrailList GetTrail(bool inRefresh = true) override; + void OnBreadCrumbClicked(long inTrailIndex) override; + + QPixmap GetRootImage() const override; + QPixmap GetBreadCrumbImage() const override; + QPixmap GetSeparatorImage() const override; + QPixmap GetActiveBreadCrumbImage() const override; + + void RefreshSlideList(); + void OnNameDirty(); + +protected: + void ClearSlideList(); + void FillSlideList(qt3dsdm::Qt3DSDMInstanceHandle inInstance); + +protected: + std::vector<qt3dsdm::Qt3DSDMInstanceHandle> m_BreadCrumbList; + CDoc *m_Doc; + // connections to the DataModel + std::vector<std::shared_ptr<qt3dsdm::ISignalConnection>> m_Connections; +}; + +#endif // INCLUDED_BREADCRUMBPROVIDER_H diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineTranslationManager.cpp b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineTranslationManager.cpp new file mode 100644 index 00000000..c03867a7 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineTranslationManager.cpp @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "TimelineTranslationManager.h" +#include "SlideTimelineItemBinding.h" +#include "GroupTimelineItemBinding.h" +#include "BehaviorTimelineItemBinding.h" +#include "MaterialTimelineItemBinding.h" +#include "ImageTimelineItemBinding.h" +#include "PathAnchorPointTimelineItemBinding.h" +#include "PathTimelineItemBinding.h" +#include "LayerTimelineItemBinding.h" +#include "Qt3DSDMStudioSystem.h" +#include "StudioObjectTypes.h" +#include "StudioApp.h" +#include "Core.h" +#include "Doc.h" +#include "ClientDataModelBridge.h" + +using namespace qt3dsdm; + +CTimelineTranslationManager::CTimelineTranslationManager() +{ +} + +CTimelineTranslationManager::~CTimelineTranslationManager() +{ + // clean up all bindings + Clear(); +} + +ITimelineItemBinding *CTimelineTranslationManager::GetOrCreate(Qt3DSDMInstanceHandle inInstance) +{ + ITimelineItemBinding *theBinding = GetBinding(inInstance); + if (!theBinding) { + Qt3DSDMTimelineItemBinding *theReturn = nullptr; + + EStudioObjectType objType = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem() + ->GetClientDataModelBridge()->GetObjectType(inInstance); + + if (objType & OBJTYPE_IS_MATERIAL) { + theReturn = new CMaterialTimelineItemBinding(this, inInstance); + } else if (objType == OBJTYPE_IMAGE) { + theReturn = new CImageTimelineItemBinding(this, inInstance); + } else if (objType & (OBJTYPE_GROUP | OBJTYPE_COMPONENT)) { + theReturn = new CGroupTimelineItemBinding(this, inInstance); + } else if (objType == OBJTYPE_BEHAVIOR) { + theReturn = new CBehaviorTimelineItemBinding(this, inInstance); + } else if (objType == OBJTYPE_SLIDE) { + theReturn = new CSlideTimelineItemBinding(this, inInstance); + } else if (objType == OBJTYPE_PATHANCHORPOINT) { + theReturn = new CPathAnchorPointTimelineItemBinding(this, inInstance); + } else if (objType == OBJTYPE_PATH) { + theReturn = new CPathTimelineItemBinding(this, inInstance); + } else if (objType == OBJTYPE_LAYER) { + theReturn = new CLayerTimelineItemBinding(this, inInstance); + } else if (objType & (OBJTYPE_MODEL | OBJTYPE_TEXT | OBJTYPE_CAMERA | OBJTYPE_EFFECT + | OBJTYPE_LIGHT | OBJTYPE_RENDERPLUGIN | OBJTYPE_ALIAS + | OBJTYPE_SUBPATH)) + theReturn = new Qt3DSDMTimelineItemBinding(this, inInstance); + else { + // Add support for additional DataModel types here. + Q_ASSERT(0); + } + + m_InstanceBindingMap.insert({theReturn->GetInstanceHandle(), theReturn}); + theBinding = theReturn; + } + + return theBinding; +} + +/** + * Clear all bindings, typically when a presentation is closed. + */ +void CTimelineTranslationManager::Clear() +{ + // clean up all bindings + m_InstanceBindingMap.clear(); +} + +/** + * @return the Binding object that corresponds to this instance. + */ +Qt3DSDMTimelineItemBinding * +CTimelineTranslationManager::GetBinding(Qt3DSDMInstanceHandle inHandle) const +{ + auto it = m_InstanceBindingMap.find(inHandle); + if (it != m_InstanceBindingMap.end()) + return it->second; + + return nullptr; +} + +CDoc *CTimelineTranslationManager::GetDoc() const +{ + return dynamic_cast<CDoc *>(g_StudioApp.GetCore()->GetDoc()); +} + +CStudioSystem *CTimelineTranslationManager::GetStudioSystem() const +{ + return GetDoc()->GetStudioSystem(); +} + diff --git a/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineTranslationManager.h b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineTranslationManager.h new file mode 100644 index 00000000..bbb3fa81 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/Timeline/Bindings/TimelineTranslationManager.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2008 NVIDIA Corporation. +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef TIMELINE_TRANSLATIONMANAGER_H +#define TIMELINE_TRANSLATIONMANAGER_H + +#include "Qt3DSDMHandles.h" + +class ITimelineItemBinding; +class Qt3DSDMTimelineItemBinding; +class CDoc; + +namespace qt3dsdm { +class CStudioSystem; +} + +class CTimelineTranslationManager +{ +public: + CTimelineTranslationManager(); + ~CTimelineTranslationManager(); + + ITimelineItemBinding *GetOrCreate(qt3dsdm::Qt3DSDMInstanceHandle inInstance); + void Clear(); + + Qt3DSDMTimelineItemBinding *GetBinding(qt3dsdm::Qt3DSDMInstanceHandle inHandle) const; + + qt3dsdm::CStudioSystem *GetStudioSystem() const; + CDoc *GetDoc() const; + +private: + std::map<qt3dsdm::Qt3DSDMInstanceHandle, Qt3DSDMTimelineItemBinding *> m_InstanceBindingMap; +}; + +#endif // INCLUDED_TIMELINE_TRANSLATIONMANAGER_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/Keyframe.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/Keyframe.h new file mode 100644 index 00000000..7e4ea614 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/Keyframe.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef KEYFRAME_H +#define KEYFRAME_H + +#include "Bindings/Qt3DSDMTimelineKeyframe.h" +#include "RowTimeline.h" +#include "RowTree.h" + +struct Keyframe +{ + Keyframe(long time, RowTimeline *propRow) + : time(time) + , rowProperty(propRow) + , rowMaster(propRow->parentRow()) + , propertyType(propRow->rowTree()->propertyType()) + {} + + bool selected() const + { + return binding && binding->IsSelected(); + } + + long time; + QString propertyType; + RowTimeline *rowProperty = nullptr; + RowTimeline *rowMaster = nullptr; + Qt3DSDMTimelineKeyframe *binding = nullptr; + bool dynamic = false; +}; + +#endif // KEYFRAME_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/KeyframeManager.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/KeyframeManager.cpp new file mode 100644 index 00000000..9a12aab3 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/KeyframeManager.cpp @@ -0,0 +1,589 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "KeyframeManager.h" +#include "RowTree.h" +#include "RowTimeline.h" +#include "Keyframe.h" +#include "RowManager.h" +#include "TimelineGraphicsScene.h" +#include "StudioApp.h" +#include "Core.h" +#include "Doc.h" +#include "StudioClipboard.h" +#include "CmdDataModelRemoveKeyframe.h" +#include "CmdDataModelInsertKeyframe.h" +#include "CmdDataModelChangeKeyframe.h" +#include "ClientDataModelBridge.h" +#include "Bindings/OffsetKeyframesCommandHelper.h" +#include "Bindings/PasteKeyframesCommandHelper.h" +#include "StudioPreferences.h" +#include "Dialogs.h" +#include "TimeEnums.h" + +using namespace qt3dsdm; + +KeyframeManager::KeyframeManager(TimelineGraphicsScene *scene) : m_scene(scene) +{ +} + +KeyframeManager::~KeyframeManager() +{ + delete m_pasteKeyframeCommandHelper; +} + +QList<Keyframe *> KeyframeManager::insertKeyframe(RowTimeline *row, long time, + bool selectInsertedKeyframes) +{ + QList<Keyframe *> addedKeyframes; + QList<RowTimeline *> propRows; + if (!row->rowTree()->isProperty()) { + const auto childRows = row->rowTree()->childRows(); + for (const auto r : childRows) { + if (r->isProperty()) + propRows.append(r->rowTimeline()); + } + } else { + propRows.append(row); + } + + if (!propRows.empty()) { + for (const auto &r : qAsConst(propRows)) { + Keyframe *keyframe = new Keyframe(time, r); + r->insertKeyframe(keyframe); + r->parentRow()->insertKeyframe(keyframe); + addedKeyframes.append(keyframe); + } + + if (selectInsertedKeyframes && !addedKeyframes.empty()) { + deselectAllKeyframes(); + selectKeyframes(addedKeyframes); + } + } + + return addedKeyframes; +} + +void KeyframeManager::selectKeyframe(Keyframe *keyframe) +{ + if (!m_selectedKeyframes.contains(keyframe)) { + m_selectedKeyframes.append(keyframe); + + if (!m_selectedKeyframesMasterRows.contains(keyframe->rowMaster)) + m_selectedKeyframesMasterRows.append(keyframe->rowMaster); + + keyframe->binding->SetSelected(true); + keyframe->rowMaster->putSelectedKeyframesOnTop(); + keyframe->rowMaster->updateKeyframes(); + } +} + +void KeyframeManager::selectConnectedKeyframes(Keyframe *keyframe) +{ + // Select all keyframes of same master row at same time + const auto keyframes = keyframe->rowMaster->keyframes(); + for (const auto k : keyframes) { + if (k->time == keyframe->time) + selectKeyframe(k); + } +} + +void KeyframeManager::selectKeyframes(const QList<Keyframe *> &keyframes) +{ + for (const auto keyframe : keyframes) { + if (!m_selectedKeyframes.contains(keyframe)) { + m_selectedKeyframes.append(keyframe); + + if (!m_selectedKeyframesMasterRows.contains(keyframe->rowMaster)) + m_selectedKeyframesMasterRows.append(keyframe->rowMaster); + } + } + + for (auto keyframe : qAsConst(m_selectedKeyframes)) + keyframe->binding->SetSelected(true); + + for (auto row : qAsConst(m_selectedKeyframesMasterRows)) { + row->putSelectedKeyframesOnTop(); + row->updateKeyframes(); + } +} + +QList<Keyframe *> KeyframeManager::selectedKeyframes() const +{ + return m_selectedKeyframes; +} + +// update bindings after selected keyframes are moved +void KeyframeManager::commitMoveSelectedKeyframes() +{ + CDoc *theDoc = g_StudioApp.GetCore()->GetDoc(); + COffsetKeyframesCommandHelper h(*theDoc); + + for (Keyframe *keyframe : qAsConst(m_selectedKeyframes)) + keyframe->binding->UpdateKeyframesTime(&h, keyframe->time); +} + +void KeyframeManager::selectKeyframesInRect(const QRectF &rect) +{ + deselectAllKeyframes(); + + RowTree *row = m_scene->rowManager()->getRowAtPos(QPointF(0, rect.top())); + while (row && row->y() < rect.bottom()) { + if (!row->locked()) { + const auto keyframes = row->rowTimeline()->getKeyframesInRange(rect); + for (auto keyframe : keyframes) { + if (!m_selectedKeyframes.contains(keyframe)) { + m_selectedKeyframes.append(keyframe); + + if (!m_selectedKeyframesMasterRows.contains(keyframe->rowMaster)) + m_selectedKeyframesMasterRows.append(keyframe->rowMaster); + } + } + } + row = m_scene->rowManager()->getRowAtPos(QPointF(0, row->y() + row->size().height())); + } + + for (auto keyframe : qAsConst(m_selectedKeyframes)) + keyframe->binding->SetSelected(true); + + for (auto row : qAsConst(m_selectedKeyframesMasterRows)) { + row->putSelectedKeyframesOnTop(); + row->updateKeyframes(); + } +} + +void KeyframeManager::deselectKeyframe(Keyframe *keyframe) +{ + if (m_selectedKeyframes.contains(keyframe)) { + m_selectedKeyframes.removeAll(keyframe); + keyframe->rowMaster->updateKeyframes(); + m_selectedKeyframesMasterRows.removeAll(keyframe->rowMaster); + + keyframe->binding->SetSelected(false); + keyframe->rowMaster->putSelectedKeyframesOnTop(); + } +} + +void KeyframeManager::deselectConnectedKeyframes(Keyframe *keyframe) +{ + // Deselect all keyframes of same master row at same time + const auto keyframes = keyframe->rowMaster->keyframes(); + for (const auto k : keyframes) { + if (k->time == keyframe->time) + deselectKeyframe(k); + } +} + +void KeyframeManager::deselectAllKeyframes() +{ + for (auto keyframe : qAsConst(m_selectedKeyframes)) + keyframe->binding->SetSelected(false); + + for (auto row : qAsConst(m_selectedKeyframesMasterRows)) + row->updateKeyframes(); + + m_selectedKeyframes.clear(); + m_selectedKeyframesMasterRows.clear(); +} + +void KeyframeManager::deselectRowKeyframes(RowTree *row) +{ + const QList<Keyframe *> keyframes = row->rowTimeline()->keyframes(); + for (const auto keyframe : keyframes) { + if (row->isProperty()) + deselectKeyframe(keyframe); + else + deselectConnectedKeyframes(keyframe); + } +} + +bool KeyframeManager::deleteSelectedKeyframes() +{ + if (!m_selectedKeyframes.empty()) { + CDoc *theDoc = g_StudioApp.GetCore()->GetDoc(); + CCmdDataModelRemoveKeyframe *cmd = new CCmdDataModelRemoveKeyframe(theDoc); + for (auto keyframe : qAsConst(m_selectedKeyframes)) { + cmd->addKeyframeHandles(keyframe->binding); + + keyframe->rowMaster->removeKeyframe(keyframe); + keyframe->rowProperty->removeKeyframe(keyframe); + + delete keyframe; + } + + for (auto row : qAsConst(m_selectedKeyframesMasterRows)) + row->updateKeyframes(); + + m_selectedKeyframes.clear(); + m_selectedKeyframesMasterRows.clear(); + + g_StudioApp.GetCore()->ExecuteCommand(cmd); + return true; + } + + return false; +} + +// delete all keyframes on a row +void KeyframeManager::deleteKeyframes(RowTimeline *row, bool repaint) +{ + const auto keyframes = row->keyframes(); + for (auto keyframe : keyframes) { + keyframe->rowMaster->removeKeyframe(keyframe); + keyframe->rowProperty->removeKeyframe(keyframe); + + if (m_selectedKeyframes.contains(keyframe)) + m_selectedKeyframes.removeAll(keyframe); + + delete keyframe; + } + + if (m_selectedKeyframesMasterRows.contains(row)) + m_selectedKeyframesMasterRows.removeAll(row); + + if (repaint) + row->updateKeyframes(); +} + +void KeyframeManager::copySelectedKeyframes() +{ + if (!m_selectedKeyframes.empty() && m_selectedKeyframesMasterRows.count() == 1) { + // Keyframe copying doesn't use clipboard, so clear it so that next time we paste + // it will paste the keyframes rather than the last object we copied + CStudioClipboard::ClearClipboard(); + + if (m_pasteKeyframeCommandHelper) + m_pasteKeyframeCommandHelper->Clear(); // clear out previously copied data + else + m_pasteKeyframeCommandHelper = new CPasteKeyframeCommandHelper(); + + // calc min copied frames time + long minTime = LONG_MAX; + for (auto keyframe : qAsConst(m_selectedKeyframes)) { + if (keyframe->time < minTime) + minTime = keyframe->time; + } + + qt3dsdm::IAnimationCore *animationCore = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem() + ->GetAnimationCore(); + + for (auto keyframe : qAsConst(m_selectedKeyframes)) { + Qt3DSDMTimelineKeyframe *kf = keyframe->binding; + Qt3DSDMTimelineKeyframe::TKeyframeHandleList theKeyframeHandles; + kf->GetKeyframeHandles(theKeyframeHandles); + qt3dsdm::SGetOrSetKeyframeInfo info[3]; + size_t infoCount = 0; + if (!theKeyframeHandles.empty()) { + switch (theKeyframeHandles.size()) { + case 1: + info[0] = setKeyframeInfo(theKeyframeHandles[0], *animationCore); + infoCount = 1; + break; + case 3: + info[0] = setKeyframeInfo(theKeyframeHandles[0], *animationCore); + info[1] = setKeyframeInfo(theKeyframeHandles[1], *animationCore); + info[2] = setKeyframeInfo(theKeyframeHandles[2], *animationCore); + infoCount = 3; + break; + default: + break; + } + + float dt = Qt3DSDMTimelineKeyframe::GetTimeInSecs(kf->GetTime() - minTime); + qt3dsdm::Qt3DSDMAnimationHandle animation + = animationCore->GetAnimationForKeyframe(theKeyframeHandles[0]); + m_pasteKeyframeCommandHelper->AddKeyframeData( + animationCore->GetAnimationInfo(animation).m_Property, dt, info, infoCount); + } + } + } +} + +qt3dsdm::SGetOrSetKeyframeInfo KeyframeManager::setKeyframeInfo( + qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe, qt3dsdm::IAnimationCore &inCore) +{ + qt3dsdm::TKeyframe theKeyframeData = inCore.GetKeyframeData(inKeyframe); + qt3dsdm::SEaseInEaseOutKeyframe keyframe = + qt3dsdm::get<qt3dsdm::SEaseInEaseOutKeyframe>(theKeyframeData); + bool isDynamic = false; + if (inCore.IsFirstKeyframe(inKeyframe)) { + isDynamic = inCore.GetAnimationInfo(inCore.GetAnimationForKeyframe(inKeyframe)) + .m_DynamicFirstKeyframe; + } + + return qt3dsdm::SGetOrSetKeyframeInfo(keyframe.m_KeyframeValue, keyframe.m_EaseIn, + keyframe.m_EaseOut, isDynamic); +} + +void KeyframeManager::pasteKeyframes() +{ + CDoc *theDoc = g_StudioApp.GetCore()->GetDoc(); + if (m_pasteKeyframeCommandHelper && m_pasteKeyframeCommandHelper->HasCopiedKeyframes()) { + qt3dsdm::Qt3DSDMInstanceHandle theSelectedInstance = theDoc->GetSelectedInstance(); + if (theSelectedInstance.Valid()) { + long theCurrentViewTimeInMilliseconds = theDoc->GetCurrentViewTime(); + CCmdDataModelInsertKeyframe *theInsertKeyframesCommand = + m_pasteKeyframeCommandHelper->GetCommand(theDoc, theCurrentViewTimeInMilliseconds, + theSelectedInstance); + if (theInsertKeyframesCommand) + g_StudioApp.GetCore()->ExecuteCommand(theInsertKeyframesCommand); + } + } +} + +void KeyframeManager::moveSelectedKeyframes(long newTime) +{ + Keyframe *pressedKeyframe = m_scene->pressedKeyframe(); + + Q_ASSERT(pressedKeyframe); + + // make sure the min-time keyframe doesn't go below zero + long minTime = getMinSelectedKeyframesTime(); + if (pressedKeyframe->time - minTime > newTime) + newTime = pressedKeyframe->time - minTime; + + for (auto keyframe : qAsConst(m_selectedKeyframes)) { + if (keyframe != pressedKeyframe) + keyframe->time = newTime - (pressedKeyframe->time - keyframe->time); + } + pressedKeyframe->time = newTime; + + for (auto row : qAsConst(m_selectedKeyframesMasterRows)) + row->updateKeyframes(); +} + +long KeyframeManager::getMinSelectedKeyframesTime() const +{ + long minTime = LONG_MAX; + for (auto keyframe : qAsConst(m_selectedKeyframes)) { + if (keyframe->time < minTime) + minTime = keyframe->time; + } + + return minTime; +} + +// returns the distance between the pressed keyframe and the min-time keyframe in a multiselection +long KeyframeManager::getPressedKeyframeOffset() const +{ + if (m_scene->pressedKeyframe()) + return m_scene->pressedKeyframe()->time - getMinSelectedKeyframesTime(); + + return 0; +} + +// selected keyframes belong to only one master row +bool KeyframeManager::oneMasterRowSelected() const +{ + return m_selectedKeyframesMasterRows.count() == 1; +} + +bool KeyframeManager::hasSelectedKeyframes() const +{ + return !m_selectedKeyframes.empty(); +} + +bool KeyframeManager::hasCopiedKeyframes() const +{ + return m_pasteKeyframeCommandHelper && + m_pasteKeyframeCommandHelper->HasCopiedKeyframes(); +} + +bool KeyframeManager::hasDynamicKeyframes(RowTree *row) const +{ + const QList<Keyframe *> keyframes = row->rowTimeline()->keyframes(); + for (const auto keyframe : keyframes) { + if (keyframe->binding->IsDynamic()) + return true; + } + return false; +} + +// IKeyframesManager interface +void KeyframeManager::SetKeyframeTime(long inTime) +{ + g_StudioApp.GetDialogs()->asyncDisplayTimeEditDialog(inTime, g_StudioApp.GetCore()->GetDoc(), + ASSETKEYFRAME, this); +} + +void KeyframeManager::SetKeyframesDynamic(bool inDynamic) +{ + if (!hasSelectedKeyframes()) + return; + + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + IAnimationCore *animationCore = doc->GetStudioSystem()->GetAnimationCore(); + CCmdDataModelChangeDynamicKeyframe *cmd = nullptr; + + for (int i = 0; i < m_selectedKeyframes.size(); ++i) { + Qt3DSDMTimelineKeyframe *timelineKeyframe = m_selectedKeyframes[i]->binding; + Qt3DSDMTimelineKeyframe::TKeyframeHandleList keyframeHandles; + timelineKeyframe->GetKeyframeHandles(keyframeHandles); + + for (size_t keyIndex = 0; keyIndex < keyframeHandles.size(); ++keyIndex) { + qt3dsdm::Qt3DSDMAnimationHandle animation( + animationCore->GetAnimationForKeyframe(keyframeHandles.at(keyIndex))); + if (!cmd) + cmd = new CCmdDataModelChangeDynamicKeyframe(doc, animation, inDynamic); + else + cmd->AddHandle(animation); + } + } + + if (cmd) + g_StudioApp.GetCore()->ExecuteCommand(cmd); +} + +void KeyframeManager::CommitChangedKeyframes() +{ + m_scene->resetPressedKeyframe(); + commitMoveSelectedKeyframes(); +} + +void KeyframeManager::RollbackChangedKeyframes() +{ + m_scene->resetPressedKeyframe(); + + for (Keyframe *keyframe : qAsConst(m_selectedKeyframes)) + keyframe->time = keyframe->binding->GetTime(); + + for (auto row : qAsConst(m_selectedKeyframesMasterRows)) + row->updateKeyframes(); +} + +// IKeyframesManager interface +bool KeyframeManager::HasSelectedKeyframes() +{ + return hasSelectedKeyframes(); +} + +bool KeyframeManager::HasDynamicKeyframes() +{ + return false; // Mahmoud_TODO: implement +} + +bool KeyframeManager::CanPerformKeyframeCopy() +{ + return !m_selectedKeyframes.empty() && m_selectedKeyframesMasterRows.count() == 1; +} + +bool KeyframeManager::CanPerformKeyframePaste() +{ + if (m_pasteKeyframeCommandHelper && m_pasteKeyframeCommandHelper->HasCopiedKeyframes()) { + qt3dsdm::Qt3DSDMInstanceHandle theSelectedInstance = + g_StudioApp.GetCore()->GetDoc()->GetSelectedInstance(); + if (theSelectedInstance.Valid()) + return true; + } + + return false; +} + +void KeyframeManager::CopyKeyframes() +{ + copySelectedKeyframes(); +} + +bool KeyframeManager::RemoveKeyframes(bool inPerformCopy) +{ + Q_UNUSED(inPerformCopy) + + return deleteSelectedKeyframes(); +} + +void KeyframeManager::PasteKeyframes() +{ + pasteKeyframes(); +} + +void KeyframeManager::SetKeyframeInterpolation() +{ + if (!hasSelectedKeyframes()) + return; + + float theEaseIn = 0; + float theEaseOut = 0; + if (CStudioPreferences::GetInterpolation()) + theEaseIn = theEaseOut = 100; + + CDoc *theDoc = g_StudioApp.GetCore()->GetDoc(); + IAnimationCore *theAnimationCore = theDoc->GetStudioSystem()->GetAnimationCore(); + + if (!m_selectedKeyframes.empty()) { + Qt3DSDMTimelineKeyframe *theTimelineKeyframe = m_selectedKeyframes.front()->binding; + Qt3DSDMTimelineKeyframe::TKeyframeHandleList theKeyframeHandles; + theTimelineKeyframe->GetKeyframeHandles(theKeyframeHandles); + TKeyframe theKeyframeData = theAnimationCore->GetKeyframeData(theKeyframeHandles[0]); + GetEaseInOutValues(theKeyframeData, theEaseIn, theEaseOut); + } + + if (g_StudioApp.GetDialogs()->PromptForKeyframeInterpolation(theEaseIn, theEaseOut)) { + // Note: Having "editor" variable here is important as its destructor + // creates proper transaction + Q3DStudio::ScopedDocumentEditor editor(*theDoc, QObject::tr("Set Keyframe Interpolation"), + __FILE__, __LINE__); + for (Keyframe *keyframe : qAsConst(m_selectedKeyframes)) { + Qt3DSDMTimelineKeyframe *theTimelineKeyframe = keyframe->binding; + Qt3DSDMTimelineKeyframe::TKeyframeHandleList theKeyframeHandles; + theTimelineKeyframe->GetKeyframeHandles(theKeyframeHandles); + for (size_t i = 0; i < theKeyframeHandles.size(); ++i) { + TKeyframe theKeyframeData = + theAnimationCore->GetKeyframeData(theKeyframeHandles[i]); + SetEaseInOutValues(theKeyframeData, theEaseIn, theEaseOut); + theAnimationCore->SetKeyframeData(theKeyframeHandles[i], theKeyframeData); + } + } + } +} + +void KeyframeManager::DeselectAllKeyframes() +{ + deselectAllKeyframes(); +} + +void KeyframeManager::SetChangedKeyframes() +{ + CDoc *theDoc = g_StudioApp.GetCore()->GetDoc(); + qt3dsdm::Qt3DSDMInstanceHandle selectedInstance = theDoc->GetSelectedInstance(); + if (selectedInstance.Valid()) { + using namespace Q3DStudio; + Q3DStudio::ScopedDocumentEditor editor(*theDoc, QObject::tr("Set Changed Keyframes"), + __FILE__, __LINE__); + CStudioSystem *theStudioSystem = theDoc->GetStudioSystem(); + // Get all animated properties. + TPropertyHandleList properties; + theStudioSystem->GetPropertySystem()->GetAggregateInstanceProperties(selectedInstance, + properties); + for (size_t i = 0; i < properties.size(); ++i) { + if (theStudioSystem->GetAnimationSystem()->IsPropertyAnimated( + selectedInstance, properties[i])) { + editor->KeyframeProperty(selectedInstance, properties[i], true); + } + } + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/KeyframeManager.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/KeyframeManager.h new file mode 100644 index 00000000..9c160687 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/KeyframeManager.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef KEYFRAMEMANAGER_H +#define KEYFRAMEMANAGER_H + +#include "IKeyframesManager.h" +#include "Qt3DSDMAnimation.h" +#include <QtCore/qlist.h> +#include <StudioObjectTypes.h> + +class RowTimeline; +class RowTree; +class TimelineGraphicsScene; +class CPasteKeyframeCommandHelper; +struct Keyframe; + +QT_FORWARD_DECLARE_CLASS(QGraphicsSceneContextMenuEvent) +QT_FORWARD_DECLARE_CLASS(QRectF) + +class KeyframeManager : public IKeyframesManager +{ +public: + KeyframeManager(TimelineGraphicsScene *m_scene); + virtual ~KeyframeManager() override; + + QList<Keyframe *> insertKeyframe(RowTimeline *row, long time, + bool selectInsertedKeyframes = true); + void selectKeyframe(Keyframe *keyframe); + void selectConnectedKeyframes(Keyframe *keyframe); + void selectKeyframesInRect(const QRectF &rect); + void selectKeyframes(const QList<Keyframe *> &keyframes); + QList<Keyframe *> selectedKeyframes() const; + void deselectKeyframe(Keyframe *keyframe); + void deselectConnectedKeyframes(Keyframe *keyframe); + void deselectAllKeyframes(); + void deselectRowKeyframes(RowTree *row); + void deleteKeyframes(RowTimeline *row, bool repaint = true); + void copySelectedKeyframes(); + void pasteKeyframes(); + void moveSelectedKeyframes(long newTime); + void commitMoveSelectedKeyframes(); + bool deleteSelectedKeyframes(); + bool oneMasterRowSelected() const; + bool hasSelectedKeyframes() const; + bool hasCopiedKeyframes() const; + bool hasDynamicKeyframes(RowTree *row) const; + + // IKeyframesManager interface + void SetKeyframeTime(long inTime) override; + void SetKeyframesDynamic(bool inDynamic) override; + void CommitChangedKeyframes() override; + void RollbackChangedKeyframes() override; + bool HasSelectedKeyframes() override; + bool HasDynamicKeyframes() override; + bool CanPerformKeyframeCopy() override; + bool CanPerformKeyframePaste() override; + void CopyKeyframes() override; + bool RemoveKeyframes(bool inPerformCopy) override; + void PasteKeyframes() override; + void SetKeyframeInterpolation() override; + void DeselectAllKeyframes() override; + void SetChangedKeyframes() override; + long getPressedKeyframeOffset() const; + +private: + qt3dsdm::SGetOrSetKeyframeInfo setKeyframeInfo(qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe, + qt3dsdm::IAnimationCore &inCore); + long getMinSelectedKeyframesTime() const; + + CPasteKeyframeCommandHelper *m_pasteKeyframeCommandHelper = nullptr; + TimelineGraphicsScene *m_scene; + QList<Keyframe *> m_selectedKeyframes; + QList<RowTimeline *> m_selectedKeyframesMasterRows; +}; + +#endif // KEYFRAMEMANAGER_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowManager.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowManager.cpp new file mode 100644 index 00000000..50eff996 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowManager.cpp @@ -0,0 +1,414 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "RowManager.h" +#include "RowTree.h" +#include "TimelineGraphicsScene.h" +#include "Ruler.h" +#include "TreeHeader.h" +#include "KeyframeManager.h" +#include "Keyframe.h" +#include "StudioObjectTypes.h" +#include "Bindings/ITimelineItemBinding.h" +#include "Bindings/Qt3DSDMTimelineItemBinding.h" +#include "Bindings/ITimelineTimebar.h" +#include "Bindings/Qt3DSDMTimelineKeyframe.h" +#include "StudioApp.h" +#include "Core.h" +#include "Doc.h" +#include "StudioObjectTypes.h" +#include "Qt3DSDMStudioSystem.h" +#include "ClientDataModelBridge.h" + +#include <QtWidgets/qgraphicslinearlayout.h> +#include <QtCore/qpointer.h> +#include <QtCore/qtimer.h> + +RowManager::RowManager(TimelineGraphicsScene *scene, QGraphicsLinearLayout *layoutLabels, + QGraphicsLinearLayout *layoutTimeline) + : m_scene(scene) + , m_layoutTree(layoutLabels) + , m_layoutTimeline(layoutTimeline) +{ + +} + +RowManager::~RowManager() +{ + finalizeRowDeletions(); +} + +void RowManager::recreateRowsFromBinding(ITimelineItemBinding *rootBinding) +{ + removeAllRows(); + createRowsFromBindingRecursive(rootBinding); +} + +void RowManager::removeAllRows() +{ + m_scene->keyframeManager()->deselectAllKeyframes(); + clearSelection(); + + // delete rows + RowTree *row_i; + for (int i = m_layoutTree->count() - 1; i >= 1; --i) { + row_i = static_cast<RowTree *>(m_layoutTree->itemAt(i)->graphicsItem()); + m_layoutTree->removeAt(i); + m_layoutTimeline->removeAt(i); + delete row_i; // this will also delete the timeline row + } +} + +RowTree *RowManager::createRowFromBinding(ITimelineItemBinding *binding, RowTree *parentRow, + int index) +{ + RowTree *newRow = createRow(binding->GetTimelineItem()->GetObjectType(), parentRow, + binding->GetTimelineItem()->GetName().toQString(), + QString(), index); + + // connect the new row and its binding + binding->setRowTree(newRow); + newRow->setBinding(binding); + + // hide if material container + auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge(); + if (bridge->isMaterialContainer(newRow->instance())) { + newRow->setVisible(false); + newRow->rowTimeline()->setVisible(false); + } + + // set row start/end time & color + ITimelineTimebar *timebar = binding->GetTimelineItem()->GetTimebar(); + RowTimeline *rowTimeline = newRow->rowTimeline(); + rowTimeline->clearBoundChildren(); + rowTimeline->setStartTime(timebar->GetStartTime()); + rowTimeline->setEndTime(timebar->GetEndTime()); + rowTimeline->setBarColor(timebar->GetTimebarColor()); + + // create property rows + for (int i = 0; i < binding->GetPropertyCount(); i++) { + ITimelineItemProperty *prop_i = binding->GetProperty(i); + RowTree *propRow = getOrCreatePropertyRow(newRow, prop_i->GetName().toQString()); + + // connect the property row and its binding + prop_i->setRowTree(propRow); + propRow->setPropBinding(prop_i); + + // add keyframes + for (int j = 0; j < prop_i->GetKeyframeCount(); j++) { + Qt3DSDMTimelineKeyframe *kf + = static_cast<Qt3DSDMTimelineKeyframe *>(prop_i->GetKeyframeByIndex(j)); + + QList<Keyframe *> addedKeyframes + = m_scene->keyframeManager()->insertKeyframe(propRow->rowTimeline(), + kf->GetTime(), false); + + Keyframe *kfUI = addedKeyframes.at(0); + kf->setUI(kfUI); + kfUI->binding = kf; + kfUI->dynamic = kf->IsDynamic(); + } + } + + updateRulerDuration(); + + return newRow; +} + +void RowManager::createRowsFromBindingRecursive(ITimelineItemBinding *binding, RowTree *parentRow) +{ + auto instance = static_cast<Qt3DSDMTimelineItemBinding *>(binding)->GetInstance(); + + RowTree *newRow = createRowFromBinding(binding, parentRow); + // create child rows recursively + const QList<ITimelineItemBinding *> children = binding->GetChildren(); + for (auto child : children) + createRowsFromBindingRecursive(child, newRow); +} + +RowTree *RowManager::getOrCreatePropertyRow(RowTree *masterRow, const QString &propType, int index) +{ + RowTree *propertyRow = masterRow->getPropertyRow(propType); + if (!propertyRow) + propertyRow = createRow(OBJTYPE_UNKNOWN, masterRow, {}, propType, index); + + propertyRow->updateLock(masterRow->locked()); + + return propertyRow; +} + +RowTree *RowManager::createRow(EStudioObjectType rowType, RowTree *parentRow, const QString &label, + const QString &propType, int index) +{ + if (parentRow && parentRow->isProperty()) { + qWarning() << __FUNCTION__ << "Property row cannot have children. No row added."; + } else { + // If the row doesnt have a parent, insert it under the scene (first row is the tree header) + if (!parentRow && rowType != OBJTYPE_SCENE && m_layoutTree->count() > 1) + parentRow = static_cast<RowTree *>(m_layoutTree->itemAt(1)); + + RowTree *rowTree = nullptr; + + if (!propType.isEmpty()) // property row + rowTree = new RowTree(m_scene, propType); + else + rowTree = new RowTree(m_scene, rowType, label); + + if (parentRow) { + if (index != -1) + parentRow->addChildAt(rowTree, index); + else + parentRow->addChild(rowTree); + } else { + // root element, no parent + m_layoutTree->insertItem(1, rowTree); + m_layoutTimeline->insertItem(1, rowTree->rowTimeline()); + } + + return rowTree; + } + + return nullptr; +} + +RowTree *RowManager::getRowAtPos(const QPointF &scenePos) const +{ + const QList<QGraphicsItem *> items = m_scene->items(scenePos); + + for (auto item : items) { + if (item->type() == TimelineItem::TypeRowTree) + return static_cast<RowTree *>(item); + } + + return nullptr; +} + +// Call this to select/unselect row, affecting bindings +void RowManager::selectRow(RowTree *row, bool multiSelect) +{ + if (!row) { + g_StudioApp.GetCore()->GetDoc()->DeselectAllItems(); + return; + } + + if (row->locked()) + return; + + if (row->isProperty()) + row = row->parentRow(); + + if (multiSelect && m_selectedRows.size() > 0) { + // Do not allow singular object types into multiselection + if ((row->objectType() | m_selectedRows[0]->objectType()) & OBJTYPE_IS_SINGULAR) + return; + } + + Qt3DSDMTimelineItemBinding *binding + = static_cast<Qt3DSDMTimelineItemBinding *>(row->getBinding()); + if (binding) + binding->SetSelected(multiSelect); +} + +// Call this to update row selection UI status +void RowManager::setRowSelection(RowTree *row, bool selected) +{ + if (!row) + return; + + if (selected) { + if (!m_selectedRows.contains(row)) + m_selectedRows.append(row); + row->setState(InteractiveTimelineItem::Selected); + // Expand parents if not expanded + QPointer<RowTree> pRow = row->parentRow(); + if (!pRow.isNull()) { + QTimer::singleShot(0, [this, pRow]() { + if (!pRow.isNull()) + ensureRowExpandedAndVisible(pRow, false); + }); + } + } else { + m_selectedRows.removeAll(row); + row->setState(InteractiveTimelineItem::Normal); + } +} + +// Call this to clear all selections UI status +void RowManager::clearSelection() +{ + for (auto row : qAsConst(m_selectedRows)) + row->setState(InteractiveTimelineItem::Normal); + m_selectedRows.clear(); +} + +// Updates duration of ruler +// When you don't want to update max duration (so width of timeline, scrollbar) +// set updateMaxDuration to false. +void RowManager::updateRulerDuration(bool updateMaxDuration) +{ + long duration = 0; + long maxDuration = 0; // for setting correct size for the view so scrollbars appear correctly + if (m_layoutTree->count() > 1) { + auto rootRow = static_cast<RowTree *>(m_layoutTree->itemAt(1)->graphicsItem()); + bool isComponent = rootRow->objectType() == OBJTYPE_COMPONENT; + for (int i = 1; i < m_layoutTree->count(); ++i) { + RowTree *row_i = static_cast<RowTree *>(m_layoutTree->itemAt(i)->graphicsItem()); + long dur_i = row_i->rowTimeline()->getEndTime(); + + if (((isComponent && i != 1) || row_i->objectType() == OBJTYPE_LAYER) + && dur_i > duration) { + duration = dur_i; + } + + if (dur_i > maxDuration) + maxDuration = dur_i; + } + rootRow->rowTimeline()->setEndTime(duration); + } + + m_scene->ruler()->setDuration(duration); + + if (updateMaxDuration) + m_scene->ruler()->setMaxDuration(maxDuration); +} + +void RowManager::updateFiltering(RowTree *row) +{ + if (!row) // update all rows + row = static_cast<RowTree *>(m_layoutTree->itemAt(1)); + updateRowFilterRecursive(row); +} + +void RowManager::updateRowFilterRecursive(RowTree *row) +{ + row->updateFilter(); + + if (!row->empty()) { + const auto childRows = row->childRows(); + for (auto child : childRows) + updateRowFilterRecursive(child); + row->updateArrowVisibility(); + } +} + +void RowManager::deleteRow(RowTree *row) +{ + if (row && row->objectType() != OBJTYPE_SCENE) { + if (row->parentRow()) + row->parentRow()->removeChild(row); + + deleteRowRecursive(row, true); + } +} + +void RowManager::finalizeRowDeletions() +{ + for (auto row : qAsConst(m_deletedRows)) { + // If the row has been reparented, no need to delete it + if (!row->parentRow()) + deleteRowRecursive(row, false); + } + m_deletedRows.clear(); +} + +void RowManager::deleteRowRecursive(RowTree *row, bool deferChildRows) +{ + if (!row->childProps().empty()) { + const auto childProps = row->childProps(); + for (auto child : childProps) + deleteRowRecursive(child, false); + } + + if (!row->childRows().empty()) { + const auto childRows = row->childRows(); + for (auto child : childRows) { + if (deferChildRows) { + // Let's not delete child rows just yet, there may be a pending move for them. + // This happens when the same transaction contains parent deletion and child row + // move, such as ungrouping items. + child->setParentRow(nullptr); + m_deletedRows.append(child); + } else { + deleteRowRecursive(child, false); + } + } + } + + m_selectedRows.removeAll(row); + m_deletedRows.removeAll(row); // Row actually deleted, remove it from pending deletes + + m_scene->keyframeManager()->deleteKeyframes(row->rowTimeline(), false); + + if (row->getBinding()) + static_cast<Qt3DSDMTimelineItemBinding *>(row->getBinding())->setRowTree(nullptr); + + delete row; +} + +RowTree *RowManager::selectedRow() const +{ + if (m_selectedRows.size() == 1) + return m_selectedRows.first(); + return nullptr; +} + +bool RowManager::isComponentRoot() const +{ + if (m_layoutTree->count() > 1) { + RowTree *root = static_cast<RowTree *>(m_layoutTree->itemAt(1)->graphicsItem()); + return root->objectType() == OBJTYPE_COMPONENT; + } + return false; +} + +bool RowManager::isRowSelected(RowTree *row) const +{ + return m_selectedRows.contains(row); +} + +QVector<RowTree *> RowManager::selectedRows() const +{ + return m_selectedRows; +} + +void RowManager::ensureRowExpandedAndVisible(RowTree *row, bool forceChildUpdate) const +{ + RowTree *parentRow = row; + while (parentRow) { + parentRow->updateExpandStatus(parentRow->expandHidden() + ? RowTree::ExpandState::HiddenExpanded + : RowTree::ExpandState::Expanded, false, + forceChildUpdate); + parentRow = parentRow->parentRow(); + } +} + +bool RowManager::isSingleSelected() const +{ + return m_selectedRows.size() == 1; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowManager.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowManager.h new file mode 100644 index 00000000..3a018577 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowManager.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef ROWMANAGER_H +#define ROWMANAGER_H + +#include "RowTypes.h" +#include "StudioObjectTypes.h" +#include <QtCore/qstring.h> + +class TimelineGraphicsScene; +class RowTree; +class RowTimeline; +class ITimelineItemBinding; +class Qt3DSDMTimelineItemBinding; + +QT_FORWARD_DECLARE_CLASS(QGraphicsLinearLayout) + +class RowManager +{ +public: + RowManager(TimelineGraphicsScene *scene, QGraphicsLinearLayout *layoutLabels, + QGraphicsLinearLayout *layoutTimeline); + ~RowManager(); + + void selectRow(RowTree *row, bool multiSelect = false); + void setRowSelection(RowTree *row, bool selected); + void deleteRow(RowTree *row); + void finalizeRowDeletions(); + void clearSelection(); + void updateFiltering(RowTree *rowTree = nullptr); + void recreateRowsFromBinding(ITimelineItemBinding *rootBinding); + void updateRulerDuration(bool updateMaxDuration = true); + bool isSingleSelected() const; + RowTree *createRowFromBinding(ITimelineItemBinding *binding, RowTree *parentRow = nullptr, + int index = -1); + RowTree *getOrCreatePropertyRow(RowTree *masterRow, const QString &propType, int index = -1); + RowTree *createRow(EStudioObjectType rowType, RowTree *parentRow = nullptr, + const QString &label = QString(), const QString &propType = QString(), + int index = -1); + RowTree *getRowAtPos(const QPointF &scenePos) const; + RowTree *selectedRow() const; + bool isComponentRoot() const; + bool isRowSelected(RowTree *row) const; + QVector<RowTree *> selectedRows() const; + void ensureRowExpandedAndVisible(RowTree *row, bool forceChildUpdate) const; + +private: + void deleteRowRecursive(RowTree *row, bool deferChildRows); + void updateRowFilterRecursive(RowTree *row); + void createRowsFromBindingRecursive(ITimelineItemBinding *binding, + RowTree *parentRow = nullptr); + void removeAllRows(); + + QVector<RowTree *> m_selectedRows; + TimelineGraphicsScene *m_scene; + QGraphicsLinearLayout *m_layoutTree; + QGraphicsLinearLayout *m_layoutTimeline; + QVector<RowTree *> m_deletedRows; +}; + +#endif // ROWMANAGER_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowMover.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowMover.cpp new file mode 100644 index 00000000..3e96f2dc --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowMover.cpp @@ -0,0 +1,451 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "RowMover.h" +#include "RowTree.h" +#include "RowManager.h" +#include "TimelineGraphicsScene.h" +#include "TimelineConstants.h" +#include "StudioPreferences.h" + +#include <QtGui/qpainter.h> +#include <QtWidgets/qapplication.h> +#include <QtWidgets/qgraphicsitem.h> +#include <QtWidgets/qgraphicslinearlayout.h> + +RowMover::RowMover(TimelineGraphicsScene *scene) + : TimelineItem() + , m_scene(scene) +{ + setZValue(99); + setGeometry(0, 0, TimelineConstants::TREE_MAX_W, 10); + + m_autoExpandTimer.setSingleShot(true); + connect(&m_autoExpandTimer, &QTimer::timeout, [this]() { + if (m_rowAutoExpand) { + m_rowAutoExpand->updateExpandStatus(RowTree::ExpandState::Expanded, true); + // Update RowMover after the expansion. The +50 below is just a small margin to ensure + // correct row heights before updateTargetRowLater is called. + QTimer::singleShot(TimelineConstants::EXPAND_ANIMATION_DURATION + 50, [this]() { + if (updateTargetRowLater) + updateTargetRowLater(); + }); + } + }); +} + +void RowMover::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(option) + Q_UNUSED(widget) + + static const QPolygon polygon({QPoint(0, 0), QPoint(0, 3), QPoint(7, 3), QPoint(7, 1), + QPoint(int(TimelineConstants::TREE_BOUND_W), 1), + QPoint(int(TimelineConstants::TREE_BOUND_W), 0)}); + painter->setPen(QPen(CStudioPreferences::timelineRowMoverColor(), 1)); + painter->setBrush(CStudioPreferences::timelineRowMoverColor()); + painter->drawConvexPolygon(polygon); +} + +RowTree *RowMover::insertionTarget() const +{ + return m_insertionTarget.data(); +} + +RowTree *RowMover::insertionParent() const +{ + return m_insertionParent; +} + +QVector<RowTree *> RowMover::sourceRows() const +{ + return m_sourceRows; +} + +void RowMover::removeSourceRow(RowTree *row) +{ + m_sourceRows.remove(m_sourceRows.indexOf(row)); +} + +bool RowMover::shouldDeleteAfterMove() const +{ + return m_deleteAfterMove; +} + +void RowMover::resetInsertionParent(RowTree *newParent) +{ + if (m_insertionParent) { + m_insertionParent->setDnDState(RowTree::DnDState::None, RowTree::DnDState::Parent); + m_insertionParent = nullptr; + } + + if (newParent) { + m_insertionParent = newParent; + m_insertionParent->setDnDState(RowTree::DnDState::Parent, RowTree::DnDState::None); + } else { + m_insertionTarget = nullptr; + } +} + +bool RowMover::isActive() const +{ + return m_active; +} + +void RowMover::start(const QVector<RowTree *> &rows) +{ + m_deleteAfterMove = false; + m_sourceRows.clear(); + if (!rows.isEmpty()) { + // Remove rows that have an ancestor included in the selection or ones that are of + // invalid type for moving + for (auto candidateRow : rows) { + bool omit = !candidateRow->draggable(); + if (!omit) { + for (auto checkRow : rows) { + if (candidateRow->isDecendentOf(checkRow)) + omit = true; + } + if (!omit) + m_sourceRows.append(candidateRow); + } + } + if (!m_sourceRows.isEmpty()) { + m_active = true; + for (auto row : qAsConst(m_sourceRows)) + row->setDnDState(RowTree::DnDState::Source, RowTree::DnDState::None, true); + qApp->setOverrideCursor(Qt::ClosedHandCursor); + } + } +} + +void RowMover::end(bool force) +{ + if (m_active || force) { + m_active = false; + for (auto row : qAsConst(m_sourceRows)) + row->setDnDState(RowTree::DnDState::None, RowTree::DnDState::Any, true); + + m_sourceRows.clear(); + + if (!m_insertionTarget.isNull()) + m_insertionTarget->setDnDState(RowTree::DnDState::None); + + setVisible(false); + resetInsertionParent(); + updateTargetRowLater = {}; + qApp->changeOverrideCursor(Qt::ArrowCursor); + qApp->restoreOverrideCursor(); + + m_autoExpandTimer.stop(); + } +} + +void RowMover::updateState(int depth, double y) +{ + setPos(24 + depth * TimelineConstants::ROW_DEPTH_STEP, y); + setVisible(true); +} + +bool RowMover::isNextSiblingRow(RowTree *rowMain, RowTree *rowSibling) const +{ + // order matters, rowSibling is below rowMain + return rowMain->parentRow() == rowSibling->parentRow() + && rowSibling->index() - rowMain->index() == 1; +} + +bool RowMover::sourceRowsHasMaster() const +{ + for (auto sourceRow : qAsConst(m_sourceRows)) { + if (sourceRow->isMaster() && sourceRow->objectType() != OBJTYPE_LAYER) + return true; + } + + return false; +} + +bool RowMover::isSourceRowsDescendant(RowTree *row) const +{ + if (row) { + for (auto sourceRow : qAsConst(m_sourceRows)) { + if (row == sourceRow || row->isDecendentOf(sourceRow)) + return true; + } + } + + return false; +} + +// rowType parameter is used to highlight the target row when RowMover is not active, +// i.e. when dragging from project or basic objects palettes +void RowMover::updateTargetRow(const QPointF &scenePos, EStudioObjectType rowType, + Q3DStudio::DocumentEditorFileType::Enum fileType, + bool firstTry) +{ + // DnD a presentation / Qml stream from the project panel (to set it as a subpresentation) + if (rowType & (OBJTYPE_PRESENTATION | OBJTYPE_QML_STREAM | OBJTYPE_MATERIALDATA)) { + if (!m_insertionTarget.isNull()) + m_insertionTarget->setDnDState(RowTree::DnDState::None, RowTree::DnDState::SP_TARGET); + + RowTree *rowAtMouse = m_scene->rowManager()->getRowAtPos(scenePos); + if (rowAtMouse) { + // m_insertionTarget will go through CFileDropSource::ValidateTarget() which will + // filter out invalid drop rows + m_insertionTarget = rowAtMouse; + m_insertType = Q3DStudio::DocumentEditorInsertType::LastChild; + + if (rowType == OBJTYPE_MATERIALDATA) { + if (rowAtMouse->objectType() & OBJTYPE_IS_MATERIAL) + m_insertionTarget->setDnDState(RowTree::DnDState::SP_TARGET); + } else { + if (rowAtMouse->objectType() & (OBJTYPE_LAYER | OBJTYPE_IS_MATERIAL | OBJTYPE_IMAGE) + && !rowAtMouse->isDefaultMaterial()) { + m_insertionTarget->setDnDState(RowTree::DnDState::SP_TARGET); + } + } + m_rowAutoExpand = rowAtMouse; + m_autoExpandTimer.start(TimelineConstants::AUTO_EXPAND_TIME); + } else { + m_rowAutoExpand = nullptr; + m_autoExpandTimer.stop(); + } + return; + } else if (fileType == Q3DStudio::DocumentEditorFileType::Image) { // DnD an image + if (!m_insertionTarget.isNull()) + m_insertionTarget->setDnDState(RowTree::DnDState::None, RowTree::DnDState::SP_TARGET); + // if draggin in the middle of a layer, mat, or image row, set the image as a texture. + RowTree *rowAtMouse = m_scene->rowManager()->getRowAtPos(scenePos); + if (rowAtMouse) { + double y = rowAtMouse->mapFromScene(scenePos).y(); + if (y > TimelineConstants::ROW_H * .25 && y < TimelineConstants::ROW_H * .75) { + if (rowAtMouse->objectType() & (OBJTYPE_LAYER | OBJTYPE_IS_MATERIAL | OBJTYPE_IMAGE) + && !rowAtMouse->isDefaultMaterial()) { + m_rowAutoExpand = nullptr; + m_autoExpandTimer.stop(); + setVisible(false); + resetInsertionParent(); + + m_insertionTarget = rowAtMouse; + m_insertionTarget->setDnDState(RowTree::DnDState::SP_TARGET); + m_insertType = Q3DStudio::DocumentEditorInsertType::LastChild; + return; + } + } + } + } + + EStudioObjectType theRowType = rowType; + if (theRowType == OBJTYPE_UNKNOWN && m_sourceRows.size() == 1) + theRowType = m_sourceRows[0]->objectType(); + + // row will be inserted just below rowInsert1 and just above rowInsert2 (if it exists) + RowTree *rowInsert1 = m_scene->rowManager() + ->getRowAtPos(scenePos + QPointF(0, TimelineConstants::ROW_H * -.5)); + RowTree *rowInsert2 = m_scene->rowManager() + ->getRowAtPos(scenePos + QPointF(0, TimelineConstants::ROW_H * .5)); + + // If on top half of the top row, adjust half row down, as we can never insert anything + // above the top row + if (!rowInsert1 && rowInsert2 && rowInsert2->index() == 0) { + rowInsert1 = m_scene->rowManager()->getRowAtPos(scenePos); + rowInsert2 = m_scene->rowManager() + ->getRowAtPos(scenePos + QPointF(0, TimelineConstants::ROW_H)); + } + + bool valid = rowInsert1 != nullptr; + + if (valid) { + // if dragging over a property or a parent of a property, move to the first row + // after the property + if (rowInsert1->isProperty()) { + rowInsert1 = rowInsert1->parentRow()->childProps().last(); + rowInsert2 = m_scene->rowManager() + ->getRowAtPos(QPointF(0, rowInsert1->y() + TimelineConstants::ROW_H)); + } else if (rowInsert1->hasPropertyChildren() && rowInsert1->expanded()) { + rowInsert1 = rowInsert1->childProps().last(); + rowInsert2 = m_scene->rowManager() + ->getRowAtPos(QPointF(0, rowInsert1->y() + TimelineConstants::ROW_H)); + } + + // calc insertion depth + bool inAComponent = static_cast<RowTree *>(m_scene->layoutTree()->itemAt(1))->isComponent(); + int depth; + int depthMin = 2; + if (rowInsert2) + depthMin = rowInsert2->depth(); + else if (theRowType != OBJTYPE_LAYER && !inAComponent) + depthMin = 3; + + int depthMax = rowInsert1->depth(); + bool srcHasMaster = sourceRowsHasMaster(); + if (!rowInsert1->locked() && rowInsert1->isContainer() && !m_sourceRows.contains(rowInsert1) + // prevent insertion a master row under a non-master unless under a component root + && (!(srcHasMaster && !rowInsert1->isMaster()) || rowInsert1->isComponentRoot())) { + depthMax++; // Container: allow insertion as a child + } else { + RowTree *row = rowInsert1->parentRow(); + if (rowInsert1->isPropertyOrMaterial() && !rowInsert1->parentRow()->isContainer()) { + depthMax--; // non-container with properties and/or a material + if (row) + row = row->parentRow(); + } + if (srcHasMaster) { + while (row && !row->isMaster() && !row->isComponent()) { + depthMax--; + row = row->parentRow(); + } + } + } + + if (theRowType == OBJTYPE_LAYER) { + depth = 2; // layers can only be moved on depth 2 + } else if (theRowType == OBJTYPE_EFFECT) { + depth = 3; // effects can only be moved on depth 3 (layer direct child) + } else { + static const int LEFT_MARGIN = 20; + depth = (int(scenePos.x()) - LEFT_MARGIN) / TimelineConstants::ROW_DEPTH_STEP; + depth = qBound(depthMin, depth, depthMax); + } + // calc insertion parent + RowTree *insertParent = rowInsert1; + // If we are dragging objects to a component, + // let insertParent be the component itself, not + // the parent for the component and set the source rows + // to be deleted soon (their duplicates will be inserted + // in the component). Do this only if m_active is true + // i.e. user is dragging a row within timeline (not from object/project panel) + // AND drop depth is larger than for the component (user is dropping items _in_ + // the component, not at the same depth as the component itself) + if (insertParent->isComponent() && !insertParent->isComponentRoot() + && depth > insertParent->depth() && m_active) { + m_deleteAfterMove = true; + } else { + m_deleteAfterMove = false; + for (int i = rowInsert1->depth(); i >= depth; --i) + insertParent = insertParent->parentRow(); + } + + resetInsertionParent(insertParent); + + if (depth < depthMin || depth > depthMax + || (theRowType != OBJTYPE_UNKNOWN + && !CStudioObjectTypes::AcceptableParent(theRowType, + m_insertionParent->objectType())) + || m_insertionParent->locked()) { + valid = false; + } + + for (auto sourceRow : qAsConst(m_sourceRows)) { + if (m_insertionParent == sourceRow + || m_insertionParent->isDecendentOf(sourceRow) + || !CStudioObjectTypes::AcceptableParent(sourceRow->objectType(), + m_insertionParent->objectType())) { + // prevent insertion under itself, or under unacceptable parent + valid = false; + break; + } + } + + // calc insertion target and type + if (rowInsert1 == m_insertionParent) { + if (m_insertionParent->expanded() && !m_insertionParent->childRows().empty()) { + m_insertionTarget = m_insertionParent->childRows().at(0); + m_insertType = Q3DStudio::DocumentEditorInsertType::PreviousSibling; + } else { + m_insertionTarget = m_insertionParent; + m_insertType = Q3DStudio::DocumentEditorInsertType::LastChild; + } + } else if (rowInsert1->isProperty() && depth == rowInsert1->depth()) { + if (m_insertionParent->childRows().isEmpty()) { + m_insertionTarget = m_insertionParent; + m_insertType = Q3DStudio::DocumentEditorInsertType::LastChild; + } else { + m_insertionTarget = m_insertionParent->childRows().at(0); + m_insertType = Q3DStudio::DocumentEditorInsertType::PreviousSibling; + } + } else { + m_insertionTarget = rowInsert1; + m_insertType = Q3DStudio::DocumentEditorInsertType::NextSibling; + if (depth < rowInsert1->depth()) { + for (int i = depth; i < rowInsert1->depth(); ++i) + m_insertionTarget = m_insertionTarget->parentRow(); + } + } + // Don't allow single move right next to moving row at same depth + if (m_sourceRows.size() == 1 + && (m_insertionTarget == m_sourceRows[0] + || ((m_insertType == Q3DStudio::DocumentEditorInsertType::NextSibling + && isNextSiblingRow(m_insertionTarget, m_sourceRows[0])) + || (m_insertType == Q3DStudio::DocumentEditorInsertType::PreviousSibling + && isNextSiblingRow(m_sourceRows[0], m_insertionTarget))))) { + valid = false; + } + if (valid) { + updateState(depth, rowInsert1->y() + rowInsert1->size().height()); + + // auto expand + if (!rowInsert1->locked() && !rowInsert1->expanded() && rowInsert1->isContainer() + && !rowInsert1->empty() && !isSourceRowsDescendant(rowInsert1) + && depth == rowInsert1->depth() + 1) { + updateTargetRowLater = std::bind(&RowMover::updateTargetRow, this, + scenePos, rowType, fileType, true); + m_rowAutoExpand = rowInsert1; + m_autoExpandTimer.start(TimelineConstants::AUTO_EXPAND_TIME); + } else { + m_rowAutoExpand = nullptr; + m_autoExpandTimer.stop(); + } + } else if (firstTry && rowInsert2 + && m_scene->rowManager()->getRowAtPos(scenePos) == rowInsert2) { + // If the current drop location turns out to be invalid and we are on the upper half of + // a row, we try to resolve target row as if we were half row below. This allows drags + // to be accepted over the entire row if inserting above the row is not valid. + updateTargetRow(scenePos + QPointF(0, TimelineConstants::ROW_H * .5), + rowType, fileType, false); + valid = !m_insertionTarget.isNull(); + } + } + + if (!valid) { + m_rowAutoExpand = nullptr; + m_autoExpandTimer.stop(); + setVisible(false); + resetInsertionParent(); + } +} + +int RowMover::type() const +{ + // Enable the use of qgraphicsitem_cast with this item. + return TypeRowMover; +} + +Q3DStudio::DocumentEditorInsertType::Enum RowMover::insertionType() const +{ + return m_insertType; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowMover.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowMover.h new file mode 100644 index 00000000..a8fc99e5 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowMover.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef ROWMOVER_H +#define ROWMOVER_H + +#include "TimelineConstants.h" +#include "TimelineItem.h" +#include "DocumentEditorEnumerations.h" +#include "StudioObjectTypes.h" +#include <QtCore/qtimer.h> +#include <QtCore/qpointer.h> + +class RowTree; +class TimelineGraphicsScene; + +class RowMover : public TimelineItem +{ +public: + RowMover(TimelineGraphicsScene *m_scene); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget = nullptr) override; + + void start(const QVector<RowTree *> &rows); + void end(bool force = false); + void updateTargetRow(const QPointF &scenePos, EStudioObjectType rowType = OBJTYPE_UNKNOWN, + Q3DStudio::DocumentEditorFileType::Enum fileType + = Q3DStudio::DocumentEditorFileType::Unknown, + bool firstTry = true); + bool isActive() const; + RowTree *insertionTarget() const; + RowTree *insertionParent() const; + QVector<RowTree *> sourceRows() const; + void removeSourceRow(RowTree *row); + bool shouldDeleteAfterMove() const; + int type() const; + Q3DStudio::DocumentEditorInsertType::Enum insertionType() const; + +private: + void updateState(int depth, double y); + void resetInsertionParent(RowTree *newParent = nullptr); + bool isSourceRowsDescendant(RowTree *row) const; + bool sourceRowsHasMaster() const; + bool isNextSiblingRow(RowTree *r1, RowTree *r2) const; + + TimelineGraphicsScene *m_scene = nullptr; + QPointer<RowTree> m_insertionTarget; // insertion target + RowTree *m_insertionParent = nullptr; // insertion parent + RowTree *m_rowAutoExpand = nullptr; + QVector<RowTree *> m_sourceRows; // dragged rows + bool m_active = false; + bool m_deleteAfterMove = false; + Q3DStudio::DocumentEditorInsertType::Enum m_insertType = + Q3DStudio::DocumentEditorInsertType::Unknown; + QTimer m_autoExpandTimer; + std::function<void()> updateTargetRowLater = {}; +}; + +#endif // ROWMOVER_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowTypes.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowTypes.h new file mode 100644 index 00000000..58a93115 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/RowTypes.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef ROWTYPES_H +#define ROWTYPES_H + +#include <qglobal.h> + +enum class TimelineControlType { + None, + KeyFrame, + Duration, + StartHandle, + EndHandle +}; + +enum class TreeControlType { + None, + Arrow, + Shy, + Hide, + Lock +}; + +#endif // ROWTYPES_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/SelectionRect.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/SelectionRect.cpp new file mode 100644 index 00000000..429b0170 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/SelectionRect.cpp @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "SelectionRect.h" +#include "TimelineConstants.h" +#include "Ruler.h" + +#include <QtGui/qpainter.h> + +SelectionRect::SelectionRect() +{ + setZValue(100); + setActive(false); +} + +void SelectionRect::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_UNUSED(option) + Q_UNUSED(widget) + + if (m_active) + painter->drawRect(m_rect); +} + +void SelectionRect::start(const QPointF &origin) +{ + m_rect.setTopLeft(origin); + m_rect.setWidth(0); + m_rect.setHeight(0); + m_active = true; +} + +void SelectionRect::updateSize(const QPointF &pos, const QRectF &visibleScene) +{ + QPointF newPos = pos; + if (newPos.x() < visibleScene.left()) + newPos.setX(visibleScene.left()); + else if (newPos.x() > visibleScene.right()) + newPos.setX(visibleScene.right()); + if (newPos.y() < visibleScene.top()) + newPos.setY(visibleScene.top()); + else if (newPos.y() > visibleScene.bottom()) + newPos.setY(visibleScene.bottom()); + + m_rect.setBottomRight(newPos); + setRect(m_rect.normalized()); +} + +void SelectionRect::end() +{ + setRect(QRectF()); + m_active = false; +} + +bool SelectionRect::isActive() const +{ + return m_active; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/SelectionRect.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/SelectionRect.h new file mode 100644 index 00000000..224284c0 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/SelectionRect.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef SELECTIONRECT_H +#define SELECTIONRECT_H + +#include <QtWidgets/qgraphicsitem.h> + +class SelectionRect : public QGraphicsRectItem +{ +public: + explicit SelectionRect(); + + void start(const QPointF &origin); + void updateSize(const QPointF &pos, const QRectF &visibleScene); + void end(); + bool isActive() const; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget = nullptr) override; + +private: + bool m_active = false; + QRectF m_rect; +}; + +#endif // SELECTIONRECT_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineConstants.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineConstants.h new file mode 100644 index 00000000..61525718 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineConstants.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef TIMELINECONSTANTS_H +#define TIMELINECONSTANTS_H + +namespace TimelineConstants +{ + // Dimensions + const int ROW_H = 20; + const int ROW_H_EXPANDED = 120; // property rows height when graph is shown + const int ROW_SPACING = 2; + const int ROW_DEPTH_STEP = 15; // x-distance between 2 consecutive depths + const double RULER_SEC_W = 30; // width of 1 second section (at scale 1) + const double RULER_MILLI_W = RULER_SEC_W / 1000.0; // width of 1 millisecond section at scale 1 + const int RULER_SEC_DIV = 10; // second divisions + const int RULER_DIV_H1 = 5; // height of main divisions + const int RULER_DIV_H2 = 3; // height of secondary divisions + const int RULER_DIV_H3 = 1; // height of minor divisions + const int RULER_BASE_Y = 18; // baseline Y + const int RULER_LABEL_W = 60; + const int RULER_LABEL_H = 10; + const int RULER_TICK_SCALE1 = 2; + const int RULER_TICK_SCALE2 = 3; + const int RULER_TICK_SCALE3 = 6; + const int RULER_TICK_SCALE4 = 21; + const int TOOLBAR_MARGIN = 10; // margin between the timeline and the toolbar + const int ROW_TEXT_OFFSET_Y = 3; // offset Y of comment text on row + + const double RULER_EDGE_OFFSET = 15; + const double TREE_MIN_W = 160; + const double TREE_MAX_W = 600; + const double TREE_DEFAULT_W = 250; + const double TREE_BOUND_W = 10000; // real width of the row (> max possible visible tree area) + const double TREE_ICONS_W = 53; + const int SPLITTER_W = 4; + const int PLAYHEAD_W = 14; + const int DURATION_HANDLE_W = 14; // width of duration end handles in a timeline row + const int NAVIGATION_BAR_H = 30; // height of navigation/breadcrumb bar + const int TIMEBAR_TOOLTIP_OFFSET_V = 10; + + // Other + const int EXPAND_ANIMATION_DURATION = 200; + const int AUTO_SCROLL_PERIOD = 50; // time steps (millis) while auto scrolling + const int AUTO_SCROLL_DELTA = 8; // increment in scroll at each time step + const int AUTO_SCROLL_TRIGGER = 500; // time after which auto scroll starts (millis) + const int AUTO_EXPAND_TIME = 500; // auto expand a hovered row (while DnD-ing) + const long MAX_SLIDE_TIME = 3599000; // milliseconds + + const int TIMELINE_SCROLL_MAX_DELTA = 25; // Maximum amount of pixels to scroll per frame + const int TIMELINE_SCROLL_DIVISOR = 6; // Divisor for timeline autoscroll distance + + // TODO: move the colors and dimensions to StudioPreferences. +} + +#endif // TIMELINECONSTANTS_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineControl.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineControl.cpp new file mode 100644 index 00000000..378b20da --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineControl.cpp @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "TimelineControl.h" +#include "TimelineGraphicsScene.h" +#include "RowManager.h" +#include "RowTree.h" +#include "Bindings/ITimelineItemBinding.h" +#include "StudioApp.h" +#include "Dialogs.h" + +TimelineControl::TimelineControl(TimelineGraphicsScene *scene) + : m_scene(scene) +{ +} + +void TimelineControl::setRowTimeline(RowTimeline *rowTimeline) +{ + m_rowTimeline = rowTimeline; + m_timebar = m_rowTimeline->rowTree()->getBinding()->GetTimelineItem()->GetTimebar(); + m_startTime = m_rowTimeline->getStartTime(); + m_endTime = m_rowTimeline->getEndTime(); + m_rowTimeline->updateBoundChildren(true); + m_rowTimeline->updateBoundChildren(false); +} + +void TimelineControl::showDurationEditDialog() +{ + g_StudioApp.GetDialogs()->asyncDisplayDurationEditDialog(m_startTime, m_endTime, this); +} + +void TimelineControl::ChangeStartTime(long inTime) +{ + m_rowTimeline->setStartTime(inTime); +} + +void TimelineControl::ChangeEndTime(long inTime) +{ + m_rowTimeline->setEndTime(inTime); + m_scene->rowManager()->updateRulerDuration(); +} + +void TimelineControl::Commit() +{ + m_timebar->ChangeTime(m_rowTimeline->getStartTime(), true); + m_timebar->ChangeTime(m_rowTimeline->getEndTime(), false); + m_timebar->CommitTimeChange(); +} + +void TimelineControl::Rollback() +{ + m_rowTimeline->setStartTime(m_startTime); + m_rowTimeline->setEndTime(m_endTime); + m_scene->rowManager()->updateRulerDuration(); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineControl.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineControl.h new file mode 100644 index 00000000..3c348016 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineControl.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef TIMELINECONTROL_H +#define TIMELINECONTROL_H + +#include "DurationEditDlg.h" +#include "RowTimeline.h" +#include "Bindings/ITimelineTimebar.h" + +class TimelineGraphicsScene; + +class TimelineControl : public ITimeChangeCallback +{ +public: + TimelineControl(TimelineGraphicsScene *scene); + + void setRowTimeline(RowTimeline *rowTimeline); + void showDurationEditDialog(); + + // ITimeChangeCallback + void ChangeStartTime(long) override; + void ChangeEndTime(long) override; + void Commit() override; + void Rollback() override; + +private: + TimelineGraphicsScene *m_scene = nullptr; + RowTimeline *m_rowTimeline = nullptr; + ITimelineTimebar *m_timebar = nullptr; + long m_startTime = 0; + long m_endTime = 0; +}; + +#endif // TIMELINECONTROL_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineGraphicsScene.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineGraphicsScene.cpp new file mode 100644 index 00000000..b4a52760 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineGraphicsScene.cpp @@ -0,0 +1,1199 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "TimelineGraphicsScene.h" +#include "TimelineItem.h" +#include "TreeHeader.h" +#include "Ruler.h" +#include "PlayHead.h" +#include "RowTree.h" +#include "RowMover.h" +#include "RowTimeline.h" +#include "TimelineConstants.h" +#include "TimelineToolbar.h" +#include "SelectionRect.h" +#include "RowManager.h" +#include "KeyframeManager.h" +#include "Keyframe.h" +#include "IDocumentEditor.h" +#include "StudioApp.h" +#include "Core.h" +#include "Doc.h" +#include "Bindings/Qt3DSDMTimelineItemBinding.h" +#include "ResourceCache.h" +#include "TimelineControl.h" +#include "RowTreeContextMenu.h" +#include "RowTimelineContextMenu.h" +#include "StudioPreferences.h" +#include "TimeEnums.h" +#include "StudioClipboard.h" +#include "Dialogs.h" +#include "Qt3DSDMStudioSystem.h" +#include "ClientDataModelBridge.h" + +#include <QtWidgets/qcombobox.h> +#include <QtWidgets/qgraphicssceneevent.h> +#include <QtWidgets/qgraphicslinearlayout.h> +#include <QtWidgets/qgraphicswidget.h> +#include <QtWidgets/qgraphicsview.h> +#include <QtWidgets/qscrollbar.h> +#include <QtWidgets/qmenu.h> +#include <QtWidgets/qlabel.h> +#include <QtWidgets/qaction.h> +#include <QtGui/qevent.h> +#include <QtCore/qtimer.h> +#include <QtCore/qglobal.h> +#include <QtWidgets/qaction.h> + +static const QPointF invalidPoint(-999999.0, -999999.0); + +TimelineGraphicsScene::TimelineGraphicsScene(TimelineWidget *timelineWidget) + : QGraphicsScene(timelineWidget) + , m_layoutRoot(new QGraphicsLinearLayout) + , m_layoutTree(new QGraphicsLinearLayout(Qt::Vertical)) + , m_layoutTimeline(new QGraphicsLinearLayout(Qt::Vertical)) + , m_ruler(new Ruler) + , m_playHead(new PlayHead(m_ruler)) + , m_widgetTimeline(timelineWidget) + , m_widgetRoot(new QGraphicsWidget) + , m_rowMover(new RowMover(this)) + , m_selectionRect(new SelectionRect()) + , m_rowManager(new RowManager(this, m_layoutTree, m_layoutTimeline)) + , m_keyframeManager(new KeyframeManager(this)) + , m_pressPos(invalidPoint) + , m_pressScreenPos(invalidPoint) + , m_timelineControl(new TimelineControl(this)) +{ + addItem(m_playHead); + addItem(m_selectionRect); + addItem(m_rowMover); + addItem(m_widgetRoot); + + m_timebarToolTip = new QLabel(m_widgetTimeline); + m_timebarToolTip->setObjectName(QStringLiteral("timebarToolTip")); + m_timebarToolTip->setWindowModality(Qt::NonModal); + m_timebarToolTip->setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip); + m_timebarToolTip->setContentsMargins(2, 2, 2, 2); + + m_variantsToolTip = new QLabel(m_widgetTimeline); + m_variantsToolTip->setObjectName(QStringLiteral("variantsToolTip")); + m_variantsToolTip->setWindowModality(Qt::NonModal); + m_variantsToolTip->setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip + | Qt::WindowTransparentForInput); + m_variantsToolTip->setContentsMargins(2, 2, 2, 2); + + connect(qApp, &QApplication::focusChanged, + this, &TimelineGraphicsScene::handleApplicationFocusLoss); + + m_rowMover->setVisible(false); + + m_autoScrollTimelineTimer.setInterval(10); // 10 ms + + m_layoutRoot->setSpacing(0); + m_layoutRoot->setContentsMargins(0, 0, 0, 0); + m_layoutRoot->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + m_widgetRoot->setLayout(m_layoutRoot); + + m_layoutTree->setSpacing(0); + m_layoutTree->setContentsMargins(0, 0, 0, 0); + m_layoutTree->setMinimumWidth(TimelineConstants::TREE_BOUND_W); + m_layoutTree->setMaximumWidth(TimelineConstants::TREE_BOUND_W); + m_layoutTree->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + m_layoutTimeline->setSpacing(0); + m_layoutTimeline->setContentsMargins(0, 0, 0, 0); + m_layoutTimeline->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + + m_layoutRoot->addItem(m_layoutTree); + m_layoutRoot->addItem(m_layoutTimeline); + + m_treeHeader = new TreeHeader(this); + + m_layoutTree->addItem(m_treeHeader); + m_layoutTimeline->addItem(m_ruler); + + // auto scrolling (when DnD is active and hovering on top or bottom of the tree list) + connect(&m_autoScrollTimer, &QTimer::timeout, [this]() { + QScrollBar *scrollbar = m_widgetTimeline->viewTreeContent()->verticalScrollBar(); + if (m_autoScrollUpOn) + scrollbar->setValue(scrollbar->value() - TimelineConstants::AUTO_SCROLL_DELTA); + else if (m_autoScrollDownOn) + scrollbar->setValue(scrollbar->value() + TimelineConstants::AUTO_SCROLL_DELTA); + }); + + connect(&m_autoScrollTimelineTimer, &QTimer::timeout, [this]() { + if (!qApp->focusWindow() && !g_StudioApp.isOnProgress()) { + resetMousePressParams(); + return; + } + QGraphicsView *timelineContent = m_widgetTimeline->viewTimelineContent(); + const QPoint scrollBarOffsets(getScrollbarOffsets()); + const QRect contentRect = timelineContent->contentsRect(); + const double right = timelineContent->width() - scrollBarOffsets.x(); + QPoint p = m_widgetTimeline->mapFromGlobal(QCursor::pos()) + - QPoint(m_widgetTimeline->viewTreeContent()->width() + + TimelineConstants::SPLITTER_W, 0); + + // Limit the maximum scroll speed + if (p.x() < 0) { + p.setX(qMax(-TimelineConstants::TIMELINE_SCROLL_MAX_DELTA, + p.x() / TimelineConstants::TIMELINE_SCROLL_DIVISOR)); + } else if (p.x() > right) { + p.setX(qMin(right + TimelineConstants::TIMELINE_SCROLL_MAX_DELTA, + right + 1 + ((p.x() - right) + / TimelineConstants::TIMELINE_SCROLL_DIVISOR))); + } + + if (m_selectionRect->isActive()) { + p -= QPoint(0, m_widgetTimeline->navigationBar()->height() + TimelineConstants::ROW_H); + const double bottom = timelineContent->contentsRect().height() - scrollBarOffsets.y(); + if (m_lastAutoScrollX != p.x() || p.x() <= 0 || p.x() >= right + || m_lastAutoScrollY != p.y() || p.y() <= 0 || p.y() >= bottom) { + m_lastAutoScrollX = p.x(); + m_lastAutoScrollY = p.y(); + + if (p.y() < 0) { + p.setY(qMax(-TimelineConstants::TIMELINE_SCROLL_MAX_DELTA, + p.y() / TimelineConstants::TIMELINE_SCROLL_DIVISOR)); + } else if (p.y() > bottom) { + p.setY(qMin(bottom + TimelineConstants::TIMELINE_SCROLL_MAX_DELTA, + bottom + 1 + ((p.y() - bottom) + / TimelineConstants::TIMELINE_SCROLL_DIVISOR))); + } + + // Resize keyframe selection rect + const QPointF scenePoint = timelineContent->mapToScene(p); + timelineContent->ensureVisible(scenePoint.x(), scenePoint.y(), + 0, 0, 0, 0); + QRectF visibleScene( + timelineContent->mapToScene(contentRect.topLeft()), + timelineContent->mapToScene(contentRect.bottomRight() + - scrollBarOffsets)); + m_selectionRect->updateSize(scenePoint, visibleScene); + m_keyframeManager->selectKeyframesInRect(m_selectionRect->rect()); + } + } else if (m_lastAutoScrollX != p.x() || p.x() <= 0 || p.x() >= right) { + m_lastAutoScrollX = p.x(); + + bool shift = QGuiApplication::queryKeyboardModifiers() & Qt::ShiftModifier; + double scroll = timelineContent->horizontalScrollBar()->value(); + if (scroll != 0) + scroll -= TimelineConstants::TREE_BOUND_W; + + double distance = p.x() + scroll - TimelineConstants::RULER_EDGE_OFFSET; + if (m_clickedTimelineControlType == TimelineControlType::Duration + && !m_editedTimelineRow.isNull()) { + distance -= m_editedTimelineRow->getDurationMoveOffsetX(); + } + + if (shift) + snap(distance, !m_rulerPressed); + + if (m_rulerPressed) { + long time = m_ruler->distanceToTime(distance); + if (time < 0) + time = 0; + g_StudioApp.GetCore()->GetDoc()->NotifyTimeChanged(time); + } else { + if (m_editedTimelineRow.isNull()) { + resetMousePressParams(); + return; + } + + if (m_dragging) { + if (m_clickedTimelineControlType == TimelineControlType::StartHandle) { + double visiblePtX = distance > 0 ? m_editedTimelineRow->getStartX() : 0; + if (distance > m_editedTimelineRow->getEndX()) + visiblePtX += TimelineConstants::RULER_EDGE_OFFSET; + + m_editedTimelineRow->setStartX(distance); + m_editedTimelineRow->showToolTip(QCursor::pos()); + timelineContent->ensureVisible(TimelineConstants::TREE_BOUND_W + + TimelineConstants::RULER_EDGE_OFFSET + + visiblePtX, + m_editedTimelineRow->y(), 0, 0, 0, 0); + } else if (m_clickedTimelineControlType == TimelineControlType::EndHandle) { + long time = m_ruler->distanceToTime(distance); + double edgeMargin = 0; + if (time > TimelineConstants::MAX_SLIDE_TIME) { + distance = m_ruler->timeToDistance(TimelineConstants::MAX_SLIDE_TIME); + edgeMargin = TimelineConstants::RULER_EDGE_OFFSET; + } else if (time < m_editedTimelineRow->getStartTime()) { + edgeMargin = -TimelineConstants::RULER_EDGE_OFFSET; + } + m_editedTimelineRow->setEndX(distance); + m_editedTimelineRow->showToolTip(QCursor::pos()); + rowManager()->updateRulerDuration(p.x() > right); + timelineContent->ensureVisible( + TimelineConstants::TREE_BOUND_W + + TimelineConstants::RULER_EDGE_OFFSET + + m_editedTimelineRow->getEndX() + edgeMargin, + m_editedTimelineRow->y(), 0, 0, 0, 0); + } else if (m_clickedTimelineControlType == TimelineControlType::Duration) { + long time = m_ruler->distanceToTime(distance) + + m_editedTimelineRow->getDuration(); // milliseconds + double visiblePtX = distance + + m_editedTimelineRow->getDurationMoveOffsetX(); + if (time > TimelineConstants::MAX_SLIDE_TIME) { + distance = m_ruler->timeToDistance(TimelineConstants::MAX_SLIDE_TIME + - m_editedTimelineRow->getDuration()); + visiblePtX = m_editedTimelineRow->getEndX() + + TimelineConstants::RULER_EDGE_OFFSET; + } + + m_editedTimelineRow->moveDurationTo(distance); + m_editedTimelineRow->showToolTip(QCursor::pos()); + rowManager()->updateRulerDuration(p.x() > right); + timelineContent->ensureVisible( + TimelineConstants::TREE_BOUND_W + + TimelineConstants::RULER_EDGE_OFFSET + visiblePtX, + m_editedTimelineRow->y(), 0, 0, 0, 0); + } + } + } + } + }); + + connect(&m_autoScrollTriggerTimer, &QTimer::timeout, [this]() { + m_autoScrollTimer.start(TimelineConstants::AUTO_SCROLL_PERIOD); + }); + + QTimer::singleShot(0, this, [this]() { + m_playHead->setPosition(0); + m_widgetTimeline->viewTreeContent()->horizontalScrollBar()->setValue(0); + }); + + QAction *action = new QAction(this); + action->setShortcut(Qt::Key_S); + action->setShortcutContext(Qt::ApplicationShortcut); + connect(action, &QAction::triggered, this, &TimelineGraphicsScene::handleInsertKeyframe); + timelineWidget->addAction(action); + + action = new QAction(this); + action->setShortcut(QKeySequence(Qt::ControlModifier | Qt::AltModifier | Qt::Key_K)); + action->setShortcutContext(Qt::ApplicationShortcut); + connect(action, &QAction::triggered, this, + &TimelineGraphicsScene::handleDeleteChannelKeyframes); + timelineWidget->addAction(action); + + action = new QAction(this); + action->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_T)); + action->setShortcutContext(Qt::ApplicationShortcut); + connect(action, &QAction::triggered, this, &TimelineGraphicsScene::handleSetTimeBarTime); + timelineWidget->addAction(action); + + action = new QAction(this); + action->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_G)); + action->setShortcutContext(Qt::ApplicationShortcut); + connect(action, &QAction::triggered, this, &TimelineGraphicsScene::handleMakeComponent); + timelineWidget->addAction(action); + + action = new QAction(this); + action->setShortcut(QKeySequence(Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_G)); + action->setShortcutContext(Qt::ApplicationShortcut); + connect(action, &QAction::triggered, this, &TimelineGraphicsScene::handleEditComponent); + timelineWidget->addAction(action); + + action = new QAction(this); + action->setShortcut(QKeySequence(Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_C)); + action->setShortcutContext(Qt::ApplicationShortcut); + connect(action, &QAction::triggered, this, &TimelineGraphicsScene::handleCopyObjectPath); + timelineWidget->addAction(action); + + action = new QAction(this); + action->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_H)); + action->setShortcutContext(Qt::ApplicationShortcut); + connect(action, &QAction::triggered, &g_StudioApp, &CStudioApp::toggleShy); + timelineWidget->addAction(action); + + action = new QAction(this); + action->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_H)); + action->setShortcutContext(Qt::ApplicationShortcut); + connect(action, &QAction::triggered, &g_StudioApp, &CStudioApp::toggleLocked); + timelineWidget->addAction(action); +} + +TimelineGraphicsScene::~TimelineGraphicsScene() +{ + disconnect(qApp, &QApplication::focusChanged, + this, &TimelineGraphicsScene::handleApplicationFocusLoss); + delete m_dataInputSelector; +} + +void TimelineGraphicsScene::setTimelineScale(int scl) +{ + m_ruler->setTimelineScale(scl); + m_playHead->updatePosition(); + updateTimelineLayoutWidth(); + + for (int i = 1; i < m_layoutTimeline->count(); i++) + static_cast<RowTimeline *>(m_layoutTimeline->itemAt(i)->graphicsItem())->updatePosition(); +} + +void TimelineGraphicsScene::setControllerText(const QString &controller) +{ + // check that we have scene/container root item at index 1 + if (m_layoutTimeline->count() < 2) + return; + + RowTimeline *rt = static_cast<RowTimeline *>(m_layoutTimeline->itemAt(1)->graphicsItem()); + rt->setControllerText(controller); +} + +void TimelineGraphicsScene::updateTimelineLayoutWidth() +{ + double timelineWidth = TimelineConstants::RULER_EDGE_OFFSET * 2 + + m_ruler->maxDuration() * TimelineConstants::RULER_MILLI_W + * m_ruler->timelineScale(); + + m_layoutTimeline->setMinimumWidth(timelineWidth); + m_layoutTimeline->setMaximumWidth(timelineWidth); +} + +void TimelineGraphicsScene::updateControllerLayoutWidth() +{ + if (m_layoutTimeline->count() < 2) + return; + auto root = m_layoutTimeline->itemAt(1); + + static_cast<RowTimeline *>(root->graphicsItem())->setEndTime(ruler()->duration()); +} + +void TimelineGraphicsScene::updateController() +{ + setControllerText(m_widgetTimeline->toolbar()->getCurrentController()); +} + +void TimelineGraphicsScene::commitMoveRows() +{ + if (!m_rowMover->insertionTarget() + || m_rowMover->sourceRows().contains(m_rowMover->insertionTarget())) { + return; + } + + // handles for the moving rows + qt3dsdm::TInstanceHandleList sourceHandles; + const auto sourceRows = m_rowMover->sourceRows(); + for (auto sourceRow : sourceRows) { + qt3dsdm::Qt3DSDMInstanceHandle handleSource = static_cast<Qt3DSDMTimelineItemBinding *> + (sourceRow->getBinding())->GetInstance(); + sourceHandles.push_back(handleSource); + } + qt3dsdm::Qt3DSDMInstanceHandle handleTarget = static_cast<Qt3DSDMTimelineItemBinding *> + (m_rowMover->insertionTarget()->getBinding())->GetInstance(); + + if (!m_rowMover->insertionParent()->expanded()) + m_rowMover->insertionParent()->updateExpandStatus(RowTree::ExpandState::Expanded, false); + + // Remove sourcerows for items that will be deleted as result of RearrangeObjects, + // f.ex objects that will be moved to a component; otherwise we try to update + // timeline rows that no longer have valid scene objects linked to them. + // Note that we remove all sourcerows that are being dragged currently, because they + // all share the same drop target anyway. + if (m_rowMover->shouldDeleteAfterMove()) { + for (auto sourceRow : sourceRows) + m_rowMover->removeSourceRow(sourceRow); + } + + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*g_StudioApp.GetCore()->GetDoc(), + QObject::tr("Move Rows")) + ->RearrangeObjects(sourceHandles, handleTarget, m_rowMover->insertionType()); + + // updating the UI happens in TimelineWidget.onChildAdded() +} + +void TimelineGraphicsScene::updateTreeWidth(double treeWidth) +{ + if (m_treeWidth != treeWidth) { + m_treeWidth = treeWidth; + update(); + } +} + +double TimelineGraphicsScene::treeWidth() const +{ + return m_treeWidth; +} + +void TimelineGraphicsScene::setMouseCursor(CMouseCursor::Qt3DSMouseCursor cursor) +{ + if (m_currentCursor != cursor) { + if (m_currentCursor != -1) + qApp->changeOverrideCursor(CResourceCache::GetInstance()->GetCursor(cursor)); + else + qApp->setOverrideCursor(CResourceCache::GetInstance()->GetCursor(cursor)); + m_currentCursor = cursor; + } +} + +void TimelineGraphicsScene::resetMouseCursor() +{ + if (m_currentCursor != -1) { + // Restoring back to no-override state seems to not change the cursor automatically + // to the default cursor, so let's do that manually before restoring the cursor + qApp->changeOverrideCursor(Qt::ArrowCursor); + qApp->restoreOverrideCursor(); + m_currentCursor = -1; + } +} + +void TimelineGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + g_StudioApp.setLastActiveView(m_widgetTimeline); + + if ((event->modifiers() & Qt::AltModifier) && !m_dragging) { + if (event->button() == Qt::RightButton && !m_timelinePanning) { + // Start zooming + m_timelineZooming = true; + m_pressScreenPos = event->screenPos(); + event->accept(); + return; + } else if (event->button() == Qt::MiddleButton && !m_timelineZooming) { + // Start panning + m_timelinePanning = true; + m_pressPos = event->scenePos(); + event->accept(); + return; + } + } + + // Ignore non-left presses if dragging + if (event->button() != Qt::LeftButton && (m_dragging || m_startRowMoverOnNextDrag)) { + event->accept(); + return; + } + + if (m_widgetTimeline->blockMousePress()) + return; + + if (!m_widgetTimeline->isFullReconstructPending() && event->button() == Qt::LeftButton) { + resetMousePressParams(); + m_pressPos = event->scenePos(); + QGraphicsItem *item = itemAt(m_pressPos, QTransform()); + const bool ctrlKeyDown = event->modifiers() & Qt::ControlModifier; + if (item) { + item = getItemBelowType(TimelineItem::TypePlayHead, item, m_pressPos); + if (item->type() == TimelineItem::TypeRuler) { + m_rulerPressed = true; + m_autoScrollTimelineTimer.start(); + } else if (item->type() == TimelineItem::TypeTreeHeader) { + if (m_treeHeader->handleButtonsClick(m_pressPos) != TreeControlType::None) { + m_rowManager->updateFiltering(); + updateSnapSteps(); + } + } else if (item->type() == TimelineItem::TypeRowTree + || item->type() == TimelineItem::TypeRowTreeLabelItem) { + item = getItemBelowType(TimelineItem::TypeRowTreeLabelItem, item, m_pressPos); + RowTree *rowTree = static_cast<RowTree *>(item); + m_clickedTreeControlType = rowTree->getClickedControl(m_pressPos); + if (m_clickedTreeControlType == TreeControlType::Shy + || m_clickedTreeControlType == TreeControlType::Hide + || m_clickedTreeControlType == TreeControlType::Lock) { + m_rowManager->updateFiltering(rowTree); + updateSnapSteps(); + } else if (m_clickedTreeControlType == TreeControlType::None) { + // Prepare to change selection to single selection at release if a multiselected + // row is clicked without ctrl. + if (!ctrlKeyDown && m_rowManager->isRowSelected(rowTree) + && !m_rowManager->isSingleSelected() ) { + m_releaseSelectRow = rowTree; + } + m_rowManager->selectRow(rowTree, ctrlKeyDown); + if (rowTree->draggable()) + m_startRowMoverOnNextDrag = true; + } else if (m_clickedTreeControlType == TreeControlType::Arrow) { + updateSnapSteps(); + } + } else if (item->type() == TimelineItem::TypeRowTimeline) { + m_editedTimelineRow = static_cast<RowTimeline *>(item); + Keyframe *keyframe = m_editedTimelineRow->getClickedKeyframe(m_pressPos); + if (keyframe) { // pressed a keyframe + if (ctrlKeyDown && keyframe->selected()) { + if (m_editedTimelineRow->rowTree()->isProperty()) + m_keyframeManager->deselectKeyframe(keyframe); + else + m_keyframeManager->deselectConnectedKeyframes(keyframe); + } else { + if (!ctrlKeyDown && !keyframe->selected()) + m_keyframeManager->deselectAllKeyframes(); + + if (m_editedTimelineRow->rowTree()->isProperty()) + m_keyframeManager->selectKeyframe(keyframe); + else + m_keyframeManager->selectConnectedKeyframes(keyframe); + + m_pressPosInKeyframe = (m_pressPos.x() - m_ruler->x()) + - (TimelineConstants::RULER_EDGE_OFFSET + + m_ruler->timeToDistance(keyframe->time)); + m_pressedKeyframe = keyframe; + } + } else { + m_keyframeManager->deselectAllKeyframes(); + m_clickedTimelineControlType + = m_editedTimelineRow->getClickedControl(m_pressPos); + + // clicked an empty spot on a timeline row, start selection rect. + if (m_clickedTimelineControlType == TimelineControlType::None) { + m_selectionRect->start(m_pressPos); + } else if (m_clickedTimelineControlType == TimelineControlType::Duration) { + if (!ctrlKeyDown + && m_rowManager->isRowSelected(m_editedTimelineRow->rowTree()) + && !m_rowManager->isSingleSelected()) { + m_releaseSelectRow = m_editedTimelineRow->rowTree(); + } + + m_rowManager->selectRow(m_editedTimelineRow->rowTree(), ctrlKeyDown); + // click position in ruler space + m_editedTimelineRow->startDurationMove(m_pressPos.x() - m_ruler->x()); + } else if (m_clickedTimelineControlType == TimelineControlType::StartHandle + || m_clickedTimelineControlType == TimelineControlType::EndHandle) { + m_editedTimelineRow->updateBoundChildren( + m_clickedTimelineControlType + == TimelineControlType::StartHandle); + } + m_autoScrollTimelineTimer.start(); + } + } + } else { + m_keyframeManager->deselectAllKeyframes(); + + if (m_pressPos.x() > m_ruler->x() && m_pressPos.y() > TimelineConstants::ROW_H) { + m_selectionRect->start(m_pressPos); + m_autoScrollTimelineTimer.start(); + } + } + } + + QGraphicsScene::mousePressEvent(event); +} + +void TimelineGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + if (m_timelineZooming) { + int deltaY = event->screenPos().y() - m_pressScreenPos.y(); + int deltaX = event->screenPos().x() - m_pressScreenPos.x(); + // Zooming in when moving down/right. + int delta = -deltaX - deltaY; + const int threshold = 20; + if (delta < -threshold) { + m_widgetTimeline->toolbar()->onZoomInButtonClicked(); + m_pressScreenPos = event->screenPos(); + } else if (delta > threshold) { + m_widgetTimeline->toolbar()->onZoomOutButtonClicked(); + m_pressScreenPos = event->screenPos(); + } + } else if (m_timelinePanning) { + int deltaX = event->scenePos().x() - m_pressPos.x(); + QScrollBar *scrollbar = m_widgetTimeline->viewTimelineContent()->horizontalScrollBar(); + scrollbar->setValue(scrollbar->value() - deltaX); + } + + if (m_editedTimelineRow.isNull()) + updateHoverStatus(event->scenePos()); + + if (!m_dragging && !m_timelineZooming && !m_timelinePanning + && m_pressPos != invalidPoint + && (event->scenePos() - m_pressPos).manhattanLength() > 10) { + m_dragging = true; + } + + bool shift = event->modifiers() & Qt::ShiftModifier; + if (m_dragging) { + if (m_startRowMoverOnNextDrag || m_rowMover->isActive()) { + // moving rows vertically (reorder/reparent) + if (m_startRowMoverOnNextDrag) { + m_startRowMoverOnNextDrag = false; + m_rowMover->start(m_rowManager->selectedRows()); + } + if (m_rowMover->isActive()) { + m_rowMover->updateTargetRow(event->scenePos()); + updateAutoScrolling(event->scenePos().y()); + } + } else if (m_pressedKeyframe) { // moving selected keyframes + double newX = event->scenePos().x() - m_ruler->x() + - TimelineConstants::RULER_EDGE_OFFSET - m_pressPosInKeyframe; + + if (newX < 0) + newX = 0; + if (shift) + snap(newX); + + m_keyframeManager->moveSelectedKeyframes(ruler()->distanceToTime(newX)); + + m_pressPos.setX(newX); + } + } + + QGraphicsScene::mouseMoveEvent(event); +} + +// auto scroll when the mouse is at the top or bottom of the tree list +void TimelineGraphicsScene::updateAutoScrolling(double scenePosY) +{ + QScrollBar *scrollbar = m_widgetTimeline->viewTreeContent()->verticalScrollBar(); + double mouseY = scenePosY - scrollbar->value(); + int bottomY = m_widgetTimeline->height() - m_widgetTimeline->toolbar()->height() + - TimelineConstants::ROW_H; + if (mouseY > 0 && mouseY < TimelineConstants::ROW_H) { + if (!m_autoScrollUpOn) { + m_autoScrollTriggerTimer.start(TimelineConstants::AUTO_SCROLL_TRIGGER); + m_autoScrollUpOn = true; + } + } else if (m_autoScrollUpOn) { + m_autoScrollTimer.stop(); + m_autoScrollTriggerTimer.stop(); + m_autoScrollUpOn = false; + } + + if (mouseY > bottomY - TimelineConstants::ROW_H - TimelineConstants::TOOLBAR_MARGIN + && mouseY < bottomY) { + if (!m_autoScrollDownOn) { + m_autoScrollTriggerTimer.start(TimelineConstants::AUTO_SCROLL_TRIGGER); + m_autoScrollDownOn = true; + } + } else if (m_autoScrollDownOn) { + m_autoScrollTimer.stop(); + m_autoScrollTriggerTimer.stop(); + m_autoScrollDownOn = false; + } +} + +void TimelineGraphicsScene::stopAutoScroll() { + m_autoScrollTimer.stop(); + m_autoScrollTriggerTimer.stop(); + m_autoScrollUpOn = false; + m_autoScrollDownOn = false; +} + +void TimelineGraphicsScene::updateSnapSteps() +{ + m_snapSteps.clear(); + // i = 1 is always the scene row (or component root) + for (int i = 2; i < m_layoutTimeline->count(); i++) { + RowTree *rowTree = static_cast<RowTree *>(m_layoutTree->itemAt(i)->graphicsItem()); + if (rowTree->hasDurationBar() && rowTree->isVisible()) { + double startX = rowTree->rowTimeline()->getStartX(); + if (!m_snapSteps.contains(startX)) + m_snapSteps.push_back(startX); + + double endX = rowTree->rowTimeline()->getEndX(); + if (!m_snapSteps.contains(endX)) + m_snapSteps.push_back(endX); + + // add keyframes times + if (rowTree->hasPropertyChildren()) { + const QList<Keyframe *> keyframes = rowTree->rowTimeline()->keyframes(); + for (Keyframe *k : keyframes) { + double kX = m_ruler->timeToDistance(k->time); + if (!m_snapSteps.contains(kX)) + m_snapSteps.push_back(kX); + } + } + } + } +} + +TExpandMap &TimelineGraphicsScene::expandMap() +{ + return m_expandMap; +} + +void TimelineGraphicsScene::resetMousePressParams() +{ + m_autoScrollTimelineTimer.stop(); + m_selectionRect->end(); + m_rowMover->end(); + m_dragging = false; + m_timelineZooming = false; + m_timelinePanning = false; + m_startRowMoverOnNextDrag = false; + m_rulerPressed = false; + m_pressedKeyframe = nullptr; + m_clickedTimelineControlType = TimelineControlType::None; + m_editedTimelineRow.clear(); + m_releaseSelectRow.clear(); + m_autoScrollTimer.stop(); + m_autoScrollTriggerTimer.stop(); + m_timebarToolTip->hide(); + m_pressPos = invalidPoint; + m_pressScreenPos = invalidPoint; + m_lastAutoScrollX = -1.0; + m_lastAutoScrollY = -1.0; +} + +void TimelineGraphicsScene::resetPressedKeyframe() +{ + m_pressedKeyframe = nullptr; +} + +QLabel *TimelineGraphicsScene::timebarTooltip() +{ + return m_timebarToolTip; +} + +void TimelineGraphicsScene::snap(double &value, bool snapToPlayHead) +{ + // snap to play head + if (snapToPlayHead) { + double playHeadX = m_playHead->x() - TimelineConstants::TREE_BOUND_W + - TimelineConstants::RULER_EDGE_OFFSET; + if (abs(value - playHeadX) < CStudioPreferences::GetSnapRange()) { + value = playHeadX; + return; + } + } + + // duration edges snap + for (double v : qAsConst(m_snapSteps)) { + if (abs(value - v) < CStudioPreferences::GetSnapRange()) { + value = v; + return; + } + } + + // time steps snap + if (CStudioPreferences::IsTimelineSnappingGridActive()) { + double snapStep = TimelineConstants::RULER_SEC_W * m_ruler->timelineScale(); + if (CStudioPreferences::GetTimelineSnappingGridResolution() == SNAPGRID_HALFSECONDS) + snapStep *= .5; + else if (CStudioPreferences::GetTimelineSnappingGridResolution() == SNAPGRID_TICKMARKS) + snapStep *= .1; + + double snapValue = round(value / snapStep) * snapStep; + if (abs(value - snapValue) < CStudioPreferences::GetSnapRange()) + value = snapValue; + } +} + +void TimelineGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + if (m_dragging) { + if (m_rowMover->isActive()) { // moving rows (reorder/reparent) + commitMoveRows(); + } else if (m_pressedKeyframe) { + // update keyframe movement (time) to binding + m_keyframeManager->commitMoveSelectedKeyframes(); + } else if (m_clickedTimelineControlType == TimelineControlType::StartHandle) { + if (!m_editedTimelineRow.isNull()) { + ITimelineTimebar *timebar = m_editedTimelineRow->rowTree()->getBinding() + ->GetTimelineItem()->GetTimebar(); + timebar->ChangeTime(m_editedTimelineRow->getStartTime(), true); + timebar->CommitTimeChange(); + } + } else if (m_clickedTimelineControlType == TimelineControlType::EndHandle) { + if (!m_editedTimelineRow.isNull()) { + ITimelineTimebar *timebar = m_editedTimelineRow->rowTree()->getBinding() + ->GetTimelineItem()->GetTimebar(); + timebar->ChangeTime(m_editedTimelineRow->getEndTime(), false); + timebar->CommitTimeChange(); + if (m_playHead->time() > ruler()->duration()) + g_StudioApp.GetCore()->GetDoc()->NotifyTimeChanged(ruler()->duration()); + } + } else if (m_clickedTimelineControlType == TimelineControlType::Duration) { + if (!m_editedTimelineRow.isNull()) { + ITimelineTimebar *timebar = m_editedTimelineRow->rowTree()->getBinding() + ->GetTimelineItem()->GetTimebar(); + timebar->OffsetTime(m_editedTimelineRow->getDurationMoveTime()); + timebar->CommitTimeChange(); + if (m_playHead->time() > ruler()->duration()) + g_StudioApp.GetCore()->GetDoc()->NotifyTimeChanged(ruler()->duration()); + } + } + } else if (!m_rulerPressed && (!m_releaseSelectRow.isNull() || !itemAt(event->scenePos(), + QTransform()))) { + m_rowManager->selectRow(nullptr); + if (!m_releaseSelectRow.isNull()) + m_rowManager->selectRow(m_releaseSelectRow); + } + } + + if (m_timelineZooming) + updateSnapSteps(); + + resetMousePressParams(); + + QGraphicsScene::mouseReleaseEvent(event); +} + +void TimelineGraphicsScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + const QPointF scenePos = event->scenePos(); + QGraphicsItem *item = itemAt(scenePos, QTransform()); + if (item) { + QGraphicsItem *itemBelowPlayhead = + getItemBelowType(TimelineItem::TypePlayHead, item, scenePos); + if (item->type() == TimelineItem::TypeRuler + || itemBelowPlayhead->type() == TimelineItem::TypeRuler) { + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + g_StudioApp.GetDialogs()->asyncDisplayTimeEditDialog(doc->GetCurrentViewTime(), + doc, PLAYHEAD, + m_keyframeManager); + } else { + item = itemBelowPlayhead; + if (item->type() == TimelineItem::TypeRowTree) { + RowTree *treeItem = static_cast<RowTree *>(item); + if (treeItem->isProperty()) + treeItem->togglePropertyExpanded(); + } else if (item->type() == TimelineItem::TypeRowTreeLabelItem) { + RowTreeLabelItem *treeLabelItem = static_cast<RowTreeLabelItem *>(item); + if (treeLabelItem->parentRow()->isProperty()) { + treeLabelItem->parentRow()->togglePropertyExpanded(); + } else if (!treeLabelItem->isLocked() + && treeLabelItem->parentRow()->objectType() != OBJTYPE_SCENE + && treeLabelItem->parentRow()->objectType() != OBJTYPE_IMAGE) { + int instance = treeLabelItem->parentRow()->instance(); + const auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem() + ->GetClientDataModelBridge(); + if (bridge->GetObjectType(instance) != OBJTYPE_REFERENCEDMATERIAL + || bridge->GetSourcePath(instance).isEmpty()) { + // Tree labels text can be edited with double-click, + // except for Scene label and basic materials + treeLabelItem->setEnabled(true); + treeLabelItem->setFocus(); + } + } + } else if (item->type() == TimelineItem::TypeRowTimeline) { + RowTimeline *rowTimeline = static_cast<RowTimeline *>(item); + Keyframe *clickedKeyframe = rowTimeline->getClickedKeyframe(scenePos); + if (clickedKeyframe) { + m_pressedKeyframe = clickedKeyframe; + g_StudioApp.GetDialogs()->asyncDisplayTimeEditDialog( + clickedKeyframe->time, g_StudioApp.GetCore()->GetDoc(), + ASSETKEYFRAME, m_keyframeManager); + } else { + if (!rowTimeline->rowTree()->locked()) + handleSetTimeBarTime(); + } + } + } + } + } + + QGraphicsScene::mouseDoubleClickEvent(event); +} + +void TimelineGraphicsScene::wheelEvent(QGraphicsSceneWheelEvent *wheelEvent) +{ + // Make sure drag states update on wheel scrolls done during drag + m_lastAutoScrollX = -1.0; + m_lastAutoScrollY = -1.0; + QGraphicsScene::wheelEvent(wheelEvent); +} + +void TimelineGraphicsScene::keyPressEvent(QKeyEvent *keyEvent) +{ + // Eat left/right arrow keys on tree side unless some item (e.g. label) has focus + if ((keyEvent->key() == Qt::Key_Left || keyEvent->key() == Qt::Key_Right) + && (qApp->focusObject() == m_widgetTimeline->viewTreeContent() && !focusItem())) { + keyEvent->accept(); + return; + } else if (keyEvent->key() == Qt::Key_Escape && m_rowMover->isActive()) { + m_rowMover->end(); + } else if (keyEvent->key() == Qt::Key_Delete && !m_rowMover->isActive() + && !focusItem()) { + g_StudioApp.DeleteSelectedObject(); // Despite the name, this deletes objects and keyframes + } + // Make sure drag states update on keyboard scrolls done during drag + if (keyEvent->key() == Qt::Key_Left || keyEvent->key() == Qt::Key_Right + || keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down) { + m_lastAutoScrollX = -1.0; + m_lastAutoScrollY = -1.0; + } + + QGraphicsScene::keyPressEvent(keyEvent); +} + +void TimelineGraphicsScene::keyReleaseEvent(QKeyEvent *keyEvent) +{ + QGraphicsScene::keyReleaseEvent(keyEvent); +} + +void TimelineGraphicsScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) +{ + // No context menu if user is pressing ALT (so panning/zooming timeline) + bool alt = event->modifiers() & Qt::AltModifier; + RowTree *row = m_rowManager->getRowAtPos(QPointF(0, event->scenePos().y())); + if (!row || m_widgetTimeline->isFullReconstructPending() || m_dragging + || m_startRowMoverOnNextDrag || row->locked() || alt) { + return; + } + + resetMousePressParams(); // Make sure our mouse handling doesn't get confused by context menu + + // Internally some things like make component depend on the correct row being selected, + // so make sure it is. + m_rowManager->selectRow(row); + if (event->scenePos().x() > TimelineConstants::TREE_BOUND_W) { // timeline context menu + RowTimelineContextMenu timelineContextMenu(row, m_keyframeManager, event, + m_timelineControl); + timelineContextMenu.exec(event->screenPos()); + } else { // tree context menu + if (!row->isProperty()) { + RowTreeContextMenu treeContextMenu(row); + treeContextMenu.exec(event->screenPos()); + } + } +} + +bool TimelineGraphicsScene::event(QEvent *event) +{ + switch (event->type()) { + case QEvent::ShortcutOverride: + if (static_cast<QKeyEvent *>(event)->key() == Qt::Key_Delete) { + QGraphicsScene::keyPressEvent(static_cast<QKeyEvent *>(event)); + event->accept(); + return true; + } + Q_FALLTHROUGH(); + + default: + return QGraphicsScene::event(event); + } +} + +void TimelineGraphicsScene::updateHoverStatus(const QPointF &scenePos) +{ + bool variantsAreaHovered = false; + QGraphicsItem *item = itemAt(scenePos, QTransform()); + if (item) { + item = getItemBelowType(TimelineItem::TypePlayHead, item, scenePos); + // update timeline row cursor + if (item->type() == TimelineItem::TypeRowTimeline) { + RowTimeline *timelineItem = static_cast<RowTimeline *>(item); + TimelineControlType controlType = timelineItem->getClickedControl(scenePos); + if (controlType == TimelineControlType::StartHandle + || controlType == TimelineControlType::EndHandle) { + setMouseCursor(CMouseCursor::CURSOR_RESIZE_LEFTRIGHT); + } else { + resetMouseCursor(); + } + } else if (!m_dragging && (item->type() == TimelineItem::TypeRowTree + || item->type() == TimelineItem::TypeRowTreeLabelItem)) { + // update tree row variants tooltip + RowTree *rowTree = item->type() == TimelineItem::TypeRowTree + ? static_cast<RowTree *>(item) + : static_cast<RowTreeLabelItem *>(item)->parentRow(); + if (!rowTree->isProperty()) { + int left = rowTree->clipX(); + int right = (int)rowTree->treeWidth() - TimelineConstants::TREE_ICONS_W; + variantsAreaHovered = scenePos.x() > left && scenePos.x() < right; + if (variantsAreaHovered && rowTree != m_variantsRowTree) { + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem(); + const auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge(); + auto property = bridge->getVariantsProperty(rowTree->instance()); + + using namespace qt3dsdm; + SValue sValue; + if (propertySystem->GetInstancePropertyValue(rowTree->instance(), property, + sValue)) { + QString propVal = get<TDataStrPtr>(sValue)->toQString(); + if (!propVal.isEmpty()) { + // parse propVal into variantsHash (group => tags) + const QStringList tagPairs = propVal.split(QLatin1Char(',')); + QHash<QString, QStringList> variantsHash; + QStringList variantsHashKeys; // maintain traverse order + for (auto &tagPair : tagPairs) { + const QStringList pair = tagPair.split(QLatin1Char(':')); + variantsHash[pair[0]].append(pair[1]); + if (!variantsHashKeys.contains(pair[0])) + variantsHashKeys.append(pair[0]); + } + + // parse variantsHash into tooltipStr + const auto variantsDef + = g_StudioApp.GetCore()->getProjectFile().variantsDef(); + QString templ = QStringLiteral("<font color='%1'>%2</font>"); + QString tooltipStr("<table>"); + for (auto &g : qAsConst(variantsHashKeys)) { + tooltipStr.append("<tr><td>"); + tooltipStr.append(templ.arg(variantsDef[g].m_color).arg(g + ": ")); + tooltipStr.append("</td><td>"); + for (auto &t : qAsConst(variantsHash[g])) + tooltipStr.append(t + ", "); + tooltipStr.chop(2); + tooltipStr.append("</td></tr>"); + } + tooltipStr.append("</table>"); + + int ttY = int(rowTree->y()) + + widgetTimeline()->navigationBar()->height(); + + m_variantsToolTip->setText(tooltipStr); + m_variantsToolTip->adjustSize(); + m_variantsToolTip->move(m_widgetTimeline->mapToGlobal({right, ttY})); + m_variantsToolTip->raise(); + m_variantsToolTip->show(); + m_variantsRowTree = rowTree; + } + } + } + } + } + } + + if (m_variantsRowTree && !variantsAreaHovered) { + m_variantsToolTip->hide(); + m_variantsRowTree = nullptr; + } +} + +// Return next item below [type] item, or item itself +// Used at least for skipping PlayHead and RowTreeLabelItem +QGraphicsItem *TimelineGraphicsScene::getItemBelowType(TimelineItem::ItemType type, + QGraphicsItem *item, + const QPointF &scenePos) const +{ + if (item->type() == type) { + const QList<QGraphicsItem *> hoverItems = items(scenePos); + if (hoverItems.size() > 1) + return hoverItems.at(1); + } + return item; +} + +QPoint TimelineGraphicsScene::getScrollbarOffsets() const +{ + QGraphicsView *timelineContent = m_widgetTimeline->viewTimelineContent(); + return QPoint(timelineContent->verticalScrollBar()->isVisible() + ? timelineContent->verticalScrollBar()->width() : 0, + timelineContent->horizontalScrollBar()->isVisible() + ? timelineContent->horizontalScrollBar()->height() : 0); +} + +void TimelineGraphicsScene::handleInsertKeyframe() +{ + RowTree *selectedRow = m_rowManager->selectedRow(); + if (selectedRow) + selectedRow->getBinding()->InsertKeyframe(); +} + +void TimelineGraphicsScene::handleDeleteChannelKeyframes() +{ + RowTree *selectedRow = m_rowManager->selectedRow(); + if (selectedRow) + selectedRow->getBinding()->DeleteAllChannelKeyframes(); +} + +void TimelineGraphicsScene::handleSetTimeBarTime() +{ + RowTree *selectedRow = m_rowManager->selectedRow(); + if (selectedRow && selectedRow->hasDurationBar()) { + m_timelineControl->setRowTimeline(selectedRow->rowTimeline()); + m_timelineControl->showDurationEditDialog(); + } +} + +void TimelineGraphicsScene::handleMakeComponent() +{ + RowTree *selectedRow = m_rowManager->selectedRow(); + if (selectedRow) { + selectedRow->getBinding()->PerformTransaction( + ITimelineItemBinding::EUserTransaction_MakeComponent); + } +} + +void TimelineGraphicsScene::handleCopyObjectPath() +{ + RowTree *selectedRow = m_rowManager->selectedRow(); + if (selectedRow) { + CStudioClipboard::CopyTextToClipboard( + selectedRow->getBinding()->GetObjectPath().toQString()); + } +} + +void TimelineGraphicsScene::handleEditComponent() +{ + RowTree *selectedRow = m_rowManager->selectedRow(); + if (selectedRow && selectedRow->getBinding()->IsValidTransaction( + ITimelineItemBinding::EUserTransaction_EditComponent)) { + selectedRow->getBinding()->OpenAssociatedEditor(); + } +} + +void TimelineGraphicsScene::handleApplicationFocusLoss() +{ + // Hide the timebar and variants tooltips if application loses focus + if (!QApplication::focusWidget()) { + m_timebarToolTip->hide(); + m_variantsToolTip->hide(); + } +} + +void TimelineGraphicsScene::handleShowDISelector(const QString &propertyname, + qt3dsdm::Qt3DSDMInstanceHandle inInst, + const QPoint &pos) +{ + auto doc = g_StudioApp.GetCore()->GetDoc(); + qt3dsdm::Qt3DSDMPropertyHandle propHandle = doc->GetPropertySystem() + ->GetAggregateInstancePropertyByName(inInst, propertyname.toStdWString().c_str()); + + QVector<EDataType> allowedTypes = CDataInputDlg::getAcceptedTypes( + doc->GetPropertySystem()->GetDataType(propHandle)); + + // Instantiate selector in TimelineGraphicsScene instead of the originating context menu, + // as context menu gets destructed when a selection is made. + if (!m_dataInputSelector) + m_dataInputSelector = new DataInputSelectView(allowedTypes, widgetTimeline()); + + QVector<QPair<QString, int>> dataInputList; + for (auto &it : qAsConst(g_StudioApp.m_dataInputDialogItems)) + dataInputList.append({it->name, it->type}); + // needs to be set just in case we are reusing an existing datainput selector instance + m_dataInputSelector->setMatchingTypes(allowedTypes); + m_dataInputSelector->setTypeFilter(DataInputTypeFilter::MatchingTypes); + m_dataInputSelector->setData(dataInputList, m_dataInputSelector->getNoneString(), + propHandle, inInst); + m_dataInputSelector->setCurrentController(doc->GetCurrentController(inInst, propHandle)); + + connect(m_dataInputSelector, &DataInputSelectView::dataInputChanged, + [&](int handle, int instance, const QString &controllerName) { + bool controlled = controllerName != m_dataInputSelector->getNoneString(); + g_StudioApp.GetCore()->GetDoc() + ->SetInstancePropertyControlled(instance, Q3DStudio::CString(), handle, + Q3DStudio::CString::fromQString(controllerName), + controlled); + }); + + CDialogs::showWidgetBrowser(widgetTimeline(), m_dataInputSelector, pos); +} + +// Getters +Ruler *TimelineGraphicsScene::ruler() const { return m_ruler; } +PlayHead *TimelineGraphicsScene::playHead() const { return m_playHead; } +TreeHeader *TimelineGraphicsScene::treeHeader() const { return m_treeHeader; } +RowMover *TimelineGraphicsScene::rowMover() const { return m_rowMover; } +RowManager *TimelineGraphicsScene::rowManager() const { return m_rowManager; } +QGraphicsWidget *TimelineGraphicsScene::widgetRoot() const { return m_widgetRoot; } +KeyframeManager *TimelineGraphicsScene::keyframeManager() const { return m_keyframeManager; } +QGraphicsLinearLayout *TimelineGraphicsScene::layoutTree() const { return m_layoutTree; } +QGraphicsLinearLayout *TimelineGraphicsScene::layoutTimeline() const { return m_layoutTimeline; } +TimelineWidget *TimelineGraphicsScene::widgetTimeline() const { return m_widgetTimeline; } +Keyframe *TimelineGraphicsScene::pressedKeyframe() const { return m_pressedKeyframe; } diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineGraphicsScene.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineGraphicsScene.h new file mode 100644 index 00000000..146009ee --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineGraphicsScene.h @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef TIMELINEGRAPHICSSCENE_H +#define TIMELINEGRAPHICSSCENE_H + +#include "RowTree.h" +#include "TimelineWidget.h" +#include "RowTimeline.h" +#include "RowTypes.h" +#include "TimelineConstants.h" +#include "MouseCursor.h" +#include "DataInputSelectView.h" + +#include <QtWidgets/qgraphicsscene.h> +#include <QtCore/qlist.h> +#include <QtCore/qhash.h> +#include <QtCore/qpointer.h> + +class Ruler; +class PlayHead; +class TreeHeader; +class SelectionRect; +class RowMover; +class RowManager; +class KeyframeManager; +class TimelineControl; +class IKeyframesManager; +struct Keyframe; + +QT_FORWARD_DECLARE_CLASS(QGraphicsLinearLayout) +QT_FORWARD_DECLARE_CLASS(QGraphicsView) +QT_FORWARD_DECLARE_CLASS(QLabel) + +typedef QHash<qt3dsdm::Qt3DSDMInstanceHandle, RowTree::ExpandState> TExpandMap; + +class TimelineGraphicsScene : public QGraphicsScene +{ + Q_OBJECT + +public: + explicit TimelineGraphicsScene(TimelineWidget *timelineWidget); + virtual ~TimelineGraphicsScene(); + + void setTimelineScale(int scale); + void setControllerText(const QString &controller); + void updateTimelineLayoutWidth(); + void updateControllerLayoutWidth(); + void updateController(); + Ruler *ruler() const; + PlayHead *playHead() const; + RowManager *rowManager() const; + RowMover *rowMover() const; + QGraphicsWidget *widgetRoot() const; + KeyframeManager *keyframeManager() const; + QGraphicsLinearLayout *layoutTree() const; + QGraphicsLinearLayout *layoutTimeline() const; + TreeHeader *treeHeader() const; + double treeWidth() const; + TimelineWidget *widgetTimeline() const; + void updateTreeWidth(double x); + void setMouseCursor(CMouseCursor::Qt3DSMouseCursor cursor); + void resetMouseCursor(); + void updateSnapSteps(); + TExpandMap &expandMap(); + void resetMousePressParams(); + QLabel *timebarTooltip(); + void updateAutoScrolling(double scenePosY); + void stopAutoScroll(); + QPoint getScrollbarOffsets() const; + void handleShowDISelector(const QString &propertyname, qt3dsdm::Qt3DSDMInstanceHandle inInst, + const QPoint &pos); + void resetPressedKeyframe(); + Keyframe *pressedKeyframe() const; + +protected: + bool event(QEvent *event) override; + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; + void wheelEvent(QGraphicsSceneWheelEvent *wheelEvent) override; + void keyPressEvent(QKeyEvent *keyEvent) override; + void keyReleaseEvent(QKeyEvent *keyEvent) override; + + void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; + +private: + void commitMoveRows(); + void updateHoverStatus(const QPointF &scenePos); + void snap(double &value, bool snapToPlayHead = true); + QGraphicsItem *getItemBelowType(TimelineItem::ItemType type, + QGraphicsItem *item, + const QPointF &scenePos) const; + void handleInsertKeyframe(); + void handleDeleteChannelKeyframes(); + void handleSetTimeBarTime(); + void handleMakeComponent(); + void handleCopyObjectPath(); + void handleEditComponent(); + void handleApplicationFocusLoss(); + + QGraphicsLinearLayout *m_layoutRoot; + QGraphicsLinearLayout *m_layoutTree; + QGraphicsLinearLayout *m_layoutTimeline; + + TreeHeader *m_treeHeader; + Ruler *m_ruler; + PlayHead *m_playHead; + TimelineWidget *m_widgetTimeline; + QGraphicsWidget *m_widgetRoot; + RowMover *m_rowMover = nullptr; + QPointer<RowTimeline> m_editedTimelineRow = nullptr; + SelectionRect *m_selectionRect; + RowManager *m_rowManager = nullptr; + KeyframeManager *m_keyframeManager = nullptr; + QPointF m_pressPos; + QPointF m_pressScreenPos; + QList<double> m_snapSteps; + CMouseCursor::Qt3DSMouseCursor m_currentCursor = -1; + TimelineControl *m_timelineControl = nullptr; + DataInputSelectView *m_dataInputSelector = nullptr; // triggered by context menu but owned by + // rowtree + + bool m_rulerPressed = false; + Keyframe *m_pressedKeyframe = nullptr; + bool m_dragging = false; + bool m_startRowMoverOnNextDrag = false; + bool m_timelineZooming = false; + bool m_timelinePanning = false; + TimelineControlType m_clickedTimelineControlType = TimelineControlType::None; + TreeControlType m_clickedTreeControlType = TreeControlType::None; + double m_pressPosInKeyframe; + double m_treeWidth = TimelineConstants::TREE_DEFAULT_W; + double m_lastAutoScrollX = -1.0; + double m_lastAutoScrollY = -1.0; + TExpandMap m_expandMap; + QPointer<RowTree> m_releaseSelectRow = nullptr; + bool m_autoScrollDownOn = false; + bool m_autoScrollUpOn = false; + QTimer m_autoScrollTimelineTimer; + QTimer m_autoScrollTimer; + QTimer m_autoScrollTriggerTimer; // triggers m_autoScrollTimer + QLabel *m_timebarToolTip = nullptr; + QLabel *m_variantsToolTip = nullptr; + RowTree* m_variantsRowTree = nullptr; +}; + +#endif // TIMELINEGRAPHICSSCENE_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineSplitter.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineSplitter.cpp new file mode 100644 index 00000000..b8c71f26 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineSplitter.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "TimelineSplitter.h" +#include "TimelineConstants.h" + +#include <QtGui/qevent.h> +#include <QtWidgets/qapplication.h> + +TimelineSplitter::TimelineSplitter(QWidget *parent) : QWidget(parent) +{ + setFixedWidth(TimelineConstants::SPLITTER_W); + setAttribute(Qt::WA_Hover, true); +} + +void TimelineSplitter::enterEvent(QEvent *event) +{ + qApp->setOverrideCursor(Qt::SplitHCursor); + QWidget::enterEvent(event); +} + +void TimelineSplitter::leaveEvent(QEvent *event) +{ + qApp->changeOverrideCursor(Qt::ArrowCursor); + qApp->restoreOverrideCursor(); + QWidget::leaveEvent(event); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineSplitter.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineSplitter.h new file mode 100644 index 00000000..1be64967 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineSplitter.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef TIMELINESPLITTER_H +#define TIMELINESPLITTER_H + +#include <QtWidgets/qwidget.h> + +QT_FORWARD_DECLARE_CLASS(QEvent) + +class TimelineSplitter : public QWidget +{ + Q_OBJECT + +public: + explicit TimelineSplitter(QWidget *parent = nullptr); + + protected: + void enterEvent(QEvent *event) override; + void leaveEvent(QEvent *event) override; +}; + +#endif // TIMELINESPLITTER_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineWidget.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineWidget.cpp new file mode 100644 index 00000000..d37825cb --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineWidget.cpp @@ -0,0 +1,1292 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "TimelineWidget.h" +#include "TimelineGraphicsScene.h" +#include "TimelineConstants.h" +#include "TimelineToolbar.h" +#include "RowManager.h" +#include "RowMover.h" +#include "KeyframeManager.h" +#include "RowTree.h" +#include "Keyframe.h" +#include "PlayHead.h" +#include "Ruler.h" +#include "TimelineSplitter.h" +#include "StudioApp.h" +#include "Core.h" +#include "Doc.h" +#include "Dispatch.h" +#include "MainFrm.h" +#include "Qt3DSDMStudioSystem.h" +#include "Qt3DSDMSlides.h" +#include "ClientDataModelBridge.h" +#include "Bindings/TimelineTranslationManager.h" +#include "Bindings/ITimelineItemBinding.h" +#include "Bindings/ITimelineTimebar.h" +#include "Bindings/Qt3DSDMTimelineItemBinding.h" +#include "Bindings/Qt3DSDMTimelineItemProperty.h" +#include "Bindings/TimelineBreadCrumbProvider.h" +#include "IDocumentEditor.h" +#include "Control.h" +#include "TimelineDropTarget.h" +#include "StudioPreferences.h" +#include "Dialogs.h" +#include "TimeEnums.h" + +#include <QtGui/qevent.h> +#include <QtWidgets/qgraphicslinearlayout.h> +#include <QtWidgets/qgraphicsview.h> +#include <QtWidgets/qboxlayout.h> +#include <QtWidgets/qscrollbar.h> +#include <QtWidgets/qslider.h> +#include <QtWidgets/qlabel.h> + +class Eventfilter : public QObject +{ +public: + Eventfilter(QObject *parent) : QObject(parent) {} + + bool eventFilter(QObject *, QEvent *event) override + { + if (event->type() == QEvent::Wheel) { + event->accept(); + return true; + } + + return false; + } +}; + +TimelineWidget::TimelineWidget(const QSize &preferredSize, QWidget *parent) + : QWidget(parent) + , m_viewTreeHeader(new TreeHeaderView(this)) + , m_viewTreeContent(new QGraphicsView(this)) + , m_viewTimelineHeader(new QGraphicsView(this)) + , m_viewTimelineContent(new QGraphicsView(this)) + , m_toolbar(new TimelineToolbar()) + , m_graphicsScene(new TimelineGraphicsScene(this)) + , m_preferredSize(preferredSize) +{ + int treeWidth = CStudioPreferences::GetTimelineSplitterLocation(); + + // Mahmoud_TODO: CTimelineTranslationManager should be eventually removed or cleaned. Already + // most of its functionality is implemented in this class + m_translationManager = new CTimelineTranslationManager(); + m_BreadCrumbProvider = new CTimelineBreadCrumbProvider(g_StudioApp.GetCore()->GetDoc()); + + setWindowTitle(tr("Timeline", "Title of timeline view")); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + m_viewTreeHeader->setScene(m_graphicsScene); + m_viewTreeHeader->setFixedHeight(TimelineConstants::ROW_H); + m_viewTreeHeader->setAlignment(Qt::AlignLeft | Qt::AlignTop); + m_viewTreeHeader->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_viewTreeHeader->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_viewTreeHeader->viewport()->installEventFilter(new Eventfilter(this)); + m_viewTreeHeader->viewport()->setFocusPolicy(Qt::NoFocus); + m_viewTreeHeader->setFixedWidth(treeWidth); + + m_viewTreeContent->setScene(m_graphicsScene); + m_viewTreeContent->setAlignment(Qt::AlignLeft | Qt::AlignTop); + m_viewTreeContent->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_viewTreeContent->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + m_viewTreeContent->setFixedWidth(treeWidth); + + m_viewTimelineHeader->setScene(m_graphicsScene); + m_viewTimelineHeader->setFixedHeight(TimelineConstants::ROW_H); + m_viewTimelineHeader->setAlignment(Qt::AlignLeft | Qt::AlignTop); + m_viewTimelineHeader->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_viewTimelineHeader->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + m_viewTimelineHeader->verticalScrollBar()->hide(); + m_viewTimelineHeader->viewport()->installEventFilter(new Eventfilter(this)); + m_viewTimelineHeader->viewport()->setFocusPolicy(Qt::NoFocus); + m_viewTimelineHeader->setViewportUpdateMode(QGraphicsView::FullViewportUpdate); + + m_viewTimelineContent->setScene(m_graphicsScene); + m_viewTimelineContent->setAlignment(Qt::AlignLeft | Qt::AlignTop); + m_viewTimelineContent->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_viewTimelineContent->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + m_viewTimelineContent->setViewportUpdateMode(QGraphicsView::FullViewportUpdate); + + setTreeWidth(treeWidth); + + auto *layoutTree = new QVBoxLayout; + layoutTree->setContentsMargins(QMargins(0, 0, 0, 0)); + layoutTree->addWidget(m_viewTreeHeader); + layoutTree->addWidget(m_viewTreeContent); + + m_splitter = new TimelineSplitter(this); + + auto *layoutTimeline = new QVBoxLayout; + layoutTimeline->setContentsMargins(QMargins(0, 0, 0, 0)); + layoutTimeline->addWidget(m_viewTimelineHeader); + layoutTimeline->addWidget(m_viewTimelineContent); + + auto *layoutContent = new QHBoxLayout; + layoutContent->setContentsMargins(QMargins(0, 0, 0, TimelineConstants::TOOLBAR_MARGIN)); + layoutContent->addLayout(layoutTree); + layoutContent->addWidget(m_splitter); + layoutContent->addLayout(layoutTimeline); + + m_navigationBar = new NavigationBar(this); + + auto *layoutRoot = new QVBoxLayout; + layoutRoot->setContentsMargins(0, 0, 0, 0); + layoutRoot->setSpacing(0); + layoutRoot->addWidget(m_navigationBar); + layoutRoot->addLayout(layoutContent); + layoutRoot->addWidget(m_toolbar); + setLayout(layoutRoot); + + g_StudioApp.GetCore()->GetDoc()->SetKeyframesManager( + static_cast<IKeyframesManager *>(m_graphicsScene->keyframeManager())); + + // connect graphics scene geometryChanged + connect(m_graphicsScene->widgetRoot(), &QGraphicsWidget::geometryChanged, this, [this]() { + const QRectF rect = m_graphicsScene->widgetRoot()->rect(); + + m_viewTreeContent->setSceneRect(QRectF(0, TimelineConstants::ROW_H, + TimelineConstants::TREE_MAX_W, rect.height())); + + m_viewTimelineHeader->setSceneRect(QRectF(TimelineConstants::TREE_BOUND_W, 0, + rect.width() - TimelineConstants::TREE_BOUND_W, + TimelineConstants::ROW_H)); + + m_viewTimelineContent->setSceneRect(QRectF(TimelineConstants::TREE_BOUND_W, + TimelineConstants::ROW_H, + rect.width() - TimelineConstants::TREE_BOUND_W, + rect.height())); + + m_graphicsScene->playHead()->setHeight(m_graphicsScene->widgetRoot()->geometry().height()); + }); + + // connect timeline and ruler horizontalScrollBars + connect(m_viewTimelineContent->horizontalScrollBar(), &QAbstractSlider::valueChanged, this, + [this](int value) { + m_viewTimelineHeader->horizontalScrollBar()->setValue(value); + // Note: Slider value starts from TREE_BOUND_W, make start from 0 + int viewportX = std::max(0, value - (int)TimelineConstants::TREE_BOUND_W); + m_graphicsScene->ruler()->setViewportX(viewportX); + }); + + // connect timeline and tree verticalScrollBars + connect(m_viewTimelineContent->verticalScrollBar(), &QAbstractSlider::valueChanged, this, + [this](int value) { + m_viewTreeContent->verticalScrollBar()->setValue(value); + + // make sure the 2 scrollbars stay in sync + if (m_viewTreeContent->verticalScrollBar()->value() != value) { + m_viewTimelineContent->verticalScrollBar()->setValue( + m_viewTreeContent->verticalScrollBar()->value()); + } + }); + + // connect tree and timeline verticalScrollBars + connect(m_viewTreeContent->verticalScrollBar(), &QAbstractSlider::valueChanged, this, + [this](int value) { + m_viewTimelineContent->verticalScrollBar()->setValue(value); + }); + + // connect tree and tree header horizontalScrollBars + connect(m_viewTreeContent->horizontalScrollBar(), &QAbstractSlider::valueChanged, this, + [this](int value) { + m_viewTreeHeader->horizontalScrollBar()->setValue(value); + // Keep m_viewTreeContent always positioned at 0 + // This hack is required due to RowTimelineCommentItem (QGraphicsTextItem) + // ensuring that all views see the text item when it gets focus or content + // changes with setPlainText(). See QTBUG-71241 and QT3DS-1508. + if (value != 0) + m_viewTreeContent->horizontalScrollBar()->setValue(0); + }); + + connect(m_toolbar, &TimelineToolbar::newLayerTriggered, this, [this]() { + using namespace Q3DStudio; + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + + // If active instance is component, just bail as we can't add layers to components + qt3dsdm::Qt3DSDMInstanceHandle rootInstance = doc->GetActiveRootInstance(); + if (m_bridge->GetObjectType(rootInstance) == OBJTYPE_COMPONENT) + return; + + qt3dsdm::Qt3DSDMSlideHandle slide = doc->GetActiveSlide(); + qt3dsdm::Qt3DSDMInstanceHandle layer = doc->GetActiveLayer(); + + SCOPED_DOCUMENT_EDITOR(*doc, QObject::tr("Add Layer")) + ->CreateSceneGraphInstance(qt3dsdm::ComposerObjectTypes::Layer, layer, slide, + DocumentEditorInsertType::PreviousSibling, + CPt(), PRIMITIVETYPE_UNKNOWN, -1); + }); + + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + connect(m_toolbar, &TimelineToolbar::deleteLayerTriggered, + [=](){ doc->deleteSelectedObject(); }); + + connect(m_toolbar, &TimelineToolbar::gotoTimeTriggered, this, [=]() { + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + g_StudioApp.GetDialogs()->asyncDisplayTimeEditDialog(doc->GetCurrentViewTime(), + doc, PLAYHEAD, + m_graphicsScene->keyframeManager()); + }); + + connect(m_toolbar, &TimelineToolbar::firstFrameTriggered, this, []() { + g_StudioApp.GetCore()->GetDoc()->NotifyTimeChanged(0); + }); + + connect(m_toolbar, &TimelineToolbar::stopTriggered, this, []() { + g_StudioApp.PlaybackStopNoRestore(); + }); + + connect(m_toolbar, &TimelineToolbar::playTriggered, this, [this]() { + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + if (getPlaybackMode() == "Stop at end" && doc->isPlayHeadAtEnd()) + g_StudioApp.PlaybackRewind(); + + g_StudioApp.PlaybackPlay(); + }); + + connect(m_toolbar, &TimelineToolbar::lastFrameTriggered, this, [this]() { + long dur = m_graphicsScene->ruler()->duration(); + g_StudioApp.GetCore()->GetDoc()->NotifyTimeChanged(dur); + }); + + connect(m_toolbar, &TimelineToolbar::timelineScaleChanged, this, [this](int scale) { + m_graphicsScene->setTimelineScale(scale); + }); + + connect(m_toolbar, &TimelineToolbar::controllerChanged, this, + [this](const QString &controller) { + m_graphicsScene->setControllerText(controller); + }); + + connect(m_toolbar, &TimelineToolbar::variantsFilterToggled, this, + std::bind(&TimelineWidget::updateVariantsFiltering, this, nullptr, true)); + + connect(m_graphicsScene->ruler(), &Ruler::maxDurationChanged, this, [this]() { + m_graphicsScene->updateTimelineLayoutWidth(); + }); + + connect(m_graphicsScene->ruler(), &Ruler::durationChanged, this, [this]() { + m_graphicsScene->updateControllerLayoutWidth(); + }); + + // data model listeners + g_StudioApp.GetCore()->GetDispatch()->AddPresentationChangeListener(this); + g_StudioApp.GetCore()->GetDispatch()->AddClientPlayChangeListener(this); + + m_asyncUpdateTimer.setInterval(0); + m_asyncUpdateTimer.setSingleShot(true); + connect(&m_asyncUpdateTimer, &QTimer::timeout, this, &TimelineWidget::onAsyncUpdate); +} + +Q3DStudio::CString TimelineWidget::getPlaybackMode() +{ + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + qt3dsdm::Qt3DSDMSlideHandle theActiveSlide(doc->GetActiveSlide()); + // clock has passed the end, check whether needs to switch slide + qt3dsdm::Qt3DSDMInstanceHandle instance = doc->GetStudioSystem()->GetSlideSystem() + ->GetSlideInstance(theActiveSlide); + + CClientDataModelBridge *bridge = doc->GetStudioSystem()->GetClientDataModelBridge(); + qt3dsdm::IPropertySystem *propertySystem = doc->GetStudioSystem()->GetPropertySystem(); + qt3dsdm::SValue theValue; + propertySystem->GetInstancePropertyValue(instance, bridge->GetSlide().m_PlayMode, theValue); + return qt3dsdm::get<qt3dsdm::TDataStrPtr>(theValue)->GetData(); +} + +TimelineWidget::~TimelineWidget() +{ + CStudioPreferences::SetTimelineSplitterLocation(m_graphicsScene->treeWidth()); + m_graphicsScene->keyframeManager()->deselectAllKeyframes(); + delete m_BreadCrumbProvider; +} + +QSize TimelineWidget::sizeHint() const +{ + return m_preferredSize; +} + +void TimelineWidget::OnNewPresentation() +{ + // Disable scrolling of treeview now that all show related initial singnaling is behind us + m_viewTreeHeader->disableScrolling(); + + // Register callbacks + auto studioSystem = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem(); + qt3dsdm::IStudioFullSystemSignalProvider *theSignalProvider + = studioSystem->GetFullSystemSignalProvider(); + m_bridge = studioSystem->GetClientDataModelBridge(); + + m_connections.push_back(theSignalProvider->ConnectActiveSlide( + std::bind(&TimelineWidget::onActiveSlide, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3))); + + CDispatch *theDispatch = g_StudioApp.GetCore()->GetDispatch(); + + m_connections.push_back(theDispatch->ConnectSelectionChange( + std::bind(&TimelineWidget::onSelectionChange, this, std::placeholders::_1))); + + // object created/deleted + m_connections.push_back(theSignalProvider->ConnectInstanceCreated( + std::bind(&TimelineWidget::onAssetCreated, this, std::placeholders::_1))); + m_connections.push_back(theSignalProvider->ConnectInstanceDeleted( + std::bind(&TimelineWidget::onAssetDeleted, this, std::placeholders::_1))); + + // animation created/deleted + m_connections.push_back(theSignalProvider->ConnectAnimationCreated( + std::bind(&TimelineWidget::onAnimationCreated, this, + std::placeholders::_2, std::placeholders::_3))); + m_connections.push_back(theSignalProvider->ConnectAnimationDeleted( + std::bind(&TimelineWidget::onAnimationDeleted, this, + std::placeholders::_2, std::placeholders::_3))); + + // keyframe added/deleted + m_connections.push_back(theSignalProvider->ConnectKeyframeInserted( + std::bind(&TimelineWidget::onKeyframeInserted, this, + std::placeholders::_1, std::placeholders::_2))); + m_connections.push_back(theSignalProvider->ConnectKeyframeErased( + std::bind(&TimelineWidget::onKeyframeDeleted, this, + std::placeholders::_1, std::placeholders::_2))); + m_connections.push_back(theSignalProvider->ConnectKeyframeUpdated( + std::bind(&TimelineWidget::onKeyframeUpdated, this, std::placeholders::_1))); + m_connections.push_back(theSignalProvider->ConnectInstancePropertyValue( + std::bind(&TimelineWidget::onPropertyChanged, this, + std::placeholders::_1, std::placeholders::_2))); + m_connections.push_back(theSignalProvider->ConnectFirstKeyframeDynamicSet( + std::bind(&TimelineWidget::onFirstKeyframeDynamicSet, this, + std::placeholders::_1))); + + // action created/deleted + m_connections.push_back(theSignalProvider->ConnectActionCreated( + std::bind(&TimelineWidget::onActionEvent, this, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))); + m_connections.push_back(theSignalProvider->ConnectActionDeleted( + std::bind(&TimelineWidget::onActionEvent, this, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))); + + // connect property linked/unlinked + m_connections.push_back(theSignalProvider->ConnectPropertyLinked( + std::bind(&TimelineWidget::onPropertyLinked, this, + std::placeholders::_2, std::placeholders::_3))); + m_connections.push_back(theSignalProvider->ConnectPropertyUnlinked( + std::bind(&TimelineWidget::onPropertyUnlinked, this, + std::placeholders::_2, std::placeholders::_3))); + + // object add, remove, move + Q3DStudio::CGraph &theGraph(*g_StudioApp.GetCore()->GetDoc()->GetAssetGraph()); + m_connections.push_back(theGraph.ConnectChildAdded( + std::bind(&TimelineWidget::onChildAdded, this, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))); + m_connections.push_back(theGraph.ConnectChildRemoved( + std::bind(&TimelineWidget::onChildRemoved, this, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))); + m_connections.push_back(theGraph.ConnectChildMoved( + std::bind(&TimelineWidget::onChildMoved, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4))); + + // Connect toolbar play/stop now when m_pMainWnd exists + connect(g_StudioApp.m_pMainWnd, &CMainFrame::playStateChanged, + m_toolbar, &TimelineToolbar::updatePlayButtonState); + + // Clear active slide + m_activeSlide = qt3dsdm::Qt3DSDMSlideHandle(); + + // Reset timeline time + OnTimeChanged(0); +} + +void TimelineWidget::OnClosingPresentation() +{ + m_connections.clear(); + m_graphicsScene->expandMap().clear(); +} + +void TimelineWidget::OnTimeChanged(long inTime) +{ + m_graphicsScene->playHead()->setTime(inTime); + m_toolbar->setTime(inTime); + + double left = m_viewTimelineHeader->horizontalScrollBar()->value() + + TimelineConstants::PLAYHEAD_W * .5; + double right = m_viewTimelineHeader->horizontalScrollBar()->value() + + m_viewTimelineHeader->width() - TimelineConstants::RULER_EDGE_OFFSET; + double playHeadX = m_graphicsScene->playHead()->x(); + + if (playHeadX < left || playHeadX > right) { + m_viewTimelineContent->ensureVisible(m_graphicsScene->playHead()->x() + - TimelineConstants::PLAYHEAD_W * .5, + m_viewTimelineContent->verticalScrollBar()->value() + + TimelineConstants::ROW_H, + TimelineConstants::PLAYHEAD_W, 0, 0, 0); + } + + if (inTime <= 0 && g_StudioApp.IsPlaying() && getPlaybackMode() == "Ping" + && !g_StudioApp.isPlaybackPreviewOn()) { + g_StudioApp.PlaybackStopNoRestore(); + } +} + +void TimelineWidget::onActiveSlide(const qt3dsdm::Qt3DSDMSlideHandle &inMaster, int inIndex, + const qt3dsdm::Qt3DSDMSlideHandle &inSlide) +{ + Q_UNUSED(inMaster); + Q_UNUSED(inIndex); + + if (m_activeSlide == inSlide) + return; + + m_activeSlide = inSlide; + + if (!m_fullReconstruct) { + m_fullReconstruct = true; + m_graphicsScene->resetMousePressParams(); + if (!m_asyncUpdateTimer.isActive()) + m_asyncUpdateTimer.start(); + } +} + +void TimelineWidget::insertToHandlesMapRecursive(Qt3DSDMTimelineItemBinding *binding) +{ + insertToHandlesMap(binding); + + const QList<ITimelineItemBinding *> children = binding->GetChildren(); + for (auto child : children) + insertToHandlesMapRecursive(static_cast<Qt3DSDMTimelineItemBinding *>(child)); +} + +void TimelineWidget::insertToHandlesMap(Qt3DSDMTimelineItemBinding *binding) +{ + m_handlesMap.insert(binding->GetInstance(), binding->getRowTree()); +} + +void TimelineWidget::onSelectionChange(Q3DStudio::SSelectedValue inNewSelectable) +{ + // Full update will set selection anyway + if (m_fullReconstruct) + return; + + qt3dsdm::TInstanceHandleList theInstances = inNewSelectable.GetSelectedInstances(); + + // First deselect all items in UI + m_graphicsScene->rowManager()->clearSelection(); + + if (theInstances.size() > 0) { + for (size_t idx = 0, end = theInstances.size(); idx < end; ++idx) { + qt3dsdm::Qt3DSDMInstanceHandle theInstance(theInstances[idx]); + + if (g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->IsInstance(theInstance)) { + auto *binding = getBindingForHandle(theInstance, m_binding); + if (binding) + m_graphicsScene->rowManager()->setRowSelection(binding->getRowTree(), true); + } + } + } +} + +void TimelineWidget::onAssetCreated(qt3dsdm::Qt3DSDMInstanceHandle inInstance) +{ + if (m_fullReconstruct) + return; + + if (m_bridge->IsSceneGraphInstance(inInstance)) { + Qt3DSDMTimelineItemBinding *binding = getBindingForHandle(inInstance, m_binding); + + if (!binding) { + // if binding is not found, refresh it (so far the only known case where this is needed + // is when setting a subpresentation on a ref mat row and checking 'Detach material') + m_fullReconstruct = true; + if (!m_asyncUpdateTimer.isActive()) + m_asyncUpdateTimer.start(); + } else { + if (!binding->getRowTree()) { // row doesn't exist + auto parentInstance = m_bridge->GetParentInstance(inInstance); + Qt3DSDMTimelineItemBinding *bindingParent = getBindingForHandle(parentInstance, + m_binding); + if (bindingParent) { + RowTree *row = m_graphicsScene->rowManager() + ->createRowFromBinding(binding, bindingParent->getRowTree()); + row->updateSubpresentations(); + insertToHandlesMap(binding); + + // refresh the created object variants if it has a variants property set. + if (m_bridge->GetObjectType(inInstance) & OBJTYPE_IS_VARIANT) { + const auto propertySystem = g_StudioApp.GetCore()->GetDoc() + ->GetPropertySystem(); + qt3dsdm::SValue sValue; + if (propertySystem->GetInstancePropertyValue(inInstance, + m_bridge->getVariantsProperty(inInstance), + sValue)) { + if (qt3dsdm::get<qt3dsdm::TDataStrPtr>(sValue)->GetLength() != 0) + refreshVariants(inInstance); + } + } + } else { + qWarning() << "Binding parent was not found."; + } + } + } + } +} + +void TimelineWidget::onAssetDeleted(qt3dsdm::Qt3DSDMInstanceHandle inInstance) +{ + if (m_fullReconstruct) + return; + + RowTree *row = m_handlesMap.value(inInstance); + if (row) { // scene object exists + row->updateSubpresentations(-1); + m_graphicsScene->rowManager()->deleteRow(row); + m_handlesMap.remove(inInstance); + m_graphicsScene->expandMap().remove(inInstance); + // Ensure row deletions are finalized + if (!m_asyncUpdateTimer.isActive()) + m_asyncUpdateTimer.start(); + } +} + +void TimelineWidget::onAnimationCreated(qt3dsdm::Qt3DSDMInstanceHandle parentInstance, + qt3dsdm::Qt3DSDMPropertyHandle property) +{ + if (m_fullReconstruct) + return; + + Qt3DSDMTimelineItemBinding *binding = getBindingForHandle(parentInstance, m_binding); + if (binding) { + ITimelineItemProperty *propBinding = binding->GetPropertyBinding(property); + + // create the binding if doesn't exist + if (!propBinding) { + propBinding = binding->GetOrCreatePropertyBinding(property); + + // create the property UI row + RowTree *propRow = m_graphicsScene->rowManager() + ->getOrCreatePropertyRow(binding->getRowTree(), propBinding->GetName().toQString(), + binding->getAnimatedPropertyIndex(property)); + + // connect the row and binding + propBinding->setRowTree(propRow); + propRow->setPropBinding(propBinding); + + // add keyframes + for (int i = 0; i < propBinding->GetKeyframeCount(); i++) { + IKeyframe *kf = propBinding->GetKeyframeByIndex(i); + Keyframe *kfUI = m_graphicsScene->keyframeManager()->insertKeyframe( + propRow->rowTimeline(), kf->GetTime(), false).at(0); + + kf->setUI(kfUI); + kfUI->binding = static_cast<Qt3DSDMTimelineKeyframe *>(kf); + kfUI->dynamic = kf->IsDynamic(); + } + + propRow->update(); + } + } +} + +void TimelineWidget::onAnimationDeleted(qt3dsdm::Qt3DSDMInstanceHandle parentInstance, + qt3dsdm::Qt3DSDMPropertyHandle property) +{ + if (m_fullReconstruct) + return; + + Qt3DSDMTimelineItemBinding *binding = getBindingForHandle(parentInstance, m_binding); + if (binding) { + ITimelineItemProperty *propBinding = binding->GetPropertyBinding(property); + bool propAnimated = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem() + ->GetAnimationSystem()->IsPropertyAnimated(parentInstance, property); + + if (propBinding && !propAnimated) { + m_graphicsScene->rowManager()->deleteRow(propBinding->getRowTree()); + binding->RemovePropertyRow(property); + m_keyframeChangesMap.insert(parentInstance, property); + // Ensure row deletions are finalized + if (!m_asyncUpdateTimer.isActive()) + m_asyncUpdateTimer.start(); + } + } +} + +void TimelineWidget::onKeyframeInserted(qt3dsdm::Qt3DSDMAnimationHandle inAnimation, + qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe) +{ + if (m_fullReconstruct) + return; + + refreshKeyframe(inAnimation, inKeyframe, ETimelineKeyframeTransaction_Add); +} + +void TimelineWidget::onKeyframeDeleted(qt3dsdm::Qt3DSDMAnimationHandle inAnimation, + qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe) +{ + if (m_fullReconstruct) + return; + + refreshKeyframe(inAnimation, inKeyframe, ETimelineKeyframeTransaction_Delete); +} + +void TimelineWidget::onKeyframeUpdated(qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe) +{ + if (m_fullReconstruct) + return; + + qt3dsdm::IAnimationCore *theAnimationCore = + g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetAnimationCore(); + if (theAnimationCore->KeyframeValid(inKeyframe)) { + qt3dsdm::Qt3DSDMAnimationHandle theAnimationHandle = + theAnimationCore->GetAnimationForKeyframe(inKeyframe); + refreshKeyframe(theAnimationHandle, inKeyframe, ETimelineKeyframeTransaction_Update); + } +} + +void TimelineWidget::refreshKeyframe(qt3dsdm::Qt3DSDMAnimationHandle inAnimation, + qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe, + ETimelineKeyframeTransaction inTransaction) +{ + qt3dsdm::CStudioSystem *studioSystem = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem(); + if (studioSystem->GetAnimationCore()->AnimationValid(inAnimation)) { + qt3dsdm::SAnimationInfo theAnimationInfo = + studioSystem->GetAnimationCore()->GetAnimationInfo(inAnimation); + Qt3DSDMTimelineItemBinding *binding = getBindingForHandle(theAnimationInfo.m_Instance, + m_binding); + if (binding) { + binding->RefreshPropertyKeyframe(theAnimationInfo.m_Property, inKeyframe, + inTransaction); + + // update UI asynchronously to make sure binding is completely up to date. + m_keyframeChangesMap.insert(theAnimationInfo.m_Instance, theAnimationInfo.m_Property); + if (!m_asyncUpdateTimer.isActive()) + m_asyncUpdateTimer.start(); + } + } +} + +void TimelineWidget::onFirstKeyframeDynamicSet(qt3dsdm::Qt3DSDMAnimationHandle inAnimation) +{ + refreshKeyframe(inAnimation, 0, ETimelineKeyframeTransaction_DynamicChanged); +} + +void TimelineWidget::updateActionStates(const QSet<RowTree *> &rows) +{ + for (RowTree *row : rows) { + Qt3DSDMTimelineItemBinding *binding = + static_cast<Qt3DSDMTimelineItemBinding *>(row->getBinding()); + + RowTree::ActionStates states = RowTree::ActionState::None; + if (binding->HasAction(true)) // has master action + states |= RowTree::ActionState::MasterAction; + else if (binding->HasAction(false)) // has action + states |= RowTree::ActionState::Action; + + if (binding->ChildrenHasAction(true)) // children have master action + states |= RowTree::ActionState::MasterChildAction; + else if (binding->ChildrenHasAction(false)) // children have action + states |= RowTree::ActionState::ChildAction; + + if (row->isComponent()) { + if (binding->ComponentHasAction(true)) // component has master action + states |= RowTree::ActionState::MasterComponentAction; + else if (binding->ComponentHasAction(false)) // component has action + states |= RowTree::ActionState::ComponentAction; + } + row->setActionStates(states); + } +} + +void TimelineWidget::setTreeWidth(int width) +{ + int treeWidth = qBound(int(TimelineConstants::TREE_MIN_W), width, + int(TimelineConstants::TREE_MAX_W)); + + m_viewTreeHeader->setFixedWidth(treeWidth); + m_viewTreeContent->setFixedWidth(treeWidth); + m_graphicsScene->updateTreeWidth(treeWidth); +} + +void TimelineWidget::onPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance, + qt3dsdm::Qt3DSDMPropertyHandle inProperty) +{ + if (m_fullReconstruct) + return; + + if (!m_bridge->IsSceneGraphInstance(inInstance)) + return; + + const SDataModelSceneAsset &asset = m_bridge->GetSceneAsset(); + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + auto ctrldPropHandle = doc->GetPropertySystem() + ->GetAggregateInstancePropertyByName(inInstance, L"controlledproperty"); + + if (inProperty == asset.m_Eyeball || inProperty == asset.m_Locked || inProperty == asset.m_Shy + || inProperty == asset.m_StartTime || inProperty == asset.m_EndTime + || inProperty == m_bridge->GetNameProperty() || inProperty == ctrldPropHandle) { + m_dirtyProperties.insert(inInstance, inProperty); + if (!m_asyncUpdateTimer.isActive()) + m_asyncUpdateTimer.start(); + } else if (inProperty == m_bridge->GetSceneImage().m_SubPresentation + || (inProperty == m_bridge->GetSourcePathProperty() + && m_bridge->GetObjectType(inInstance) == OBJTYPE_LAYER)) { + m_subpresentationChanges.insert(inInstance); + if (!m_asyncUpdateTimer.isActive()) + m_asyncUpdateTimer.start(); + } else if (inProperty == m_bridge->getVariantsProperty(inInstance)) { + qt3dsdm::SValue sValue; + if (doc->GetPropertySystem()->GetInstancePropertyValue(inInstance, inProperty, sValue)) { + QString propVal = qt3dsdm::get<qt3dsdm::TDataStrPtr>(sValue)->toQString(); + if (!propVal.isEmpty()) { + QStringList tagPairs = propVal.split(QLatin1Char(',')); + QStringList groups; + for (int i = 0; i < tagPairs.size(); ++i) { + QString group = tagPairs[i].left(tagPairs[i].indexOf(QLatin1Char(':'))); + if (!groups.contains(group)) + groups.append(group); + } + + m_variantsMap[inInstance] = groups; + } else { + m_variantsMap[inInstance].clear(); + } + + if (!m_asyncUpdateTimer.isActive()) + m_asyncUpdateTimer.start(); + } + } +} + +void TimelineWidget::onAsyncUpdate() +{ + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + + if (m_fullReconstruct) { + m_translationManager->Clear(); + m_binding = static_cast<Qt3DSDMTimelineItemBinding *>( + m_translationManager->GetOrCreate( + doc->GetStudioSystem()->GetSlideSystem()->GetSlideInstance(m_activeSlide))); + m_graphicsScene->rowManager()->recreateRowsFromBinding(m_binding); + m_handlesMap.clear(); + insertToHandlesMapRecursive(m_binding); + updateActionStates(m_handlesMap.values().toSet()); + m_navigationBar->updateNavigationItems(m_BreadCrumbProvider); + m_graphicsScene->updateSnapSteps(); + m_fullReconstruct = false; + m_graphicsScene->updateController(); + onSelectionChange(doc->GetSelectedValue()); + m_toolbar->setNewLayerEnabled(!m_graphicsScene->rowManager()->isComponentRoot()); + refreshVariants(); + updateVariantsFiltering(); + + // update sub-presentation indicators + for (auto *row : qAsConst(m_handlesMap)) + row->updateSubpresentations(); + } else { + if (!m_moveMap.isEmpty()) { + // Flip the hash around so that we collect moves by parent. + // We can't do this with m_moveMap originally, as things break if + // same row receives consecutive moves to different parents. + QMultiHash<int, int> flippedMap; + QHashIterator<int, int> it(m_moveMap); + while (it.hasNext()) { + it.next(); + flippedMap.insert(it.value(), it.key()); + } + const auto parentHandles = flippedMap.keys(); + QSet<RowTree *> expandRows; + for (const auto parentHandle : parentHandles) { + QSet<int> movedInstances(flippedMap.values(parentHandle).toSet()); + RowTree *rowParent = m_handlesMap.value(parentHandle); + if (rowParent) { + Qt3DSDMTimelineItemBinding *bindingParent + = static_cast<Qt3DSDMTimelineItemBinding *>(rowParent->getBinding()); + if (bindingParent) { + // Resolve indexes for handles. QMap used for its automatic sorting by keys. + QMap<int, int> indexMap; + bindingParent->getTimeContextIndices(movedInstances, indexMap); + QMapIterator<int, int> indexIt(indexMap); + while (indexIt.hasNext()) { + indexIt.next(); + RowTree *row = m_handlesMap.value(indexIt.value()); + if (row) { + bool isReparent = rowParent != row->parentRow(); + if (isReparent) + row->updateSubpresentations(-1); + rowParent->addChildAt(row, indexIt.key()); + if (isReparent) + row->updateSubpresentations(1); + } + } + expandRows.insert(rowParent); + } + } + } + + // Make sure selections on UI matches bindings + onSelectionChange(doc->GetSelectedValue()); + + // Expand the parents of the added rows, but only for topmost ancestors of the moved + // rows as expanding all moved rows indiscriminately would not work intuitively + // in case of e.g. mass delete undo. + // Rest of expandRows will be force-updated to their current state to ensure + // their children are in proper state. This is relevant in cases like grouping, + // where existing potentially visible rows are moved under newly created group, + // which is collapsed by default. + for (RowTree *row : qAsConst(expandRows)) { + if (!expandRows.contains(row->parentRow())) + m_graphicsScene->rowManager()->ensureRowExpandedAndVisible(row, true); + else + row->updateExpandStatus(row->expandState(), false, true); + } + } + // Update properties + if (!m_dirtyProperties.isEmpty()) { + const SDataModelSceneAsset &asset = m_bridge->GetSceneAsset(); + qt3dsdm::Qt3DSDMPropertyHandle nameProp = m_bridge->GetNameProperty(); + const auto instances = m_dirtyProperties.keys(); + QSet<RowTree *> updateArrowParents; + for (int instance : instances) { + bool filterProperty = false; + bool timeProperty = false; + bool nameProperty = false; + const auto props = m_dirtyProperties.values(instance); + const auto ctrldPropHandle = + doc->GetPropertySystem()->GetAggregateInstancePropertyByName( + instance, L"controlledproperty"); + for (auto prop : props) { + filterProperty = filterProperty || prop == asset.m_Eyeball + || prop == asset.m_Locked || prop == asset.m_Shy + || prop == ctrldPropHandle; + timeProperty = timeProperty + || prop == asset.m_StartTime || prop == asset.m_EndTime; + nameProperty = nameProperty || prop == nameProp; + } + if (filterProperty || timeProperty || nameProperty) { + Qt3DSDMTimelineItemBinding *binding = getBindingForHandle(instance, m_binding); + if (binding) { + RowTree *row = binding->getRowTree(); + if (row) { + if (timeProperty) { + row->rowTimeline()->updateDurationFromBinding(); + m_graphicsScene->rowManager()->updateRulerDuration(); + } + if (filterProperty) { + row->updateFromBinding(); + m_graphicsScene->rowManager()->updateFiltering(row); + // Filtering changes to children affect arrow visibility in parents. + if (row->parentRow()) + updateArrowParents.insert(row->parentRow()); + + update(); + } + if (nameProperty) + row->updateLabel(); + } + } + } + } + for (RowTree *row : qAsConst(updateArrowParents)) + row->updateArrowVisibility(); + m_graphicsScene->updateSnapSteps(); + } + if (!m_actionChanges.isEmpty()) { + QSet<RowTree *> rowSet; + for (int id : qAsConst(m_actionChanges)) { + RowTree *row = m_handlesMap.value(id); + if (row) { + rowSet.insert(row); + RowTree *parentRow = row->parentRow(); + while (parentRow) { + rowSet.insert(parentRow); + parentRow = parentRow->parentRow(); + } + } + } + updateActionStates(rowSet); + } + + if (!m_subpresentationChanges.isEmpty()) { + for (int id : qAsConst(m_subpresentationChanges)) { + RowTree *row = m_handlesMap.value(id); + if (row) + row->updateSubpresentations(); + } + } + + if (!m_keyframeChangesMap.isEmpty()) { + const auto objects = m_keyframeChangesMap.keys(); + for (int object : objects) { + RowTree *row = m_handlesMap.value(object); + if (row) { + const auto properties = m_keyframeChangesMap.values(object); + row->rowTimeline()->updateKeyframesFromBinding(properties); + } + } + m_graphicsScene->updateSnapSteps(); + } + + if (!m_variantsMap.isEmpty()) { + const auto instances = m_variantsMap.keys(); + for (int instance : instances) { + if (m_handlesMap.contains(instance)) { + RowTree *row = m_handlesMap[instance]; + if (row) { + row->updateVariants(m_variantsMap[instance]); // variants groups names + updateVariantsFiltering(row); + } + } + } + } + } + m_dirtyProperties.clear(); + m_moveMap.clear(); + m_actionChanges.clear(); + m_variantsMap.clear(); + m_subpresentationChanges.clear(); + m_keyframeChangesMap.clear(); + m_graphicsScene->rowManager()->finalizeRowDeletions(); +} + +void TimelineWidget::onActionEvent(qt3dsdm::Qt3DSDMActionHandle inAction, + qt3dsdm::Qt3DSDMSlideHandle inSlide, + qt3dsdm::Qt3DSDMInstanceHandle inOwner) +{ + Q_UNUSED(inAction) + Q_UNUSED(inSlide) + + if (m_fullReconstruct) + return; + + m_actionChanges.insert(inOwner); + if (!m_asyncUpdateTimer.isActive()) + m_asyncUpdateTimer.start(); +} + +void TimelineWidget::onPropertyLinked(qt3dsdm::Qt3DSDMInstanceHandle inInstance, + qt3dsdm::Qt3DSDMPropertyHandle inProperty) +{ + if (m_fullReconstruct) + return; + + Qt3DSDMTimelineItemBinding *binding = getBindingForHandle(inInstance, m_binding); + + if (binding) { + ITimelineItemProperty *propBinding = binding->GetPropertyBinding(inProperty); + + if (propBinding) { + RowTree *propRow = binding->GetPropertyBinding(inProperty)->getRowTree(); + + // this call deletes and recreates the property binding so we need to reconnect the + // property binding and its RowTree, and the keyframes also + binding->OnPropertyLinked(inProperty); + + propBinding = binding->GetPropertyBinding(inProperty); + propBinding->setRowTree(propRow); + propRow->setPropBinding(propBinding); + + // recreate and connect prop row keyframes + m_graphicsScene->keyframeManager()->deleteKeyframes(propRow->rowTimeline(), false); + for (int i = 0; i < propBinding->GetKeyframeCount(); i++) { + IKeyframe *kf = propBinding->GetKeyframeByIndex(i); + Keyframe *kfUI = m_graphicsScene->keyframeManager()->insertKeyframe( + propRow->rowTimeline(), kf->GetTime(), false).at(0); + + kf->setUI(kfUI); + kfUI->binding = static_cast<Qt3DSDMTimelineKeyframe *>(kf); + kfUI->dynamic = kf->IsDynamic(); + } + } + } +} + +void TimelineWidget::onPropertyUnlinked(qt3dsdm::Qt3DSDMInstanceHandle inInstance, + qt3dsdm::Qt3DSDMPropertyHandle inProperty) +{ + onPropertyLinked(inInstance, inProperty); +} + +void TimelineWidget::onChildAdded(int inParent, int inChild, long inIndex) +{ + Q_UNUSED(inIndex) + + if (m_fullReconstruct) + return; + + // Handle row moves async, as we won't be able to get the final order correct otherwise + m_moveMap.insert(inChild, inParent); + m_actionChanges.insert(inParent); + if (!m_asyncUpdateTimer.isActive()) + m_asyncUpdateTimer.start(); +} + +void TimelineWidget::onChildRemoved(int inParent, int inChild, long inIndex) +{ + Q_UNUSED(inParent) + Q_UNUSED(inChild) + Q_UNUSED(inIndex) + + m_actionChanges.insert(inParent); + if (!m_asyncUpdateTimer.isActive()) + m_asyncUpdateTimer.start(); + + // Note: Actual child removal handling unimplemented by design, see QT3DS-1684 +} + +void TimelineWidget::onChildMoved(int inParent, int inChild, long inOldIndex, + long inNewIndex) +{ + Q_UNUSED(inOldIndex) + + // Move and add are essentially the same operation + onChildAdded(inParent, inChild, inNewIndex); +} + +CDropTarget *TimelineWidget::FindDropCandidate(CPt &inMousePoint, Qt::KeyboardModifiers inFlags, + EStudioObjectType objectType, + Q3DStudio::DocumentEditorFileType::Enum fileType) +{ + Q_UNUSED(inFlags) + + CTimeLineDropTarget *theTarget = new CTimeLineDropTarget(); + + int mouseY = inMousePoint.y - m_navigationBar->height() + + viewTreeContent()->verticalScrollBar()->value() + - viewTreeContent()->verticalScrollBar()->minimum(); + RowMover *mover = m_graphicsScene->rowMover(); + mover->updateTargetRow(QPointF(inMousePoint.x, mouseY), objectType, fileType); + + if (mover->insertionTarget() && !mover->insertionTarget()->isProperty()) { + mover->insertionTarget()->getBinding()->SetDropTarget(theTarget); + + switch (mover->insertionType()) { + case Q3DStudio::DocumentEditorInsertType::LastChild: + theTarget->SetDestination(EDROPDESTINATION_ON); + break; + case Q3DStudio::DocumentEditorInsertType::PreviousSibling: + theTarget->SetDestination(EDROPDESTINATION_ABOVE); + break; + default: + theTarget->SetDestination(EDROPDESTINATION_BELOW); + break; + } + } + m_graphicsScene->updateAutoScrolling(mouseY); + + return theTarget; +} + +void TimelineWidget::OnMouseMove(CPt inPoint, Qt::KeyboardModifiers inFlags) +{ + Q_UNUSED(inFlags) + + if (inPoint.x == -1 && inPoint.y == -1) { // drag leave + // upon cancelling a DnD, the mouse press event fires, this bool is to prevent that + m_blockMousePress = true; + QTimer::singleShot(500, [this]() { + m_blockMousePress = false; + }); + } +} + +bool TimelineWidget::blockMousePress() const +{ + return m_blockMousePress; +} + +CPt TimelineWidget::GetPreferredSize() +{ + return CPt(m_preferredSize.width(), m_preferredSize.height()); +} + +void TimelineWidget::SetSize(long inX, long inY) +{ + setFixedSize(inX, inY); +} + +// If views are interactive they block the DnD. If we could think of a way to make them do not block +// DnD, then this method can be removed (and it's callers) +void TimelineWidget::enableDnD(bool b) +{ + m_viewTreeHeader->setEnabled(!b); + m_viewTreeContent->setEnabled(!b); + m_viewTimelineHeader->setEnabled(!b); + m_viewTimelineContent->setEnabled(!b); + + if (!b) { // object successfully dropped on the timeline tree + m_graphicsScene->rowMover()->end(true); + m_graphicsScene->stopAutoScroll(); + } +} + +Qt3DSDMTimelineItemBinding *TimelineWidget::getBindingForHandle(int handle, + Qt3DSDMTimelineItemBinding *binding) const +{ + const RowTree *row = m_handlesMap.value(handle); + if (row && row->getBinding()) + return static_cast<Qt3DSDMTimelineItemBinding *>(row->getBinding()); + + if (binding) { + if (binding->GetInstance().GetHandleValue() == handle) + return binding; + + const QList<ITimelineItemBinding *> children = binding->GetChildren(); + for (auto child : children) { + Qt3DSDMTimelineItemBinding *b = getBindingForHandle(handle, + static_cast<Qt3DSDMTimelineItemBinding *>(child)); + + if (b) + return b; + } + } + return nullptr; +} + +void TimelineWidget::mousePressEvent(QMouseEvent *event) +{ + if (childAt(event->pos()) == m_splitter) + m_splitterPressed = true; + g_StudioApp.setLastActiveView(this); +} + +void TimelineWidget::mouseMoveEvent(QMouseEvent *event) +{ + if (m_splitterPressed) + setTreeWidth(event->pos().x() - m_splitter->size().width() * .5); +} + +void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) +{ + Q_UNUSED(event) + m_splitterPressed = false; +} + +QGraphicsView *TimelineWidget::viewTimelineContent() const +{ + return m_viewTimelineContent; +} + +QGraphicsView *TimelineWidget::viewTreeContent() const +{ + return m_viewTreeContent; +} + +TimelineToolbar *TimelineWidget::toolbar() const +{ + return m_toolbar; +} + +bool TimelineWidget::dndActive() const +{ + return m_graphicsScene->rowMover()->isActive(); +} + +bool TimelineWidget::hasSelectedKeyframes() const +{ + return m_graphicsScene->keyframeManager()->hasSelectedKeyframes(); +} + +QVector<RowTree *> TimelineWidget::selectedRows() const +{ + return m_graphicsScene->rowManager()->selectedRows(); +} + +void TimelineWidget::openBarColorDialog() +{ + auto rows = selectedRows(); + if (rows.isEmpty()) + return; + + // Note: Setup color dialog with bar color of last selected row as it can only default to one. + QColor previousColor = rows.first()->rowTimeline()->barColor(); + CDialogs *dialogs = g_StudioApp.GetDialogs(); + connect(dialogs, &CDialogs::onColorChanged, this, &TimelineWidget::onTimeBarColorChanged); + QColor selectedColor = dialogs->displayColorDialog(previousColor); + disconnect(dialogs, &CDialogs::onColorChanged, this, &TimelineWidget::onTimeBarColorChanged); + setSelectedTimeBarsColor(selectedColor, selectedColor == previousColor); +} + +void TimelineWidget::onTimeBarColorChanged(const QColor &color) +{ + setSelectedTimeBarsColor(color, true); +} + +// Set the color of all currently selected timeline bars. +// When preview, only set the UI without property changes. +void TimelineWidget::setSelectedTimeBarsColor(const QColor &color, bool preview) +{ + using namespace Q3DStudio; // Needed for SCOPED_DOCUMENT_EDITOR macro + const auto rows = selectedRows(); + for (RowTree *row : rows) { + row->rowTimeline()->setBarColor(color); + if (!preview) { + Qt3DSDMTimelineItemBinding *timelineItemBinding = + static_cast<Qt3DSDMTimelineItemBinding *>(row->getBinding()); + SCOPED_DOCUMENT_EDITOR(*g_StudioApp.GetCore()->GetDoc(), + QObject::tr("Set Timebar Color")) + ->SetTimebarColor(timelineItemBinding->GetInstanceHandle(), color); + } + } +} + +void TimelineWidget::refreshVariants(int instance) +{ + const auto propertySystem = g_StudioApp.GetCore()->GetDoc()->GetPropertySystem(); + QVector<int> instances; + if (instance) + instances << instance; + else + instances = g_StudioApp.GetCore()->GetDoc()->getVariantInstances(); + + for (auto instance : qAsConst(instances)) { + if (!m_handlesMap.contains(instance)) + continue; + + qt3dsdm::SValue sValue; + if (propertySystem->GetInstancePropertyValue(instance, + m_bridge->getVariantsProperty(instance), + sValue)) { + QString propVal = qt3dsdm::get<qt3dsdm::TDataStrPtr>(sValue)->toQString(); + if (!propVal.isEmpty()) { + QStringList tagPairs = propVal.split(QLatin1Char(',')); + QStringList groups; + for (int i = 0; i < tagPairs.size(); ++i) { + QString group = tagPairs[i].left(tagPairs[i].indexOf(QLatin1Char(':'))); + if (!groups.contains(group)) + groups.append(group); + } + + m_handlesMap[instance]->updateVariants(groups); + } else { + m_handlesMap[instance]->updateVariants({}); + } + } + } +} + +void TimelineWidget::updateVariantsFiltering(RowTree *row, bool force) +{ + if (force || m_toolbar->isVariantsFilterOn()) + m_graphicsScene->rowManager()->updateFiltering(row); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineWidget.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineWidget.h new file mode 100644 index 00000000..a45be156 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/TimelineWidget.h @@ -0,0 +1,179 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef TIMELINEWIDGET_H +#define TIMELINEWIDGET_H + +#include <QtWidgets/qwidget.h> +#include <QtCore/qtimer.h> +#include "DispatchListeners.h" +#include "ObjectListModel.h" +#include "Qt3DSDMHandles.h" +#include "Qt3DSDMSignals.h" +#include "SelectedValueImpl.h" +#include "TreeHeaderView.h" +#include "Bindings/Qt3DSDMTimeline.h" +#include "NavigationBar.h" +#include "Control.h" + +class RowTree; +class TimelineToolbar; +class TimelineSplitter; +class TimelineGraphicsScene; +class CTimelineTranslationManager; +class Qt3DSDMTimelineItemBinding; +class CClientDataModelBridge; +class IBreadCrumbProvider; + +QT_FORWARD_DECLARE_CLASS(QMouseEvent) +QT_FORWARD_DECLARE_CLASS(QGraphicsView) + +class TimelineWidget : public QWidget, + public CPresentationChangeListener, + public CClientPlayChangeListener, + public CControl +{ + Q_OBJECT + +public: + explicit TimelineWidget(const QSize &preferredSize, QWidget *parent = nullptr); + ~TimelineWidget(); + + QSize sizeHint() const override; + + TimelineToolbar *toolbar() const; + QGraphicsView *viewTimelineContent() const; + QGraphicsView *viewTreeContent() const; + QVector<RowTree *> selectedRows() const; + void openBarColorDialog(); + void onTimeBarColorChanged(const QColor &color); + void setSelectedTimeBarsColor(const QColor &color, bool preview); + void refreshVariants(int instance = 0); + void updateVariantsFiltering(RowTree *row = nullptr, bool force = false); + void enableDnD(bool b = true); + bool dndActive() const; + bool blockMousePress() const; + + // Presentation Change Listener + void OnNewPresentation() override; + void OnClosingPresentation() override; + void onSelectionChange(Q3DStudio::SSelectedValue inNewSelectable); + + //CClientPlayChangeListener + void OnTimeChanged(long inTime) override; + bool hasSelectedKeyframes() const; + + // CControl + CDropTarget *FindDropCandidate(CPt &inMousePoint, Qt::KeyboardModifiers inFlags, + EStudioObjectType objectType, + Q3DStudio::DocumentEditorFileType::Enum fileType) override; + void OnMouseMove(CPt inPoint, Qt::KeyboardModifiers inFlags) override; + CPt GetPreferredSize() override; + void SetSize(long inX, long inY) override; + bool isFullReconstructPending() const { return m_fullReconstruct; } + NavigationBar *navigationBar() const { return m_navigationBar; } + +protected: + // DataModel callbacks + virtual void onActiveSlide(const qt3dsdm::Qt3DSDMSlideHandle &inMaster, int inIndex, + const qt3dsdm::Qt3DSDMSlideHandle &inSlide); + void onAssetCreated(qt3dsdm::Qt3DSDMInstanceHandle inInstance); + void onAssetDeleted(qt3dsdm::Qt3DSDMInstanceHandle inInstance); + void onAnimationCreated(qt3dsdm::Qt3DSDMInstanceHandle parentInstance, + qt3dsdm::Qt3DSDMPropertyHandle property); + void onKeyframeInserted(qt3dsdm::Qt3DSDMAnimationHandle inAnimation, + qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe); + void onKeyframeDeleted(qt3dsdm::Qt3DSDMAnimationHandle inAnimation, + qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe); + void onKeyframeUpdated(qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe); + void onFirstKeyframeDynamicSet(qt3dsdm::Qt3DSDMAnimationHandle inAnimation); + void onAnimationDeleted(qt3dsdm::Qt3DSDMInstanceHandle parentInstance, + qt3dsdm::Qt3DSDMPropertyHandle property); + void onActionEvent(qt3dsdm::Qt3DSDMActionHandle inAction, qt3dsdm::Qt3DSDMSlideHandle inSlide, + qt3dsdm::Qt3DSDMInstanceHandle inOwner); + void onPropertyLinked(qt3dsdm::Qt3DSDMInstanceHandle inInstance, + qt3dsdm::Qt3DSDMPropertyHandle inProperty); + void onPropertyUnlinked(qt3dsdm::Qt3DSDMInstanceHandle inInstance, + qt3dsdm::Qt3DSDMPropertyHandle inProperty); + void onChildAdded(int inParent, int inChild, long inIndex); + void onChildRemoved(int inParent, int inChild, long inIndex); + void onChildMoved(int inParent, int inChild, long inOldIndex, long inNewIndex); + void onPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance, + qt3dsdm::Qt3DSDMPropertyHandle inProperty); + void onAsyncUpdate(); + + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + +private: + typedef QHash<qt3dsdm::Qt3DSDMInstanceHandle, RowTree *> THandleMap; + + Qt3DSDMTimelineItemBinding *getBindingForHandle(int handle, + Qt3DSDMTimelineItemBinding *binding) const; + void insertToHandlesMapRecursive(Qt3DSDMTimelineItemBinding *binding); + void insertToHandlesMap(Qt3DSDMTimelineItemBinding *binding); + Q3DStudio::CString getPlaybackMode(); + void refreshKeyframe(qt3dsdm::Qt3DSDMAnimationHandle inAnimation, + qt3dsdm::Qt3DSDMKeyframeHandle inKeyframe, + ETimelineKeyframeTransaction inTransaction); + void updateActionStates(const QSet<RowTree *> &rows); + void setTreeWidth(int width); + + TreeHeaderView *m_viewTreeHeader = nullptr; + QGraphicsView *m_viewTreeContent = nullptr; + QGraphicsView *m_viewTimelineHeader = nullptr; + QGraphicsView *m_viewTimelineContent = nullptr; + NavigationBar *m_navigationBar = nullptr; + TimelineToolbar *m_toolbar = nullptr; + TimelineGraphicsScene *m_graphicsScene; + TimelineSplitter *m_splitter = nullptr; + CTimelineTranslationManager *m_translationManager = nullptr; + FlatObjectListModel *m_model = nullptr; + Qt3DSDMTimelineItemBinding *m_binding = nullptr; + bool m_splitterPressed = false; + QSize m_preferredSize; + QMultiHash<qt3dsdm::Qt3DSDMInstanceHandle, qt3dsdm::Qt3DSDMPropertyHandle> m_dirtyProperties; + QHash<int, int> m_moveMap; // key: child handle, value: parent handle + QHash<int, QStringList> m_variantsMap; // key: obj handle, value: variant groups + QSet<int> m_actionChanges; // key: object handle + QSet<int> m_subpresentationChanges; // key: object handle + QMultiHash<int, int> m_keyframeChangesMap; // key: object handle, value: property handle + QTimer m_asyncUpdateTimer; + bool m_fullReconstruct = false; + bool m_blockMousePress = false; + CClientDataModelBridge *m_bridge = nullptr; + IBreadCrumbProvider *m_BreadCrumbProvider = nullptr; + + // data model connection + std::vector<std::shared_ptr<qt3dsdm::ISignalConnection>> m_connections; + qt3dsdm::Qt3DSDMSlideHandle m_activeSlide; + THandleMap m_handlesMap; +}; + +#endif // TIMELINEWIDGET_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/InteractiveTimelineItem.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/InteractiveTimelineItem.cpp new file mode 100644 index 00000000..efc7a2c1 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/InteractiveTimelineItem.cpp @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "InteractiveTimelineItem.h" + +InteractiveTimelineItem::InteractiveTimelineItem(TimelineItem *parent) : TimelineItem(parent) +{ + setAcceptHoverEvents(true); +} + +void InteractiveTimelineItem::setState(State state) +{ + m_state = state; +} + +int InteractiveTimelineItem::type() const +{ + // Enable the use of qgraphicsitem_cast with this item. + return TypeInteractiveTimelineItem; +} + +void InteractiveTimelineItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event) + if (m_state != Selected) + setState(Hovered); +} + +void InteractiveTimelineItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event) + if (m_state != Selected) + setState(Normal); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/InteractiveTimelineItem.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/InteractiveTimelineItem.h new file mode 100644 index 00000000..a6a5e5ee --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/InteractiveTimelineItem.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef INTERACTIVETIMELINEITEM_H +#define INTERACTIVETIMELINEITEM_H + +#include "TimelineItem.h" + +class InteractiveTimelineItem : public TimelineItem { + Q_OBJECT + +public: + enum State { + Pressed, + Hovered, + Selected, + Normal + }; + + explicit InteractiveTimelineItem(TimelineItem *parent = nullptr); + + virtual void setState(State state); + + int type() const override; + +protected: + void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; + + State m_state = Normal; +}; + +#endif // INTERACTIVETIMELINEITEM_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBar.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBar.cpp new file mode 100644 index 00000000..f41c2952 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBar.cpp @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "NavigationBar.h" +#include "NavigationBarItem.h" +#include "TimelineConstants.h" +#include <QtCore/qdebug.h> + +NavigationBar::NavigationBar(QWidget *parent) + : QWidget(parent) +{ + setMaximumHeight(0); + m_layout = new QHBoxLayout(this); + m_layout->setMargin(4); + m_layout->setSpacing(4); + setLayout(m_layout); + // Initialize hide/show animation + m_expandAnimation.setTargetObject(this); + m_expandAnimation.setPropertyName("maximumHeight"); + m_expandAnimation.setDuration(TimelineConstants::EXPAND_ANIMATION_DURATION); +} + +void NavigationBar::updateNavigationItems(IBreadCrumbProvider *inBreadCrumbProvider) +{ + if (!inBreadCrumbProvider) + return; + + m_breadCrumbProvider = inBreadCrumbProvider; + + const IBreadCrumbProvider::TTrailList &trailList = m_breadCrumbProvider->GetTrail(); + int listSize = (int)trailList.size(); + + // Remove "stretch" from end + QLayoutItem *stretch = m_layout->takeAt(m_layout->count() - 1); + if (stretch) + delete stretch; + + // Update current items or create new as needed + for (int i = 0; i < listSize; ++i) { + SBreadCrumb item = trailList.at(i); + NavigationBarItem *barItem = nullptr; + bool newItem = (m_itemAmount <= 0) || (i > m_itemAmount - 1); + if (newItem) { + barItem = new NavigationBarItem(this); + } else { + // Every other item is NavigationBarItem, every other separator + int barItemIndex = i * 2; + barItem = static_cast<NavigationBarItem *>( + m_layout->itemAt(barItemIndex)->widget()); + barItem->setHighlight(false); + } + bool isLastItem = (i == listSize - 1); + barItem->setEnabled(!isLastItem); + barItem->setIndex(i); + barItem->setText(item.m_String); + if (i == 0) + barItem->setIcon(m_breadCrumbProvider->GetRootImage()); + else + barItem->setIcon(m_breadCrumbProvider->GetBreadCrumbImage()); + + if (newItem) { + QObject::connect(barItem, &NavigationBarItem::clicked, + this, &NavigationBar::itemClicked); + if (i != 0) { + // Separator before all items except first + QLabel *separator = new QLabel(this); + separator->setPixmap(m_breadCrumbProvider->GetSeparatorImage()); + m_layout->addWidget(separator); + } + m_layout->addWidget(barItem); + } + } + + // Remove possible extra items, when user navigates back + // First item (scene) is never removed + QLayoutItem *child; + int lastIndex = (listSize <= 1) ? 1 : (listSize * 2) - 1; + while ((child = m_layout->takeAt(lastIndex)) != 0) { + if (child->widget()) + delete child->widget(); + delete child; + } + + // When list contains single item (scene), hide the bar + setBarVisibility(listSize > 1); + + // Stretch at end for proper item sizing + m_layout->addStretch(1); + + m_itemAmount = listSize; +} + +void NavigationBar::itemClicked(int index) +{ + m_breadCrumbProvider->OnBreadCrumbClicked((long)index); +} + +void NavigationBar::setBarVisibility(bool visible) +{ + int endHeight = visible ? TimelineConstants::NAVIGATION_BAR_H : 0; + if (height() != endHeight) { + m_expandAnimation.setEndValue(endHeight); + m_expandAnimation.start(); + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBar.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBar.h new file mode 100644 index 00000000..f08e4220 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBar.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef NAVIGATIONBAR_H +#define NAVIGATIONBAR_H + +#include <QtCore/qpropertyanimation.h> +#include <QtWidgets/qwidget.h> +#include <QtWidgets/qboxlayout.h> +#include "Bindings/IBreadCrumbProvider.h" + +class NavigationBar : public QWidget +{ + Q_OBJECT +public: + explicit NavigationBar(QWidget *parent = nullptr); + void updateNavigationItems(IBreadCrumbProvider *inBreadCrumbProvider); + +public slots: + void itemClicked(int index); + +private: + void setBarVisibility(bool visible); + IBreadCrumbProvider *m_breadCrumbProvider = nullptr; + QHBoxLayout *m_layout = nullptr; + int m_itemAmount = 0; + QPropertyAnimation m_expandAnimation; +}; + +#endif // NAVIGATIONBAR_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBarItem.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBarItem.cpp new file mode 100644 index 00000000..b9b47642 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBarItem.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "NavigationBarItem.h" +#include "StudioPreferences.h" +#include "ResourceCache.h" + +#include <QtCore/qdebug.h> +#include <QtWidgets/qsizepolicy.h> + +NavigationBarItem::NavigationBarItem(QWidget *parent) + : QWidget(parent) +{ + setHighlight(false); + m_layout.setMargin(0); + m_layout.setSpacing(0); + m_iconLabel.setFixedWidth(20); + m_iconLabel.setStyleSheet("padding: 0 0 0 4;"); + m_textLabel.setStyleSheet("padding: 0 4 0 0;"); + m_textLabel.setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + m_layout.addWidget(&m_iconLabel); + m_layout.addWidget(&m_textLabel); + setLayout(&m_layout); +} + +void NavigationBarItem::setIndex(int index) +{ + m_index = index; +} + +void NavigationBarItem::setIcon(const QPixmap &pixmap) +{ + m_iconLabel.setPixmap(pixmap); +} + +void NavigationBarItem::setText(const QString &text) +{ + QColor textColor = isEnabled() ? CStudioPreferences::GetNormalColor() + : CStudioPreferences::GetInactiveColor(); + const QString fonttemplate = tr("<font color='%1'>%2</font>"); + m_textLabel.setText(fonttemplate.arg(textColor.name(), text)); +} + +void NavigationBarItem::setHighlight(bool highlight) +{ + if (highlight) { + QColor bgColor = CStudioPreferences::GetMouseOverHighlightColor(); + QString bgColorStyle = QStringLiteral("background-color: ") + bgColor.name(); + setStyleSheet(bgColorStyle); + } else { + setStyleSheet("background-color: transparent;"); + } +} + +void NavigationBarItem::mousePressEvent(QMouseEvent *event) +{ + Q_UNUSED(event); + emit clicked(m_index); +} + +void NavigationBarItem::enterEvent(QEvent *event) +{ + Q_UNUSED(event); + if (isEnabled()) + setHighlight(true); +} + +void NavigationBarItem::leaveEvent(QEvent *event) +{ + Q_UNUSED(event); + if (isEnabled()) + setHighlight(false); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBarItem.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBarItem.h new file mode 100644 index 00000000..8b1fc3ab --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/NavigationBarItem.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef NAVIGATIONBARITEM_H +#define NAVIGATIONBARITEM_H + +#include <QtGui/qpixmap.h> +#include <QtWidgets/qwidget.h> +#include <QtWidgets/qboxlayout.h> +#include <QtWidgets/qlabel.h> + +class NavigationBarItem : public QWidget +{ + Q_OBJECT +public: + explicit NavigationBarItem(QWidget *parent = nullptr); + + void setIndex(int index); + void setText(const QString &text); + void setIcon(const QPixmap &pixmap); + void setHighlight(bool highlight); + +protected: + void mousePressEvent(QMouseEvent *event) override; + void enterEvent(QEvent *event) override; + void leaveEvent(QEvent *event) override; + +signals: + void clicked(int index); + +private: + int m_index = 0; + QHBoxLayout m_layout; + QLabel m_iconLabel; + QLabel m_textLabel; +}; + +#endif // NAVIGATIONBARITEM_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/PlayHead.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/PlayHead.cpp new file mode 100644 index 00000000..3ddfdcad --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/PlayHead.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "PlayHead.h" +#include "Ruler.h" +#include "TimelineConstants.h" +#include "StudioPreferences.h" +#include "StudioUtils.h" + +#include <QtGui/qpainter.h> +#include <QtGui/qcursor.h> +#include <QtWidgets/qwidget.h> + +PlayHead::PlayHead(Ruler *ruler) + : QGraphicsRectItem() + , m_ruler(ruler) +{ + setZValue(99); + setRect(-TimelineConstants::PLAYHEAD_W * .5, 0, TimelineConstants::PLAYHEAD_W, 0); +} + +void PlayHead::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(option) + Q_UNUSED(widget) + + bool hiResIcons = StudioUtils::devicePixelRatio(widget->window()->windowHandle()) > 1.0; + static const QPixmap pixHead = QPixmap(":/images/PlaybackHead.png"); + static const QPixmap pixHead2x = QPixmap(":/images/PlaybackHead@2x.png"); + + static const int PLAY_HEAD_H = 999999; // theoretically big enough height + painter->drawPixmap(-TimelineConstants::PLAYHEAD_W * .5, 0, hiResIcons ? pixHead2x : pixHead); + painter->setPen(CStudioPreferences::timelinePlayheadLineColor()); + painter->drawLine(0, 0, 0, PLAY_HEAD_H); +} + +void PlayHead::setHeight(int height) +{ + setRect(rect().x(), rect().y(), rect().width(), height); +} + +void PlayHead::setTime(long time) +{ + if (time < 0) + time = 0; + else if (time > m_ruler->duration()) + time = m_ruler->duration(); + + m_time = time; + updatePosition(); +} + +void PlayHead::setPosition(double posX) +{ + posX = qBound(TimelineConstants::RULER_EDGE_OFFSET, posX, m_ruler->duration() + * TimelineConstants::RULER_MILLI_W * m_ruler->timelineScale() + + TimelineConstants::RULER_EDGE_OFFSET); + + setX(m_ruler->x() + posX); + m_time = (posX - TimelineConstants::RULER_EDGE_OFFSET) + / (TimelineConstants::RULER_MILLI_W * m_ruler->timelineScale()); +} + +void PlayHead::updatePosition() +{ + setX(m_ruler->x() + TimelineConstants::RULER_EDGE_OFFSET + + m_time * TimelineConstants::RULER_MILLI_W * m_ruler->timelineScale()); +} + +long PlayHead::time() const +{ + return m_time; +} + +int PlayHead::type() const +{ + // Enable the use of qgraphicsitem_cast with this item. + return TimelineItem::TypePlayHead; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/PlayHead.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/PlayHead.h new file mode 100644 index 00000000..395e6317 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/PlayHead.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef PLAYHEAD_H +#define PLAYHEAD_H + +#include "TimelineItem.h" + +#include <QtWidgets/qgraphicsitem.h> + +class Ruler; + +class PlayHead : public QGraphicsRectItem +{ + +public: + explicit PlayHead(Ruler *m_ruler); + + void setHeight(int height); + void setPosition(double posX); // set x poisiotn + void updatePosition(); // sync x poisiotn based on time value + void setTime(long time); // set time (sets x based on time (ms) input) + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget = nullptr) override; + long time() const; + int type() const override; + +private: + long m_time = 0; + Ruler *m_ruler; +}; + +#endif // PLAYHEAD_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimeline.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimeline.cpp new file mode 100644 index 00000000..25b66911 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimeline.cpp @@ -0,0 +1,1035 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "RowTimeline.h" +#include "RowTimelinePropertyGraph.h" +#include "RowTree.h" +#include "RowManager.h" +#include "Ruler.h" +#include "TimelineConstants.h" +#include "Keyframe.h" +#include "KeyframeManager.h" +#include "TimelineGraphicsScene.h" +#include "Bindings/ITimelineItemBinding.h" +#include "Bindings/ITimelineTimebar.h" +#include "Bindings/Qt3DSDMTimelineItemProperty.h" +#include "AppFonts.h" +#include "StudioPreferences.h" +#include "TimelineToolbar.h" +#include "StudioUtils.h" + +#include <QtGui/qpainter.h> +#include <QtGui/qbrush.h> +#include <QtWidgets/qdesktopwidget.h> +#include <QtWidgets/qapplication.h> +#include <QtWidgets/qgraphicssceneevent.h> +#include <QtWidgets/qwidget.h> +#include <QtWidgets/qlabel.h> +#include <QtCore/qdatetime.h> + +RowTimeline::RowTimeline() + : InteractiveTimelineItem() +{ + // 999999: theoretically big enough row width (~ 4.6 hrs of presentation length) + setMinimumWidth(999999); + setMaximumWidth(999999); +} + +RowTimeline::~RowTimeline() +{ + // remove keyframes + if (!m_keyframes.empty()) { + if (m_isProperty) // non-property rows use the same keyframes from property rows. + qDeleteAll(m_keyframes); + + m_keyframes.clear(); + } +} + +void RowTimeline::initialize() +{ + // Called once m_rowTree exists + + m_commentItem = new RowTimelineCommentItem(this); + m_commentItem->setParentRow(m_rowTree); + updateCommentItemPos(); + + TimelineToolbar *toolbar = m_rowTree->m_scene->widgetTimeline()->toolbar(); + connect(toolbar, &TimelineToolbar::showRowTextsToggled, this, [this]() { + updateCommentItem(); + }); + + connect(m_commentItem, &RowTimelineCommentItem::labelChanged, this, + [this](const QString &label) { + // Update label on timeline and on model + ITimelineTimebar *timebar = m_rowTree->m_binding->GetTimelineItem()->GetTimebar(); + timebar->SetTimebarComment(label); + }); + + connect(m_rowTree->m_scene->ruler(), &Ruler::viewportXChanged, this, + &RowTimeline::updateCommentItemPos); +} + +void RowTimeline::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(option) + + bool hiResIcons = StudioUtils::devicePixelRatio(widget->window()->windowHandle()) > 1.0; + + if (!y()) // prevents flickering when the row is just inserted to the layout + return; + + const int currentHeight = size().height() - 1; + + if (isColorProperty() && !m_keyframes.empty()) { + drawColorPropertyGradient(painter, widget->width()); + } else { + // Background + QColor bgColor; + if (m_rowTree->isProperty()) + bgColor = CStudioPreferences::timelineRowColorNormalProp(); + else if (m_state == Selected) + bgColor = CStudioPreferences::timelineRowColorSelected(); + else if (m_state == Hovered && !m_rowTree->m_locked) + bgColor = CStudioPreferences::timelineRowColorOver(); + else + bgColor = CStudioPreferences::timelineRowColorNormal(); + painter->fillRect(0, 0, size().width(), currentHeight, bgColor); + } + + const double edgeOffset = TimelineConstants::RULER_EDGE_OFFSET; + + // Duration. Draw duration bar (for scene/component root) also if it has + // datainput controller + if (m_rowTree->hasDurationBar() || m_controllerDataInput.size()) { + painter->save(); + + // fully outside ancestors' limits, draw fully hashed + if (m_minStartX > m_endX || m_maxEndX < m_startX) { + painter->setBrush(QBrush(CStudioPreferences::timelineRowColorDurationOff1(), + Qt::BDiagPattern)); + painter->setPen(Qt::NoPen); + painter->fillRect(QRect(edgeOffset + m_startX, 0, m_endX - m_startX, currentHeight), + CStudioPreferences::timelineRowColorDurationOff2()); + painter->drawRect(QRect(edgeOffset + m_startX, 0, m_endX - m_startX, currentHeight)); + + painter->setPen(QPen(CStudioPreferences::timelineRowColorDurationEdge(), 2)); + painter->drawLine(edgeOffset + m_startX, 0, edgeOffset + m_startX, currentHeight); + painter->drawLine(edgeOffset + m_endX, 0, edgeOffset + m_endX, currentHeight); + } else { + // draw main duration part + double x = edgeOffset + qMax(m_startX, m_minStartX); + double w = edgeOffset + qMin(m_endX, m_maxEndX) - x; + static const int marginY = 3; + + painter->setPen(Qt::NoPen); + + if (m_controllerDataInput.size()) { + painter->fillRect(QRect(x, 0, w, currentHeight), + CStudioPreferences::dataInputColor()); + } else if (m_rowTree->indexInLayout() != 1) { + painter->fillRect(QRect(x, 0, w, currentHeight), m_barColor); + } + + if (m_state == Selected) { + // draw selection overlay on bar + painter->fillRect(QRect(x, marginY, w, currentHeight - marginY * 2), + CStudioPreferences::timelineRowColorDurationSelected()); + } + + if (m_controllerDataInput.size()) { + static const QPixmap pixDataInput = QPixmap(":/images/Objects-DataInput-White.png"); + static const QPixmap pixDataInput2x + = QPixmap(":/images/Objects-DataInput-White@2x.png"); + static const QFontMetrics fm(painter->font()); + + // need clip region to limit datainput icon visibility to the same rect as we use + // for text + painter->setClipRect(x, 0, w, currentHeight); + painter->setClipping(true); + painter->setPen(QPen(CStudioPreferences::textColor(), 2)); + // +5 added to text location to make margin comparable to other datainput controls + painter->drawText(QRect(x + pixDataInput.width() + 5, 0, w, currentHeight), + m_controllerDataInput, QTextOption(Qt::AlignCenter)); + // place the icon in front of the text + int textwidth = fm.width(m_controllerDataInput); + int iconx = x + (w - textwidth) / 2; + if (iconx < x) + iconx = x; + painter->drawPixmap(iconx, marginY, hiResIcons ? pixDataInput2x : pixDataInput); + painter->setPen(Qt::NoPen); + painter->setClipping(false); + } + + // draw hashed part before + painter->setBrush(QBrush(CStudioPreferences::timelineRowColorDurationOff1(), + Qt::BDiagPattern)); + if (m_startX < m_minStartX) { + painter->setPen(Qt::NoPen); + painter->fillRect(QRect(edgeOffset + m_startX, 0, m_minStartX - m_startX, + currentHeight), + CStudioPreferences::timelineRowColorDurationOff2()); + painter->drawRect(QRect(edgeOffset + m_startX, 0, m_minStartX - m_startX, + currentHeight)); + painter->setPen(CStudioPreferences::timelineRowColorDurationEdge()); + painter->drawLine(edgeOffset + m_minStartX, 0, edgeOffset + m_minStartX, + currentHeight); + } + + // draw hashed part after + if (m_endX > m_maxEndX) { + painter->setPen(Qt::NoPen); + painter->fillRect(QRect(edgeOffset + m_maxEndX, 0, m_endX - m_maxEndX, + currentHeight), + CStudioPreferences::timelineRowColorDurationOff2()); + painter->drawRect(QRect(edgeOffset + m_maxEndX, 0, m_endX - m_maxEndX, + currentHeight)); + painter->setPen(CStudioPreferences::timelineRowColorDurationEdge()); + painter->drawLine(edgeOffset + m_maxEndX, 0, edgeOffset + m_maxEndX, currentHeight); + } + + if (m_rowTree->indexInLayout() != 1) { + painter->setPen(QPen(CStudioPreferences::timelineRowColorDurationEdge(), 2)); + painter->drawLine(edgeOffset + m_startX, 0, edgeOffset + m_startX, currentHeight); + painter->drawLine(edgeOffset + m_endX, 0, edgeOffset + m_endX, currentHeight); + } + } + + painter->restore(); + } + + if (m_propertyGraph) { // Property graph + QRectF graphRect(edgeOffset, 0, widget->width(), currentHeight); + m_propertyGraph->paintGraphs(painter, graphRect); + } + + // Keyframes + const qreal keyFrameH = 16.0; + const qreal keyFrameHalfH = keyFrameH / 2.0; + const qreal keyFrameY = (qMin(currentHeight, TimelineConstants::ROW_H) / 2.0) - keyFrameHalfH; + const qreal hiddenKeyFrameY = keyFrameY + (keyFrameH * 2.0 / 3.0) + 2.0; + const qreal keyFrameOffset = hiResIcons ? 8 : 7.5; + + // Hidden descendant keyframe indicators + if (!m_rowTree->expanded()) { + static const QPixmap pixKeyframeHidden = QPixmap(":/images/keyframe-hidden-normal.png"); + static const QPixmap pixKeyframeHidden2x + = QPixmap(":/images/keyframe-hidden-normal@2x.png"); + QVector<long> childKeyframeTimes; + collectChildKeyframeTimes(childKeyframeTimes); + + const qreal oldOpacity = painter->opacity(); + painter->setOpacity(0.75); + for (const auto time : qAsConst(childKeyframeTimes)) { + const qreal xCoord = edgeOffset + m_rowTree->m_scene->ruler()->timeToDistance(time) + - 2.5; + painter->drawPixmap(QPointF(xCoord, hiddenKeyFrameY), hiResIcons ? pixKeyframeHidden2x + : pixKeyframeHidden); + } + painter->setOpacity(oldOpacity); + } + + if (m_rowTree->hasPropertyChildren()) { // object row keyframes + static const QPixmap pixKeyframeMasterDisabled + = QPixmap(":/images/Keyframe-Master-Disabled.png"); + static const QPixmap pixKeyframeMasterNormal + = QPixmap(":/images/Keyframe-Master-Normal.png"); + static const QPixmap pixKeyframeMasterSelected + = QPixmap(":/images/Keyframe-Master-Selected.png"); + static const QPixmap pixKeyframeMasterDynamicDisabled + = QPixmap(":/images/Keyframe-MasterDynamic-Disabled.png"); + static const QPixmap pixKeyframeMasterDynamicNormal + = QPixmap(":/images/Keyframe-MasterDynamic-Normal.png"); + static const QPixmap pixKeyframeMasterDynamicSelected + = QPixmap(":/images/Keyframe-MasterDynamic-Selected.png"); + static const QPixmap pixKeyframeMasterDisabled2x + = QPixmap(":/images/Keyframe-Master-Disabled@2x.png"); + static const QPixmap pixKeyframeMasterNormal2x + = QPixmap(":/images/Keyframe-Master-Normal@2x.png"); + static const QPixmap pixKeyframeMasterSelected2x + = QPixmap(":/images/Keyframe-Master-Selected@2x.png"); + static const QPixmap pixKeyframeMasterDynamicDisabled2x + = QPixmap(":/images/Keyframe-MasterDynamic-Disabled@2x.png"); + static const QPixmap pixKeyframeMasterDynamicNormal2x + = QPixmap(":/images/Keyframe-MasterDynamic-Normal@2x.png"); + static const QPixmap pixKeyframeMasterDynamicSelected2x + = QPixmap(":/images/Keyframe-MasterDynamic-Selected@2x.png"); + for (auto keyframe : qAsConst(m_keyframes)) { + QPixmap pixmap; + if (m_rowTree->locked()) { + if (keyframe->dynamic) { + pixmap = hiResIcons ? pixKeyframeMasterDynamicDisabled2x + : pixKeyframeMasterDynamicDisabled; + } else { + pixmap = hiResIcons ? pixKeyframeMasterDisabled2x + : pixKeyframeMasterDisabled; + } + } else if (keyframe->selected()) { + if (keyframe->dynamic) { + pixmap = hiResIcons ? pixKeyframeMasterDynamicSelected2x + : pixKeyframeMasterDynamicSelected; + } else { + pixmap = hiResIcons ? pixKeyframeMasterSelected2x + : pixKeyframeMasterSelected; + } + } else { + if (keyframe->dynamic) { + pixmap = hiResIcons ? pixKeyframeMasterDynamicNormal2x + : pixKeyframeMasterDynamicNormal; + } else { + pixmap = hiResIcons ? pixKeyframeMasterNormal2x + : pixKeyframeMasterNormal; + } + } + painter->drawPixmap(QPointF(edgeOffset + m_rowTree->m_scene->ruler() + ->timeToDistance(keyframe->time) - keyFrameOffset, + keyFrameY), pixmap); + + // highlight the pressed keyframe in a multi-selection (the keyframe that is affected + // by snapping, and setting time dialog) + if (m_rowTree->m_scene->keyframeManager()->selectedKeyframes().size() > 1 + && m_rowTree->m_scene->pressedKeyframe() == keyframe) { + painter->setPen(QPen(CStudioPreferences::timelinePressedKeyframeColor(), 1)); + painter->drawArc(edgeOffset + m_rowTree->m_scene->ruler() + ->timeToDistance(keyframe->time) - 4, keyFrameY + 4, 9, 9, 0, + 5760); + } + } + } else if (m_rowTree->isProperty()) { // property row keyframes + static const QPixmap pixKeyframePropertyDisabled + = QPixmap(":/images/Keyframe-Property-Disabled.png"); + static const QPixmap pixKeyframePropertyNormal + = QPixmap(":/images/Keyframe-Property-Normal.png"); + static const QPixmap pixKeyframePropertySelected + = QPixmap(":/images/Keyframe-Property-Selected.png"); + static const QPixmap pixKeyframePropertyDynamicDisabled + = QPixmap(":/images/Keyframe-PropertyDynamic-Disabled.png"); + static const QPixmap pixKeyframePropertyDynamicNormal + = QPixmap(":/images/Keyframe-PropertyDynamic-Normal.png"); + static const QPixmap pixKeyframePropertyDynamicSelected + = QPixmap(":/images/Keyframe-PropertyDynamic-Selected.png"); + static const QPixmap pixKeyframePropertyDisabled2x + = QPixmap(":/images/Keyframe-Property-Disabled@2x.png"); + static const QPixmap pixKeyframePropertyNormal2x + = QPixmap(":/images/Keyframe-Property-Normal@2x.png"); + static const QPixmap pixKeyframePropertySelected2x + = QPixmap(":/images/Keyframe-Property-Selected@2x.png"); + static const QPixmap pixKeyframePropertyDynamicDisabled2x + = QPixmap(":/images/Keyframe-PropertyDynamic-Disabled@2x.png"); + static const QPixmap pixKeyframePropertyDynamicNormal2x + = QPixmap(":/images/Keyframe-PropertyDynamic-Normal@2x.png"); + static const QPixmap pixKeyframePropertyDynamicSelected2x + = QPixmap(":/images/Keyframe-PropertyDynamic-Selected@2x.png"); + for (auto keyframe : qAsConst(m_keyframes)) { + QPixmap pixmap; + if (m_rowTree->locked()) { + if (keyframe->dynamic) { + pixmap = hiResIcons ? pixKeyframePropertyDynamicDisabled2x + : pixKeyframePropertyDynamicDisabled; + + } else { + pixmap = hiResIcons ? pixKeyframePropertyDisabled2x + : pixKeyframePropertyDisabled; + } + } else if (keyframe->selected()) { + if (keyframe->dynamic) { + pixmap = hiResIcons ? pixKeyframePropertyDynamicSelected2x + : pixKeyframePropertyDynamicSelected; + + } else { + pixmap = hiResIcons ? pixKeyframePropertySelected2x + : pixKeyframePropertySelected; + } + } else { + if (keyframe->dynamic) { + pixmap = hiResIcons ? pixKeyframePropertyDynamicNormal2x + : pixKeyframePropertyDynamicNormal; + + } else { + pixmap = hiResIcons ? pixKeyframePropertyNormal2x + : pixKeyframePropertyNormal; + } + } + painter->drawPixmap(QPointF(edgeOffset + m_rowTree->m_scene->ruler() + ->timeToDistance(keyframe->time) - keyFrameOffset, + keyFrameY), pixmap); + } + } +} + +bool RowTimeline::isColorProperty() const +{ + ITimelineItemProperty *propBinding = m_rowTree->propBinding(); + if (propBinding) { + qt3dsdm::TDataTypePair type = propBinding->GetType(); + if (m_rowTree->isProperty() + && type.first == qt3dsdm::DataModelDataType::Float4 + && type.second == qt3dsdm::AdditionalMetaDataType::Color) { + return true; + } + } + return false; +} + +void RowTimeline::drawColorPropertyGradient(QPainter *painter, int width) +{ + // Gradient scaled width, or at least widget width + double minWidth = width; + double timelineScale = m_rowTree->m_scene->ruler()->timelineScale(); + double scaledWidth = width * (timelineScale / 2); + width = qMax(minWidth, scaledWidth); + + ITimelineItemProperty *propBinding = m_rowTree->propBinding(); + QLinearGradient bgGradient(0, 0, width, 0); + + for (auto keyframe : qAsConst(m_keyframes)) { + double xPos = m_rowTree->m_scene->ruler()->timeToDistance(keyframe->time); + double gradPos = xPos / width; + gradPos = qBound(0.0, gradPos, 1.0); + QColor currentColor; + // Get the color at the specified time. + currentColor.setRed(propBinding->GetChannelValueAtTime(0, keyframe->time)); + currentColor.setGreen(propBinding->GetChannelValueAtTime(1, keyframe->time)); + currentColor.setBlue(propBinding->GetChannelValueAtTime(2, keyframe->time)); + bgGradient.setColorAt(gradPos, currentColor); + } + painter->fillRect(TimelineConstants::RULER_EDGE_OFFSET, 0, + width, size().height() - 1, bgGradient); +} + +Keyframe *RowTimeline::getClickedKeyframe(const QPointF &scenePos) +{ + if (rowTree()->locked()) + return nullptr; + + QPointF p = mapFromScene(scenePos.x(), scenePos.y()); + double x; + + QList<Keyframe *> keyframes; + if (m_rowTree->hasPropertyChildren()) { + const auto childProps = m_rowTree->childProps(); + for (auto child : childProps) + keyframes.append(child->rowTimeline()->m_keyframes); + } else { + keyframes = m_keyframes; + } + + for (const auto keyframe : qAsConst(keyframes)) { + x = TimelineConstants::RULER_EDGE_OFFSET + + m_rowTree->m_scene->ruler()->timeToDistance(keyframe->time); + + if (p.x() > x - 5 && p.x() < x + 5 && p.y() > 3 && p.y() < 16) + return keyframe; + } + + return nullptr; +} + +QList<Keyframe *> RowTimeline::getKeyframesInRange(const QRectF &rect) const +{ + double x; + QRectF localRect = mapFromScene(rect).boundingRect(); + + QList<Keyframe *> result; + + static const int KF_CENTER_Y = 10; + for (auto keyframe : qAsConst(m_keyframes)) { + x = TimelineConstants::RULER_EDGE_OFFSET + + m_rowTree->m_scene->ruler()->timeToDistance(keyframe->time); + + if (localRect.left() < x && localRect.right() > x + && localRect.top() < KF_CENTER_Y && localRect.bottom() > KF_CENTER_Y) { + result.append(keyframe); + } + } + + return result; +} + +void RowTimeline::updateDurationFromBinding() +{ + if (m_rowTree->isProperty()) // this method works for main rows only + return; + + ITimelineTimebar *timebar = m_rowTree->m_binding->GetTimelineItem()->GetTimebar(); + clearBoundChildren(); + setStartTime(timebar->GetStartTime()); + setEndTime(timebar->GetEndTime()); +} + +void RowTimeline::updateKeyframesFromBinding(const QList<int> &properties) +{ + if (m_rowTree->isProperty()) // this method works for main rows only + return; + + const auto childProps = m_rowTree->childProps(); + for (auto child : childProps) { + qt3dsdm::Qt3DSDMPropertyHandle propertyHandle = + static_cast<Qt3DSDMTimelineItemProperty *>(child->m_PropBinding) + ->getPropertyHandle(); + if (properties.contains(propertyHandle)) { + m_rowTree->m_scene->keyframeManager()->deleteKeyframes(child->rowTimeline(), false); + + for (int i = 0; i < child->m_PropBinding->GetKeyframeCount(); i++) { + Qt3DSDMTimelineKeyframe *kf = static_cast<Qt3DSDMTimelineKeyframe *> + (child->m_PropBinding->GetKeyframeByIndex(i)); + + Keyframe *kfUI = new Keyframe(kf->GetTime(), child->rowTimeline()); + kfUI->binding = kf; + kfUI->dynamic = kf->IsDynamic(); + kf->setUI(kfUI); + child->rowTimeline()->insertKeyframe(kfUI); + child->parentRow()->rowTimeline()->insertKeyframe(kfUI); + if (kf->IsSelected()) + m_rowTree->m_scene->keyframeManager()->selectKeyframe(kfUI); + } + + if (isVisible()) { + child->rowTimeline()->update(); + } else { + // Find the first visible parent and update that to show hidden keyframes + RowTree *updateRow = m_rowTree->parentRow(); + while (updateRow && !updateRow->isVisible()) + updateRow = updateRow->parentRow(); + if (updateRow) + updateRow->rowTimeline()->update(); + } + } + } + update(); +} + +void RowTimeline::insertKeyframe(Keyframe *keyframe) +{ + if (!m_keyframes.contains(keyframe)) + m_keyframes.append(keyframe); +} + +void RowTimeline::removeKeyframe(Keyframe *keyframe) +{ + m_keyframes.removeAll(keyframe); +} + +void RowTimeline::putSelectedKeyframesOnTop() +{ + if (!m_keyframes.empty()) { + std::partition(m_keyframes.begin(), m_keyframes.end(), [](Keyframe *kf) { + return !kf->selected(); + }); + } + + if (m_rowTree->hasPropertyChildren()) { // has property rows + const auto childProps = m_rowTree->childProps(); + for (auto child : childProps) { + std::partition(child->rowTimeline()->m_keyframes.begin(), + child->rowTimeline()->m_keyframes.end(), [](Keyframe *kf) { + return !kf->selected(); + }); + } + } +} + +void RowTimeline::updateKeyframes() +{ + update(); + + if (m_rowTree->hasPropertyChildren()) { // master keyframes + const auto childProps = m_rowTree->childProps(); + for (const auto child : childProps) + child->rowTimeline()->update(); + } +} + +TimelineControlType RowTimeline::getClickedControl(const QPointF &scenePos) const +{ + if (!m_rowTree->hasDurationBar()) + return TimelineControlType::None; + + if (!m_rowTree->locked()) { + QPointF p = mapFromScene(scenePos.x(), scenePos.y()); + p.setX(p.x() - TimelineConstants::RULER_EDGE_OFFSET); + + const int halfHandle = TimelineConstants::DURATION_HANDLE_W * .5; + // Never choose start handle if end time is zero, as you cannot adjust it in that case + bool startHandle = p.x() > m_startX - halfHandle && p.x() < m_startX + halfHandle + && m_endTime > 0; + bool endHandle = p.x() > m_endX - halfHandle && p.x() < m_endX + halfHandle; + if (startHandle && endHandle) { + // If handles overlap, choose the handle based on the side of the click relative to start + startHandle = p.x() < m_startX; + endHandle = !startHandle; + } + if (startHandle) + return TimelineControlType::StartHandle; + else if (endHandle) + return TimelineControlType::EndHandle; + else if (p.x() > m_startX && p.x() < m_endX && !rowTree()->locked()) + return TimelineControlType::Duration; + } + + return TimelineControlType::None; +} + +void RowTimeline::startDurationMove(double clickX) +{ + // clickX is in ruler coordinate space + m_startDurationMoveStartTime = m_startTime; + m_startDurationMoveOffsetX = clickX - m_startX; +} + +void RowTimeline::updateBoundChildren(bool start) +{ + // Collect all bound children + // Children are considered bound if the start/end time matches the parent time + if (start) + m_boundChildrenStart.clear(); + else + m_boundChildrenEnd.clear(); + if (m_rowTree->hasDurationBar()) { + const auto childRows = m_rowTree->childRows(); + for (auto child : childRows) { + if (child->hasDurationBar() && !child->locked()) { + RowTimeline *rowTimeline = child->rowTimeline(); + if (start && rowTimeline->m_startX == m_startX) { + m_boundChildrenStart.append(rowTimeline); + rowTimeline->updateBoundChildren(start); + } else if (!start && rowTimeline->m_endX == m_endX) { + m_boundChildrenEnd.append(rowTimeline); + rowTimeline->updateBoundChildren(start); + } + } + } + } +} + +void RowTimeline::clearBoundChildren() +{ + m_boundChildrenStart.clear(); + m_boundChildrenEnd.clear(); +} + +// move the duration area (start/end x) +void RowTimeline::moveDurationBy(double dx) +{ + if (m_startX + dx < 0) + dx = -m_startX; + + m_startX += dx; + m_endX += dx; + + if (!m_rowTree->parentRow() || m_rowTree->objectType() == OBJTYPE_LAYER + || m_rowTree->hasComponentAncestor()) { + m_minStartX = m_startX; + m_maxEndX = m_endX; + } + + Ruler *ruler = m_rowTree->m_scene->ruler(); + m_startTime = ruler->distanceToTime(m_startX); + m_endTime = ruler->distanceToTime(m_endX); + + // move keyframes with the row + if (!m_rowTree->isProperty()) { // make sure we don't move the keyframes twice + for (Keyframe *keyframe : qAsConst(m_keyframes)) + keyframe->time += rowTree()->m_scene->ruler()->distanceToTime(dx); + } + + update(); + + if (!m_rowTree->empty()) { + updateChildrenMinStartXRecursive(m_rowTree); + updateChildrenMaxEndXRecursive(m_rowTree); + + for (RowTree *child : qAsConst(m_rowTree->m_childRows)) { + if (!child->locked()) + child->m_rowTimeline->moveDurationBy(dx); + } + } +} + +void RowTimeline::moveDurationTo(double newX) +{ + if (newX < 0) + newX = 0; + + double dx = newX - m_startX; + double durationX = m_endX - m_startX; + + m_startX = newX; + m_endX = m_startX + durationX; + + if (!m_rowTree->parentRow() || m_rowTree->objectType() == OBJTYPE_LAYER + || m_rowTree->hasComponentAncestor()) { + m_minStartX = m_startX; + m_maxEndX = m_endX; + } + + Ruler *ruler = m_rowTree->m_scene->ruler(); + m_startTime = ruler->distanceToTime(m_startX); + m_endTime = ruler->distanceToTime(m_endX); + + // move keyframes with the row + if (!m_rowTree->isProperty()) { // make sure we don't move the keyframes twice + for (Keyframe *keyframe : qAsConst(m_keyframes)) + keyframe->time += ruler->distanceToTime(dx); + } + + update(); + + if (!m_rowTree->empty()) { + updateChildrenMinStartXRecursive(m_rowTree); + updateChildrenMaxEndXRecursive(m_rowTree); + + for (RowTree *child : qAsConst(m_rowTree->m_childRows)) { + if (!child->locked()) + child->m_rowTimeline->moveDurationBy(dx); + } + } +} + +long RowTimeline::getDurationMoveTime() const +{ + return m_startTime - m_startDurationMoveStartTime; +} + +double RowTimeline::getDurationMoveOffsetX() const +{ + return m_startDurationMoveOffsetX; +} + +long RowTimeline::getDuration() const +{ + return m_endTime - m_startTime; +} + +void RowTimeline::collectChildKeyframeTimes(QVector<long> &childKeyframeTimes) +{ + const auto childRows = m_rowTree->childRows(); + for (const auto row : childRows) { + row->rowTimeline()->collectChildKeyframeTimes(childKeyframeTimes); + const auto keyframes = row->rowTimeline()->keyframes(); + for (const auto kf : keyframes) + childKeyframeTimes.append(kf->time); + } +} + +// called after timeline scale is changed to update duration star/end positions +void RowTimeline::updatePosition() +{ + clearBoundChildren(); + setStartTime(m_startTime); + setEndTime(m_endTime); +} + +// Set the position of the start of the row duration +void RowTimeline::setStartX(double startX) +{ + if (startX < 0) + startX = 0; + else if (startX > m_endX) + startX = m_endX; + + m_startX = startX; + m_startTime = m_rowTree->m_scene->ruler()->distanceToTime(startX); + + if (!m_rowTree->parentRow() || m_rowTree->parentRow()->objectType() == OBJTYPE_SCENE + || m_rowTree->hasComponentAncestor()) { + m_minStartX = 0; + } + + updateChildrenStartRecursive(); + updateChildrenMinStartXRecursive(m_rowTree); + update(); +} + +// Set the position of the end of the row duration +void RowTimeline::setEndX(double endX) +{ + if (endX < m_startX) + endX = m_startX; + + m_endX = endX; + m_endTime = m_rowTree->m_scene->ruler()->distanceToTime(endX); + + if (!m_rowTree->parentRow() || m_rowTree->parentRow()->objectType() == OBJTYPE_SCENE + || m_rowTree->hasComponentAncestor()) { + m_maxEndX = 999999; + } + + updateChildrenEndRecursive(); + updateChildrenMaxEndXRecursive(m_rowTree); + update(); +} + +QColor RowTimeline::barColor() const +{ + return m_barColor; +} + +void RowTimeline::setBarColor(const QColor &color) +{ + m_barColor = color; + update(); +} + +void RowTimeline::setControllerText(const QString &controller) +{ + m_controllerDataInput = controller; + update(); +} + +void RowTimeline::updateChildrenStartRecursive() +{ + for (auto child : qAsConst(m_boundChildrenStart)) { + if (!child.isNull()) { + child->m_startX = m_startX; + child->m_startTime = m_startTime; + child->updateChildrenStartRecursive(); + child->update(); + } + } +} + +void RowTimeline::updateChildrenEndRecursive() +{ + for (auto child : qAsConst(m_boundChildrenEnd)) { + if (!child.isNull()) { + child->m_endX = m_endX; + child->m_endTime = m_endTime; + child->updateChildrenEndRecursive(); + child->update(); + } + } +} + +void RowTimeline::updateChildrenMinStartXRecursive(RowTree *rowTree) +{ + if (m_rowTree->objectType() != OBJTYPE_SCENE && !rowTree->empty()) { + const auto childRows = rowTree->childRows(); + bool isComponentChild = m_rowTree->objectType() == OBJTYPE_COMPONENT + || m_rowTree->hasComponentAncestor(); + for (auto child : childRows) { + if (isComponentChild) { + child->rowTimeline()->m_minStartX = 0; + } else { + child->rowTimeline()->m_minStartX = qMax(rowTree->rowTimeline()->m_startX, + rowTree->rowTimeline()->m_minStartX); + } + child->rowTimeline()->update(); + + updateChildrenMinStartXRecursive(child); + } + } +} + +void RowTimeline::updateChildrenMaxEndXRecursive(RowTree *rowTree) +{ + if (m_rowTree->objectType() != OBJTYPE_SCENE && !rowTree->empty()) { + const auto childRows = rowTree->childRows(); + bool isComponentChild = m_rowTree->objectType() == OBJTYPE_COMPONENT + || m_rowTree->hasComponentAncestor(); + for (auto child : childRows) { + if (isComponentChild) { + child->rowTimeline()->m_maxEndX = 999999; + } else { + child->rowTimeline()->m_maxEndX = qMin(rowTree->rowTimeline()->m_endX, + rowTree->rowTimeline()->m_maxEndX); + } + child->rowTimeline()->update(); + + updateChildrenMaxEndXRecursive(child); + } + } +} + +void RowTimeline::updateCommentItem() +{ + if (!m_commentItem) + return; + TimelineToolbar *toolbar = m_rowTree->m_scene->widgetTimeline()->toolbar(); + // Backend allows storing comments for rows with duration bar + bool canHaveComment = m_rowTree->hasDurationBar(); + bool showComments = canHaveComment && toolbar->actionShowRowTexts()->isChecked(); + m_commentItem->setVisible(showComments); + if (showComments && m_rowTree->m_binding) { + ITimelineTimebar *timebar = m_rowTree->m_binding->GetTimelineItem()->GetTimebar(); + m_commentItem->setLabel(timebar->GetTimebarComment()); + } +} + +void RowTimeline::updateCommentItemPos() +{ + if (!m_commentItem) + return; + + Ruler *ruler = m_rowTree->m_scene->ruler(); + m_commentItem->setPos(TimelineConstants::RULER_EDGE_OFFSET + ruler->viewportX(), + -TimelineConstants::ROW_TEXT_OFFSET_Y); +} + +void RowTimeline::setStartTime(long startTime) +{ + m_startTime = startTime; + m_startX = m_rowTree->m_scene->ruler()->timeToDistance(startTime); + + if (!m_rowTree->parentRow() || m_rowTree->parentRow()->objectType() == OBJTYPE_SCENE + || m_rowTree->hasComponentAncestor()) { + m_minStartX = 0; + } + + updateChildrenStartRecursive(); + updateChildrenMinStartXRecursive(m_rowTree); + update(); +} + +void RowTimeline::setEndTime(long endTime) +{ + m_endTime = endTime; + m_endX = m_rowTree->m_scene->ruler()->timeToDistance(endTime); + + if (!m_rowTree->parentRow() || m_rowTree->parentRow()->objectType() == OBJTYPE_SCENE + || m_rowTree->hasComponentAncestor()) { + m_maxEndX = 999999; + } + + updateChildrenEndRecursive(); + updateChildrenMaxEndXRecursive(m_rowTree); + update(); +} + +// duration start x in local space (x=0 at time=0) +double RowTimeline::getStartX() const +{ + return m_startX; +} + +// duration end x in local space +double RowTimeline::getEndX() const +{ + return m_endX; +} + +long RowTimeline::getStartTime() const +{ + return m_startTime; +} + +long RowTimeline::getEndTime() const +{ + return m_endTime; +} + +void RowTimeline::setState(State state) +{ + m_state = state; + m_rowTree->m_state = state; + + update(); + m_rowTree->update(); +} + +void RowTimeline::setRowTree(RowTree *rowTree) +{ + m_rowTree = rowTree; + if (m_rowTree->isProperty()) { + if (m_propertyGraph) + delete m_propertyGraph; + m_propertyGraph = new RowTimelinePropertyGraph(this); + } + initialize(); +} + +RowTree *RowTimeline::rowTree() const +{ + return m_rowTree; +} + +QList<Keyframe *> RowTimeline::keyframes() const +{ + return m_keyframes; +} + +QString RowTimeline::formatTime(long millis) const +{ + static const QString timeTemplate = tr("%1:%2.%3"); + static const QChar fillChar = tr("0").at(0); + + long mins = millis % 3600000 / 60000; + long secs = millis % 60000 / 1000; + long mils = millis % 1000; + + return timeTemplate.arg(mins).arg(secs, 2, 10, fillChar).arg(mils, 3, 10, fillChar); +} + +void RowTimeline::showToolTip(const QPointF &pos) +{ + QLabel *tooltip = m_rowTree->m_scene->timebarTooltip(); + + tooltip->setText(formatTime(m_startTime) + " - " + formatTime(m_endTime) + + " (" + formatTime(m_endTime - m_startTime) + ")"); + + tooltip->adjustSize(); + + QPoint newPos = pos.toPoint() + QPoint(-tooltip->width() / 2, + -tooltip->height() - TimelineConstants::TIMEBAR_TOOLTIP_OFFSET_V); + + // Confine the tooltip to the current screen area to avoid artifacts from different pixel ratios + static const int MARGIN = 5; + const QRect screenGeometry = QApplication::desktop()->screenGeometry( + m_rowTree->m_scene->widgetTimeline()); + int xMin = screenGeometry.x() + MARGIN; + int xMax = screenGeometry.x() + screenGeometry.width() - tooltip->width() - MARGIN; + if (newPos.x() < xMin) + newPos.setX(xMin); + else if (newPos.x() > xMax) + newPos.setX(xMax); + + tooltip->move(newPos); + tooltip->raise(); + tooltip->show(); +} + +RowTimeline *RowTimeline::parentRow() const +{ + if (!m_rowTree->m_parentRow) + return nullptr; + + return m_rowTree->m_parentRow->rowTimeline(); +} + +void RowTimeline::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + InteractiveTimelineItem::hoverLeaveEvent(event); + // Make sure mouse cursor is reseted when moving away from timeline row + m_rowTree->m_scene->resetMouseCursor(); +} + +int RowTimeline::type() const +{ + // Enable the use of qgraphicsitem_cast with this item. + return TypeRowTimeline; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimeline.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimeline.h new file mode 100644 index 00000000..00c81696 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimeline.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef ROWTIMELINE_H +#define ROWTIMELINE_H + +#include "InteractiveTimelineItem.h" +#include "RowTypes.h" +#include "Bindings/Qt3DSDMTimelineItemProperty.h" +#include <QtCore/qpointer.h> +#include "RowTimelineCommentItem.h" + +class RowTree; +class RowTimelinePropertyGraph; +struct Keyframe; + +class RowTimeline : public InteractiveTimelineItem +{ + Q_OBJECT + +public: + explicit RowTimeline(); + ~RowTimeline(); + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget = nullptr) override; + void setState(State state) override; + void setRowTree(RowTree *rowTree); + void updatePosition(); + void startDurationMove(double clickX); + void updateBoundChildren(bool start); + void clearBoundChildren(); + void moveDurationBy(double dx); + void moveDurationTo(double newX); + void setStartTime(long startTime); + void setEndTime(long endTime); + void setStartX(double startX); + void setEndX(double endX); + void setBarColor(const QColor &color); + void setControllerText(const QString &controller); + void putSelectedKeyframesOnTop(); + void updateKeyframes(); + void insertKeyframe(Keyframe *keyframe); + void removeKeyframe(Keyframe *keyframe); + void updateKeyframesFromBinding(const QList<int> &properties); + void updateDurationFromBinding(); + TimelineControlType getClickedControl(const QPointF &scenePos) const; + double getStartX() const; + double getEndX() const; + long getStartTime() const; + long getEndTime() const; + long getDurationMoveTime() const; // the time a row duration has moved (to commit to binding) + double getDurationMoveOffsetX() const; + long getDuration() const; + QColor barColor() const; + int type() const override; + RowTimeline *parentRow() const; + RowTree *rowTree() const; + Keyframe *getClickedKeyframe(const QPointF &scenePos); + QList<Keyframe *> getKeyframesInRange(const QRectF &rect) const; + QList<Keyframe *> keyframes() const; + void showToolTip(const QPointF &pos); + +protected: + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; + +private: + void initialize(); + void updateChildrenStartRecursive(); + void updateChildrenEndRecursive(); + void updateChildrenMinStartXRecursive(RowTree *rowTree); + void updateChildrenMaxEndXRecursive(RowTree *rowTree); + void updateCommentItem(); + void updateCommentItemPos(); + void drawColorPropertyGradient(QPainter *painter, int width); + bool isColorProperty() const; + QString formatTime(long millis) const; + void collectChildKeyframeTimes(QVector<long> &childKeyframeTimes); + + RowTree *m_rowTree; + RowTimelinePropertyGraph *m_propertyGraph = nullptr; + RowTimelineCommentItem *m_commentItem = nullptr; + long m_startTime = 0; + long m_startDurationMoveStartTime = 0; + double m_startDurationMoveOffsetX = 0; + long m_endTime = 0; + double m_startX = 0; + double m_endX = 0; + double m_minStartX = 0; + double m_maxEndX = 0; + bool m_isProperty = false; // used in the destructor + QString m_controllerDataInput; + QList<Keyframe *> m_keyframes; + QColor m_barColor; + QVector<QPointer<RowTimeline>> m_boundChildrenStart; + QVector<QPointer<RowTimeline>> m_boundChildrenEnd; + + friend class RowTree; +}; + +#endif // ROWTIMELINE_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineCommentItem.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineCommentItem.cpp new file mode 100644 index 00000000..1bfb7163 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineCommentItem.cpp @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "RowTimelineCommentItem.h" +#include "TimelineConstants.h" +#include "TimelineItem.h" +#include "RowTree.h" +#include "StudioPreferences.h" + +#include <QtWidgets/qstyleoption.h> +#include <QtGui/qevent.h> +#include <QtGui/qtextcursor.h> +#include <QtGui/qpainter.h> +#include <QtGui/qtextoption.h> +#include <QtGui/qtextdocument.h> + +static const int MAX_COMMENT_SIZE = 2000; // Should be enough + +RowTimelineCommentItem::RowTimelineCommentItem(QGraphicsItem *parent) + : QGraphicsTextItem(parent) + , m_acceptOnFocusOut(true) +{ + setTextInteractionFlags(Qt::TextEditorInteraction); + setTextWidth(MAX_COMMENT_SIZE); + setDefaultTextColor(CStudioPreferences::textColor()); + setVisible(false); +} + +QString RowTimelineCommentItem::label() const +{ + return m_label; +} + +void RowTimelineCommentItem::setLabel(const QString &label) +{ + setPlainText(label); + if (m_label != label) { + m_label = label; + emit labelChanged(m_label); + } +} + +RowTree *RowTimelineCommentItem::parentRow() const +{ + return m_rowTree; +} + +void RowTimelineCommentItem::setParentRow(RowTree *row) +{ + m_rowTree = row; +} + +int RowTimelineCommentItem::type() const +{ + // Enable the use of qgraphicsitem_cast with this item. + return TimelineItem::TypeRowTimelineCommentItem; +} + +void RowTimelineCommentItem::paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + // prevents flickering when the row is just inserted to the layout + if (m_rowTree && !m_rowTree->y()) + return; + + // Paint background + QRectF r = boundingRect(); + r.adjust(-TimelineConstants::RULER_EDGE_OFFSET, + TimelineConstants::ROW_TEXT_OFFSET_Y, 0, + TimelineConstants::ROW_TEXT_OFFSET_Y); + painter->fillRect(r, CStudioPreferences::timelineRowCommentBgColor()); + + // Remove the HasFocus style state, to prevent the dotted line from being drawn. + QStyleOptionGraphicsItem *style = const_cast<QStyleOptionGraphicsItem *>(option); + style->state &= ~QStyle::State_HasFocus; + + QGraphicsTextItem::paint(painter, option, widget); +} + +void RowTimelineCommentItem::focusOutEvent(QFocusEvent *event) +{ + if (m_acceptOnFocusOut) + validateLabel(); + else + setPlainText(m_label); + + // Remove possible selection + QTextCursor cursor = textCursor(); + cursor.clearSelection(); + setTextCursor(cursor); + QGraphicsTextItem::focusOutEvent(event); + // Next time default to accepting + m_acceptOnFocusOut = true; +} + +void RowTimelineCommentItem::keyPressEvent(QKeyEvent *event) +{ + int key = event->key(); + if (key == Qt::Key_Return || key == Qt::Key_Enter) { + m_acceptOnFocusOut = true; + clearFocus(); + event->accept(); + return; + } else if (key == Qt::Key_Escape) { + m_acceptOnFocusOut = false; + clearFocus(); + event->accept(); + return; + } + + QGraphicsTextItem::keyPressEvent(event); +} + +QRectF RowTimelineCommentItem::boundingRect() const +{ + return QRectF(0, 0, parentItem()->boundingRect().width(), + TimelineConstants::ROW_H); +} + +void RowTimelineCommentItem::validateLabel() +{ + QString text = toPlainText().trimmed(); + text = text.left(MAX_COMMENT_SIZE); + setLabel(text); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineCommentItem.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineCommentItem.h new file mode 100644 index 00000000..48c5065f --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineCommentItem.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef ROWTIMELINECOMMENTITEM_H +#define ROWTIMELINECOMMENTITEM_H + +#include "StudioObjectTypes.h" +#include <QtWidgets/qgraphicsitem.h> +#include <QtCore/qstring.h> +#include <QtWidgets/qgraphicssceneevent.h> +#include <QtGui/qevent.h> + +class RowTree; + +class RowTimelineCommentItem : public QGraphicsTextItem +{ + Q_OBJECT +public: + explicit RowTimelineCommentItem(QGraphicsItem *parent = nullptr); + + QString label() const; + void setLabel(const QString &label); + RowTree *parentRow() const; + void setParentRow(RowTree *row); + int type() const override; + +protected: + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + void focusOutEvent(QFocusEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + QRectF boundingRect() const override; + +signals: + void labelChanged(const QString label); + +private: + void validateLabel(); + + RowTree *m_rowTree = nullptr; + QString m_label; + bool m_acceptOnFocusOut; + +}; + +#endif // ROWTIMELINECOMMENTITEM_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineContextMenu.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineContextMenu.cpp new file mode 100644 index 00000000..7861bb19 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineContextMenu.cpp @@ -0,0 +1,275 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "RowTimelineContextMenu.h" +#include "RowTree.h" +#include "Keyframe.h" +#include "KeyframeManager.h" +#include "MainFrm.h" +#include "StudioApp.h" +#include "TimelineControl.h" +#include "Bindings/ITimelineItemBinding.h" +#include "TimelineGraphicsScene.h" +#include "TimelineToolbar.h" + +RowTimelineContextMenu::RowTimelineContextMenu(RowTree *inRowTree, + KeyframeManager *inKeyframeManager, + QGraphicsSceneContextMenuEvent *inEvent, + TimelineControl *timelineControl, + QWidget *parent) + : QMenu(parent) + , m_rowTree(inRowTree) + , m_keyframeManager(inKeyframeManager) + , m_menuEvent(inEvent) + , m_timelineControl(timelineControl) +{ + initialize(); +} + +RowTimelineContextMenu::~RowTimelineContextMenu() +{ +} + +void RowTimelineContextMenu::initialize() +{ + m_insertKeyframeAction = new QAction(tr("Insert Keyframe"), this); + m_insertKeyframeAction->setShortcut(Qt::Key_S); + m_insertKeyframeAction->setShortcutVisibleInContextMenu(true); + connect(m_insertKeyframeAction, &QAction::triggered, this, + &RowTimelineContextMenu::insertKeyframe); + addAction(m_insertKeyframeAction); + + m_cutSelectedKeyframesAction = new QAction(tr("Cut Selected Keyframe"), this); + m_cutSelectedKeyframesAction->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_X)); + m_cutSelectedKeyframesAction->setShortcutVisibleInContextMenu(true); + connect(m_cutSelectedKeyframesAction, &QAction::triggered, this, + &RowTimelineContextMenu::cutSelectedKeyframes); + addAction(m_cutSelectedKeyframesAction); + + m_copySelectedKeyframesAction = new QAction(tr("Copy Selected Keyframe"), this); + m_copySelectedKeyframesAction->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_C)); + m_copySelectedKeyframesAction->setShortcutVisibleInContextMenu(true); + connect(m_copySelectedKeyframesAction, &QAction::triggered, this, + &RowTimelineContextMenu::copySelectedKeyframes); + addAction(m_copySelectedKeyframesAction); + + m_pasteKeyframesAction = new QAction(tr("Paste Keyframes"), this); + m_pasteKeyframesAction->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_V)); + m_pasteKeyframesAction->setShortcutVisibleInContextMenu(true); + connect(m_pasteKeyframesAction, &QAction::triggered, this, + &RowTimelineContextMenu::pasteKeyframes); + addAction(m_pasteKeyframesAction); + + m_deleteSelectedKeyframesAction = new QAction(tr("Delete Selected Keyframe"), this); + m_deleteSelectedKeyframesAction->setShortcut(Qt::Key_Delete); + m_deleteSelectedKeyframesAction->setShortcutVisibleInContextMenu(true); + connect(m_deleteSelectedKeyframesAction, &QAction::triggered, this, + &RowTimelineContextMenu::deleteSelectedKeyframes); + addAction(m_deleteSelectedKeyframesAction); + + m_deleteRowKeyframesAction = new QAction(tr("Delete All Channel Keyframes"), this); + m_deleteRowKeyframesAction->setShortcut( + QKeySequence(Qt::ControlModifier | Qt::AltModifier | Qt::Key_K)); + m_deleteRowKeyframesAction->setShortcutVisibleInContextMenu(true); + connect(m_deleteRowKeyframesAction, &QAction::triggered, this, + &RowTimelineContextMenu::deleteRowKeyframes); + addAction(m_deleteRowKeyframesAction); + + m_keyframe = m_rowTree->rowTimeline()->getClickedKeyframe(m_menuEvent->scenePos()); + bool ctrlPressed = m_menuEvent->modifiers() & Qt::ControlModifier; + if (m_keyframe) { + if (!m_keyframe->selected() && !ctrlPressed) + m_keyframeManager->deselectAllKeyframes(); + + m_keyframeManager->selectKeyframe(m_keyframe); + } else { + m_keyframeManager->deselectAllKeyframes(); + } + + if (m_rowTree->rowTimeline()->keyframes().size()) { + m_hasDynamicKeyframes = m_keyframeManager->hasDynamicKeyframes(m_rowTree); + QString label; + if (m_hasDynamicKeyframes) + label = tr("Make Animations Static"); + else + label = tr("Make Animations Dynamic"); + + m_dynamicKeyframesAction = new QAction(label, this); + connect(m_dynamicKeyframesAction, &QAction::triggered, this, + &RowTimelineContextMenu::toggleDynamicKeyframes); + addAction(m_dynamicKeyframesAction); + } + + addSeparator(); + + if (m_keyframe) { + m_setInterpolationAction = new QAction(tr("Set Interpolation..."), this); + m_setInterpolationAction->setShortcut(Qt::Key_I); + m_setInterpolationAction->setShortcutVisibleInContextMenu(true); + connect(m_setInterpolationAction, &QAction::triggered, this, + &RowTimelineContextMenu::setInterpolation); + addAction(m_setInterpolationAction); + + m_setKeyframeTimeAction = new QAction(tr("Set Keyframe Time..."), this); + connect(m_setKeyframeTimeAction, &QAction::triggered, this, + &RowTimelineContextMenu::setKeyframeTime); + addAction(m_setKeyframeTimeAction); + } else { + m_setTimeBarColorAction = new QAction(tr("Change Time Bar Color..."), this); + connect(m_setTimeBarColorAction, &QAction::triggered, this, + &RowTimelineContextMenu::changeTimeBarColor); + addAction(m_setTimeBarColorAction); + + m_setTimeBarTimeAction = new QAction(tr("Set Time Bar Time..."), this); + m_setTimeBarTimeAction->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_T)); + m_setTimeBarTimeAction->setShortcutVisibleInContextMenu(true); + connect(m_setTimeBarTimeAction, &QAction::triggered, this, + &RowTimelineContextMenu::setTimeBarTime); + addAction(m_setTimeBarTimeAction); + + QAction *showRowTextsAction + = m_rowTree->m_scene->widgetTimeline()->toolbar()->actionShowRowTexts(); + showRowTextsAction->setShortcutVisibleInContextMenu(true); + addAction(showRowTextsAction); + } +} + +void RowTimelineContextMenu::showEvent(QShowEvent *event) +{ + bool propRow = m_rowTree->isProperty(); + bool hasPropRows = m_rowTree->hasPropertyChildren(); + + m_insertKeyframeAction->setEnabled(!m_keyframe && (propRow || hasPropRows)); + m_cutSelectedKeyframesAction->setEnabled(m_keyframeManager->oneMasterRowSelected()); + m_copySelectedKeyframesAction->setEnabled(m_keyframeManager->oneMasterRowSelected()); + m_pasteKeyframesAction->setEnabled(m_keyframeManager->hasCopiedKeyframes()); + m_deleteSelectedKeyframesAction->setEnabled(m_keyframeManager->hasSelectedKeyframes()); + m_deleteRowKeyframesAction->setEnabled(!m_rowTree->rowTimeline()->keyframes().empty()); + if (!m_keyframe) { + m_setTimeBarColorAction->setEnabled(m_rowTree->hasDurationBar()); + m_setTimeBarTimeAction->setEnabled(m_rowTree->hasDurationBar()); + } + + QMenu::showEvent(event); +} + +void RowTimelineContextMenu::insertKeyframe() +{ + RowTree *destinationRowTree = nullptr; + if (m_rowTree->isProperty()) { + // When inserting into a property, insert actually into + // its parent rowtree + destinationRowTree = m_rowTree->parentRow(); + } else { + destinationRowTree = m_rowTree; + } + + destinationRowTree->getBinding()->InsertKeyframe(); +} + +void RowTimelineContextMenu::cutSelectedKeyframes() +{ + m_keyframeManager->copySelectedKeyframes(); + m_keyframeManager->deleteSelectedKeyframes(); +} + +void RowTimelineContextMenu::copySelectedKeyframes() +{ + m_keyframeManager->copySelectedKeyframes(); +} + +void RowTimelineContextMenu::pasteKeyframes() +{ + m_keyframeManager->pasteKeyframes(); +} + +void RowTimelineContextMenu::deleteSelectedKeyframes() +{ + m_keyframeManager->deleteSelectedKeyframes(); +} + +void RowTimelineContextMenu::deleteRowKeyframes() +{ + RowTree *destinationRowTree = nullptr; + if (m_rowTree->isProperty()) { + // Can't delete nicely just from property, so get the actual object row + destinationRowTree = m_rowTree->parentRow(); + } else { + destinationRowTree = m_rowTree; + } + destinationRowTree->getBinding()->DeleteAllChannelKeyframes(); +} + +void RowTimelineContextMenu::setInterpolation() +{ + m_keyframeManager->SetKeyframeInterpolation(); +} + +void RowTimelineContextMenu::setKeyframeTime() +{ + m_keyframeManager->SetKeyframeTime(m_keyframe->time); +} + +void RowTimelineContextMenu::changeTimeBarColor() +{ + g_StudioApp.m_pMainWnd->OnTimelineSetTimeBarColor(); +} + +void RowTimelineContextMenu::setTimeBarTime() +{ + if (m_timelineControl) { + m_timelineControl->setRowTimeline(m_rowTree->rowTimeline()); + m_timelineControl->showDurationEditDialog(); + } +} + +void RowTimelineContextMenu::toggleDynamicKeyframes() +{ + QList<Keyframe *> selectedKeyframes = m_keyframeManager->selectedKeyframes(); + + if (selectedKeyframes.isEmpty()) { + // If property row is clicked, only make that property's first keyframe dynamic. + // Otherwise make all properties' first keyframes dynamic + // Note that it doesn't matter which keyframe we make dynamic, as the dynamic keyframe will + // automatically change to the first one in time order. + QList<Keyframe *> keyframes; + if (m_rowTree->isProperty()) { + keyframes.append(m_rowTree->rowTimeline()->keyframes().first()); + } else { + const auto childProps = m_rowTree->childProps(); + for (const auto prop : childProps) + keyframes.append(prop->rowTimeline()->keyframes().first()); + } + m_keyframeManager->selectKeyframes(keyframes); + } + + m_keyframeManager->SetKeyframesDynamic(!m_hasDynamicKeyframes); + + if (selectedKeyframes.isEmpty()) + m_keyframeManager->deselectAllKeyframes(); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineContextMenu.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineContextMenu.h new file mode 100644 index 00000000..b8c2f922 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelineContextMenu.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef ROWTIMELINECONTEXTMENU_H +#define ROWTIMELINECONTEXTMENU_H + +#include <QtWidgets/qmenu.h> +#include <QtWidgets/qaction.h> +#include <QtWidgets/qgraphicssceneevent.h> + +class RowTree; +class KeyframeManager; +class TimelineControl; +struct Keyframe; + +class RowTimelineContextMenu : public QMenu +{ + Q_OBJECT +public: + explicit RowTimelineContextMenu(RowTree *inRowTree, + KeyframeManager *inKeyframeManager, + QGraphicsSceneContextMenuEvent *inEvent, + TimelineControl *timelineControl, + QWidget *parent = nullptr); + virtual ~RowTimelineContextMenu(); + +protected: + void showEvent(QShowEvent *event) override; + +private: + void initialize(); + void insertKeyframe(); + void cutSelectedKeyframes(); + void copySelectedKeyframes(); + void pasteKeyframes(); + void deleteSelectedKeyframes(); + void deleteRowKeyframes(); + void setInterpolation(); + void setKeyframeTime(); + void changeTimeBarColor(); + void setTimeBarTime(); + void toggleDynamicKeyframes(); + + RowTree *m_rowTree = nullptr; + Keyframe *m_keyframe = nullptr; + KeyframeManager *m_keyframeManager = nullptr; + QGraphicsSceneContextMenuEvent *m_menuEvent = nullptr; + QAction *m_insertKeyframeAction = nullptr; + QAction *m_cutSelectedKeyframesAction = nullptr; + QAction *m_copySelectedKeyframesAction = nullptr; + QAction *m_pasteKeyframesAction = nullptr; + QAction *m_deleteSelectedKeyframesAction = nullptr; + QAction *m_deleteRowKeyframesAction = nullptr; + QAction *m_setInterpolationAction = nullptr; + QAction *m_setKeyframeTimeAction = nullptr; + QAction *m_setTimeBarColorAction = nullptr; + QAction *m_setTimeBarTimeAction = nullptr; + QAction *m_dynamicKeyframesAction = nullptr; + TimelineControl *m_timelineControl = nullptr; + bool m_hasDynamicKeyframes = false; +}; + +#endif // ROWTIMELINECONTEXTMENU_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelinePropertyGraph.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelinePropertyGraph.cpp new file mode 100644 index 00000000..3c82d73b --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelinePropertyGraph.cpp @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "RowTimelinePropertyGraph.h" +#include "RowTimeline.h" +#include "RowTree.h" +#include "Ruler.h" +#include "TimelineGraphicsScene.h" +#include "Bindings/ITimelineItemProperty.h" + +RowTimelinePropertyGraph::RowTimelinePropertyGraph(QObject *parent) + : QObject(parent) +{ + m_rowTimeline = static_cast<RowTimeline *>(parent); +} + +void RowTimelinePropertyGraph::paintGraphs(QPainter *painter, const QRectF &rect) +{ + m_rect = rect; + m_propBinding = m_rowTimeline->rowTree()->propBinding(); + + // Animate alpha 0..255 while expanding + int alpha = 255 * (m_rect.height() - TimelineConstants::ROW_H) + / (TimelineConstants::ROW_H_EXPANDED - TimelineConstants::ROW_H); + alpha = std::max(0, alpha); + + if (alpha == 0) + return; + + // Available line colors + QColor colors[6] = { QColor(255, 0, 0, alpha), QColor(0, 255, 0, alpha), + QColor(0, 0, 255, alpha), QColor(255, 255, 0, alpha), + QColor(255, 0, 255, alpha), QColor(0, 255, 255, alpha) }; + + long channelCount = m_propBinding->GetChannelCount(); + + // Don't want to overflow the color array + if (channelCount <= 6) { + // For each channel graph it. + for (long i = 0; i < channelCount; ++i) + paintSingleChannel(painter, i, colors[i]); + } +} + +void RowTimelinePropertyGraph::paintSingleChannel(QPainter *painter, long inChannelIndex, + const QColor &inColor) +{ + float maxVal = m_propBinding->GetMaximumValue(); + float minVal = m_propBinding->GetMinimumValue(); + + double timelineScale = m_rowTimeline->rowTree()->m_scene->ruler()->timelineScale(); + + // Step in pixels + int interval = 5; + // Margin at top & bottom of graph + float marginY = 10; + float graphY = m_rect.y() + marginY; + float graphHeight = m_rect.height() - marginY * 2; + + QPainterPath path; + for (int i = 0; i < m_rect.width(); i += interval) { + // Value time in ms + long time = i / (TimelineConstants::RULER_MILLI_W * timelineScale); + float value = m_propBinding->GetChannelValueAtTime(inChannelIndex, time); + float yPos = graphY + (1.0 - (value - minVal) / (maxVal - minVal)) * graphHeight; + + if (i == 0) + path.moveTo(m_rect.x() + i, yPos); + else + path.lineTo(m_rect.x() + i, yPos); + } + + painter->setPen(QPen(inColor, 2)); + painter->drawPath(path); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelinePropertyGraph.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelinePropertyGraph.h new file mode 100644 index 00000000..275c24e2 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTimelinePropertyGraph.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef ROWTIMELINEPROPERTYGRAPH_H +#define ROWTIMELINEPROPERTYGRAPH_H + +#include <QtCore/qobject.h> +#include <QtGui/qpainter.h> + +class RowTimeline; +class ITimelineItemProperty; + +class RowTimelinePropertyGraph : public QObject +{ + Q_OBJECT +public: + explicit RowTimelinePropertyGraph(QObject *parent = nullptr); + void paintGraphs(QPainter *painter, const QRectF &rect); + +private: + void paintSingleChannel(QPainter *painter, long inChannelIndex, + const QColor &inColor); + + RowTimeline *m_rowTimeline = nullptr; + ITimelineItemProperty *m_propBinding = nullptr; + QRectF m_rect; +}; + +#endif // ROWTIMELINEPROPERTYGRAPH_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTree.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTree.cpp new file mode 100644 index 00000000..e15258cd --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTree.cpp @@ -0,0 +1,1337 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "RowTree.h" +#include "RowTimeline.h" +#include "RowManager.h" +#include "TimelineConstants.h" +#include "StudioObjectTypes.h" +#include "TimelineGraphicsScene.h" +#include "Bindings/ITimelineItemBinding.h" +#include "Bindings/Qt3DSDMTimelineItemBinding.h" +#include "Qt3DSString.h" +#include "TreeHeader.h" +#include "StudioPreferences.h" +#include "KeyframeManager.h" +#include "StudioApp.h" +#include "MainFrm.h" +#include "Core.h" +#include "Doc.h" +#include "ClientDataModelBridge.h" +#include "Qt3DSDMStudioSystem.h" +#include "Qt3DSDMSlides.h" +#include "StudioUtils.h" +#include "TimelineToolbar.h" + +#include <QtGui/qpainter.h> +#include "QtGui/qtextcursor.h" +#include <QtWidgets/qgraphicslinearlayout.h> +#include <QtWidgets/qgraphicssceneevent.h> + +// object row constructor +RowTree::RowTree(TimelineGraphicsScene *timelineScene, EStudioObjectType objType, + const QString &label) + : m_rowTimeline(new RowTimeline()) + , m_scene(timelineScene) + , m_objectType(objType) + , m_label(label) +{ + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + m_onMasterSlide = doc->GetStudioSystem()->GetSlideSystem() + ->IsMasterSlide(doc->GetActiveSlide()); + + initialize(); +} + +// property row constructor +RowTree::RowTree(TimelineGraphicsScene *timelineScene, const QString &propType) + : InteractiveTimelineItem() + , m_rowTimeline(new RowTimeline()) + , m_isProperty(true) + , m_scene(timelineScene) + , m_propertyType(propType) + , m_label(propType) +{ + m_rowTimeline->m_isProperty = true; + + initialize(); +} + +RowTree::~RowTree() +{ + delete m_rowTimeline; // this will also delete the keyframes + m_rowTimeline = nullptr; +} + +ITimelineItemBinding *RowTree::getBinding() const +{ + return m_binding; +} + +// object instance handle + qt3dsdm::Qt3DSDMInstanceHandle RowTree::instance() const +{ + if (m_isProperty || !m_binding) + return 0; + + return static_cast<Qt3DSDMTimelineItemBinding *>(m_binding)->GetInstance(); +} + +void RowTree::initialize() +{ + setTimelineRow(m_rowTimeline); + m_rowTimeline->setRowTree(this); + + setMinimumWidth(TimelineConstants::TREE_BOUND_W); + + initializeAnimations(); + + m_labelItem.setParentItem(this); + m_labelItem.setParentRow(this); + m_labelItem.setLabel(m_label); + updateLabelPosition(); + + // Default all rows to collapsed + setRowVisible(false); + m_expandState = ExpandState::HiddenCollapsed; + + connect(&m_labelItem, &RowTreeLabelItem::labelChanged, this, + [this](const QString &label) { + // Update label on timeline and on model + m_label = label; + // TODO: Get rid of CString APIs + auto clabel = Q3DStudio::CString::fromQString(m_label); + m_binding->GetTimelineItem()->SetName(clabel); + }); +} + +void RowTree::initializeAnimations() +{ + // Init left side expand animations + m_expandHeightAnimation = new QPropertyAnimation(this, "maximumSize"); + m_expandHeightAnimation->setDuration(TimelineConstants::EXPAND_ANIMATION_DURATION); + m_expandAnimation.addAnimation(m_expandHeightAnimation); + m_expandOpacityAnimation = new QPropertyAnimation(this, "opacity"); + m_expandOpacityAnimation->setDuration(TimelineConstants::EXPAND_ANIMATION_DURATION / 3); + m_expandAnimation.addAnimation(m_expandOpacityAnimation); + + // Init right side expand animations + m_expandTimelineHeightAnimation = new QPropertyAnimation(m_rowTimeline, "maximumSize"); + m_expandTimelineHeightAnimation->setDuration(TimelineConstants::EXPAND_ANIMATION_DURATION); + m_expandAnimation.addAnimation(m_expandTimelineHeightAnimation); + m_expandTimelineOpacityAnimation = new QPropertyAnimation(m_rowTimeline, "opacity"); + m_expandTimelineOpacityAnimation->setDuration(TimelineConstants::EXPAND_ANIMATION_DURATION / 3); + m_expandAnimation.addAnimation(m_expandTimelineOpacityAnimation); + + connect(&m_expandAnimation, &QAbstractAnimation::stateChanged, + [this](const QAbstractAnimation::State newState) { + if (m_rowTimeline) { + if (newState == QAbstractAnimation::Running) { + setVisible(true); + m_rowTimeline->setVisible(true); + } else if (newState == QAbstractAnimation::Stopped) { + if (this->maximumHeight() == 0) { + setVisible(false); + m_rowTimeline->setVisible(false); + } + } + } + }); +} + +void RowTree::animateExpand(ExpandState state) +{ + int endHeight = 0; // hidden states + float endOpacity = 0; + if (state == ExpandState::Expanded) { + endHeight = m_isPropertyExpanded ? TimelineConstants::ROW_H_EXPANDED + : TimelineConstants::ROW_H; + endOpacity = 1; + } else if (state == ExpandState::Collapsed) { + endHeight = TimelineConstants::ROW_H; + endOpacity = 1; + } + // Changing end values while animation is running does not affect currently running animation, + // so let's make sure the animation is stopped first. + m_expandAnimation.stop(); + + m_expandHeightAnimation->setEndValue(QSizeF(size().width(), endHeight)); + m_expandTimelineHeightAnimation->setEndValue(QSizeF(m_rowTimeline->size().width(), + endHeight)); + m_expandOpacityAnimation->setEndValue(endOpacity); + m_expandTimelineOpacityAnimation->setEndValue(endOpacity); + + m_expandAnimation.start(); +} + +void RowTree::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(option) + Q_UNUSED(widget) + + bool hiResIcons = StudioUtils::devicePixelRatio(widget->window()->windowHandle()) > 1.0; + + if (!y()) // prevents flickering when the row is just inserted to the layout + return; + + static const int ICON_SIZE = 16; + static const int LEFT_DIVIDER = 18; + const int offset = 5 + m_depth * TimelineConstants::ROW_DEPTH_STEP; + const int iconY = (TimelineConstants::ROW_H / 2) - (ICON_SIZE / 2); + + // update button bounds rects + m_rectArrow .setRect(offset, iconY, ICON_SIZE, ICON_SIZE); + m_rectType .setRect(offset + ICON_SIZE, iconY, ICON_SIZE, ICON_SIZE); + m_rectShy .setRect(treeWidth() - 16 * 3.3, iconY, ICON_SIZE, ICON_SIZE); + m_rectVisible.setRect(treeWidth() - 16 * 2.2, iconY, ICON_SIZE, ICON_SIZE); + m_rectLocked .setRect(treeWidth() - 16 * 1.1, iconY, ICON_SIZE, ICON_SIZE); + + // Background + QColor bgColor; + if (m_dndState == DnDState::Source) + bgColor = CStudioPreferences::timelineRowColorDndSource(); + else if (m_dndState == DnDState::SP_TARGET) + bgColor = CStudioPreferences::timelineRowColorDndTargetSP(); + else if (m_isProperty) + bgColor = CStudioPreferences::timelineRowColorNormalProp(); + else if (m_dndHover) + bgColor = CStudioPreferences::timelineRowColorDndTarget(); + else if (m_state == Selected) + bgColor = CStudioPreferences::timelineRowColorSelected(); + else if (m_state == Hovered && !m_locked) + bgColor = CStudioPreferences::timelineRowColorOver(); + else + bgColor = CStudioPreferences::timelineRowColorNormal(); + + painter->fillRect(QRect(0, 0, size().width(), size().height() - 1), bgColor); + + // left divider + painter->setPen(CStudioPreferences::timelineWidgetBgColor()); + painter->drawLine(LEFT_DIVIDER, 0, LEFT_DIVIDER, size().height() - 1); + + // Shy, eye, lock separator + painter->fillRect(QRect(treeWidth() - TimelineConstants::TREE_ICONS_W, + 0, 1, size().height()), + CStudioPreferences::timelineWidgetBgColor()); + + // Shy, eye, lock + static const QPixmap pixEmpty = QPixmap(":/images/Toggle-Empty.png"); + static const QPixmap pixShy = QPixmap(":/images/Toggle-Shy.png"); + static const QPixmap pixHide = QPixmap(":/images/Toggle-HideShow.png"); + static const QPixmap pixHideDisabled = QPixmap(":/images/Toggle-HideShow-disabled.png"); + static const QPixmap pixHideCtrld = QPixmap(":/images/Toggle-HideShowControlled.png"); + static const QPixmap pixLock = QPixmap(":/images/Toggle-Lock.png"); + static const QPixmap pixEmpty2x = QPixmap(":/images/Toggle-Empty@2x.png"); + static const QPixmap pixShy2x = QPixmap(":/images/Toggle-Shy@2x.png"); + static const QPixmap pixHide2x = QPixmap(":/images/Toggle-HideShow@2x.png"); + static const QPixmap pixHideDisabled2x = QPixmap(":/images/Toggle-HideShow-disabled@2x.png"); + static const QPixmap pixHideCtrld2x = QPixmap(":/images/Toggle-HideShowControlled@2x.png"); + static const QPixmap pixLock2x = QPixmap(":/images/Toggle-Lock@2x.png"); + if (hasActionButtons()) { + painter->drawPixmap(m_rectShy, hiResIcons ? (m_shy ? pixShy2x : pixEmpty2x) + : (m_shy ? pixShy : pixEmpty)); + // Eyeball visibility follows the visibility setting for the object even if it has + // datainput controller + // Disable eyeball from master slide + if (m_onMasterSlide) { + painter->drawPixmap(m_rectVisible, hiResIcons ? pixHideDisabled2x + : pixHideDisabled); + } else if (m_visibilityCtrld) { + painter->drawPixmap(m_rectVisible, hiResIcons + ? (m_visible ? pixHideCtrld2x : pixEmpty2x) + : (m_visible ? pixHideCtrld : pixEmpty)); + } else { + painter->drawPixmap(m_rectVisible, hiResIcons + ? (m_visible ? pixHide2x : pixEmpty2x) + : (m_visible ? pixHide : pixEmpty)); + } + painter->drawPixmap(m_rectLocked, hiResIcons ? (m_locked ? pixLock2x : pixEmpty2x) + : (m_locked ? pixLock : pixEmpty)); + } + + static const QPixmap pixInsertLeft = QPixmap(":/images/Insert-Left.png"); + static const QPixmap pixInsertRight = QPixmap(":/images/Insert-Right.png"); + static const QPixmap pixInsertLeft2x = QPixmap(":/images/Insert-Left@2x.png"); + static const QPixmap pixInsertRight2x = QPixmap(":/images/Insert-Right@2x.png"); + if (m_dndState == DnDState::SP_TARGET) { // Candidate target of a subpresentation drop + painter->drawPixmap(19, 2, hiResIcons ? pixInsertLeft2x : pixInsertLeft); + painter->drawPixmap(treeWidth() - TimelineConstants::TREE_ICONS_W - 8, 2, hiResIcons + ? pixInsertRight2x : pixInsertRight); + } else if (m_dndState == DnDState::Parent) { // Candidate parent of a dragged row + painter->setPen(QPen(CStudioPreferences::timelineRowMoverColor(), 1)); + painter->drawRect(QRect(1, 1, treeWidth() - 2, size().height() - 3)); + } + + // Action indicators + static const QPixmap pixMasterAction = QPixmap(":/images/Action-MasterAction.png"); + static const QPixmap pixAction = QPixmap(":/images/Action-Action.png"); + static const QPixmap pixChildMasterAction = QPixmap(":/images/Action-ChildMasterAction.png"); + static const QPixmap pixChildAction = QPixmap(":/images/Action-ChildAction.png"); + static const QPixmap pixCompMasterAction = QPixmap(":/images/Action-ComponentMasterAction.png"); + static const QPixmap pixCompAction = QPixmap(":/images/Action-ComponentAction.png"); + static const QPixmap pixMasterAction2x = QPixmap(":/images/Action-MasterAction@2x.png"); + static const QPixmap pixAction2x = QPixmap(":/images/Action-Action@2x.png"); + static const QPixmap pixChildMasterAction2x + = QPixmap(":/images/Action-ChildMasterAction@2x.png"); + static const QPixmap pixChildAction2x = QPixmap(":/images/Action-ChildAction@2x.png"); + static const QPixmap pixCompMasterAction2x + = QPixmap(":/images/Action-ComponentMasterAction@2x.png"); + static const QPixmap pixCompAction2x = QPixmap(":/images/Action-ComponentAction@2x.png"); + + if (!isProperty()) { + // subpresentation indicators + if (m_hasSubpresentation) { + painter->fillRect(QRect(0, 0, LEFT_DIVIDER, size().height() - 1), + CStudioPreferences::timelineRowSubpColor()); + } else if (!expanded() && m_numDescendantSubpresentations > 0) { + painter->fillRect(QRect(0, 0, LEFT_DIVIDER, size().height() - 1), + CStudioPreferences::timelineRowSubpDescendantColor()); + } + + if (m_actionStates & ActionState::MasterAction) // has master action + painter->drawPixmap(0, 0, hiResIcons ? pixMasterAction2x : pixMasterAction); + else if (m_actionStates & ActionState::Action) // has action + painter->drawPixmap(0, 0, hiResIcons ? pixAction2x : pixAction); + + if (!expanded()) { + if (m_actionStates & ActionState::MasterChildAction) { + // children have master action + painter->drawPixmap(0, 0, hiResIcons ? pixChildMasterAction2x + : pixChildMasterAction); + } else if (m_actionStates & ActionState::ChildAction) { + // children have action + painter->drawPixmap(0, 0, hiResIcons ? pixChildAction2x : pixChildAction); + } + } + + if (m_actionStates & ActionState::MasterComponentAction) // component has master action + painter->drawPixmap(0, 0, hiResIcons ? pixCompMasterAction2x : pixCompMasterAction); + else if (m_actionStates & ActionState::ComponentAction) // component has action + painter->drawPixmap(0, 0, hiResIcons ? pixCompAction2x : pixCompAction); + } + + // variants indicator + if (m_variantsGroups.size() > 0) { + const auto variantsDef = g_StudioApp.GetCore()->getProjectFile().variantsDef(); + for (int i = 0; i < m_variantsGroups.size(); ++i) { + painter->fillRect(QRect(clipX() + 2 + i * 8, 6, 6, 6), + variantsDef[m_variantsGroups[i]].m_color); + painter->setPen(CStudioPreferences::timelineWidgetBgColor()); + painter->drawRect(QRect(clipX() + 2 + i * 8, 6, 6, 6)); + } + } + + // The following items need to be clipped so that they do not draw overlapping shy etc. buttons + + painter->setClipRect(0, 0, clipX(), TimelineConstants::ROW_H); + + // expand/collapse arrow + static const QPixmap pixArrow = QPixmap(":/images/arrow.png"); + static const QPixmap pixArrowDown = QPixmap(":/images/arrow_down.png"); + static const QPixmap pixArrow2x = QPixmap(":/images/arrow@2x.png"); + static const QPixmap pixArrowDown2x = QPixmap(":/images/arrow_down@2x.png"); + if (m_arrowVisible) { + painter->drawPixmap(m_rectArrow, hiResIcons ? (expanded() ? pixArrowDown2x : pixArrow2x) + : (expanded() ? pixArrowDown : pixArrow)); + } + + // Row type icon + static const QPixmap pixSceneNormal = QPixmap(":/images/Objects-Scene-Normal.png"); + static const QPixmap pixLayerNormal = QPixmap(":/images/Objects-Layer-Normal.png"); + static const QPixmap pixObjectNormal = QPixmap(":/images/Objects-Model-Normal.png"); + static const QPixmap pixLightNormal = QPixmap(":/images/Objects-Light-Normal.png"); + static const QPixmap pixCameraNormal = QPixmap(":/images/Objects-Camera-Normal.png"); + static const QPixmap pixTextNormal = QPixmap(":/images/Objects-Text-Normal.png"); + static const QPixmap pixAliasNormal = QPixmap(":/images/Objects-Alias-Normal.png"); + static const QPixmap pixGroupNormal = QPixmap(":/images/Objects-Group-Normal.png"); + static const QPixmap pixComponentNormal = QPixmap(":/images/Objects-Component-Normal.png"); + static const QPixmap pixMaterialNormal = QPixmap(":/images/Objects-Material-Normal.png"); + static const QPixmap pixPropertyNormal = QPixmap(":/images/Objects-Property-Normal.png"); + static const QPixmap pixImageNormal = QPixmap(":/images/Objects-Image-Normal.png"); + static const QPixmap pixBehaviorNormal = QPixmap(":/images/Objects-Behavior-Normal.png"); + static const QPixmap pixEffectNormal= QPixmap(":/images/Objects-Effect-Normal.png"); + static const QPixmap pixSceneNormal2x = QPixmap(":/images/Objects-Scene-Normal@2x.png"); + static const QPixmap pixLayerNormal2x = QPixmap(":/images/Objects-Layer-Normal@2x.png"); + static const QPixmap pixObjectNormal2x = QPixmap(":/images/Objects-Model-Normal@2x.png"); + static const QPixmap pixLightNormal2x = QPixmap(":/images/Objects-Light-Normal@2x.png"); + static const QPixmap pixCameraNormal2x = QPixmap(":/images/Objects-Camera-Normal@2x.png"); + static const QPixmap pixTextNormal2x = QPixmap(":/images/Objects-Text-Normal@2x.png"); + static const QPixmap pixAliasNormal2x = QPixmap(":/images/Objects-Alias-Normal@2x.png"); + static const QPixmap pixGroupNormal2x = QPixmap(":/images/Objects-Group-Normal@2x.png"); + static const QPixmap pixComponentNormal2x = QPixmap(":/images/Objects-Component-Normal@2x.png"); + static const QPixmap pixMaterialNormal2x = QPixmap(":/images/Objects-Material-Normal@2x.png"); + static const QPixmap pixPropertyNormal2x = QPixmap(":/images/Objects-Property-Normal@2x.png"); + static const QPixmap pixImageNormal2x = QPixmap(":/images/Objects-Image-Normal@2x.png"); + static const QPixmap pixBehaviorNormal2x = QPixmap(":/images/Objects-Behavior-Normal@2x.png"); + static const QPixmap pixEffectNormal2x = QPixmap(":/images/Objects-Effect-Normal@2x.png"); + + static const QPixmap pixSceneDisabled = QPixmap(":/images/Objects-Scene-Disabled.png"); + static const QPixmap pixLayerDisabled = QPixmap(":/images/Objects-Layer-Disabled.png"); + static const QPixmap pixObjectDisabled = QPixmap(":/images/Objects-Model-Disabled.png"); + static const QPixmap pixLightDisabled = QPixmap(":/images/Objects-Light-Disabled.png"); + static const QPixmap pixCameraDisabled = QPixmap(":/images/Objects-Camera-Disabled.png"); + static const QPixmap pixTextDisabled = QPixmap(":/images/Objects-Text-Disabled.png"); + static const QPixmap pixAliasDisabled = QPixmap(":/images/Objects-Alias-Disabled.png"); + static const QPixmap pixGroupDisabled = QPixmap(":/images/Objects-Group-Disabled.png"); + static const QPixmap pixComponentDisabled = QPixmap(":/images/Objects-Component-Disabled.png"); + static const QPixmap pixMaterialDisabled = QPixmap(":/images/Objects-Material-Disabled.png"); + static const QPixmap pixPropertyDisabled = QPixmap(":/images/Objects-Property-Disabled.png"); + static const QPixmap pixImageDisabled = QPixmap(":/images/Objects-Image-Disabled.png"); + static const QPixmap pixBehaviorDisabled = QPixmap(":/images/Objects-Behavior-Disabled.png"); + static const QPixmap pixEffectDisabled = QPixmap(":/images/Objects-Effect-Disabled.png"); + static const QPixmap pixSceneDisabled2x = QPixmap(":/images/Objects-Scene-Disabled@2x.png"); + static const QPixmap pixLayerDisabled2x = QPixmap(":/images/Objects-Layer-Disabled@2x.png"); + static const QPixmap pixObjectDisabled2x = QPixmap(":/images/Objects-Model-Disabled@2x.png"); + static const QPixmap pixLightDisabled2x = QPixmap(":/images/Objects-Light-Disabled@2x.png"); + static const QPixmap pixCameraDisabled2x = QPixmap(":/images/Objects-Camera-Disabled@2x.png"); + static const QPixmap pixTextDisabled2x = QPixmap(":/images/Objects-Text-Disabled@2x.png"); + static const QPixmap pixAliasDisabled2x = QPixmap(":/images/Objects-Alias-Disabled@2x.png"); + static const QPixmap pixGroupDisabled2x = QPixmap(":/images/Objects-Group-Disabled@2x.png"); + static const QPixmap pixComponentDisabled2x + = QPixmap(":/images/Objects-Component-Disabled@2x.png"); + static const QPixmap pixMaterialDisabled2x + = QPixmap(":/images/Objects-Material-Disabled@2x.png"); + static const QPixmap pixPropertyDisabled2x + = QPixmap(":/images/Objects-Property-Disabled@2x.png"); + static const QPixmap pixImageDisabled2x = QPixmap(":/images/Objects-Image-Disabled@2x.png"); + static const QPixmap pixBehaviorDisabled2x + = QPixmap(":/images/Objects-Behavior-Disabled@2x.png"); + static const QPixmap pixEffectDisabled2x = QPixmap(":/images/Objects-Effect-Disabled@2x.png"); + + QPixmap pixRowType; + if (m_isProperty) { + pixRowType = hiResIcons ? (m_locked ? pixPropertyDisabled2x : pixPropertyNormal2x) + : (m_locked ? pixPropertyDisabled : pixPropertyNormal); + } else { + switch (m_objectType) { + case OBJTYPE_SCENE: + pixRowType = hiResIcons ? (m_locked ? pixSceneDisabled2x : pixSceneNormal2x) + : (m_locked ? pixSceneDisabled : pixSceneNormal); + break; + case OBJTYPE_LAYER: + pixRowType = hiResIcons ? (m_locked ? pixLayerDisabled2x : pixLayerNormal2x) + : (m_locked ? pixLayerDisabled : pixLayerNormal); + break; + case OBJTYPE_MODEL: + pixRowType = hiResIcons ? (m_locked ? pixObjectDisabled2x : pixObjectNormal2x) + : (m_locked ? pixObjectDisabled : pixObjectNormal); + break; + case OBJTYPE_LIGHT: + pixRowType = hiResIcons ? (m_locked ? pixLightDisabled2x : pixLightNormal2x) + : (m_locked ? pixLightDisabled : pixLightNormal); + break; + case OBJTYPE_CAMERA: + pixRowType = hiResIcons ? (m_locked ? pixCameraDisabled2x : pixCameraNormal2x) + : (m_locked ? pixCameraDisabled : pixCameraNormal); + break; + case OBJTYPE_TEXT: + pixRowType = hiResIcons ? (m_locked ? pixTextDisabled2x : pixTextNormal2x) + : (m_locked ? pixTextDisabled : pixTextNormal); + break; + case OBJTYPE_ALIAS: + pixRowType = hiResIcons ? (m_locked ? pixAliasDisabled2x : pixAliasNormal2x) + : (m_locked ? pixAliasDisabled : pixAliasNormal); + break; + case OBJTYPE_GROUP: + pixRowType = hiResIcons ? (m_locked ? pixGroupDisabled2x : pixGroupNormal2x) + : (m_locked ? pixGroupDisabled : pixGroupNormal); + break; + case OBJTYPE_COMPONENT: + pixRowType = hiResIcons ? (m_locked ? pixComponentDisabled2x : pixComponentNormal2x) + : (m_locked ? pixComponentDisabled : pixComponentNormal); + break; + case OBJTYPE_MATERIAL: + case OBJTYPE_CUSTOMMATERIAL: + case OBJTYPE_REFERENCEDMATERIAL: + pixRowType = hiResIcons ? (m_locked ? pixMaterialDisabled2x : pixMaterialNormal2x) + : (m_locked ? pixMaterialDisabled : pixMaterialNormal); + break; + case OBJTYPE_IMAGE: + pixRowType = hiResIcons ? (m_locked ? pixImageDisabled2x : pixImageNormal2x) + : (m_locked ? pixImageDisabled : pixImageNormal); + break; + case OBJTYPE_BEHAVIOR: + pixRowType = hiResIcons ? (m_locked ? pixBehaviorDisabled2x : pixBehaviorNormal2x) + : (m_locked ? pixBehaviorDisabled : pixBehaviorNormal); + break; + case OBJTYPE_EFFECT: + pixRowType = hiResIcons ? (m_locked ? pixEffectDisabled2x : pixEffectNormal2x) + : (m_locked ? pixEffectDisabled : pixEffectNormal); + break; + default: + break; + } + } + + painter->drawPixmap(m_rectType, pixRowType); +} + +void RowTree::updateVariants(const QStringList &groups) +{ + m_variantsGroups = groups; + update(); +} + +int RowTree::treeWidth() const +{ + return m_scene->treeWidth() - m_scene->getScrollbarOffsets().x(); +} + +void RowTree::setBinding(ITimelineItemBinding *binding) +{ + m_binding = binding; + + // Restore the expansion state of rows + m_expandState = m_scene->expandMap().value(instance(), ExpandState::Unknown); + + if (m_expandState == ExpandState::Unknown) { + // Everything but scene/component is initially collapsed and hidden + if (m_objectType == OBJTYPE_SCENE || m_objectType == OBJTYPE_COMPONENT) + m_expandState = ExpandState::Expanded; + else + m_expandState = ExpandState::HiddenCollapsed; + } + + // Make sure all children of visible expanded parents are shown, and vice versa + if (parentRow()) { + if (parentRow()->expanded()) { + if (m_expandState == ExpandState::HiddenCollapsed) + m_expandState = ExpandState::Collapsed; + else if (m_expandState == ExpandState::HiddenExpanded) + m_expandState = ExpandState::Expanded; + } else { + if (m_expandState == ExpandState::Collapsed) + m_expandState = ExpandState::HiddenCollapsed; + else if (m_expandState == ExpandState::Expanded) + m_expandState = ExpandState::HiddenExpanded; + } + } + + setRowVisible(m_expandState == ExpandState::Collapsed + || m_expandState == ExpandState::Expanded); + + updateFromBinding(); +} + +// x value where label should clip +int RowTree::clipX() const +{ + return treeWidth() - TimelineConstants::TREE_ICONS_W - m_variantsGroups.size() * 8 - 2; +} + +ITimelineItemProperty *RowTree::propBinding() +{ + return m_PropBinding; +} + +void RowTree::setPropBinding(ITimelineItemProperty *binding) +{ + m_PropBinding = binding; + + if (parentRow()->expanded()) + setRowVisible(true); + + // Update label color + m_labelItem.setMaster(m_PropBinding->IsMaster()); +} + +void RowTree::setState(State state) +{ + m_state = state; + m_rowTimeline->m_state = state; + + update(); + m_rowTimeline->update(); +} + +void RowTree::setTimelineRow(RowTimeline *rowTimeline) +{ + m_rowTimeline = rowTimeline; +} + +void RowTree::setParentRow(RowTree *parent) +{ + m_parentRow = parent; +} + +void RowTree::selectLabel() +{ + m_labelItem.setEnabled(true); + m_labelItem.setFocus(); + // Select all text + QTextCursor cursor = m_labelItem.textCursor(); + cursor.select(QTextCursor::Document); + m_labelItem.setTextCursor(cursor); +} + +RowTree *RowTree::parentRow() const +{ + return m_parentRow; +} + +int RowTree::depth() const +{ + return m_depth; +} + +EStudioObjectType RowTree::objectType() const +{ + return m_objectType; +} + +QString RowTree::propertyType() const +{ + return m_propertyType; +} + +int RowTree::type() const +{ + // Enable the use of qgraphicsitem_cast with this item. + return TypeRowTree; +} + +int RowTree::index() const +{ + // first child in a parent has index 0 + return m_index; +} + +int RowTree::indexInLayout() const +{ + // first child (scene) at index 1, tree header at index 0 (invisible rows are also counted) + return m_indexInLayout; +} + +void RowTree::addChild(RowTree *child) +{ + int index = getLastChildIndex(child->isProperty()) + 1; + addChildAt(child, index); +} + +int RowTree::getLastChildIndex(bool isProperty) const +{ + int index = -1; + if (isProperty && !m_childProps.empty()) + index = m_childProps.last()->index(); + else if (!isProperty && !m_childRows.empty()) + index = m_childRows.last()->index(); + + return index; +} + +void RowTree::updateArrowVisibility() +{ + bool oldVisibility = m_arrowVisible; + if (m_childRows.empty() && m_childProps.empty()) { + m_arrowVisible = false; + } else { + if (m_childProps.empty()) { + m_arrowVisible = false; + for (RowTree *row : qAsConst(m_childRows)) { + if (!row->m_filtered) { + m_arrowVisible = true; + break; + } + } + } else { + m_arrowVisible = true; + } + } + if (oldVisibility != m_arrowVisible) + update(); +} + +bool RowTree::isInVariantsFilter() const +{ + const QString filterStr = g_StudioApp.m_pMainWnd->getVariantsFilterStr(); + + if (m_objectType & ~OBJTYPE_IS_VARIANT || filterStr.isEmpty() + || !m_scene->widgetTimeline()->toolbar()->isVariantsFilterOn()) { + return true; + } + + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + const auto propertySystem = doc->GetStudioSystem()->GetPropertySystem(); + const auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge(); + auto property = bridge->getVariantsProperty(instance()); + + using namespace qt3dsdm; + SValue sValue; + if (propertySystem->GetInstancePropertyValue(instance(), property, sValue)) { + QString propVal = get<TDataStrPtr>(sValue)->toQString(); + const QStringList filterPairs = filterStr.split(QLatin1Char(',')); + QHash<QString, bool> matches; + for (auto &filterPair : filterPairs) { + QString group = filterPair.left(filterPair.indexOf(QLatin1Char(':')) + 1); + if (propVal.contains(group)) { // the layer has 1 or more tags from this filter group + if (propVal.contains(filterPair)) + matches[group] = true; // filter tag exists in the property variant group + else if (!matches.contains(group)) + matches[group] = false; + } + } + + for (auto m : qAsConst(matches)) { + if (!m) + return false; + } + } + + return true; +} + +void RowTree::updateFilter() +{ + auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge(); + if (bridge->isMaterialContainer(instance())) + return; + + bool parentOk = !m_parentRow || m_parentRow->isVisible(); + bool shyOk = !m_shy || !m_scene->treeHeader()->filterShy(); + bool visibleOk = m_visible || !m_scene->treeHeader()->filterHidden(); + bool lockOk = !m_locked || !m_scene->treeHeader()->filterLocked(); + bool expandOk = !expandHidden(); + bool variantsOk = isInVariantsFilter(); + + m_filtered = !(shyOk && visibleOk && lockOk && variantsOk); + const bool visible = parentOk && expandOk && !m_filtered; + setVisible(visible); + m_rowTimeline->setVisible(visible); + for (auto propRow : qAsConst(m_childProps)) { + propRow->setVisible(visible); + propRow->m_rowTimeline->setVisible(visible); + } +} + +int RowTree::getCountDecendentsRecursive() const +{ + int num = m_childProps.count(); + + for (auto child : qAsConst(m_childRows)) { + num++; + num += child->getCountDecendentsRecursive(); + } + + return num; +} + +void RowTree::addChildAt(RowTree *child, int index) +{ + // Mahmoud_TODO: improvement: implement moving the child (instead of remove/add) if it is added + // under the same parent. + + int maxIndex = getLastChildIndex(child->isProperty()) + 1; + + if (index > maxIndex) + index = maxIndex; + + if (child->parentRow() == this && index == child->m_index) // same place + return; + + if (child->parentRow()) + child->parentRow()->removeChild(child); + + child->m_index = index; + + QList<RowTree *> &childRows = child->isProperty() ? m_childProps : m_childRows; + int updateIndexInLayout = child->m_indexInLayout; + child->m_indexInLayout = m_indexInLayout + index + 1; + + if (!child->isProperty()) { + child->m_indexInLayout += m_childProps.count(); + + if (m_childRows.size() >= index) { + for (int i = 0; i < index; ++i) + child->m_indexInLayout += m_childRows.at(i)->getCountDecendentsRecursive(); + } + } + + if (!childRows.contains(child)) + childRows.insert(index, child); + + child->m_parentRow = this; + child->updateDepthRecursive(); + if (!child->isProperty()) { + m_rowTimeline->updateChildrenMinStartXRecursive(this); + m_rowTimeline->updateChildrenMaxEndXRecursive(this); + } + + // update the layout + child->addToLayout(child->m_indexInLayout); + + // update indices + updateIndexInLayout = std::min(updateIndexInLayout, child->m_indexInLayout); + updateIndices(true, child->m_index + 1, updateIndexInLayout, child->isProperty()); + updateArrowVisibility(); +} + +int RowTree::addToLayout(int indexInLayout) +{ + m_scene->layoutTree()->insertItem(indexInLayout, this); + m_scene->layoutTimeline()->insertItem(indexInLayout, rowTimeline()); + + indexInLayout++; + + for (auto p : qAsConst(m_childProps)) + indexInLayout = p->addToLayout(indexInLayout); + + for (auto c : qAsConst(m_childRows)) + indexInLayout = c->addToLayout(indexInLayout); + + return indexInLayout; +} + +RowTree *RowTree::getChildAt(int index) const +{ + if (index < 0 || index > m_childRows.count() - 1) + return nullptr; + + return m_childRows.at(index); +} + +// this does not destroy the row, just remove it from the layout and parenting hierarchy +void RowTree::removeChild(RowTree *child) +{ + if (m_childProps.contains(child) || m_childRows.contains(child)) { // child exists + removeChildFromLayout(child); + + // detach from parent + if (child->isProperty()) + m_childProps.removeAll(child); + else + m_childRows.removeAll(child); + + child->m_depth = -1; + child->m_parentRow = nullptr; + + updateIndices(false, child->m_index, child->m_indexInLayout, child->isProperty()); + updateArrowVisibility(); + } +} + +int RowTree::removeChildFromLayout(RowTree *child) const +{ + int numRemoved = 0; + int deleteIndex = child->m_indexInLayout; + for (;;) { + RowTree *row_i = static_cast<RowTree *>(m_scene->layoutTree()->itemAt(deleteIndex) + ->graphicsItem()); + if (row_i->depth() <= child->depth() && numRemoved > 0) + break; + + m_scene->layoutTree()->removeItem(row_i); + m_scene->layoutTimeline()->removeItem(row_i->rowTimeline()); + numRemoved++; + + if (m_scene->layoutTree()->count() == deleteIndex) // reached end of the list + break; + } + + return numRemoved; +} + +bool RowTree::draggable() const +{ + return !m_locked && !isProperty() + && m_objectType & ~(OBJTYPE_IMAGE | OBJTYPE_SCENE | OBJTYPE_IS_MATERIAL); +} + +void RowTree::updateDepthRecursive() +{ + if (m_parentRow) { + m_depth = m_parentRow->m_depth + 1; + updateLabelPosition(); + + for (auto p : qAsConst(m_childProps)) + p->updateDepthRecursive(); + + for (auto r : qAsConst(m_childRows)) + r->updateDepthRecursive(); + } +} + +// update this parent's children indices after a child row is inserted or removed +void RowTree::updateIndices(bool isInsertion, int index, int indexInLayout, bool isProperty) +{ + // update index + if (isProperty && index < m_childProps.count()) { + for (int i = index; i < m_childProps.count(); i++) + m_childProps.at(i)->m_index += isInsertion ? 1 : -1; + } else if (!isProperty && index < m_childRows.count()) { + for (int i = index; i < m_childRows.count(); i++) + m_childRows.at(i)->m_index += isInsertion ? 1 : -1; + } + + // update indexInLayout + for (int i = indexInLayout; i < m_scene->layoutTree()->count(); ++i) { + RowTree *row_i = static_cast<RowTree *>(m_scene->layoutTree()->itemAt(i)->graphicsItem()); + row_i->m_indexInLayout = i; + } +} + +void RowTree::updateFromBinding() +{ + // update view (shy, visible, locked) + m_shy = m_binding->GetTimelineItem()->IsShy(); + m_visible = m_binding->GetTimelineItem()->IsVisible(); + updateLock(m_binding->GetTimelineItem()->IsLocked()); + m_visibilityCtrld = m_binding->GetTimelineItem()->IsVisibilityControlled(); + + // Update label color + Qt3DSDMTimelineItemBinding *itemBinding = + static_cast<Qt3DSDMTimelineItemBinding *>(m_binding); + m_master = itemBinding->IsMaster(); + m_labelItem.setMaster(m_master); + // Update timeline comments + m_rowTimeline->updateCommentItem(); +} + +void RowTree::updateLabel() +{ + if (m_binding) + m_labelItem.setLabel(m_binding->GetTimelineItem()->GetName().toQString()); +} + +void RowTree::setRowVisible(bool visible) +{ + auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge(); + if (bridge->isMaterialContainer(instance())) + return; + + if (visible) { + setMaximumHeight(TimelineConstants::ROW_H); + setOpacity(1.0); + setVisible(true); + m_rowTimeline->setMaximumHeight(TimelineConstants::ROW_H); + m_rowTimeline->setOpacity(1.0); + m_rowTimeline->setVisible(true); + } else { + setMaximumHeight(0.0); + setOpacity(0.0); + setVisible(false); + m_rowTimeline->setMaximumHeight(0.0); + m_rowTimeline->setOpacity(0.0); + m_rowTimeline->setVisible(false); + } +} + +bool RowTree::hasPropertyChildren() const +{ + return !m_childProps.empty(); +} + +void RowTree::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + QPointF p = event->pos(); + if (m_rectType.contains(p.x(), p.y()) && !m_locked) + if (m_binding) + m_binding->OpenAssociatedEditor(); +} + +// handle clicked control and return its type +TreeControlType RowTree::getClickedControl(const QPointF &scenePos) +{ + QPointF p = mapFromScene(scenePos.x(), scenePos.y()); + if (m_arrowVisible && m_rectArrow.contains(p.x(), p.y())) { + updateExpandStatus(m_expandState == ExpandState::Expanded ? ExpandState::Collapsed + : ExpandState::Expanded, false); + update(); + return TreeControlType::Arrow; + } + + if (hasActionButtons()) { + if (m_rectShy.contains(p.x(), p.y())) { + toggleShy(); + return TreeControlType::Shy; + } else if (!m_onMasterSlide && m_rectVisible.contains(p.x(), p.y())) { + // Prevent toggling hide on master slide + toggleVisible(); + return TreeControlType::Hide; + } else if (m_rectLocked.contains(p.x(), p.y())) { + toggleLocked(); + return TreeControlType::Lock; + } + } + + return TreeControlType::None; +} + +void RowTree::updateExpandStatus(ExpandState state, bool animate, bool forceChildUpdate) +{ + const bool changed = m_expandState != state; + if (!forceChildUpdate && !changed) + return; + + m_expandState = state; + + if (m_scene->widgetTimeline()->isFullReconstructPending()) + return; + + // Store the expanded state of items so we can restore it on slide change + if (changed && m_binding) + m_scene->expandMap().insert(instance(), m_expandState); + + if (animate) + animateExpand(m_expandState); + + // updateFilter updates the row visibility. It must be called before children are handled + // to ensure parent visibility is up to date. + if (changed) + updateFilter(); + + if (!m_childRows.empty()) { + for (auto child : qAsConst(m_childRows)) { + if (state == ExpandState::Expanded) { + if (child->m_expandState == ExpandState::HiddenExpanded) + child->updateExpandStatus(ExpandState::Expanded); + else if (child->m_expandState == ExpandState::HiddenCollapsed) + child->updateExpandStatus(ExpandState::Collapsed); + } else { + if (child->m_expandState == ExpandState::Expanded) + child->updateExpandStatus(ExpandState::HiddenExpanded); + else if (child->m_expandState == ExpandState::Collapsed) + child->updateExpandStatus(ExpandState::HiddenCollapsed); + } + } + } + + if (!m_childProps.empty()) { + for (auto child : qAsConst(m_childProps)) { + // Properties can never be collapsed + if (state == ExpandState::Expanded) + child->updateExpandStatus(ExpandState::Expanded); + else + child->updateExpandStatus(ExpandState::HiddenExpanded); + } + } +} + +void RowTree::updateLockRecursive(bool state) +{ + updateLock(state); + if (!m_childRows.empty()) { + for (auto child : qAsConst(m_childRows)) + child->updateLockRecursive(m_locked); + } +} + +void RowTree::updateLock(bool state) +{ + m_locked = state; + + m_labelItem.setLocked(m_locked); + update(); + if (!m_childProps.empty()) { + for (auto child : qAsConst(m_childProps)) + child->updateLock(m_locked); + } + if (m_locked) + m_scene->keyframeManager()->deselectRowKeyframes(this); +} + +void RowTree::updateSubpresentations(int updateParentsOnlyVal) +{ + if (updateParentsOnlyVal != 0) { + int n = m_numDescendantSubpresentations; + if (m_hasSubpresentation) + n++; + if (n > 0) { + RowTree *parentRow = m_parentRow; + while (parentRow) { + parentRow->m_numDescendantSubpresentations += n * updateParentsOnlyVal; + parentRow->update(); + parentRow = parentRow->m_parentRow; + } + } + } else { + auto binding = static_cast<Qt3DSDMTimelineItemBinding *>(m_binding); + bool hasSubp = binding->hasSubpresentation(); + + if (m_hasSubpresentation != hasSubp) { + m_hasSubpresentation = hasSubp; + int n = hasSubp ? 1 : -1; + RowTree *parentRow = m_parentRow; + while (parentRow) { + parentRow->m_numDescendantSubpresentations += n; + parentRow->update(); + parentRow = parentRow->m_parentRow; + } + } + } + update(); +} + +void RowTree::updateLabelPosition() +{ + int offset = 5 + m_depth * TimelineConstants::ROW_DEPTH_STEP + 30; + m_labelItem.setPos(offset, -1); +} + +bool RowTree::expanded() const +{ + if (m_isProperty) + return false; + else + return m_expandState == ExpandState::Expanded; +} + +bool RowTree::expandHidden() const +{ + return m_expandState == ExpandState::HiddenExpanded + || m_expandState == ExpandState::HiddenCollapsed; +} + +bool RowTree::isDecendentOf(RowTree *row) const +{ + RowTree *parentRow = m_parentRow; + + while (parentRow) { + if (parentRow == row) + return true; + + parentRow = parentRow->parentRow(); + } + + return false; +} + +void RowTree::setDnDHover(bool val) +{ + m_dndHover = val; + update(); +} + +void RowTree::setDnDState(DnDState state, DnDState onlyIfState, bool recursive) +{ + if (m_dndState == onlyIfState || onlyIfState == DnDState::Any) { + m_dndState = state; + update(); + + if (recursive) { // used by source rows to highlights all of their descendants + for (auto child : qAsConst(m_childProps)) + child->setDnDState(state, onlyIfState, true); + + for (auto child : qAsConst(m_childRows)) + child->setDnDState(state, onlyIfState, true); + } + } +} + +RowTree::DnDState RowTree::getDnDState() const +{ + return m_dndState; +} + +void RowTree::setActionStates(ActionStates states) +{ + if (states != m_actionStates) { + m_actionStates = states; + update(); + } +} + +bool RowTree::isContainer() const +{ + return !m_isProperty && m_objectType & OBJTYPE_IS_CONTAINER; +} + +bool RowTree::isProperty() const +{ + return m_isProperty; +} + +RowTree *RowTree::getPropertyRow(const QString &type) const +{ + for (RowTree *prop : qAsConst(m_childProps)) { + if (prop->label() == type) + return prop; + } + + return nullptr; +} + + +bool RowTree::isPropertyOrMaterial() const +{ + return m_isProperty || m_objectType & (OBJTYPE_IS_MATERIAL | OBJTYPE_IMAGE); +} + +bool RowTree::isComponent() const +{ + return m_objectType == OBJTYPE_COMPONENT; +} + +bool RowTree::isComponentRoot() const +{ + if (m_objectType == OBJTYPE_COMPONENT && m_binding) + return static_cast<Qt3DSDMTimelineItemBinding *>(m_binding)->isRootComponent(); + + return false; +} + +bool RowTree::isMaster() const +{ + return m_master; +} + +bool RowTree::isDefaultMaterial() const +{ + if (m_binding) + return static_cast<Qt3DSDMTimelineItemBinding *>(m_binding)->isDefaultMaterial(); + + return false; +} + +bool RowTree::empty() const +{ + return m_childRows.empty() && m_childProps.empty(); +} + +bool RowTree::selected() const +{ + return m_state == Selected; +} + +QList<RowTree *> RowTree::childRows() const +{ + return m_childRows; +} + +QList<RowTree *> RowTree::childProps() const +{ + return m_childProps; +} + +RowTimeline *RowTree::rowTimeline() const +{ + return m_rowTimeline; +} + +QString RowTree::label() const +{ + return m_label; +} + +void RowTree::toggleShy() +{ + if (hasActionButtons()) { + m_shy = !m_shy; + update(); + m_binding->GetTimelineItem()->SetShy(m_shy); + } +} + +void RowTree::toggleVisible() +{ + if (hasActionButtons()) { + m_visible = !m_visible; + update(); + m_binding->GetTimelineItem()->SetVisible(m_visible); + } +} + +void RowTree::toggleLocked() +{ + if (hasActionButtons()) { + updateLockRecursive(!m_locked); + m_binding->GetTimelineItem()->SetLocked(m_locked); + if (m_locked && selected()) + m_scene->rowManager()->clearSelection(); + } +} + +bool RowTree::shy() const +{ + return m_shy; +} + +bool RowTree::visible() const +{ + return m_visible; +} + +bool RowTree::locked() const +{ + return m_locked; +} + +// Returns true for items with shy/visible/lock buttons +bool RowTree::hasActionButtons() const +{ + return !m_isProperty && m_indexInLayout != 1 + && m_objectType & ~(OBJTYPE_SCENE | OBJTYPE_IS_MATERIAL | OBJTYPE_IMAGE); +} + +bool RowTree::hasComponentAncestor() const +{ + RowTree *parentRow = m_parentRow; + while (parentRow) { + if (parentRow->objectType() == OBJTYPE_COMPONENT) + return true; + parentRow = parentRow->parentRow(); + } + return false; +} + +// Returns true for items with duration bar +bool RowTree::hasDurationBar() const +{ + return hasActionButtons(); // Same at least now +} + +bool RowTree::propertyExpanded() const +{ + return m_isPropertyExpanded; +} + +void RowTree::togglePropertyExpanded() +{ + setPropertyExpanded(!m_isPropertyExpanded); +} + +void RowTree::setPropertyExpanded(bool expand) +{ + m_isPropertyExpanded = expand; + if (m_isPropertyExpanded) + animateExpand(ExpandState::Expanded); + else + animateExpand(ExpandState::Collapsed); +} + +void RowTree::showDataInputSelector(const QString &propertyname, const QPoint &pos) +{ + auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge(); + + // Set the datainput to control property in referenced object if this + // is a referenced material. + auto refInstance = bridge->getMaterialReference(instance()); + + m_scene->handleShowDISelector(propertyname, refInstance.Valid() ? refInstance : instance(), + pos); +} + diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTree.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTree.h new file mode 100644 index 00000000..b028e94d --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTree.h @@ -0,0 +1,228 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef ROWTREE_H +#define ROWTREE_H + +#include "InteractiveTimelineItem.h" +#include "TimelineConstants.h" +#include "RowTypes.h" +#include "StudioObjectTypes.h" +#include "RowTreeLabelItem.h" +#include "Qt3DSDMHandles.h" + +#include <QtCore/qpropertyanimation.h> +#include <QtCore/qparallelanimationgroup.h> + +class RowTimeline; +class Ruler; +class ITimelineItemBinding; +class TimelineGraphicsScene; +class ITimelineItemProperty; + +class RowTree : public InteractiveTimelineItem +{ + Q_OBJECT + +public: + enum class ExpandState { + Unknown, + Collapsed, + Expanded, + HiddenCollapsed, + HiddenExpanded + }; + + enum class DnDState { + None, + Source, // the row being dragged while DnD-ing + Parent, // parent of the insertion point + SP_TARGET, // drop target for a subpresentation (layer, material or image rows) + Any // accept any state (default value in setDnDState() method) + }; + + enum class ActionState { + None = 0, + Action = 1, + ChildAction = 2, + ComponentAction = 4, + MasterAction = 8, + MasterChildAction = 16, + MasterComponentAction = 32 + }; + Q_DECLARE_FLAGS(ActionStates, ActionState) + + explicit RowTree(TimelineGraphicsScene *timelineScene, + EStudioObjectType objectType = OBJTYPE_UNKNOWN, const QString &label = {}); + // property row constructor + explicit RowTree(TimelineGraphicsScene *timelineScene, const QString &propType); + ~RowTree(); + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget = nullptr) override; + void setState(State state) override; + void setTimelineRow(RowTimeline *rowTimeline); + void setParentRow(RowTree *parent); + void addChild(RowTree *child); + void addChildAt(RowTree *child, int index); + void removeChild(RowTree *child); + void setDnDState(DnDState state, DnDState onlyIfState = DnDState::Any, bool recursive = false); + void setActionStates(ActionStates states); + void setTreeWidth(double w); + void setBinding(ITimelineItemBinding *binding); + void setPropBinding(ITimelineItemProperty *binding); // for property rows + void selectLabel(); + void togglePropertyExpanded(); + void setPropertyExpanded(bool expand); + void showDataInputSelector(const QString &propertyname, const QPoint &pos); + ITimelineItemProperty *propBinding(); + TreeControlType getClickedControl(const QPointF &scenePos); + bool shy() const; + bool visible() const; + bool locked() const; + bool expanded() const; + bool expandHidden() const; + ExpandState expandState() const { return m_expandState; } + bool isDecendentOf(RowTree *row) const; + bool isContainer() const; + bool isProperty() const; + bool isPropertyOrMaterial() const; + bool isComponent() const; + bool isComponentRoot() const; + bool isMaster() const; + bool isDefaultMaterial() const; + bool hasPropertyChildren() const; + bool empty() const; // has zero child rows (and zero properties) + bool selected() const; + bool draggable() const; + bool hasDurationBar() const; + bool propertyExpanded() const; + int depth() const; + int type() const override; + int index() const; + int indexInLayout() const; + int treeWidth() const; + EStudioObjectType objectType() const; + QString propertyType() const; + RowTree *getChildAt(int index) const; + RowTree *parentRow() const; + RowTree *getPropertyRow(const QString &type) const; + QList<RowTree *> childRows() const; + QList<RowTree *> childProps() const; + RowTimeline *rowTimeline() const; + QString label() const; + void toggleShy(); + void toggleVisible(); + void toggleLocked(); + void updateFromBinding(); + void updateLabel(); + void setRowVisible(bool visible); + void setDnDHover(bool val); + void updateVariants(const QStringList &groups); + DnDState getDnDState() const; + + ITimelineItemBinding *getBinding() const; + void updateExpandStatus(ExpandState state, bool animate = true, bool forceChildUpdate = false); + void updateArrowVisibility(); + void updateFilter(); + void updateLock(bool state); + void updateSubpresentations(int updateParentsOnlyVal = 0); + int clipX() const; + qt3dsdm::Qt3DSDMInstanceHandle instance() const; + +protected: + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; + +private: + void initialize(); + void initializeAnimations(); + void animateExpand(ExpandState state); + void updateDepthRecursive(); + void updateLockRecursive(bool state); + void updateLabelPosition(); + void updateIndices(bool isInsertion, int startIndex, int startIndexInLayout, bool isProperty); + bool hasActionButtons() const; + bool hasComponentAncestor() const; + bool isInVariantsFilter() const; + int removeChildFromLayout(RowTree *child) const; + int getCountDecendentsRecursive() const; + int addToLayout(int indexInLayout); + int getLastChildIndex(bool isProperty) const; + + RowTree *m_parentRow = nullptr; + RowTimeline *m_rowTimeline = nullptr; + int m_depth = 1; + int m_index = 0; + int m_indexInLayout = 1; + bool m_shy = false; + bool m_visible = true; + bool m_locked = false; + bool m_isProperty = false; + bool m_isPropertyExpanded = false; + bool m_master = false; + bool m_filtered = false; + bool m_arrowVisible = false; + bool m_dndHover = false; + bool m_visibilityCtrld = false; + bool m_onMasterSlide = false; + DnDState m_dndState = DnDState::None; + ActionStates m_actionStates = ActionState::None; + bool m_hasSubpresentation = false; + int m_numDescendantSubpresentations = 0; + ExpandState m_expandState = ExpandState::HiddenCollapsed; + TimelineGraphicsScene *m_scene; + RowTreeLabelItem m_labelItem; + EStudioObjectType m_objectType = OBJTYPE_UNKNOWN; + QString m_propertyType; // for property rows + QString m_label; + QList<RowTree *> m_childRows; + QList<RowTree *> m_childProps; + QStringList m_variantsGroups; + ITimelineItemBinding *m_binding = nullptr; + ITimelineItemProperty *m_PropBinding = nullptr; // for property rows + + QRect m_rectArrow; + QRect m_rectShy; + QRect m_rectVisible; + QRect m_rectLocked; + QRect m_rectType; + + QParallelAnimationGroup m_expandAnimation; + QPropertyAnimation *m_expandHeightAnimation; + QPropertyAnimation *m_expandTimelineHeightAnimation; + QPropertyAnimation *m_expandOpacityAnimation; + QPropertyAnimation *m_expandTimelineOpacityAnimation; + + friend class RowTimeline; + friend class RowTimelinePropertyGraph; + friend class RowTimelineContextMenu; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(RowTree::ActionStates) + +#endif // ROWTREE_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeContextMenu.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeContextMenu.cpp new file mode 100644 index 00000000..ae5c0bbb --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeContextMenu.cpp @@ -0,0 +1,441 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "RowTreeContextMenu.h" +#include "RowTree.h" +#include "StudioClipboard.h" +#include "StudioApp.h" +#include "Doc.h" +#include "Core.h" +#include "Bindings/ITimelineItemBinding.h" +#include "Bindings/Qt3DSDMTimelineItemBinding.h" +#include "ChooseImagePropertyDlg.h" +#include "Qt3DSDMStudioSystem.h" +#include "ClientDataModelBridge.h" +#include "qcursor.h" + +RowTreeContextMenu::RowTreeContextMenu(RowTree *inRowTree, QWidget *parent) + : QMenu(parent) + , m_RowTree(inRowTree) + , m_TimelineItemBinding(inRowTree->getBinding()) +{ + initialize(); +} + +RowTreeContextMenu::~RowTreeContextMenu() +{ +} + +void RowTreeContextMenu::initialize() +{ + CDoc &doc(*g_StudioApp.GetCore()->GetDoc()); + qt3dsdm::Qt3DSDMInstanceHandle instance = m_RowTree->instance(); + + // add sub-presentations submenu + if (m_RowTree->objectType() & (OBJTYPE_LAYER | OBJTYPE_IS_MATERIAL | OBJTYPE_IMAGE)) { + m_subpMenu = addMenu(tr("Set sub-presentation")); + connect(m_subpMenu, &QMenu::triggered, this, &RowTreeContextMenu::addSubPresentation); + + m_subpMenu->addAction(tr("[None]")); + for (auto sp : qAsConst(g_StudioApp.m_subpresentations)) + m_subpMenu->addAction(sp.m_id); + + addSeparator(); + } + + // add datainput controller submenu + if (m_RowTree->objectType() & ~(OBJTYPE_GUIDE | OBJTYPE_EFFECT | OBJTYPE_ALIAS | OBJTYPE_SCENE) + && !m_RowTree->isDefaultMaterial()) { + + m_diMenu = addMenu(tr("Set datainput controller")); + connect(m_diMenu, &QMenu::triggered, this, &RowTreeContextMenu::addDiController); + + QVector<qt3dsdm::Qt3DSDMPropertyHandle> propList; + + // If this is a referenced material instance, we need to get the property list from + // the referenced source, and set datainput control to point to the property + // in the referenced source. + auto refInstance = doc.GetStudioSystem()->GetClientDataModelBridge() + ->getMaterialReference(instance); + propList = doc.GetStudioSystem()->GetPropertySystem() + ->GetControllableProperties(refInstance ? refInstance : instance); + + QMap<int, QAction *> sections; + for (const auto &prop : propList) { + QAction *action + = new QAction(QString::fromStdWString(doc.GetPropertySystem() + ->GetFormalName( + refInstance ? refInstance : instance, + prop).wide_str())); + action->setData(QString::fromStdWString( + doc.GetPropertySystem() + ->GetName(prop).wide_str())); + + auto metadata = doc.GetStudioSystem()->GetActionMetaData()->GetMetaDataPropertyInfo( + doc.GetStudioSystem()->GetActionMetaData()->GetMetaDataProperty( + refInstance ? refInstance : instance, prop)); + + if (sections.contains(metadata->m_CompleteType) ) { + m_diMenu->insertAction(sections[metadata->m_CompleteType], action); + } else { + // Create a QAction for a section so that we can insert properties above it + // to maintain category groupings. Sections are shown as separators in Studio + // style i.e. enum text is not shown. + QAction *section = m_diMenu->addSection(QString(metadata->m_CompleteType)); + sections.insert(metadata->m_CompleteType, section); + m_diMenu->insertAction(section, action); + } + } + } + m_renameAction = new QAction(tr("Rename Object"), this); + connect(m_renameAction, &QAction::triggered, this, &RowTreeContextMenu::renameObject); + addAction(m_renameAction); + + m_duplicateAction = new QAction(tr("Duplicate Object"), this); + m_duplicateAction->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_D)); + m_duplicateAction->setShortcutVisibleInContextMenu(true); + connect(m_duplicateAction, &QAction::triggered, + this, &RowTreeContextMenu::duplicateObject); + addAction(m_duplicateAction); + + m_deleteAction = new QAction(tr("Delete Object"), this); + m_deleteAction->setShortcut(Qt::Key_Delete); + m_deleteAction->setShortcutVisibleInContextMenu(true); + connect(m_deleteAction, &QAction::triggered, this, &RowTreeContextMenu::deleteObject); + addAction(m_deleteAction); + + m_groupAction = new QAction(tr("Group Objects"), this); + m_groupAction->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_G)); + m_groupAction->setShortcutVisibleInContextMenu(true); + connect(m_groupAction, &QAction::triggered, this, &RowTreeContextMenu::groupObjects); + addAction(m_groupAction); + + addSeparator(); + + m_addLayerAction = new QAction(tr("Add Layer"), this); + m_addLayerAction->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_L)); + m_addLayerAction->setShortcutVisibleInContextMenu(true); + connect(m_addLayerAction, &QAction::triggered, this, &RowTreeContextMenu::addLayer); + addAction(m_addLayerAction); + + addSeparator(); + + m_copyAction = new QAction(tr("Copy"), this); + m_copyAction->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_C)); + m_copyAction->setShortcutVisibleInContextMenu(true); + connect(m_copyAction, &QAction::triggered, this, &RowTreeContextMenu::copyObject); + addAction(m_copyAction); + + m_pasteAction = new QAction(tr("Paste"), this); + m_pasteAction->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_V)); + m_pasteAction->setShortcutVisibleInContextMenu(true); + connect(m_pasteAction, &QAction::triggered, this, &RowTreeContextMenu::pasteObject); + addAction(m_pasteAction); + + m_cutAction = new QAction(tr("Cut"), this); + m_cutAction->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_X)); + m_cutAction->setShortcutVisibleInContextMenu(true); + connect(m_cutAction, &QAction::triggered, this, &RowTreeContextMenu::cutObject); + addAction(m_cutAction); + addSeparator(); + + m_makeAction = new QAction(tr("Make Component"), this); + m_makeAction->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_G)); + m_makeAction->setShortcutVisibleInContextMenu(true); + connect(m_makeAction, &QAction::triggered, this, &RowTreeContextMenu::makeComponent); + addAction(m_makeAction); + + if (canInspectComponent()) { + m_inspectAction = new QAction(tr("Edit Component"), this); + m_inspectAction->setShortcut(QKeySequence( + Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_G)); + m_inspectAction->setShortcutVisibleInContextMenu(true); + connect(m_inspectAction, &QAction::triggered, this, &RowTreeContextMenu::inspectComponent); + addAction(m_inspectAction); + } + + if (canMakeAnimatable()) { + m_animAction = new QAction(tr("Make Animatable"), this); + connect(m_animAction, &QAction::triggered, this, &RowTreeContextMenu::makeAnimatable); + addAction(m_animAction); + } + + addSeparator(); + + m_copyPathAction = new QAction(tr("Copy Object Path"), this); + m_copyPathAction->setShortcut(QKeySequence( + Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_C)); + m_copyPathAction->setShortcutVisibleInContextMenu(true); + connect(m_copyPathAction, &QAction::triggered, this, &RowTreeContextMenu::copyObjectPath); + addAction(m_copyPathAction); +} + +void RowTreeContextMenu::showEvent(QShowEvent *event) +{ + if (m_subpMenu) + m_subpMenu->setEnabled(canAddSubPresentation()); + if (m_diMenu) + m_diMenu->setEnabled(true); + m_renameAction->setEnabled(canRenameObject()); + m_duplicateAction->setEnabled(canDuplicateObject()); + m_deleteAction->setEnabled(canDeleteObject()); + m_canGroupObjects = canGroupObjects(); + m_canUngroupObjects = canUngroupObjects(); + m_groupAction->setEnabled(m_canUngroupObjects || m_canGroupObjects); + if (m_canUngroupObjects) + m_groupAction->setText(tr("Ungroup Objects")); + + m_cutAction->setEnabled(canCutObject()); + m_copyAction->setEnabled(canCopyObject()); + m_pasteAction->setEnabled(canPasteObject()); + + m_makeAction->setEnabled(canMakeComponent()); + + m_addLayerAction->setEnabled(canAddLayer()); + + QMenu::showEvent(event); +} + +bool RowTreeContextMenu::canAddSubPresentation() const +{ + return !g_StudioApp.m_subpresentations.empty(); +} + +bool RowTreeContextMenu::canRenameObject() const +{ + return m_TimelineItemBinding->IsValidTransaction( + ITimelineItemBinding::EUserTransaction_Rename); +} + +void RowTreeContextMenu::addSubPresentation(QAction *action) +{ + CDoc &doc(*g_StudioApp.GetCore()->GetDoc()); + auto &bridge(*doc.GetStudioSystem()->GetClientDataModelBridge()); + + qt3dsdm::Qt3DSDMInstanceHandle instance = m_RowTree->instance(); + Q3DStudio::CString presentationId; + if (action->text() != tr("[None]")) + presentationId = Q3DStudio::CString::fromQString(action->text()); + + if (m_RowTree->objectType() == OBJTYPE_LAYER) { + qt3dsdm::Qt3DSDMPropertyHandle propHandle = bridge.GetSourcePathProperty(); + + Q3DStudio::SCOPED_DOCUMENT_EDITOR(doc, tr("Set layer sub-presentation")) + ->SetInstancePropertyValueAsRenderable(instance, propHandle, presentationId); + } else if (m_RowTree->objectType() & OBJTYPE_IS_MATERIAL) { + // if this is a ref material, update the material it references + qt3dsdm::Qt3DSDMInstanceHandle refInstance = bridge.getMaterialReference(instance); + + ChooseImagePropertyDlg dlg(refInstance ? refInstance : instance, refInstance != 0); + if (dlg.exec() == QDialog::Accepted) { + qt3dsdm::Qt3DSDMPropertyHandle propHandle = dlg.getSelectedPropertyHandle(); + if (dlg.detachMaterial()) { + Q3DStudio::ScopedDocumentEditor editor(Q3DStudio::SCOPED_DOCUMENT_EDITOR(doc, + tr("Set material sub-presentation"))); + editor->BeginAggregateOperation(); + editor->SetMaterialType(instance, QStringLiteral("Standard Material")); + editor->setInstanceImagePropertyValue(instance, propHandle, presentationId); + editor->EndAggregateOperation(); + } else { + Q3DStudio::SCOPED_DOCUMENT_EDITOR(doc, tr("Set material sub-presentation")) + ->setInstanceImagePropertyValue(refInstance ? refInstance : instance, propHandle, + presentationId); + } + } + } else if (m_RowTree->objectType() == OBJTYPE_IMAGE) { + qt3dsdm::Qt3DSDMPropertyHandle propHandle = bridge.getSubpresentationProperty(); + + Q3DStudio::SCOPED_DOCUMENT_EDITOR(doc, tr("Set image sub-presentation")) + ->SetInstancePropertyValueAsRenderable(instance, propHandle, presentationId); + } +} + +void RowTreeContextMenu::addDiController(QAction *action) +{ + m_RowTree->showDataInputSelector(action->data().toString(), QCursor::pos()); +} + +void RowTreeContextMenu::renameObject() +{ + m_RowTree->selectLabel(); +} + +bool RowTreeContextMenu::canDuplicateObject() const +{ + return m_TimelineItemBinding->IsValidTransaction( + ITimelineItemBinding::EUserTransaction_Duplicate); +} + +void RowTreeContextMenu::duplicateObject() +{ + m_TimelineItemBinding->PerformTransaction( + ITimelineItemBinding::EUserTransaction_Duplicate); +} + +bool RowTreeContextMenu::canDeleteObject() const +{ + return m_TimelineItemBinding->IsValidTransaction( + ITimelineItemBinding::EUserTransaction_Delete); +} + +bool RowTreeContextMenu::canGroupObjects() const +{ + return m_TimelineItemBinding->IsValidTransaction( + ITimelineItemBinding::EUserTransaction_Group); +} + +bool RowTreeContextMenu::canUngroupObjects() const +{ + return m_TimelineItemBinding->IsValidTransaction( + ITimelineItemBinding::EUserTransaction_Ungroup); +} + +void RowTreeContextMenu::deleteObject() +{ + m_TimelineItemBinding->PerformTransaction( + ITimelineItemBinding::EUserTransaction_Delete); +} + +void RowTreeContextMenu::groupObjects() +{ + if (m_canUngroupObjects) + m_TimelineItemBinding->PerformTransaction(ITimelineItemBinding::EUserTransaction_Ungroup); + else if (m_canGroupObjects) + m_TimelineItemBinding->PerformTransaction(ITimelineItemBinding::EUserTransaction_Group); +} + +bool RowTreeContextMenu::canInspectComponent() const +{ + return m_TimelineItemBinding->IsValidTransaction( + ITimelineItemBinding::EUserTransaction_EditComponent); +} + +/** + * Inspect the State (Component). + * This will make the component the top level item of the timelineview. + */ +void RowTreeContextMenu::inspectComponent() +{ + m_TimelineItemBinding->OpenAssociatedEditor(); +} + +/** + * Checks to see if the object can be wrapped in a component. + * @return true if the object is allowed to be wrapped in a component. + */ +bool RowTreeContextMenu::canMakeComponent() const +{ + return m_TimelineItemBinding->IsValidTransaction( + ITimelineItemBinding::EUserTransaction_MakeComponent); +} + +/** + * Wraps the specified asset hierarchy under a component. + */ +void RowTreeContextMenu::makeComponent() +{ + m_TimelineItemBinding->PerformTransaction( + ITimelineItemBinding::EUserTransaction_MakeComponent); +} + +/** + * Returns true if the object is a referenced material and thus can be made animatable + */ +bool RowTreeContextMenu::canMakeAnimatable() const +{ + return m_TimelineItemBinding->IsValidTransaction( + ITimelineItemBinding::EUserTransaction_MakeAnimatable); +} + +/** + * Makes a referenced material animatable aka changing it to a standard or a custom material + * with the same properties + */ +void RowTreeContextMenu::makeAnimatable() +{ + m_TimelineItemBinding->PerformTransaction( + ITimelineItemBinding::EUserTransaction_MakeAnimatable); +} + +/** + * Get the full Scripting path of the object and copy it to the clipboard. + * This will figure out the proper way to address the object via scripting + * and put that path into the clipboard. + */ +void RowTreeContextMenu::copyObjectPath() +{ + CStudioClipboard::CopyTextToClipboard( + m_TimelineItemBinding->GetObjectPath().toQString()); +} + +bool RowTreeContextMenu::canCopyObject() const +{ + return m_TimelineItemBinding->IsValidTransaction( + ITimelineItemBinding::EUserTransaction_Copy); +} + +void RowTreeContextMenu::copyObject() +{ + m_TimelineItemBinding->PerformTransaction( + ITimelineItemBinding::EUserTransaction_Copy); +} + +bool RowTreeContextMenu::canCutObject() const +{ + return m_TimelineItemBinding->IsValidTransaction( + ITimelineItemBinding::EUserTransaction_Cut); +} + +void RowTreeContextMenu::cutObject() +{ + m_TimelineItemBinding->PerformTransaction( + ITimelineItemBinding::EUserTransaction_Cut); +} + +bool RowTreeContextMenu::canAddLayer() const +{ + return m_TimelineItemBinding->IsValidTransaction( + ITimelineItemBinding::EUserTransaction_AddLayer); +} +void RowTreeContextMenu::addLayer() +{ + m_TimelineItemBinding->PerformTransaction( + ITimelineItemBinding::EUserTransaction_AddLayer); +} + +bool RowTreeContextMenu::canPasteObject() const +{ + return m_TimelineItemBinding->IsValidTransaction( + ITimelineItemBinding::EUserTransaction_Paste); +} + +void RowTreeContextMenu::pasteObject() +{ + m_TimelineItemBinding->PerformTransaction( + ITimelineItemBinding::EUserTransaction_Paste); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeContextMenu.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeContextMenu.h new file mode 100644 index 00000000..0ef87552 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeContextMenu.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef ROWTREECONTEXTMENU_H +#define ROWTREECONTEXTMENU_H + +#include <QtWidgets/qmenu.h> +#include <QtWidgets/qaction.h> + +class ITimelineItemBinding; +class RowTree; + +class RowTreeContextMenu : public QMenu +{ + Q_OBJECT +public: + RowTreeContextMenu(RowTree *inRowTree, + QWidget *parent = nullptr); + virtual ~RowTreeContextMenu(); + +protected: + void showEvent(QShowEvent *event) override; + +private Q_SLOTS: + void addSubPresentation(QAction *action); + void addDiController(QAction *action); + void renameObject(); + void duplicateObject(); + void deleteObject(); + void groupObjects(); + void inspectComponent(); + void makeComponent(); + void makeAnimatable(); + void copyObject(); + void copyObjectPath(); + void pasteObject(); + void cutObject(); + void addLayer(); + +private: + void initialize(); + + bool canAddSubPresentation() const; + bool canRenameObject() const; + bool canDuplicateObject() const; + bool canDeleteObject() const; + bool canGroupObjects() const; + bool canUngroupObjects() const; + bool canInspectComponent() const; + bool canMakeComponent() const; + bool canMakeAnimatable() const; + bool canCopyObject() const; + bool canPasteObject() const; + bool canCutObject() const; + bool canAddLayer() const; + + RowTree *m_RowTree; + ITimelineItemBinding *m_TimelineItemBinding; + QMenu *m_subpMenu = nullptr; // sub-presentation submenu + QMenu *m_diMenu = nullptr; // datainput submenu + QAction *m_renameAction = nullptr; + QAction *m_duplicateAction = nullptr; + QAction *m_deleteAction = nullptr; + QAction *m_groupAction = nullptr; + QAction *m_addLayerAction = nullptr; + QAction *m_inspectAction = nullptr; + QAction *m_makeAction = nullptr; + QAction *m_animAction = nullptr; + QAction *m_copyPathAction = nullptr; + QAction *m_cutAction = nullptr; + QAction *m_copyAction = nullptr; + QAction *m_pasteAction = nullptr; + bool m_canGroupObjects = false; + bool m_canUngroupObjects = false; +}; +#endif // ROWTREECONTEXTMENU_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeLabelItem.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeLabelItem.cpp new file mode 100644 index 00000000..21b57aaa --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeLabelItem.cpp @@ -0,0 +1,175 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "RowTreeLabelItem.h" +#include "TimelineConstants.h" +#include "TimelineItem.h" +#include "RowTree.h" +#include "StudioPreferences.h" + +#include <QtWidgets/qstyleoption.h> +#include <QtGui/qevent.h> +#include <QtGui/qtextcursor.h> + +RowTreeLabelItem::RowTreeLabelItem(QGraphicsItem *parent) + : QGraphicsTextItem(parent) + , m_locked(false) + , m_master(false) + , m_acceptOnFocusOut(true) +{ + setTextInteractionFlags(Qt::TextEditorInteraction); + setEnabled(false); + updateLabelColor(); +} + +QString RowTreeLabelItem::label() const +{ + return m_label; +} + +void RowTreeLabelItem::setLabel(const QString &label) +{ + setPlainText(label); + if (m_label != label) { + m_label = label; + emit labelChanged(m_label); + } +} + +void RowTreeLabelItem::setMaster(bool isMaster) { + if (m_master != isMaster) { + m_master = isMaster; + updateLabelColor(); + } +} + +void RowTreeLabelItem::setLocked(bool isLocked) { + if (m_locked != isLocked) { + m_locked = isLocked; + updateLabelColor(); + } +} + +RowTree *RowTreeLabelItem::parentRow() const +{ + return m_rowTree; +} + +void RowTreeLabelItem::setParentRow(RowTree *row) +{ + m_rowTree = row; +} + +int RowTreeLabelItem::type() const +{ + // Enable the use of qgraphicsitem_cast with this item. + return TimelineItem::TypeRowTreeLabelItem; +} + +void RowTreeLabelItem::paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + if (!m_rowTree->y()) // prevents flickering when the row is just inserted to the layout + return; + + // Remove the HasFocus style state, to prevent the dotted line from being drawn. + QStyleOptionGraphicsItem *style = const_cast<QStyleOptionGraphicsItem *>(option); + style->state &= ~QStyle::State_HasFocus; + + QGraphicsTextItem::paint(painter, option, widget); +} + +void RowTreeLabelItem::focusOutEvent(QFocusEvent *event) +{ + if (m_acceptOnFocusOut) + validateLabel(); + else + setPlainText(m_label); + + // Remove possible selection and make disabled again + QTextCursor cursor = textCursor(); + cursor.clearSelection(); + setTextCursor(cursor); + setEnabled(false); + QGraphicsTextItem::focusOutEvent(event); + // Next time default to accepting + m_acceptOnFocusOut = true; +} + +void RowTreeLabelItem::keyPressEvent(QKeyEvent *event) +{ + int key = event->key(); + if (key == Qt::Key_Return || key == Qt::Key_Enter) { + m_acceptOnFocusOut = true; + clearFocus(); + event->accept(); + return; + } else if (key == Qt::Key_Escape) { + m_acceptOnFocusOut = false; + clearFocus(); + event->accept(); + return; + } + + QGraphicsTextItem::keyPressEvent(event); +} + +QRectF RowTreeLabelItem::boundingRect() const +{ + if (!m_rowTree) + return QGraphicsTextItem::boundingRect(); + + double w = m_rowTree->clipX() - x(); + // Bounding rect width must be at least 1 + w = std::max(w, 1.0); + return QRectF(0, 0, w, TimelineConstants::ROW_H); +} + +void RowTreeLabelItem::validateLabel() +{ + QString text = toPlainText().trimmed(); + if (text.isEmpty()) { + // Inform label was empty and return previous label + emit labelChanged(""); + setLabel(m_label); + return; + } + + setLabel(text); +} + +void RowTreeLabelItem::updateLabelColor() +{ + if (m_locked) + setDefaultTextColor(CStudioPreferences::GetDisabledTextColor()); + else if (m_master) + setDefaultTextColor(CStudioPreferences::GetMasterColor()); + else + setDefaultTextColor(CStudioPreferences::GetNormalColor()); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeLabelItem.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeLabelItem.h new file mode 100644 index 00000000..edffbef5 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/RowTreeLabelItem.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef ROWTREELABELITEM_H +#define ROWTREELABELITEM_H + +#include "StudioObjectTypes.h" +#include <QtWidgets/qgraphicsitem.h> +#include <QtCore/qstring.h> +#include <QtWidgets/qgraphicssceneevent.h> +#include <QtGui/qevent.h> + +class RowTree; + +class RowTreeLabelItem : public QGraphicsTextItem +{ + Q_OBJECT +public: + explicit RowTreeLabelItem(QGraphicsItem *parent = nullptr); + + QString label() const; + void setLabel(const QString &label); + void setLocked(bool isLocked); + void setMaster(bool isMaster); + RowTree *parentRow() const; + void setParentRow(RowTree *row); + int type() const; + bool isLocked() const { return m_locked; } + +protected: + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + void focusOutEvent(QFocusEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + QRectF boundingRect() const override; + +signals: + void labelChanged(const QString label); + +private: + void validateLabel(); + void updateLabelColor(); + + RowTree *m_rowTree = nullptr; + QString m_label; + bool m_locked; + bool m_master; + bool m_acceptOnFocusOut; + +}; + +#endif // ROWTREELABELITEM_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/Ruler.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/Ruler.cpp new file mode 100644 index 00000000..62c6de01 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/Ruler.cpp @@ -0,0 +1,219 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Ruler.h" +#include "TimelineConstants.h" +#include "StudioPreferences.h" + +#include <QtGui/qpainter.h> +#include <QtWidgets/qwidget.h> + +Ruler::Ruler(TimelineItem *parent) : TimelineItem(parent) +{ + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); +} + +void Ruler::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(option) + + double xStep = TimelineConstants::RULER_SEC_W / TimelineConstants::RULER_SEC_DIV * m_timeScale; + double activeSegmentsWidth = TimelineConstants::RULER_EDGE_OFFSET + + m_duration / 1000.0 * xStep * TimelineConstants::RULER_SEC_DIV; + double totalSegmentsWidth = TimelineConstants::RULER_EDGE_OFFSET + + m_maxDuration / 1000.0 * xStep * TimelineConstants::RULER_SEC_DIV; + + // Ruler painted width to be at least widget width + double minRulerWidth = widget->width(); + double rowXMax = std::max(minRulerWidth, totalSegmentsWidth); + + painter->save(); + painter->setPen(CStudioPreferences::timelineRulerColorDisabled()); + painter->drawLine(TimelineConstants::RULER_EDGE_OFFSET, + TimelineConstants::RULER_BASE_Y, + rowXMax + TimelineConstants::RULER_EDGE_OFFSET, + TimelineConstants::RULER_BASE_Y); + painter->setPen(CStudioPreferences::timelineRulerColor()); + painter->drawLine(TimelineConstants::RULER_EDGE_OFFSET, + TimelineConstants::RULER_BASE_Y, + activeSegmentsWidth, + TimelineConstants::RULER_BASE_Y); + + QFont font = painter->font(); + font.setPointSize(8); + painter->setFont(font); + + const int margin = 50; + const int secDiv = TimelineConstants::RULER_SEC_DIV; + double rowX = 0; + bool useDisabledColor = false; + for (int i = 0; rowX < rowXMax; i++) { + rowX = TimelineConstants::RULER_EDGE_OFFSET + xStep * i; + + // Optimization to skip painting outside the visible area + if (rowX < (m_viewportX - margin) || rowX > (m_viewportX + minRulerWidth + margin)) + continue; + + const int h = i % secDiv == 0 ? TimelineConstants::RULER_DIV_H1 + : i % secDiv == secDiv * 0.5 ? TimelineConstants::RULER_DIV_H2 + : TimelineConstants::RULER_DIV_H3; + + if (!useDisabledColor && rowX > activeSegmentsWidth) { + painter->setPen(CStudioPreferences::timelineRulerColorDisabled()); + useDisabledColor = true; + } + painter->drawLine(QPointF(rowX, TimelineConstants::RULER_BASE_Y - h), + QPointF(rowX, TimelineConstants::RULER_BASE_Y - 1)); + + // See if label should be shown at this tick at this zoom level + bool drawTimestamp = false; + if ((i % (secDiv * 4) == 0) + || (i % (secDiv * 2) == 0 && m_timeScale >= TimelineConstants::RULER_TICK_SCALE1) + || (i % secDiv == 0 && m_timeScale >= TimelineConstants::RULER_TICK_SCALE2) + || (i % secDiv == secDiv * 0.5 + && m_timeScale >= TimelineConstants::RULER_TICK_SCALE3) + || (m_timeScale >= TimelineConstants::RULER_TICK_SCALE4)) { + drawTimestamp = true; + } + + if (drawTimestamp) { + QRectF timestampPos = QRectF(TimelineConstants::RULER_EDGE_OFFSET + + xStep * i - TimelineConstants::RULER_LABEL_W / 2, + 1, TimelineConstants::RULER_LABEL_W, + TimelineConstants::RULER_LABEL_H); + painter->drawText(timestampPos, Qt::AlignCenter, + timestampString(i * 1000 / TimelineConstants::RULER_SEC_DIV)); + } + + } + + painter->restore(); +} + +void Ruler::setTimelineScale(double scl) +{ + m_timeScale = scl; + update(); +} + +// convert distance values to time (milliseconds) +long Ruler::distanceToTime(double distance) const +{ + return distance / (TimelineConstants::RULER_MILLI_W * m_timeScale); +} + +// convert time (milliseconds) values to distance +double Ruler::timeToDistance(long time) const +{ + return time * TimelineConstants::RULER_MILLI_W * m_timeScale; +} + +double Ruler::timelineScale() const +{ + return m_timeScale; +} + +// Returns end of right-most layer/component row. +// Active color of ruler is used up to this point. +// Slide plays up to this point. +long Ruler::duration() const +{ + return m_duration; +} + +// Returns end of right-most row. +// Ruler steps & labels are drawn up to this point. +// Timeline scrollbar allows scrolling up to this point. +long Ruler::maxDuration() const +{ + return m_maxDuration; +} + +void Ruler::setDuration(long duration) +{ + if (m_duration != duration) { + m_duration = duration; + update(); + emit durationChanged(m_duration); + } +} + +void Ruler::setMaxDuration(long maxDuration) +{ + if (m_maxDuration != maxDuration) { + m_maxDuration = maxDuration; + update(); + emit maxDurationChanged(m_maxDuration); + } +} + +void Ruler::setViewportX(int viewportX) +{ + if (m_viewportX != viewportX) { + m_viewportX = viewportX; + emit viewportXChanged(m_viewportX); + update(); + } +} + +int Ruler::viewportX() const +{ + return m_viewportX; +} + +// Returns timestamp in mm:ss.ttt or ss.ttt format +const QString Ruler::timestampString(int timeMs) +{ + static const QString zeroString = tr("0"); + static const QChar fillChar = tr("0").at(0); + static const QString noMinutesTemplate = tr("%1.%2"); + static const QString minutesTemplate = tr("%1:%2.%3"); + + int ms = timeMs % 1000; + int s = timeMs % 60000 / 1000; + int m = timeMs % 3600000 / 60000; + const QString msString = QString::number(ms).rightJustified(3, fillChar); + const QString sString = QString::number(s); + + if (timeMs == 0) { + return zeroString; + } else if (m == 0) { + if (s < 10) + return noMinutesTemplate.arg(sString).arg(msString); + else + return noMinutesTemplate.arg(sString.rightJustified(2, fillChar)).arg(msString); + } else { + return minutesTemplate.arg(m).arg(sString.rightJustified(2, fillChar)).arg(msString); + } +} + +int Ruler::type() const +{ + // Enable the use of qgraphicsitem_cast with this item. + return TypeRuler; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/Ruler.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/Ruler.h new file mode 100644 index 00000000..005a23c3 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/Ruler.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef RULER_H +#define RULER_H + +#include "TimelineItem.h" + +class Ruler : public TimelineItem +{ + Q_OBJECT + +signals: + void rulerClicked(const double &pos); + +public: + explicit Ruler(TimelineItem *parent = nullptr); + + void setTimelineScale(double scl); + long distanceToTime(double distance) const; + double timeToDistance(long time) const; + double timelineScale() const; + long duration() const; + long maxDuration() const; + void setDuration(long duration); + void setMaxDuration(long maxDuration); + void setViewportX(int viewportX); + int viewportX() const; + int type() const override; + +protected: + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget = nullptr) override; + +signals: + void maxDurationChanged(long maxDuration); + void durationChanged(long duration); + void viewportXChanged(int viewportX); + +private: + const QString timestampString(int timeMs); + double m_timeScale = 2; + long m_duration = 0; // milliseconds + long m_maxDuration = 0; // milliseconds + int m_viewportX = 0; +}; + +#endif // RULER_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineItem.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineItem.cpp new file mode 100644 index 00000000..866d8109 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineItem.cpp @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "TimelineItem.h" +#include "TimelineConstants.h" + +#include <QtGui/qpainter.h> + +TimelineItem::TimelineItem(TimelineItem *parent) : QGraphicsWidget(parent) +{ + setPreferredHeight(TimelineConstants::ROW_H_EXPANDED); + setMaximumHeight(TimelineConstants::ROW_H); +} + +int TimelineItem::type() const +{ + // Enable the use of qgraphicsitem_cast with this item. + return TypeTimelineItem; +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineItem.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineItem.h new file mode 100644 index 00000000..71d26c0d --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineItem.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef TIMELINEITEM_H +#define TIMELINEITEM_H + +#include <QtWidgets/qgraphicswidget.h> + +class TimelineItem : public QGraphicsWidget +{ + Q_OBJECT + +public: + explicit TimelineItem(TimelineItem *parent = nullptr); + + enum ItemType { + TypeTimelineItem = UserType + 1, + TypeInteractiveTimelineItem, + TypeTreeHeader, + TypeRowTree, + TypeRowTreeLabelItem, + TypeRowTimeline, + TypeRowTimelineCommentItem, + TypePlayHead, + TypeRuler, + TypeRowMover + }; + + int type() const override; +}; + +#endif // TIMELINEITEM_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineToolbar.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineToolbar.cpp new file mode 100644 index 00000000..05d24fa6 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineToolbar.cpp @@ -0,0 +1,456 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "TimelineToolbar.h" +#include "StudioApp.h" +#include "Core.h" +#include "Doc.h" +#include "Dispatch.h" +#include "DataInputSelectView.h" +#include "DataInputDlg.h" +#include "Qt3DSDMStudioSystem.h" +#include "StudioPreferences.h" +#include "ClientDataModelBridge.h" +#include "IDocumentEditor.h" +#include "DocumentEditorEnumerations.h" +#include "Dialogs.h" + +#include <QtWidgets/qslider.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qtimer.h> +#include <QtWidgets/qpushbutton.h> +#include <QtWidgets/qshortcut.h> + +TimelineToolbar::TimelineToolbar() : QToolBar() +{ + setContentsMargins(0, 0, 0, 0); + setIconSize(QSize(16, 16)); + + // create icons + static const QIcon iconLayer = QIcon(":/images/Objects-Layer-Normal.png"); + static const QIcon iconDelete = QIcon(":/images/Action-Trash-Normal.png"); + static const QIcon iconFirst = QIcon(":/images/playback_tools_first.png"); + static const QIcon iconLast = QIcon(":/images/playback_tools_last.png"); + static const QIcon iconZoomIn = QIcon(":/images/zoom_in.png"); + static const QIcon iconZoomOut = QIcon(":/images/zoom_out.png"); + m_iconDiActive = QIcon(":/images/Objects-DataInput-Active.png"); + m_iconDiInactive = QIcon(":/images/Objects-DataInput-Inactive.png"); + m_iconStop = QIcon(":/images/playback_tools_stop.png"); + m_iconPlay = QIcon(":/images/playback_tools_play.png"); + + // create actions + QString ctrlKey(QStringLiteral("Ctrl+")); + QString altKey(QStringLiteral("Alt+")); +#ifdef Q_OS_MACOS + ctrlKey = "⌘"; + altKey = "⌥"; +#endif + QString newLayerString = tr("Add New Layer (%1L)").arg(ctrlKey); + m_actionNewLayer = new QAction(iconLayer, newLayerString, this); + QAction *actionFirst = new QAction(iconFirst, tr("Go to Timeline Start"), this); + QAction *actionLast = new QAction(iconLast, tr("Go to Timeline End"), this); + m_actionDataInput = new QAction(m_iconDiInactive, tr("No Controller"), this); + m_actionDeleteRow = new QAction(iconDelete, tr("Delete Selected Object (Del)"), this); + m_actionPlayStop = new QAction(this); + m_timeLabel = new QPushButton(this); + m_diLabel = new QLabel(this); + m_actionZoomIn = new QAction(iconZoomIn, tr("Zoom In"), this); + m_actionZoomOut = new QAction(iconZoomOut, tr("Zoom Out"), this); + + m_scaleSlider = new QSlider(); + m_scaleSlider->setOrientation(Qt::Horizontal); + m_scaleSlider->setFixedWidth(100); + m_scaleSlider->setMinimum(1); + m_scaleSlider->setMaximum(22); + m_scaleSlider->setValue(2); + + m_timeLabel->setObjectName(QLatin1String("timelineButton")); + m_timeLabel->setFlat(true); + m_timeLabel->setMinimumWidth(80); + m_timeLabel->setToolTip(tr("Go To Time (%1%2T)").arg(ctrlKey).arg(altKey)); + + m_diLabel->setText(""); + m_diLabel->setMinimumWidth(100); + m_diLabel->setAlignment(Qt::AlignCenter); + QString styleString = "QLabel { background: transparent; color: " + + QString(CStudioPreferences::dataInputColor().name()) + "; }"; + m_diLabel->setStyleSheet(styleString); + + m_actionShowRowTexts = new QAction(tr("Toggle Timebars Text Visibility"), this); + QIcon rowTextIcon { QPixmap(":/images/timeline_text_hidden.png") }; + rowTextIcon.addPixmap(QPixmap(":/images/timeline_text_shown.png"), QIcon::Normal, QIcon::On); + m_actionShowRowTexts->setIcon(rowTextIcon); + m_actionShowRowTexts->setCheckable(true); + m_actionFilter = new QAction(tr("Filter Timeline Rows Visibility According to Variants Filter"), + this); + m_actionFilter->setCheckable(true); + QIcon filterIcon { QPixmap(":/images/filter.png") }; + filterIcon.addPixmap(QPixmap(":/images/filter-colored.png"), QIcon::Normal, QIcon::On); + m_actionFilter->setIcon(filterIcon); + + updatePlayButtonState(false); + + // connections + connect(m_actionNewLayer, &QAction::triggered, this, &TimelineToolbar::newLayerTriggered); + connect(m_actionDeleteRow, &QAction::triggered, this, &TimelineToolbar::deleteLayerTriggered); + connect(m_timeLabel, &QPushButton::clicked, this, &TimelineToolbar::gotoTimeTriggered); + connect(actionFirst, &QAction::triggered, this, &TimelineToolbar::firstFrameTriggered); + connect(m_actionPlayStop, &QAction::triggered, this, &TimelineToolbar::onPlayButtonClicked); + connect(actionLast, &QAction::triggered, this, &TimelineToolbar::lastFrameTriggered); + connect(m_scaleSlider, &QSlider::valueChanged, this, &TimelineToolbar::onZoomLevelChanged); + connect(m_actionZoomIn, &QAction::triggered, this, &TimelineToolbar::onZoomInButtonClicked); + connect(m_actionZoomOut, &QAction::triggered, this, &TimelineToolbar::onZoomOutButtonClicked); + connect(m_actionDataInput, &QAction::triggered, this, &TimelineToolbar::onDiButtonClicked); + connect(m_actionShowRowTexts, &QAction::toggled, this, &TimelineToolbar::showRowTextsToggled); + connect(m_actionFilter, &QAction::toggled, this, &TimelineToolbar::variantsFilterToggled); + + // add actions + addAction(m_actionNewLayer); + addAction(m_actionDeleteRow); + addAction(m_actionDataInput); + addSpacing(2); + addAction(m_actionShowRowTexts); + addAction(m_actionFilter); + addWidget(m_diLabel); + addSpacing(20); + addWidget(m_timeLabel); + addSpacing(20); + addAction(actionFirst); + addAction(m_actionPlayStop); + addAction(actionLast); + addSpacing(30); + addAction(m_actionZoomOut); + addWidget(m_scaleSlider); + addAction(m_actionZoomIn); + + // add keyboard shortcuts + m_actionZoomOut->setShortcut(Qt::Key_Minus); + m_actionZoomOut->setShortcutContext(Qt::ApplicationShortcut); + m_actionZoomIn->setShortcut(Qt::Key_Plus); + m_actionZoomIn->setShortcutContext(Qt::ApplicationShortcut); + m_actionNewLayer->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_L)); + m_actionNewLayer->setShortcutContext(Qt::ApplicationShortcut); + + QShortcut *gotoTimeShortcut = new QShortcut(this); + gotoTimeShortcut->setKey(QKeySequence(Qt::ControlModifier | Qt::AltModifier | Qt::Key_T)); + gotoTimeShortcut->setContext(Qt::ApplicationShortcut); + connect(gotoTimeShortcut, &QShortcut::activated, this, &TimelineToolbar::gotoTimeTriggered); + + m_connectSelectionChange = g_StudioApp.GetCore()->GetDispatch()->ConnectSelectionChange( + std::bind(&TimelineToolbar::onSelectionChange, this, std::placeholders::_1)); + + // make datainput indicator listen to selection dialog choice + const QVector<EDataType> acceptedTypes = { EDataType::DataTypeRangedNumber }; + m_dataInputSelector = new DataInputSelectView(acceptedTypes, this); + g_StudioApp.GetCore()->GetDispatch()->AddDataModelListener(this); + connect(m_dataInputSelector, &DataInputSelectView::dataInputChanged, + this, &TimelineToolbar::onDataInputChange); +} + +TimelineToolbar::~TimelineToolbar() +{ + delete m_dataInputSelector; +} + +void TimelineToolbar::onSelectionChange(Q3DStudio::SSelectedValue inNewSelectable) +{ + qt3dsdm::TInstanceHandleList selectedInstances = inNewSelectable.GetSelectedInstances(); + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + CClientDataModelBridge *theClientBridge = doc->GetStudioSystem()->GetClientDataModelBridge(); + bool canDelete = false; + for (size_t idx = 0, end = selectedInstances.size(); idx < end; ++idx) { + if (theClientBridge->CanDelete(selectedInstances[idx])) { + canDelete = true; + break; + } + } + + m_actionDeleteRow->setEnabled(canDelete); +} + +// add a spacer widget +void TimelineToolbar::addSpacing(int width) +{ + auto *widget = new QWidget; + widget->setStyleSheet("background:transparent;"); + widget->setFixedWidth(width); + addWidget(widget); +} + +void TimelineToolbar::setTime(long totalMillis) +{ + long mins = totalMillis % 3600000 / 60000; + long secs = totalMillis % 60000 / 1000; + long millis = totalMillis % 1000; + + m_timeLabel->setText(QString::asprintf("%01d:%02d.%03d", mins, secs, millis)); +} + +QString TimelineToolbar::getCurrentController() const +{ + return m_currController; +} + +QAction *TimelineToolbar::actionShowRowTexts() const +{ + return m_actionShowRowTexts; +} + +void TimelineToolbar::setNewLayerEnabled(bool enable) +{ + m_actionNewLayer->setEnabled(enable); +} + +void TimelineToolbar::updatePlayButtonState(bool started) +{ + if (started) { + m_actionPlayStop->setIcon(m_iconStop); + m_actionPlayStop->setText(tr("Stop Playing (Enter)")); + } else { + m_actionPlayStop->setIcon(m_iconPlay); + m_actionPlayStop->setText(tr("Start Playing (Enter)")); + } +} + +void TimelineToolbar::onPlayButtonClicked() +{ + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + if (doc->IsPlaying()) + emit stopTriggered(); + else + emit playTriggered(); +} + +void TimelineToolbar::onZoomLevelChanged(int scale) +{ + m_actionZoomIn->setEnabled(scale < m_scaleSlider->maximum()); + m_actionZoomOut->setEnabled(scale > m_scaleSlider->minimum()); + + emit timelineScaleChanged(scale); +} + +void TimelineToolbar::onZoomInButtonClicked() +{ + m_scaleSlider->setValue(m_scaleSlider->value() + 1); +} + +void TimelineToolbar::onZoomOutButtonClicked() +{ + m_scaleSlider->setValue(m_scaleSlider->value() - 1); +} + +void TimelineToolbar::onDiButtonClicked() +{ + QWidget *diButton = widgetForAction(m_actionDataInput); + if (diButton) { + QPoint chooserPos = diButton->pos() + QPoint(diButton->size().width(), + diButton->size().height()); + showDataInputChooser(mapToGlobal(chooserPos)); + } +} + +bool TimelineToolbar::isVariantsFilterOn() const +{ + return m_actionFilter->isChecked(); +} + +// Update datainput button state according to this timecontext control state. +void TimelineToolbar::updateDataInputStatus() +{ + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + qt3dsdm::Qt3DSDMPropertyHandle ctrldProp; + qt3dsdm::Qt3DSDMInstanceHandle timeCtxRoot = doc->GetActiveRootInstance(); + CClientDataModelBridge *theClientBridge = doc->GetStudioSystem()->GetClientDataModelBridge(); + if (theClientBridge->GetObjectType(timeCtxRoot) == EStudioObjectType::OBJTYPE_SCENE) { + ctrldProp = theClientBridge->GetObjectDefinitions().m_Scene.m_ControlledProperty; + } else if (theClientBridge->GetObjectType(timeCtxRoot) == + EStudioObjectType::OBJTYPE_COMPONENT) { + ctrldProp = theClientBridge->GetObjectDefinitions().m_Component.m_ControlledProperty; + } else { + Q_ASSERT(false); + } + + qt3dsdm::SValue controlledPropertyVal; + doc->GetStudioSystem()->GetPropertySystem()->GetInstancePropertyValue( + timeCtxRoot, ctrldProp, controlledPropertyVal); + auto existingCtrl = qt3dsdm::get<QString>(controlledPropertyVal); + + QString newController; + int timelineStrPos = existingCtrl.indexOf("@timeline"); + if (timelineStrPos != -1) { + int ctrStrPos = existingCtrl.lastIndexOf("$", timelineStrPos - 2); + newController = existingCtrl.mid(ctrStrPos + 1, timelineStrPos - ctrStrPos - 2); + } + if (newController != m_currController) { + m_currController = newController; + // Toggle if we changed to a controlled time context, or if icon current state + // differs from the control state of current time context + if (!m_currController.isEmpty()) { + m_actionDataInput->setToolTip( + tr("Timeline Controller:\n%1").arg(m_currController)); + m_actionDataInput->setIcon(m_iconDiActive); + updateTimelineTitleColor(true); + } else { + // TODO actually delete the entire property instead of setting it as empty string + m_actionDataInput->setIcon(m_iconDiInactive); + m_actionDataInput->setToolTip(tr("No Controller")); + updateTimelineTitleColor(false); + } + m_diLabel->setText(m_currController); + emit controllerChanged(m_currController); + if (m_dataInputSelector && m_dataInputSelector->isVisible()) + m_dataInputSelector->setCurrentController(m_currController); + } +} + +void TimelineToolbar::showDataInputChooser(const QPoint &point) +{ + QString currCtr = m_currController.size() ? + m_currController : m_dataInputSelector->getNoneString(); + QVector<QPair<QString, int>> dataInputList; + + for (auto &it : qAsConst(g_StudioApp.m_dataInputDialogItems)) + dataInputList.append({it->name, it->type}); + + m_dataInputSelector->setData(dataInputList, currCtr); + + CDialogs::showWidgetBrowser(this, m_dataInputSelector, point, + CDialogs::WidgetBrowserAlign::ToolButton); +} + +void TimelineToolbar::onDataInputChange(int handle, int instance, const QString &dataInputName) +{ + Q_UNUSED(handle) + Q_UNUSED(instance) + + if (dataInputName == m_currController) + return; + + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + CClientDataModelBridge *bridge = doc->GetStudioSystem()->GetClientDataModelBridge(); + QString fullTimeControlStr; + + if (dataInputName != m_dataInputSelector->getNoneString()) { + m_actionDataInput->setToolTip(tr("Timeline Controller:\n%1").arg(dataInputName)); + fullTimeControlStr = "$" + dataInputName + " @timeline"; + m_actionDataInput->setIcon(m_iconDiActive); + m_currController = dataInputName; + updateTimelineTitleColor(true); + } else { + m_actionDataInput->setToolTip(tr("No Controller")); + // TODO actually delete the entire property instead of setting it as empty string + m_actionDataInput->setIcon(m_iconDiInactive); + m_currController.clear(); + updateTimelineTitleColor(false); + } + + emit controllerChanged(m_currController); + + // To indicate that this presentation timeline is controlled by data input, + // we set "controlled property" of this time context root (scene or component) + // to contain the name of controller followed by special indicator "@timeline". + // Either replace existing timeline control indicator string or append new one + // but do not touch @slide indicator string as scene can have both + qt3dsdm::Qt3DSDMPropertyHandle ctrldPropertyHandle; + qt3dsdm::Qt3DSDMInstanceHandle timeCtxRoot = doc->GetActiveRootInstance(); + // Time context root is either scene or component + if (bridge->GetObjectType(timeCtxRoot) == EStudioObjectType::OBJTYPE_SCENE) + ctrldPropertyHandle = bridge->GetObjectDefinitions().m_Scene.m_ControlledProperty; + else if (bridge->GetObjectType(timeCtxRoot) == EStudioObjectType::OBJTYPE_COMPONENT) + ctrldPropertyHandle = bridge->GetObjectDefinitions().m_Component.m_ControlledProperty; + else + Q_ASSERT(false); + + qt3dsdm::SValue controlledPropertyVal; + doc->GetStudioSystem()->GetPropertySystem()->GetInstancePropertyValue( + timeCtxRoot, ctrldPropertyHandle, controlledPropertyVal); + + auto existingCtrl = qt3dsdm::get<QString>(controlledPropertyVal); + int slideStrPos = existingCtrl.indexOf("@timeline"); + if (slideStrPos != -1) { + // find the controlling datainput name and build the string to replace + int ctrStrPos = existingCtrl.lastIndexOf("$", slideStrPos - 2); + QString prevCtrler = existingCtrl.mid(ctrStrPos, slideStrPos - ctrStrPos); + existingCtrl.replace(prevCtrler + "@timeline", fullTimeControlStr); + } else { + if (!existingCtrl.isEmpty() && m_currController.size()) + existingCtrl.append(" "); + existingCtrl.append(fullTimeControlStr); + } + + if (existingCtrl.endsWith(" ")) + existingCtrl.chop(1); + + if (existingCtrl.startsWith(" ")) + existingCtrl.remove(0, 1); + + m_diLabel->setText(m_currController); + qt3dsdm::SValue fullCtrlPropVal + = std::make_shared<qt3dsdm::CDataStr>( + Q3DStudio::CString::fromQString(existingCtrl)); + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, QObject::tr("Set Timeline control")) + ->SetInstancePropertyValue(timeCtxRoot, ctrldPropertyHandle, fullCtrlPropVal); +} + +void TimelineToolbar::OnBeginDataModelNotifications() +{ +} + +void TimelineToolbar::OnEndDataModelNotifications() +{ + updateDataInputStatus(); +} + +void TimelineToolbar::OnImmediateRefreshInstanceSingle(qt3dsdm::Qt3DSDMInstanceHandle inInstance) +{ + Q_UNUSED(inInstance) +} + +void TimelineToolbar::OnImmediateRefreshInstanceMultiple(qt3dsdm::Qt3DSDMInstanceHandle *inInstance, + long inInstanceCount) +{ + Q_UNUSED(inInstance) + Q_UNUSED(inInstanceCount) +} + +// Notify the user about control state change also with timeline dock +// title color change. +void TimelineToolbar::updateTimelineTitleColor(bool controlled) +{ + QString styleString; + if (controlled) { + styleString = "QDockWidget#timeline { color: " + + QString(CStudioPreferences::dataInputColor().name()) + "; }"; + } else { + styleString = "QDockWidget#timeline { color: " + + QString(CStudioPreferences::textColor().name()) + "; }"; + } + + QWidget *timelineDock = parentWidget()->parentWidget()->parentWidget(); + timelineDock->setStyleSheet(styleString); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineToolbar.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineToolbar.h new file mode 100644 index 00000000..53a87b18 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TimelineToolbar.h @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef TIMELINETOOLBAR_H +#define TIMELINETOOLBAR_H + +#include "SelectedValueImpl.h" +#include "Qt3DSDMSignals.h" +#include "DispatchListeners.h" +#include "Dispatch.h" +#include "DataInputSelectView.h" +#include <QtWidgets/qtoolbar.h> +#include <QtWidgets/qlabel.h> + +QT_FORWARD_DECLARE_CLASS(QAction) +QT_FORWARD_DECLARE_CLASS(QSlider) + +class TimelineToolbar : public QToolBar, + public IDataModelListener +{ + Q_OBJECT + +signals: + void newLayerTriggered(); + void deleteLayerTriggered(); + void gotoTimeTriggered(); + void firstFrameTriggered(); + void stopTriggered(); + void playTriggered(); + void controllerChanged(const QString &controller); + void lastFrameTriggered(); + void timelineScaleChanged(int scale); + void setDurationTriggered(); + void showRowTextsToggled(bool toggled); + void variantsFilterToggled(bool toggled); + +public: + TimelineToolbar(); + virtual ~TimelineToolbar() override; + void setTime(long totalMillis); + QString getCurrentController() const; + void setNewLayerEnabled(bool enable); + QAction *actionShowRowTexts() const; + bool isVariantsFilterOn() const; + + // IDataModelListener + void OnBeginDataModelNotifications() override; + void OnEndDataModelNotifications() override; + void OnImmediateRefreshInstanceSingle(qt3dsdm::Qt3DSDMInstanceHandle inInstance) override; + void OnImmediateRefreshInstanceMultiple(qt3dsdm::Qt3DSDMInstanceHandle *inInstance, + long inInstanceCount) override; + +public Q_SLOTS: + void updatePlayButtonState(bool started); + void onZoomInButtonClicked(); + void onZoomOutButtonClicked(); + +private Q_SLOTS: + void onPlayButtonClicked(); + void onZoomLevelChanged(int scale); + void onDiButtonClicked(); + +private: + void addSpacing(int width); + void onSelectionChange(Q3DStudio::SSelectedValue inNewSelectable); + void onDataInputChange(int handle, int instance, const QString &dataInputName); + void showDataInputChooser(const QPoint &point); + void updateDataInputStatus(); + void updateTimelineTitleColor(bool controlled); + + QPushButton *m_timeLabel = nullptr; + QLabel *m_diLabel = nullptr; + QAction *m_actionDeleteRow = nullptr; + QAction *m_actionPlayStop = nullptr; + QAction *m_actionZoomIn = nullptr; + QAction *m_actionZoomOut = nullptr; + QAction *m_actionDataInput = nullptr; + QAction *m_actionNewLayer = nullptr; + QAction *m_actionShowRowTexts = nullptr; + QAction *m_actionFilter = nullptr; + QSlider *m_scaleSlider = nullptr; + qt3dsdm::TSignalConnectionPtr m_connectSelectionChange; + QIcon m_iconStop; + QIcon m_iconPlay; + QIcon m_iconDiActive; + QIcon m_iconDiInactive; + + QString m_currController; + + DataInputSelectView *m_dataInputSelector; +}; +#endif // TIMELINETOOLBAR_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeader.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeader.cpp new file mode 100644 index 00000000..55e0a8fe --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeader.cpp @@ -0,0 +1,176 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "TreeHeader.h" +#include "StudioPreferences.h" +#include "StudioUtils.h" + +#include <QtGui/qpainter.h> + + +TreeHeader::TreeHeader(TimelineGraphicsScene *timelineScene, TimelineItem *parent) + : TimelineItem(parent) + , m_scene(timelineScene) +{ + setAcceptHoverEvents(true); +} + +void TreeHeader::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_UNUSED(option) + Q_UNUSED(widget) + + bool hiResIcons = StudioUtils::devicePixelRatio(widget->window()->windowHandle()) > 1.0; + + double treeWidth = m_scene->treeWidth() - m_scene->getScrollbarOffsets().x(); + m_rectShy .setRect(treeWidth - 16 * 3.3, size().height() * .5 - 8, 16, 16); + m_rectVisible.setRect(treeWidth - 16 * 2.2, size().height() * .5 - 8, 16, 16); + m_rectLock .setRect(treeWidth - 16 * 1.1, size().height() * .5 - 8, 16, 16); + + static const QPixmap pixShy = QPixmap(":/images/Toggle-Shy.png"); + static const QPixmap pixShy2x = QPixmap(":/images/Toggle-Shy@2x.png"); + static const QPixmap pixVisible = QPixmap(":/images/Toggle-HideShow.png"); + static const QPixmap pixVisible2x = QPixmap(":/images/Toggle-HideShow@2x.png"); + static const QPixmap pixLock = QPixmap(":/images/Toggle-Lock.png"); + static const QPixmap pixLock2x = QPixmap(":/images/Toggle-Lock@2x.png"); + + const QColor selectedColor = CStudioPreferences::timelineFilterButtonSelectedColor(); + const QColor hoveredColor = CStudioPreferences::timelineFilterButtonHoveredColor(); + + if (m_shy) + painter->fillRect(m_rectShy, selectedColor); + + if (m_visible) + painter->fillRect(m_rectVisible, selectedColor); + + if (m_lock) + painter->fillRect(m_rectLock, selectedColor); + + // Paint hovering as semi-transparent overlay + if (m_hoveredItem == TreeControlType::Shy) + painter->fillRect(m_rectShy, hoveredColor); + else if (m_hoveredItem == TreeControlType::Hide) + painter->fillRect(m_rectVisible, hoveredColor); + else if (m_hoveredItem == TreeControlType::Lock) + painter->fillRect(m_rectLock, hoveredColor); + + painter->drawPixmap(m_rectShy , hiResIcons ? pixShy2x : pixShy); + painter->drawPixmap(m_rectVisible , hiResIcons ? pixVisible2x : pixVisible); + painter->drawPixmap(m_rectLock , hiResIcons ? pixLock2x : pixLock); +} + +TreeControlType TreeHeader::handleButtonsClick(const QPointF &scenePos) +{ + QPointF p = mapFromScene(scenePos.x(), scenePos.y()); + + if (m_rectShy.contains(p.x(), p.y())) { + toggleFilterShy(); + return TreeControlType::Shy; + } else if (m_rectVisible.contains(p.x(), p.y())) { + toggleFilterHidden(); + return TreeControlType::Hide; + } else if (m_rectLock.contains(p.x(), p.y())) { + toggleFilterLocked(); + return TreeControlType::Lock; + } + + return TreeControlType::None; +} + +bool TreeHeader::filterShy() const +{ + return m_shy; +} + +bool TreeHeader::filterHidden() const +{ + return m_visible; +} + +bool TreeHeader::filterLocked() const +{ + return m_lock; +} + +int TreeHeader::type() const +{ + // Enable the use of qgraphicsitem_cast with this item. + return TypeTreeHeader; +} + +void TreeHeader::toggleFilterShy() +{ + m_shy = !m_shy; + update(); +} + +void TreeHeader::toggleFilterHidden() +{ + m_visible = !m_visible; + update(); +} + +void TreeHeader::toggleFilterLocked() +{ + m_lock = !m_lock; + update(); +} + +void TreeHeader::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + QPointF p = event->scenePos(); + TreeControlType hoveredItem = TreeControlType::None; + if (m_rectShy.contains(p.x(), p.y())) { + QString action = m_shy ? tr("Show") : tr("Hide"); + setToolTip(tr("%1 shy objects").arg(action)); + hoveredItem = TreeControlType::Shy; + } else if (m_rectVisible.contains(p.x(), p.y())) { + QString action = m_visible ? tr("Show") : tr("Hide"); + setToolTip(tr("%1 inactive objects").arg(action)); + hoveredItem = TreeControlType::Hide; + } else if (m_rectLock.contains(p.x(), p.y())) { + QString action = m_lock ? tr("Show") : tr("Hide"); + setToolTip(tr("%1 locked objects").arg(action)); + hoveredItem = TreeControlType::Lock; + } else { + setToolTip(""); + } + + if (m_hoveredItem != hoveredItem) { + // Update hover status only if it has changed + m_hoveredItem = hoveredItem; + update(); + } +} + +void TreeHeader::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + m_hoveredItem = TreeControlType::None; + update(); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeader.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeader.h new file mode 100644 index 00000000..6ebf2bad --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeader.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef TREEHEADER_H +#define TREEHEADER_H + +#include "TimelineItem.h" +#include "TimelineConstants.h" +#include "RowTypes.h" +#include "TimelineGraphicsScene.h" + +class RowTimeline; + +class TreeHeader : public TimelineItem +{ + Q_OBJECT + +public: + explicit TreeHeader(TimelineGraphicsScene *timelineScene, TimelineItem *parent = nullptr); + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget = nullptr) override; + TreeControlType handleButtonsClick(const QPointF &scenePos); + bool filterShy() const; + bool filterHidden() const; + bool filterLocked() const; + int type() const override; + +protected: + void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override; + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; + +private: + void toggleFilterShy(); + void toggleFilterHidden(); + void toggleFilterLocked(); + TimelineGraphicsScene *m_scene; + bool m_shy = false; + bool m_visible = false; + bool m_lock = false; + TreeControlType m_hoveredItem = TreeControlType::None; + QRect m_rectShy; + QRect m_rectVisible; + QRect m_rectLock; +}; + +#endif // TREEHEADER_H diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeaderView.cpp b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeaderView.cpp new file mode 100644 index 00000000..df784418 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeaderView.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "TreeHeaderView.h" + +TreeHeaderView::TreeHeaderView(QWidget *parent) + : QGraphicsView(parent) +{ +} + +void TreeHeaderView::scrollContentsBy(int dx, int dy) +{ + // Overridden to ignore scrolling after initial show related scrolling has been finished + // + // Longer explanation: When RowTreeLabelItem (QGraphicsTextItem) gets focus + // for text editing, it forces views to scroll themselves so that editable + // text item is always visible. But we don't want tree header view to move. + // See QGraphicsTextItemPrivate::textControl() and _q_ensureVisible() + if (m_allowScrolling) + QGraphicsView::scrollContentsBy(dx, dy); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeaderView.h b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeaderView.h new file mode 100644 index 00000000..7b648660 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/TimelineGraphicsView/ui/TreeHeaderView.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef TREEHEADERVIEW_H +#define TREEHEADERVIEW_H + +#include <QtWidgets/qgraphicsview.h> + +class TreeHeaderView : public QGraphicsView +{ + Q_OBJECT +public: + TreeHeaderView(QWidget *parent = nullptr); + + void disableScrolling() { m_allowScrolling = false; } + +protected: + void scrollContentsBy(int dx, int dy) override; + +private: + bool m_allowScrolling = true; +}; + +#endif // TREEHEADERVIEW_H diff --git a/src/Authoring/Qt3DStudio/Palettes/controls/BrowserCombo.qml b/src/Authoring/Qt3DStudio/Palettes/controls/BrowserCombo.qml new file mode 100644 index 00000000..727488f4 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/controls/BrowserCombo.qml @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +MouseArea { + id: root + + property alias value: value.text + property var activeBrowser + property bool blockShow: false + + signal showBrowser + + Layout.minimumHeight: _controlBaseHeight + Layout.preferredWidth: _valueWidth + + onPressed: { + // Block showBrowser event on the mouse press that makes the browser lose focus + if (activeBrowser && activeBrowser.visible) { + activeBrowser = null; + blockShow = true + } else { + blockShow = false + } + } + + onClicked: { + if (!blockShow) + root.showBrowser() + } + + Rectangle { + anchors.fill: parent + + color: _studioColor2 + + StyledLabel { + id: value + anchors.fill: parent + horizontalAlignment: Text.AlignLeft + rightPadding: 6 + img.width + leftPadding: 6 + } + Image { + id: img + // Source image size is 16x16 pixels + x: parent.width - 18 + y: parent.height / 2 - 8 + source: _resDir + "arrow_down.png" + rotation: activeBrowser && activeBrowser.focused ? 180 : 0 + } + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/controls/FloatTextField.qml b/src/Authoring/Qt3DStudio/Palettes/controls/FloatTextField.qml new file mode 100644 index 00000000..95458889 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/controls/FloatTextField.qml @@ -0,0 +1,203 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +/* Use for: Position, Rotation, Scale, Pivot ... */ + +TextField { + id: floatTextFieldId + property alias decimalValue: validator.decimals + property bool ignoreHotkeys: true + + signal previewValueChanged + + selectByMouse: true + text: "0.000" + Layout.preferredWidth: _valueWidth / 3 + Layout.preferredHeight: _controlBaseHeight + + topPadding: 0 + bottomPadding: 0 + rightPadding: 6 + + onTextEdited: { + if (text.search(",")) + text = text.replace(",",".") + } + + activeFocusOnPress: false + + horizontalAlignment: TextInput.AlignRight + verticalAlignment: TextInput.AlignVCenter + validator: DoubleValidator { + id: validator + decimals: 3 + locale: "C" + notation: DoubleValidator.StandardNotation + } + + selectionColor: _selectionColor + selectedTextColor: _textColor + font.pixelSize: _fontSize + color: _textColor + background: Rectangle { + color: floatTextFieldId.enabled ? _studioColor2 : "transparent" + border.width: floatTextFieldId.activeFocus ? 1 : 0 + border.color: floatTextFieldId.activeFocus ? _selectionColor : _disabledColor + } + + Timer { + id: rateLimiter + interval: 10 + onTriggered: { + floatTextFieldId.previewValueChanged(); + } + } + + cursorVisible: false + onActiveFocusChanged: { + if (focusReason === Qt.OtherFocusReason) { + select(0, 0); + cursorVisible = false; + } else if (activeFocus) { + selectAll(); + } + } + + Item { + id: focusEater + // Used to eat keyboard focus after drag-modifying the text is finished + } + + MouseArea { + id: mouseArea + property int clickedPos: 0 + property int pressedX: 0 + property bool draggingActive: false + + acceptedButtons: Qt.LeftButton + preventStealing: true + anchors.fill: parent + onPressed: { + pressedX = mouse.x; + draggingActive = false; + if (parent.activeFocus) { + clickedPos = parent.positionAt(mouse.x, mouse.y); + parent.cursorPosition = clickedPos; + } else { + parent.forceActiveFocus(); + } + } + onClicked: { + if (!draggingActive && !parent.cursorVisible) { + parent.cursorVisible = true; + parent.selectAll(); + } + } + onReleased: { + if (draggingActive) { + _mouseHelper.endUnboundedDrag(); + rateLimiter.stop(); + floatTextFieldId.onEditingFinished(); + focusEater.forceActiveFocus(); + } + } + + onCanceled: { + if (draggingActive) { + _mouseHelper.endUnboundedDrag(); + rateLimiter.stop(); + floatTextFieldId.onEditingFinished(); + focusEater.forceActiveFocus(); + } + } + + onDoubleClicked: { + parent.selectAll(); + parent.cursorVisible = true; + } + + onPositionChanged: { + if (parent.cursorVisible) { + parent.cursorPosition = parent.positionAt(mouse.x, mouse.y); + parent.select(clickedPos, parent.cursorPosition); + } else { + if (!draggingActive) { + var startDelta = (pressedX - mouse.x) / 2.0; + if (startDelta > 4.0 || startDelta < -4.0) { + _mouseHelper.startUnboundedDrag(); + draggingActive = true; + } + } + if (draggingActive) { + var delta = _mouseHelper.delta().x; + if (delta !== 0) { + if (mouse.modifiers & Qt.ControlModifier) + delta *= 0.1; + else if (mouse.modifiers & Qt.ShiftModifier) + delta *= 10.0; + if (floatTextFieldId.text !== "") { + floatTextFieldId.text = Number(parseFloat(floatTextFieldId.text) + + delta).toFixed(validator.decimals); + } else { + floatTextFieldId.text = Number(delta).toFixed(validator.decimals); + } + + if (!rateLimiter.running) + rateLimiter.start(); + } + } + } + } + } + + Keys.onPressed: { + if (event.key === Qt.Key_Up || event.key === Qt.Key_Down) { + event.accepted = true + var delta = 1.0; + if (event.modifiers & Qt.ControlModifier) + delta = 0.1; + else if (event.modifiers & Qt.ShiftModifier) + delta = 10.0; + if (event.key === Qt.Key_Down) + delta = -delta; + if (floatTextFieldId.text !== "") { + floatTextFieldId.text = Number(parseFloat(floatTextFieldId.text) + + delta).toFixed(validator.decimals); + } else { + floatTextFieldId.text = Number(delta).toFixed(validator.decimals); + } + + if (!rateLimiter.running) + rateLimiter.start(); + } + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/controls/StyledComboBox.qml b/src/Authoring/Qt3DStudio/Palettes/controls/StyledComboBox.qml new file mode 100644 index 00000000..4d32d410 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/controls/StyledComboBox.qml @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Window 2.2 + +ComboBox { + id: control + + Layout.preferredHeight: _controlBaseHeight + Layout.preferredWidth: _valueWidth + topPadding: 0 + bottomPadding: 0 + // hack to fix the color after Qt.Quick.Controls2 "optimization" + property alias color: backgroundBox.color + property bool showArrow: true + + delegate: ItemDelegate { + id: itemDelegate + + property bool hasSeparator: itemDelegate.text.endsWith("|separator") + + width: parent.width + height: hasSeparator ? _controlBaseHeight + 6 : _controlBaseHeight + padding: 0 + spacing: 0 + text: { + control.textRole ? (Array.isArray(control.model) ? modelData[control.textRole] + : model[control.textRole]) + : modelData + } + highlighted: control.highlightedIndex === index + hoverEnabled: control.hoverEnabled + contentItem: ColumnLayout { + anchors.fill: itemDelegate + spacing: 0 + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: _studioColor3 + visible: itemDelegate.hasSeparator + } + StyledLabel { + Layout.fillWidth: true + rightPadding: control.indicator.width + 6 + leftPadding: 6 + text: { + hasSeparator ? itemDelegate.text.replace("|separator", "") + : itemDelegate.text + } + visible: itemDelegate.text + horizontalAlignment: Text.AlignLeft + } + } + background: Rectangle { + anchors.fill: itemDelegate + color: hovered ? _selectionColor : _backgroundColor + } + } + + indicator: Image { + x: control.width - width - 2 + y: control.topPadding + (control.availableHeight - height) / 2 + source: _resDir + "arrow_down.png" + rotation: control.popup.visible ? 180 : 0 + visible: control.showArrow + } + + contentItem: StyledTextField { + text: { + var newText = control.editable ? control.editText : control.displayText; + var hasSeparator = newText.endsWith("|separator"); + hasSeparator ? newText.replace("|separator", "") : newText; + } + + enabled: control.editable + autoScroll: control.editable + readOnly: control.popup.visible + inputMethodHints: control.inputMethodHints + validator: control.validator + opacity: 1 + leftPadding: 6 + horizontalAlignment: Text.AlignLeft + } + + background: Rectangle { + id: backgroundBox + color: control.enabled ? _studioColor2 : "transparent" + border.width: 0 + } + + popup: Popup { + y: control.height + width: control.width + height: Math.min(contentItem.implicitHeight, + control.Window.height - topMargin - bottomMargin) + topMargin: 6 + bottomMargin: 6 + padding: 0 + + contentItem: ListView { + clip: true + implicitHeight: contentHeight + model: control.popup.visible ? control.delegateModel : null + currentIndex: control.highlightedIndex + highlightRangeMode: ListView.ApplyRange + highlightMoveDuration: 0 + ScrollIndicator.vertical: ScrollIndicator { + id: scrollIndicator + contentItem: Rectangle { + id: indicator + + implicitWidth: 2 + implicitHeight: 2 + + color: _studioColor3 + visible: scrollIndicator.size < 1.0 + opacity: 0.75 + } + } + Rectangle { + z: 10 + anchors.fill: parent + color: "transparent" + border.color: _studioColor3 + } + } + + background: Rectangle { + color: _studioColor2 + } + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/controls/StyledLabel.qml b/src/Authoring/Qt3DStudio/Palettes/controls/StyledLabel.qml new file mode 100644 index 00000000..5185b2ad --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/controls/StyledLabel.qml @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +Label { + id: styledLabelId + font.pixelSize: _fontSize + color: _textColor + Layout.preferredHeight: _controlBaseHeight + Layout.preferredWidth: _idWidth + verticalAlignment: Text.AlignVCenter + clip: true +} diff --git a/src/Authoring/Qt3DStudio/Palettes/controls/StyledMenuItem.qml b/src/Authoring/Qt3DStudio/Palettes/controls/StyledMenuItem.qml new file mode 100644 index 00000000..e44b733f --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/controls/StyledMenuItem.qml @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +MenuItem { + id: control + hoverEnabled: true + + contentItem: StyledLabel { + text: control.text + visible: control.text + horizontalAlignment: Text.AlignLeft + color: control.enabled ? _textColor : _disabledColor + } + background: Rectangle { + implicitWidth: _valueWidth + implicitHeight: _controlBaseHeight + color: control.hovered ? _selectionColor : _studioColor1 + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/controls/StyledMenuSeparator.qml b/src/Authoring/Qt3DStudio/Palettes/controls/StyledMenuSeparator.qml new file mode 100644 index 00000000..0fce5d0d --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/controls/StyledMenuSeparator.qml @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 + +MenuSeparator { + id: control + padding: 0 + topPadding: 4 + bottomPadding: 4 + leftPadding: 0 + rightPadding: 0 + contentItem: Rectangle { + width: control.width + implicitWidth: control.parent.width - control.leftPadding - control.rightPadding + implicitHeight: 1 + color: _studioColor3 + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/controls/StyledTextField.qml b/src/Authoring/Qt3DStudio/Palettes/controls/StyledTextField.qml new file mode 100644 index 00000000..bd123453 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/controls/StyledTextField.qml @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +TextField { + id: styledTextFieldId + property bool ignoreHotkeys: true + property bool textChanged: false + + signal editingFinished + + selectByMouse: true + text: "" + Layout.preferredWidth: _valueWidth + Layout.preferredHeight: _controlBaseHeight + + topPadding: 0 + bottomPadding: 0 + rightPadding: 6 + leftPadding: 6 + + activeFocusOnPress: false + + horizontalAlignment: TextInput.AlignRight + verticalAlignment: TextInput.AlignVCenter + + selectionColor: _selectionColor + selectedTextColor: _textColor + font.pixelSize: _fontSize + color: _textColor + background: Rectangle { + color: styledTextFieldId.enabled ? _studioColor2 : "transparent" + border.width: styledTextFieldId.activeFocus ? 1 : 0 + border.color: styledTextFieldId.activeFocus ? _selectionColor : _disabledColor + } + + cursorVisible: false + onActiveFocusChanged: { + if (!activeFocus && textChanged) { + styledTextFieldId.editingFinished(); + textChanged = false; + } + + if (focusReason === Qt.OtherFocusReason) { + select(0, 0); + cursorVisible = false; + } else if (activeFocus) { + selectAll(); + } + } + + MouseArea { + id: mouseArea + property int clickedPos: 0 + + acceptedButtons: Qt.LeftButton + preventStealing: true + anchors.fill: parent + onPressed: { + if (parent.activeFocus) { + clickedPos = parent.positionAt(mouse.x, mouse.y); + parent.cursorPosition = clickedPos; + } else { + parent.forceActiveFocus(); + } + } + + onClicked: { + if (!parent.cursorVisible) { + parent.cursorVisible = true; + parent.selectAll(); + } + } + + onDoubleClicked: { + parent.selectAll(); + parent.cursorVisible = true; + } + } + + onTextChanged: textChanged = true; + + Keys.onPressed: { + if (textChanged && (event.key === Qt.Key_Return || event.key === Qt.Key_Enter)) { + event.accepted = true + styledTextFieldId.editingFinished(); + textChanged = false; + } + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/controls/StyledToggleButton.qml b/src/Authoring/Qt3DStudio/Palettes/controls/StyledToggleButton.qml new file mode 100644 index 00000000..e548acf4 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/controls/StyledToggleButton.qml @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 + +StyledToolButton { + id: control + + property string checkedImage + property string backgroundColor: _backgroundColor + property string downColor: _buttonDownColor + + checkable: true + + background: Rectangle { + color: control.checked ? downColor : backgroundColor + border.color: backgroundColor + } + + contentItem: Image { + fillMode: Image.Pad + source: control.enabled ? control.checked ? _resDir + checkedImage : _resDir + enabledImage + : _resDir + disabledImage + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/controls/StyledToolButton.qml b/src/Authoring/Qt3DStudio/Palettes/controls/StyledToolButton.qml new file mode 100644 index 00000000..d1beea28 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/controls/StyledToolButton.qml @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 + +ToolButton { + id: control + + property string enabledImage + property string disabledImage + property alias toolTipText: toolTip.text + + hoverEnabled: true + + StyledTooltip { + id: toolTip + enabled: control.hovered + } + + background: Rectangle { + color: control.pressed ? _selectionColor : (hovered ? _studioColor1 : "transparent") + border.color: _studioColor1 + } + + contentItem: Image { + source: control.enabled ? _resDir + enabledImage : _resDir + disabledImage + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/controls/StyledTooltip.qml b/src/Authoring/Qt3DStudio/Palettes/controls/StyledTooltip.qml new file mode 100644 index 00000000..34b310a4 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/controls/StyledTooltip.qml @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** 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-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 2.2 + +ToolTip { + id: control + delay: 500 + contentItem: StyledLabel { + text: control.text + } + + // Handle tooltip visibility based on the trigger event given to the 'enabled' property and + // the 'Tooltips' view menu setting. Has to be done this way, as even though the eventFilter + // set for MainFrm catches the tooltip events for QML, it doesn't prevent showing them because + // we were/are controlling the visibility in code. + onEnabledChanged: { + visible = enabled && _parentView.toolTipsEnabled(); + } + + background: Rectangle { + border.color: _studioColor3 + color: _studioColor2 + radius: 2 + } +} diff --git a/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraglwidget.cpp b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraglwidget.cpp new file mode 100644 index 00000000..ce7d8e37 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraglwidget.cpp @@ -0,0 +1,208 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSCommonPrecompile.h" +#include "scenecameraglwidget.h" +#include "StudioApp.h" +#include "IStudioRenderer.h" +#include "WGLRenderContext.h" +#include "StudioPreferences.h" + +#include <QtGui/qopenglshaderprogram.h> +#include <QtGui/qopengltexture.h> +#include <QtGui/qopenglbuffer.h> +#include <QtGui/qopenglvertexarrayobject.h> + +const QVector4D defaultTextureOffset = QVector4D(0.0f, 0.0f, 1.0f, 1.0f); +const QVector2D defaultGeometryOffset = QVector2D(1.0f, 1.0f); + +SceneCameraGlWidget::SceneCameraGlWidget(QWidget *parent) + : QOpenGLWidget(parent) + , m_textureOffset(defaultTextureOffset) + , m_geometryOffset(defaultGeometryOffset) +{ + QSurfaceFormat format = CWGLRenderContext::selectSurfaceFormat(this); + format.setSamples(1); // We want pixel perfect view, not aliased one + setFormat(format); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); +} + +SceneCameraGlWidget::~SceneCameraGlWidget() +{ + cleanup(); +} + +void SceneCameraGlWidget::initializeGL() +{ + initializeOpenGLFunctions(); + QObject::connect(context(), &QOpenGLContext::aboutToBeDestroyed, + this, &SceneCameraGlWidget::cleanup); + + m_program = new QOpenGLShaderProgram(); + if (!m_program->addShaderFromSourceCode( + QOpenGLShader::Vertex, + "#version 330 core\n" + "in vec2 vertexPos;\n" + "in vec2 vertexTexCoord;\n" + "uniform vec4 uTexOffset;\n" + "uniform vec4 uGeomOffset;\n" + "out vec2 texCoord;\n" + "void main(void)\n" + "{\n" + " gl_Position = vec4(uGeomOffset.xy + vertexPos * uGeomOffset.zw, 0.0, 1.0);\n" + " texCoord = vec2(uTexOffset.z * vertexTexCoord.x + uTexOffset.x,\n" + " uTexOffset.w * vertexTexCoord.y + uTexOffset.y);\n" + "}")) { + qWarning() << __FUNCTION__ << "Failed to add vertex shader for scene camera preview"; + return; + } + if (!m_program->addShaderFromSourceCode( + QOpenGLShader::Fragment, + "#version 330 core\n" + "in vec2 texCoord;\n" + "uniform sampler2D uSampler;\n" + "out vec4 fragColor;\n" + "void main(void) {\n" + " vec4 oc = texture(uSampler, texCoord);\n" + " fragColor = vec4(oc);\n" + "}")) { + + qWarning() << __FUNCTION__ << "Failed to add fragment shader for scene camera preview"; + return; + } + if (!m_program->link()) { + qWarning() << __FUNCTION__ << "Failed to link program for scene camera preview"; + return; + } + if (!m_program->bind()) { + qWarning() << __FUNCTION__ << "Failed to bind program for scene camera preview"; + return; + } else { + GLint vertexAtt = GLint(m_program->attributeLocation("vertexPos")); + GLint uvAtt = GLint(m_program->attributeLocation("vertexTexCoord")); + m_uniformTextureOffset = GLint(m_program->uniformLocation("uTexOffset")); + m_uniformGeometryOffset = GLint(m_program->uniformLocation("uGeomOffset")); + m_program->setUniformValue("uSampler", 0); + + m_vao = new QOpenGLVertexArrayObject; + if (m_vao->create()) { + m_vao->bind(); + m_vertexBuffer = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer); + if (m_vertexBuffer->create() && m_vertexBuffer->bind()) { + GLfloat vertexBuffer[] = {-1.0f, 1.0f, + -1.0f, -1.0f, + 1.0f, 1.0f, + 1.0f, -1.0f}; + m_vertexBuffer->allocate(vertexBuffer, 8 * sizeof(GLfloat)); + glEnableVertexAttribArray(vertexAtt); + glVertexAttribPointer(vertexAtt, 2, GL_FLOAT, GL_FALSE, 0, (void *)0); + } else { + qWarning() << __FUNCTION__ + << "Failed to create/bind vertex buffer for scene camera preview"; + return; + } + m_uvBuffer = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer); + if (m_uvBuffer->create() && m_uvBuffer->bind()) { + GLfloat uvBuffer[] = {0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 1.0f, + 1.0f, 0.0f}; + m_uvBuffer->allocate(uvBuffer, 8 * sizeof(GLfloat)); + glEnableVertexAttribArray(uvAtt); + glVertexAttribPointer(uvAtt, 2, GL_FLOAT, GL_FALSE, 0, (void *)0); + } else { + qWarning() << __FUNCTION__ + << "Failed to create/bind UV buffer for scene camera preview"; + return; + } + + m_vao->release(); + } else { + qWarning() << __FUNCTION__ << "Failed to create/bind vertex array object"; + return; + } + } + + const QColor matteColor = CStudioPreferences::matteColor(); + glClearColor(matteColor.redF(), matteColor.greenF(), matteColor.blueF(), 1.0f); +} + +void SceneCameraGlWidget::paintGL() +{ + Q3DStudio::IStudioRenderer &renderer(g_StudioApp.getRenderer()); + if (renderer.IsInitialized()) { + m_vao->bind(); + + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_BLEND); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + QSize fboSize; + qt3ds::QT3DSU32 textureId; + renderer.getPreviewFbo(fboSize, textureId); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, GLuint(textureId)); + + m_program->setUniformValueArray(m_uniformTextureOffset, &m_textureOffset, 1); + m_program->setUniformValueArray(m_uniformGeometryOffset, &m_geometryOffset, 1); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + m_vao->release(); + } +} + +void SceneCameraGlWidget::resizeGL(int, int) +{ + // We need to update immediately to avoid flicker + update(); +} + +void SceneCameraGlWidget::cleanup() +{ + makeCurrent(); + + delete m_program; + delete m_vertexBuffer; + delete m_uvBuffer; + delete m_vao; + m_program = nullptr; + m_vertexBuffer = nullptr; + m_uvBuffer = nullptr; + m_vao = nullptr; + m_uniformTextureOffset = 0; + m_uniformGeometryOffset = 0; + m_textureOffset = defaultTextureOffset; + m_geometryOffset = defaultGeometryOffset; + + doneCurrent(); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraglwidget.h b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraglwidget.h new file mode 100644 index 00000000..93becf19 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraglwidget.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef SCENE_CAMERA_GLWIDGET_H +#define SCENE_CAMERA_GLWIDGET_H + +#include <QtWidgets/qopenglwidget.h> +#include <QtGui/qopenglfunctions.h> +#include <QtGui/qvector2d.h> +#include <QtGui/qvector4d.h> + +QT_FORWARD_DECLARE_CLASS(QOpenGLShaderProgram) +QT_FORWARD_DECLARE_CLASS(QOpenGLBuffer) +QT_FORWARD_DECLARE_CLASS(QOpenGLVertexArrayObject) + +class SceneCameraGlWidget : public QOpenGLWidget, QOpenGLFunctions +{ + Q_OBJECT +public: + explicit SceneCameraGlWidget(QWidget *parent = nullptr); + ~SceneCameraGlWidget(); + + void setTextureOffset(const QVector4D &offset) { m_textureOffset = offset; } + void setGeometryOffset(const QVector4D &offset) { m_geometryOffset = offset; } + +protected: + void initializeGL() override; + void paintGL() override; + void resizeGL(int, int) override; + +private: + void cleanup(); + + QOpenGLShaderProgram *m_program = nullptr; + QOpenGLBuffer *m_vertexBuffer = nullptr; + QOpenGLBuffer *m_uvBuffer = nullptr; + QOpenGLVertexArrayObject *m_vao = nullptr; + GLint m_uniformTextureOffset = 0; + GLint m_uniformGeometryOffset = 0; + QVector4D m_textureOffset; + QVector4D m_geometryOffset; +}; + +#endif diff --git a/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecamerascrollarea.cpp b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecamerascrollarea.cpp new file mode 100644 index 00000000..035e242c --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecamerascrollarea.cpp @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "Qt3DSCommonPrecompile.h" +#include "scenecamerascrollarea.h" +#include "scenecameraglwidget.h" +#include "Core.h" + +#include <QtWidgets/qscrollbar.h> +#include <QtGui/qevent.h> + +SceneCameraScrollArea::SceneCameraScrollArea(QWidget *parent) + : QAbstractScrollArea(parent) +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + m_glWidget = new SceneCameraGlWidget(this); +} + +SceneCameraScrollArea::~SceneCameraScrollArea() +{ +} + +void SceneCameraScrollArea::setZoom(qreal zoom, const QPoint &zoomPoint) +{ + // Calculate the actual presentation point + qreal oldH = (horizontalScrollBar()->value() + zoomPoint.x()) / m_zoom; + qreal oldV = (verticalScrollBar()->value() + zoomPoint.y()) / m_zoom; + + m_zoom = zoom; + + recalculateScrollRanges(); + + // Move the scrollbars so that the actual presentation point stays in the same location + horizontalScrollBar()->setValue(qRound(oldH * m_zoom - zoomPoint.x())); + verticalScrollBar()->setValue(qRound(oldV * m_zoom - zoomPoint.y())); + + recalculateOffsets(); + + Q_EMIT needUpdate(); +} + +void SceneCameraScrollArea::setPresentationSize(const QSize &size) +{ + if (m_presentationSize != size) { + m_presentationSize = size; + recalculateScrollRanges(); + recalculateOffsets(); + } +} + +void SceneCameraScrollArea::recalculateScrollRanges() +{ + const QSizeF presSize = zoomedPresentationSize(); + + const QSize viewSize = viewport()->size(); + horizontalScrollBar()->setRange(0, int(presSize.width() - viewSize.width())); + verticalScrollBar()->setRange(0, int(presSize.height() - viewSize.height())); + horizontalScrollBar()->setPageStep(viewSize.width()); + verticalScrollBar()->setPageStep(viewSize.height()); +} + +void SceneCameraScrollArea::recalculateOffsets() +{ + // Texture offset vector contains normalized rect of the viewable area of the texture + const QSize viewSize = viewport()->size(); + const qreal fullWidth = qreal(horizontalScrollBar()->maximum() + viewSize.width()); + const qreal fullHeight = qreal(verticalScrollBar()->maximum() + viewSize.height()); + QVector4D textureOffset( + float(horizontalScrollBar()->value() / fullWidth), + float((verticalScrollBar()->maximum() - verticalScrollBar()->value()) / fullHeight), + float(viewSize.width() / fullWidth), float(viewSize.height() / fullHeight)); + + m_glWidget->setTextureOffset(textureOffset); + + // The geometry offset is adjusted to keep aspect ratio when view area is larger than + // zoomed width/height. Since the geometry of the quad is in range [-1, 1], the width/height of + // the offset is just a direct multiplier to the coordinate. + // XY contain the subpixel offset to ensure we don't get artifacts depending on pixel alignment. + const QSizeF presSize = zoomedPresentationSize(); + float subPixelX = 0.0f; + float subPixelY = 0.0f; + qreal normWidth = 1.0; + qreal normHeight = 1.0; + if (presSize.width() < fullWidth) { + qreal diffX = (fullWidth - qRound(presSize.width())) / 2.0; + subPixelX = float((diffX - qRound(diffX)) / fullWidth); + normWidth = presSize.width() / fullWidth; + } + if (presSize.height() < fullHeight) { + qreal diffY = (fullHeight - qRound(presSize.height())) / 2.0; + subPixelY = float((diffY - qRound(diffY)) / fullHeight); + normHeight = presSize.height() / fullHeight; + } + + QVector4D geometryOffset(subPixelX, subPixelY, float(normWidth), float(normHeight)); + m_glWidget->setGeometryOffset(geometryOffset); +} + +void SceneCameraScrollArea::scrollContentsBy(int, int) +{ + recalculateOffsets(); + Q_EMIT needUpdate(); +} + +void SceneCameraScrollArea::showEvent(QShowEvent *event) +{ + QAbstractScrollArea::showEvent(event); + + recalculateScrollRanges(); + recalculateOffsets(); + resizeGlWidget(); +} + +void SceneCameraScrollArea::resizeGlWidget() +{ + m_glWidget->resize(viewport()->size()); +} + +QSizeF SceneCameraScrollArea::zoomedPresentationSize() +{ + // Multiply QSize components separately to avoid rounding to integers + QSizeF size = QSizeF(m_presentationSize.width() * m_zoom, + m_presentationSize.height() * m_zoom); + return size; +} + +void SceneCameraScrollArea::resizeEvent(QResizeEvent *event) +{ + QAbstractScrollArea::resizeEvent(event); + + recalculateScrollRanges(); + recalculateOffsets(); + resizeGlWidget(); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecamerascrollarea.h b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecamerascrollarea.h new file mode 100644 index 00000000..682dc430 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecamerascrollarea.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef SCENE_CAMERA_SCROLL_AREA +#define SCENE_CAMERA_SCROLL_AREA + +#include <QtWidgets/qabstractscrollarea.h> + +class SceneCameraGlWidget; + +class SceneCameraScrollArea : public QAbstractScrollArea +{ + Q_OBJECT + +public: + SceneCameraScrollArea(QWidget *parent = nullptr); + virtual ~SceneCameraScrollArea(); + + SceneCameraGlWidget *glWidget() const { return m_glWidget; } + + void setZoom(qreal zoom, const QPoint &zoomPoint); + void setPresentationSize(const QSize &size); + void recalculateScrollRanges(); + void recalculateOffsets(); + +Q_SIGNALS: + void needUpdate(); + +protected: + void resizeEvent(QResizeEvent *event) override; + void scrollContentsBy(int, int) override; + void showEvent(QShowEvent *event) override; + +private: + void resizeGlWidget(); + QSizeF zoomedPresentationSize(); + +protected: + SceneCameraGlWidget *m_glWidget = nullptr; + qreal m_zoom = 1.0; + QSize m_presentationSize; +}; + +#endif diff --git a/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.cpp b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.cpp new file mode 100644 index 00000000..e153c0ad --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.cpp @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "scenecameraview.h" +#include "ui_scenecameraview.h" +#include "scenecameraglwidget.h" +#include "StudioApp.h" +#include "Core.h" +#include "StudioProjectSettings.h" +#include "MainFrm.h" +#include "PlayerWnd.h" +#include "MouseCursor.h" +#include "ResourceCache.h" + +#include <QtGui/qevent.h> +#include <QtWidgets/qscrollbar.h> + +const QPoint invalidMousePoint = QPoint(-999999, -999999); + +SceneCameraView::SceneCameraView(CMainFrame *mainFrame, QWidget *parent) : + QWidget(parent) + , m_ui(new Ui::SceneCameraView) + , m_mousePressPointLeft(invalidMousePoint) + , m_mousePressPointRight(invalidMousePoint) +{ + m_ui->setupUi(this); + + m_cursorPan = CResourceCache::GetInstance()->GetCursor(CMouseCursor::CURSOR_EDIT_CAMERA_PAN); + m_cursorZoom = CResourceCache::GetInstance()->GetCursor(CMouseCursor::CURSOR_EDIT_CAMERA_ZOOM); + + // Limit the preview framerate a bit to limit amount of updates when dragging the slider + m_updateTimer.setInterval(0); + m_updateTimer.setSingleShot(true); + + connect(m_ui->zoomSlider, &QSlider::valueChanged, + this, &SceneCameraView::handleSliderValueChange); + connect(mainFrame->GetPlayerWnd(), &CPlayerWnd::newFrame, + this, &SceneCameraView::requestUpdate); + connect(m_ui->scrollArea, &SceneCameraScrollArea::needUpdate, + this, &SceneCameraView::requestUpdate); + connect(&m_updateTimer, &QTimer::timeout, this, &SceneCameraView::doUpdate); +} + +SceneCameraView::~SceneCameraView() +{ + delete m_ui; +} + +void SceneCameraView::wheelEvent(QWheelEvent *e) +{ + m_zoomPoint = m_ui->scrollArea->viewport()->mapFrom(this, e->pos()); + int currentZoomValue = m_ui->zoomSlider->value(); + // Adjust amount of change based on zoom level + int divider = qMin(120, 1000 / currentZoomValue); + m_ui->zoomSlider->setValue(currentZoomValue + (e->angleDelta().y() / divider)); +} + +void SceneCameraView::resizeEvent(QResizeEvent *e) +{ + m_zoomPoint = m_ui->scrollArea->viewport()->geometry().center(); + + QWidget::resizeEvent(e); +} + +void SceneCameraView::mousePressEvent(QMouseEvent *e) +{ + // Ignore panning starting outside scrollarea + if (!m_ui->scrollArea->rect().contains(e->pos())) + return; + + // Panning can be done with left or middle button. Left is more natural and we don't need it + // for selection. Alt+middle pans in edit camera mode, so middle button is also supported for + // panning. + if (m_mousePressPointRight == invalidMousePoint + && (e->button() == Qt::LeftButton || e->button() == Qt::MidButton)) { + m_mousePressPointLeft = e->pos(); + m_mousePressScrollValues = QPoint(m_ui->scrollArea->horizontalScrollBar()->value(), + m_ui->scrollArea->verticalScrollBar()->value()); + setCursor(m_cursorPan); + } else if (m_mousePressPointLeft == invalidMousePoint && e->button() == Qt::RightButton) { + m_mousePressPointRight = e->pos(); + m_mousePressZoomValue = m_ui->zoomSlider->value(); + setCursor(m_cursorZoom); + } +} + +void SceneCameraView::mouseMoveEvent(QMouseEvent *e) +{ + if (m_mousePressPointLeft != invalidMousePoint) { + const QPoint delta = e->pos() - m_mousePressPointLeft; + m_ui->scrollArea->horizontalScrollBar()->setValue(m_mousePressScrollValues.x() - delta.x()); + m_ui->scrollArea->verticalScrollBar()->setValue(m_mousePressScrollValues.y() - delta.y()); + } + if (m_mousePressPointRight != invalidMousePoint) { + const qreal delta = qreal(e->pos().y() - m_mousePressPointRight.y()); + m_zoomPoint = m_mousePressPointRight; + m_ui->zoomSlider->setValue(m_mousePressZoomValue - delta / 2.0); + } +} + +void SceneCameraView::mouseReleaseEvent(QMouseEvent *e) +{ + if (m_mousePressPointLeft != invalidMousePoint + && e->button() == Qt::LeftButton || e->button() == Qt::MidButton) { + m_mousePressPointLeft = invalidMousePoint; + setCursor(Qt::ArrowCursor); + } else if (m_mousePressPointRight != invalidMousePoint) { + m_zoomPoint = m_ui->scrollArea->viewport()->geometry().center(); + m_mousePressPointRight = invalidMousePoint; + m_mousePressZoomValue = 0; + setCursor(Qt::ArrowCursor); + } +} + +void SceneCameraView::handleSliderValueChange() +{ + const qreal zoom = qreal(m_ui->zoomSlider->value()) / 10.0; + QString valueString = QString::number(zoom, 'f', 1); + m_ui->slideValueLabel->setText(tr("%1x").arg(valueString)); + m_ui->scrollArea->setZoom(zoom, m_zoomPoint); + m_zoomPoint = m_ui->scrollArea->viewport()->geometry().center(); +} + +void SceneCameraView::doUpdate() +{ + // There is no event for presentation size change, so update every frame to catch the change + m_ui->scrollArea->setPresentationSize( + g_StudioApp.GetCore()->GetStudioProjectSettings()->getPresentationSize()); + m_ui->scrollArea->glWidget()->update(); +} + +void SceneCameraView::requestUpdate() +{ + if (!m_updateTimer.isActive()) + m_updateTimer.start(); +} diff --git a/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.h b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.h new file mode 100644 index 00000000..e2bfb05b --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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$ +** +****************************************************************************/ + +#ifndef SCENECAMERAVIEW_H +#define SCENECAMERAVIEW_H + +#include <QtWidgets/qwidget.h> +#include <QtCore/qtimer.h> + +#ifdef QT_NAMESPACE +using namespace QT_NAMESPACE; +#endif + +QT_BEGIN_NAMESPACE +namespace Ui { +class SceneCameraView; +} +QT_END_NAMESPACE + +class CMainFrame; + +class SceneCameraView : public QWidget +{ + Q_OBJECT + +public: + explicit SceneCameraView(CMainFrame *mainFrame, QWidget *parent = 0); + ~SceneCameraView(); + +protected: + void wheelEvent(QWheelEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + +private: + void handleSliderValueChange(); + void doUpdate(); + void requestUpdate(); + + Ui::SceneCameraView *m_ui = nullptr; + + QTimer m_updateTimer; + QPoint m_zoomPoint; + QPoint m_mousePressPointLeft; + QPoint m_mousePressPointRight; + QPoint m_mousePressScrollValues; + int m_mousePressZoomValue = 0; + + QCursor m_cursorPan; + QCursor m_cursorZoom; +}; + +#endif // SCENECAMERAVIEW_H diff --git a/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.ui b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.ui new file mode 100644 index 00000000..01aefa72 --- /dev/null +++ b/src/Authoring/Qt3DStudio/Palettes/scenecamera/scenecameraview.ui @@ -0,0 +1,107 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>SceneCameraView</class> + <widget class="QWidget" name="SceneCameraView"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="SceneCameraScrollArea" name="scrollArea" native="true"/> + </item> + <item> + <widget class="QWidget" name="widget" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="topMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QSlider" name="zoomSlider"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>100</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>300</width> + <height>16777215</height> + </size> + </property> + <property name="minimum"> + <number>2</number> + </property> + <property name="maximum"> + <number>200</number> + </property> + <property name="singleStep"> + <number>1</number> + </property> + <property name="value"> + <number>10</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="slideValueLabel"> + <property name="text"> + <string>1.0</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>1</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>SceneCameraScrollArea</class> + <extends>QWidget</extends> + <header>scenecamerascrollarea.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> |