diff options
Diffstat (limited to 'src/Authoring/Qt3DStudio/Palettes/Inspector')
62 files changed, 11381 insertions, 0 deletions
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 |