From d9f07e95b59df63d8beff595d4aaac32242ad05f Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Thu, 1 Sep 2022 15:03:01 +0300 Subject: [PATCH 01/10] QmlDesigner: Implement material bundle frontend Fixes: QDS-7500 Change-Id: I60c512bb7664d13160f8c58ce063e1e87b6875ee Reviewed-by: Miikka Heikkinen --- .../BundleMaterialItem.qml | 89 +++++++ .../MaterialBrowser.qml | 172 +++++++++++--- src/plugins/qmldesigner/CMakeLists.txt | 3 + .../components/edit3d/edit3dview.cpp | 8 + .../components/edit3d/edit3dview.h | 2 + .../components/edit3d/edit3dwidget.cpp | 9 +- .../materialbrowser/bundleimporter.cpp | 2 +- .../materialbrowser/bundlematerial.cpp | 67 ++++++ .../materialbrowser/bundlematerial.h | 68 ++++++ .../bundlematerialcategory.cpp | 70 ++++++ .../materialbrowser/bundlematerialcategory.h | 61 +++++ .../materialbrowserbundlemodel.cpp | 219 ++++++++++++++++++ .../materialbrowserbundlemodel.h | 97 ++++++++ .../materialbrowser/materialbrowsermodel.cpp | 2 + .../materialbrowser/materialbrowserview.cpp | 93 +++++++- .../materialbrowser/materialbrowserview.h | 7 +- .../materialbrowser/materialbrowserwidget.cpp | 44 +++- .../materialbrowser/materialbrowserwidget.h | 10 + .../qmldesigner/qmldesignerconstants.h | 1 + src/plugins/qmldesigner/qmldesignerplugin.qbs | 6 + 20 files changed, 982 insertions(+), 48 deletions(-) create mode 100644 share/qtcreator/qmldesigner/materialBrowserQmlSource/BundleMaterialItem.qml create mode 100644 src/plugins/qmldesigner/components/materialbrowser/bundlematerial.cpp create mode 100644 src/plugins/qmldesigner/components/materialbrowser/bundlematerial.h create mode 100644 src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.cpp create mode 100644 src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.h create mode 100644 src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.cpp create mode 100644 src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.h diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/BundleMaterialItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/BundleMaterialItem.qml new file mode 100644 index 00000000000..4b8b45be564 --- /dev/null +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/BundleMaterialItem.qml @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuickDesignerTheme 1.0 +import HelperWidgets 2.0 +import StudioTheme 1.0 as StudioTheme + +Item { + id: root + + signal showContextMenu() + + visible: modelData.bundleMaterialVisible + + MouseArea { + id: mouseArea + + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onPressed: (mouse) => { + if (mouse.button === Qt.LeftButton) + rootView.startDragBundleMaterial(modelData, mapToGlobal(mouse.x, mouse.y)) + else if (mouse.button === Qt.RightButton) + root.showContextMenu() + } + } + + Column { + anchors.fill: parent + spacing: 1 + + Item { width: 1; height: 5 } // spacer + + Image { + id: img + + width: root.width - 10 + height: img.width + anchors.horizontalCenter: parent.horizontalCenter + source: modelData.bundleMaterialIcon + cache: false + } + + TextInput { + id: matName + + text: modelData.bundleMaterialName + + width: img.width + clip: true + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: TextInput.AlignHCenter + + font.pixelSize: StudioTheme.Values.myFontSize + + readOnly: true + selectByMouse: !matName.readOnly + + color: StudioTheme.Values.themeTextColor + selectionColor: StudioTheme.Values.themeTextSelectionColor + selectedTextColor: StudioTheme.Values.themeTextSelectedTextColor + } + } +} diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml index a8692f3ef9d..d9d207b555f 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml @@ -38,13 +38,15 @@ Item { property var currentMaterial: null property int currentMaterialIdx: 0 + property var currentBundleMaterial: null property var matSectionsModel: [] // Called also from C++ to close context menu on focus out function closeContextMenu() { - contextMenu.close() + cxtMenu.close() + cxtMenuBundle.close() } // Called from C++ to refresh a preview material after it changes @@ -68,10 +70,15 @@ Item { acceptedButtons: Qt.RightButton - onClicked: { - if (!materialBrowserModel.hasMaterialRoot) { + onClicked: (mouse) => { + // root context-menu works only for user materials + var userMatsSecBottom = mapFromItem(userMaterialsSection, 0, userMaterialsSection.y).y + + userMaterialsSection.height; + + if (!materialBrowserModel.hasMaterialRoot && (!materialBrowserBundleModel.matBundleExists + || mouse.y < userMatsSecBottom)) { root.currentMaterial = null - contextMenu.popup() + cxtMenu.popup() } } } @@ -90,7 +97,7 @@ Item { } StudioControls.Menu { - id: contextMenu + id: cxtMenu closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside @@ -188,6 +195,32 @@ Item { } } + StudioControls.Menu { + id: cxtMenuBundle + + closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside + + StudioControls.MenuItem { + text: qsTr("Apply to selected (replace)") + enabled: root.currentBundleMaterial && materialBrowserModel.hasModelSelection + onTriggered: materialBrowserBundleModel.applyToSelected(root.currentBundleMaterial, false) + } + + StudioControls.MenuItem { + text: qsTr("Apply to selected (add)") + enabled: root.currentBundleMaterial && materialBrowserModel.hasModelSelection + onTriggered: materialBrowserBundleModel.applyToSelected(root.currentBundleMaterial, true) + } + + StudioControls.MenuSeparator {} + + StudioControls.MenuItem { + text: qsTr("Add to project") + + onTriggered: materialBrowserBundleModel.addMaterial(root.currentBundleMaterial) + } + } + Column { id: col y: 5 @@ -215,23 +248,12 @@ Item { } } - Text { - text: qsTr("No match found."); - color: StudioTheme.Values.themeTextColor - font.pixelSize: StudioTheme.Values.baseFontSize - leftPadding: 10 - visible: materialBrowserModel.hasQuick3DImport && materialBrowserModel.isEmpty - && !searchBox.isEmpty() && !materialBrowserModel.hasMaterialRoot - } - Text { text: { if (materialBrowserModel.hasMaterialRoot) qsTr("Material Browser is disabled inside a material component.") else if (!materialBrowserModel.hasQuick3DImport) qsTr("To use Material Browser, first add the QtQuick3D module in the Components view.") - else if (materialBrowserModel.isEmpty && searchBox.isEmpty()) - qsTr("There are no materials in this project.
Select '+' to create one.") else "" } @@ -252,31 +274,115 @@ Item { width: root.width height: root.height - searchBox.height clip: true + visible: materialBrowserModel.hasQuick3DImport && !materialBrowserModel.hasMaterialRoot - Grid { - id: grid + Column { + Section { + id: userMaterialsSection - width: scrollView.width - leftPadding: 5 - rightPadding: 5 - bottomPadding: 5 - columns: root.width / root.cellWidth + width: root.width + caption: qsTr("User materials") + hideHeader: !materialBrowserBundleModel.matBundleExists - Repeater { - id: gridRepeater + Grid { + id: grid - model: materialBrowserModel - delegate: MaterialItem { - width: root.cellWidth - height: root.cellHeight + width: scrollView.width + leftPadding: 5 + rightPadding: 5 + bottomPadding: 5 + columns: root.width / root.cellWidth - onShowContextMenu: { - if (searchBox.isEmpty()) { - root.currentMaterial = model - contextMenu.popup() + Repeater { + id: gridRepeater + + model: materialBrowserModel + delegate: MaterialItem { + width: root.cellWidth + height: root.cellHeight + + onShowContextMenu: { + if (searchBox.isEmpty()) { + root.currentMaterial = model + cxtMenu.popup() + } + } } } } + + Text { + text: qsTr("No match found."); + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.baseFontSize + leftPadding: 10 + visible: materialBrowserModel.isEmpty && !searchBox.isEmpty() && !materialBrowserModel.hasMaterialRoot + } + + Text { + text:qsTr("There are no materials in this project.
Select '+' to create one.") + visible: materialBrowserModel.isEmpty && searchBox.isEmpty() + textFormat: Text.RichText + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.mediumFontSize + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + width: root.width + } + } + + Section { + width: root.width + caption: qsTr("Material Library") + addTopPadding: noMatchText.visible + visible: materialBrowserBundleModel.matBundleExists + + Column { + Repeater { + model: materialBrowserBundleModel + + delegate: Section { + width: root.width + caption: bundleCategory + addTopPadding: false + sectionBackgroundColor: "transparent" + visible: bundleCategoryVisible + + Grid { + width: scrollView.width + leftPadding: 5 + rightPadding: 5 + bottomPadding: 5 + columns: root.width / root.cellWidth + + Repeater { + model: bundleMaterialsModel + + delegate: BundleMaterialItem { + width: root.cellWidth + height: root.cellHeight + + onShowContextMenu: { + if (searchBox.isEmpty()) { + root.currentBundleMaterial = modelData + cxtMenuBundle.popup() + } + } + } + } + } + } + } + + Text { + id: noMatchText + text: qsTr("No match found."); + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.baseFontSize + leftPadding: 10 + visible: materialBrowserBundleModel.isEmpty && !searchBox.isEmpty() && !materialBrowserModel.hasMaterialRoot + } + } } } } diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index bcd959cf0d8..5f025ff3706 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -337,6 +337,9 @@ extend_qtc_plugin(QmlDesigner materialbrowserwidget.cpp materialbrowserwidget.h materialbrowsermodel.cpp materialbrowsermodel.h bundleimporter.cpp bundleimporter.h + materialbrowserbundlemodel.cpp materialbrowserbundlemodel.h + bundlematerial.cpp bundlematerial.h + bundlematerialcategory.cpp bundlematerialcategory.h ) extend_qtc_plugin(QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index b9f91d56970..b727df84911 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -312,6 +312,8 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos assignMaterialTo3dModel(modelNode, m_droppedMaterial); }); } + } else if (m_nodeAtPosReqType == NodeAtPosReqType::BundleMaterialDrop) { + emitCustomNotification("drop_bundle_material", {modelNode}); // To MaterialBrowserView } m_nodeAtPosReqType = NodeAtPosReqType::None; } @@ -713,4 +715,10 @@ void Edit3DView::dropMaterial(const ModelNode &matNode, const QPointF &pos) QmlDesignerPlugin::instance()->viewManager().nodeInstanceView()->view3DAction({View3DActionCommand::GetNodeAtPos, pos}); } +void Edit3DView::dropBundleMaterial(const QPointF &pos) +{ + m_nodeAtPosReqType = NodeAtPosReqType::BundleMaterialDrop; + QmlDesignerPlugin::instance()->viewManager().nodeInstanceView()->view3DAction({View3DActionCommand::GetNodeAtPos, pos}); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index 68d217b38ea..cd95b9d371f 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -80,12 +80,14 @@ public: void addQuick3DImport(); void startContextMenu(const QPoint &pos); void dropMaterial(const ModelNode &matNode, const QPointF &pos); + void dropBundleMaterial(const QPointF &pos); private slots: void onEntriesChanged(); private: enum class NodeAtPosReqType { + BundleMaterialDrop, MaterialDrop, ContextMenu, None diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index e439ccf00c3..30b8894bbe9 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -331,7 +331,8 @@ void Edit3DWidget::dragEnterEvent(QDragEnterEvent *dragEnterEvent) const DesignerActionManager &actionManager = QmlDesignerPlugin::instance() ->viewManager().designerActionManager(); if (actionManager.externalDragHasSupportedAssets(dragEnterEvent->mimeData()) - || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL)) { + || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL) + || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_MATERIAL)) { dragEnterEvent->acceptProposedAction(); } } @@ -351,6 +352,12 @@ void Edit3DWidget::dropEvent(QDropEvent *dropEvent) return; } + // handle dropping bundle materials + if (dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_MATERIAL)) { + m_view->dropBundleMaterial(dropEvent->position()); + return; + } + // handle dropping external assets const DesignerActionManager &actionManager = QmlDesignerPlugin::instance() ->viewManager().designerActionManager(); diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp b/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp index 98096337aaf..251ebabe6bb 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp @@ -107,7 +107,7 @@ QString BundleImporter::importComponent(const QString &qmlFile, qmldirContent.append('\n'); } - FilePath qmlSourceFile = FilePath::fromString(qmlFile); + FilePath qmlSourceFile = bundleImportPath.resolvePath(FilePath::fromString(qmlFile)); const bool qmlFileExists = qmlSourceFile.exists(); const QString qmlType = qmlSourceFile.baseName(); m_pendingTypes.append(QStringLiteral("%1.%2") diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.cpp b/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.cpp new file mode 100644 index 00000000000..75549945f41 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.cpp @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "bundlematerial.h" + +namespace QmlDesigner { + +BundleMaterial::BundleMaterial(QObject *parent, + const QString &name, + const QString &qml, + const QUrl &icon, + const QStringList &files) + : QObject(parent), m_name(name), m_qml(qml), m_icon(icon), m_files(files) {} + +bool BundleMaterial::filter(const QString &searchText) +{ + if (m_visible != m_name.contains(searchText, Qt::CaseInsensitive)) { + m_visible = !m_visible; + emit materialVisibleChanged(); + } + + return m_visible; +} + +QUrl BundleMaterial::icon() const +{ + return m_icon; +} + +QString BundleMaterial::qml() const +{ + return m_qml; +} + +QStringList BundleMaterial::files() const +{ + return m_files; +} + +bool BundleMaterial::visible() const +{ + return m_visible; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.h b/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.h new file mode 100644 index 00000000000..04f4ae26568 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include +#include + +namespace QmlDesigner { + +class BundleMaterial : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString bundleMaterialName MEMBER m_name CONSTANT) + Q_PROPERTY(QUrl bundleMaterialIcon MEMBER m_icon CONSTANT) + Q_PROPERTY(bool bundleMaterialVisible MEMBER m_visible NOTIFY materialVisibleChanged) + +public: + BundleMaterial(QObject *parent, + const QString &name, + const QString &qml, + const QUrl &icon, + const QStringList &files); + + bool filter(const QString &searchText); + + QUrl icon() const; + QString qml() const; + QStringList files() const; + bool visible() const; + +signals: + void materialVisibleChanged(); + +private: + QString m_name; + QString m_qml; + QUrl m_icon; + QStringList m_files; + + bool m_visible = true; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.cpp b/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.cpp new file mode 100644 index 00000000000..9a18ff75014 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "bundlematerialcategory.h" + +#include "bundlematerial.h" + +namespace QmlDesigner { + +BundleMaterialCategory::BundleMaterialCategory(QObject *parent, const QString &name) + : QObject(parent), m_name(name) {} + +void BundleMaterialCategory::addBundleMaterial(BundleMaterial *bundleMat) +{ + m_categoryMaterials.append(bundleMat); +} + +bool BundleMaterialCategory::filter(const QString &searchText) +{ + bool visible = false; + for (BundleMaterial *mat : std::as_const(m_categoryMaterials)) + visible |= mat->filter(searchText); + + if (visible != m_visible) { + m_visible = visible; + emit categoryVisibleChanged(); + return true; + } + + return false; +} + +QString BundleMaterialCategory::name() const +{ + return m_name; +} + +bool BundleMaterialCategory::visible() const +{ + return m_visible; +} + +QList BundleMaterialCategory::categoryMaterials() const +{ + return m_categoryMaterials; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.h b/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.h new file mode 100644 index 00000000000..0a3a0f7c028 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace QmlDesigner { + +class BundleMaterial; + +class BundleMaterialCategory : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString categoryName MEMBER m_name CONSTANT) + Q_PROPERTY(bool categoryVisible MEMBER m_visible NOTIFY categoryVisibleChanged) + +public: + BundleMaterialCategory(QObject *parent, const QString &name); + + void addBundleMaterial(BundleMaterial *bundleMat); + bool filter(const QString &searchText); + + QString name() const; + bool visible() const; + QList categoryMaterials() const; + +signals: + void categoryVisibleChanged(); + +private: + QString m_name; + bool m_visible = true; + + QList m_categoryMaterials; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.cpp new file mode 100644 index 00000000000..cab5bc869d1 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.cpp @@ -0,0 +1,219 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "materialbrowserbundlemodel.h" + +#include "bundleimporter.h" +#include "bundlematerial.h" +#include "bundlematerialcategory.h" +#include "utils/qtcassert.h" + +#include +#include +#include + +namespace QmlDesigner { + +MaterialBrowserBundleModel::MaterialBrowserBundleModel(QObject *parent) + : QAbstractListModel(parent) +{ + loadMaterialBundle(); +} + +int MaterialBrowserBundleModel::rowCount(const QModelIndex &) const +{ + return m_bundleCategories.size(); +} + +QVariant MaterialBrowserBundleModel::data(const QModelIndex &index, int role) const +{ + QTC_ASSERT(index.isValid() && index.row() < m_bundleCategories.count(), return {}); + QTC_ASSERT(roleNames().contains(role), return {}); + + QByteArray roleName = roleNames().value(role); + if (roleName == "bundleCategory") + return m_bundleCategories.at(index.row())->name(); + + if (roleName == "bundleCategoryVisible") + return m_bundleCategories.at(index.row())->visible(); + + if (roleName == "bundleMaterialsModel") + return QVariant::fromValue(m_bundleCategories.at(index.row())->categoryMaterials()); + + return {}; +} + +bool MaterialBrowserBundleModel::isValidIndex(int idx) const +{ + return idx > -1 && idx < rowCount(); +} + +QHash MaterialBrowserBundleModel::roleNames() const +{ + static const QHash roles { + {Qt::UserRole + 1, "bundleCategory"}, + {Qt::UserRole + 2, "bundleCategoryVisible"}, + {Qt::UserRole + 3, "bundleMaterialsModel"} + }; + return roles; +} + +void MaterialBrowserBundleModel::loadMaterialBundle() +{ + if (m_matBundleExists) + return; + + QString bundlePath = qEnvironmentVariable("MATERIAL_BUNDLE_PATH"); + + if (bundlePath.isEmpty()) + return; + + QString matBundlePath = bundlePath + "material_bundle.json"; + + if (m_matBundleObj.isEmpty()) { + QFile matPropsFile(matBundlePath); + + if (!matPropsFile.open(QIODevice::ReadOnly)) { + qWarning("Couldn't open material_bundle.json"); + return; + } + + QJsonDocument matBundleJsonDoc = QJsonDocument::fromJson(matPropsFile.readAll()); + if (matBundleJsonDoc.isNull()) { + qWarning("Invalid material_bundle.json file"); + return; + } else { + m_matBundleObj = matBundleJsonDoc.object(); + } + } + + m_matBundleExists = true; + + const QJsonObject catsObj = m_matBundleObj.value("categories").toObject(); + const QStringList categories = catsObj.keys(); + for (const QString &cat : categories) { + auto category = new BundleMaterialCategory(this, cat); + + const QJsonObject matsObj = catsObj.value(cat).toObject(); + const QStringList mats = matsObj.keys(); + for (const QString &mat : mats) { + const QJsonObject matObj = matsObj.value(mat).toObject(); + + QStringList files; + const QJsonArray assetsArr = matObj.value("files").toArray(); + for (const QJsonValueRef &asset : assetsArr) + files.append(asset.toString()); + + auto bundleMat = new BundleMaterial(category, mat, matObj.value("qml").toString(), + QUrl::fromLocalFile(bundlePath + matObj.value("icon").toString()), files); + + category->addBundleMaterial(bundleMat); + } + m_bundleCategories.append(category); + } + + QStringList sharedFiles; + const QJsonArray sharedFilesArr = m_matBundleObj.value("sharedFiles").toArray(); + for (const QJsonValueRef &file : sharedFilesArr) + sharedFiles.append(file.toString()); + + m_importer = new Internal::BundleImporter(bundlePath, "MaterialBundle", sharedFiles); + connect(m_importer, &Internal::BundleImporter::importFinished, this, [&](const QmlDesigner::NodeMetaInfo &metaInfo) { + if (metaInfo.isValid()) + emit addBundleMaterialToProjectRequested(metaInfo); + }); +} + +bool MaterialBrowserBundleModel::hasQuick3DImport() const +{ + return m_hasQuick3DImport; +} + +void MaterialBrowserBundleModel::setHasQuick3DImport(bool b) +{ + if (b == m_hasQuick3DImport) + return; + + m_hasQuick3DImport = b; + emit hasQuick3DImportChanged(); +} + +bool MaterialBrowserBundleModel::hasMaterialRoot() const +{ + return m_hasMaterialRoot; +} + +void MaterialBrowserBundleModel::setHasMaterialRoot(bool b) +{ + if (m_hasMaterialRoot == b) + return; + + m_hasMaterialRoot = b; + emit hasMaterialRootChanged(); +} + +void MaterialBrowserBundleModel::setSearchText(const QString &searchText) +{ + QString lowerSearchText = searchText.toLower(); + + if (m_searchText == lowerSearchText) + return; + + m_searchText = lowerSearchText; + + bool anyCatVisible = false; + bool catVisibilityChanged = false; + + for (BundleMaterialCategory *cat : std::as_const(m_bundleCategories)) { + catVisibilityChanged |= cat->filter(m_searchText); + anyCatVisible |= cat->visible(); + } + + if (anyCatVisible == m_isEmpty) { + m_isEmpty = !anyCatVisible; + emit isEmptyChanged(); + } + + if (catVisibilityChanged) + resetModel(); +} + +void MaterialBrowserBundleModel::resetModel() +{ + beginResetModel(); + endResetModel(); +} + +void MaterialBrowserBundleModel::applyToSelected(BundleMaterial *mat, bool add) +{ + emit applyToSelectedTriggered(mat, add); +} + +void MaterialBrowserBundleModel::addMaterial(BundleMaterial *mat) +{ + m_importer->importComponent(mat->qml(), mat->files()); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.h new file mode 100644 index 00000000000..47d201a8293 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace QmlDesigner { + +class BundleMaterial; +class BundleMaterialCategory; + +namespace Internal { +class BundleImporter; +} + +class MaterialBrowserBundleModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(bool matBundleExists MEMBER m_matBundleExists CONSTANT) + Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) + Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) + Q_PROPERTY(bool hasMaterialRoot READ hasMaterialRoot WRITE setHasMaterialRoot NOTIFY hasMaterialRootChanged) + +public: + MaterialBrowserBundleModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QHash roleNames() const override; + + void setSearchText(const QString &searchText); + + bool hasQuick3DImport() const; + void setHasQuick3DImport(bool b); + + bool hasMaterialRoot() const; + void setHasMaterialRoot(bool b); + + void resetModel(); + + Q_INVOKABLE void applyToSelected(QmlDesigner::BundleMaterial *mat, bool add = false); + Q_INVOKABLE void addMaterial(QmlDesigner::BundleMaterial *mat); + +signals: + void isEmptyChanged(); + void hasQuick3DImportChanged(); + void hasMaterialRootChanged(); + void materialVisibleChanged(); + void applyToSelectedTriggered(QmlDesigner::BundleMaterial *mat, bool add = false); + void addBundleMaterialToProjectRequested(const QmlDesigner::NodeMetaInfo &metaInfo); + +private: + void loadMaterialBundle(); + bool isValidIndex(int idx) const; + + QString m_searchText; + QList m_bundleCategories; + QJsonObject m_matBundleObj; + Internal::BundleImporter *m_importer = nullptr; + + bool m_isEmpty = true; + bool m_hasQuick3DImport = false; + bool m_hasMaterialRoot = false; + bool m_matBundleExists = false; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp index 2d61b97889f..fea4a430a61 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp @@ -235,6 +235,8 @@ void MaterialBrowserModel::setSearchText(const QString &searchText) isEmpty = !isValidIndex(m_selectedIndex + inc) && !isValidIndex(m_selectedIndex - inc); } + if (!isMaterialVisible(m_selectedIndex)) // handles the case of a single material + isEmpty = true; } if (isEmpty != m_isEmpty) { diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 1b7ee3f71f9..7a4da256e53 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -26,8 +26,10 @@ #include "materialbrowserview.h" #include "bindingproperty.h" +#include "bundlematerial.h" #include "materialbrowserwidget.h" #include "materialbrowsermodel.h" +#include "materialbrowserbundlemodel.h" #include "nodeabstractproperty.h" #include "nodemetainfo.h" #include "qmlobjectnode.h" @@ -36,9 +38,12 @@ #include #include #include +#include #include #include +#include +#include namespace QmlDesigner { @@ -115,6 +120,80 @@ WidgetInfo MaterialBrowserView::widgetInfo() } }); }); + + connect(m_widget, &MaterialBrowserWidget::bundleMaterialDragStarted, this, + [&] (QmlDesigner::BundleMaterial *bundleMat) { + m_draggedBundleMaterial = bundleMat; + }); + + MaterialBrowserBundleModel *matBrowserBundleModel = m_widget->materialBrowserBundleModel().data(); + + connect(matBrowserBundleModel, &MaterialBrowserBundleModel::applyToSelectedTriggered, this, + [&] (BundleMaterial *material, bool add) { + if (!m_selectedModel.isValid()) + return; + + m_bundleMaterialDropTarget = m_selectedModel; + m_bundleMaterialAddToSelected = add; + m_widget->materialBrowserBundleModel()->addMaterial(material); + }); + + connect(matBrowserBundleModel, &MaterialBrowserBundleModel::addBundleMaterialToProjectRequested, this, + [&] (const QmlDesigner::NodeMetaInfo &metaInfo) { + ModelNode matLib = materialLibraryNode(); + if (!matLib.isValid()) + return; + + executeInTransaction("MaterialBrowserView::widgetInfo", [&] { + ModelNode newMatNode = createModelNode(metaInfo.typeName(), metaInfo.majorVersion(), + metaInfo.minorVersion()); + matLib.defaultNodeListProperty().reparentHere(newMatNode); + + static QRegularExpression rgx("([A-Z])([a-z]*)"); + QString newName = QString::fromLatin1(metaInfo.simplifiedTypeName()).replace(rgx, " \\1\\2").trimmed(); + QString newId = model()->generateIdFromName(newName, "material"); + newMatNode.setIdWithRefactoring(newId); + + VariantProperty objNameProp = newMatNode.variantProperty("objectName"); + objNameProp.setValue(newName); + + if (m_bundleMaterialDropTarget.isValid()) { + QmlObjectNode qmlObjNode(m_bundleMaterialDropTarget); + if (m_bundleMaterialAddToSelected) { + // TODO: unify this logic as it exist elsewhere also + auto expToList = [](const QString &exp) { + QString copy = exp; + copy = copy.remove("[").remove("]"); + + QStringList tmp = copy.split(',', Qt::SkipEmptyParts); + for (QString &str : tmp) + str = str.trimmed(); + + return tmp; + }; + + auto listToExp = [](QStringList &stringList) { + if (stringList.size() > 1) + return QString("[" + stringList.join(",") + "]"); + + if (stringList.size() == 1) + return stringList.first(); + + return QString(); + }; + QStringList matList = expToList(qmlObjNode.expression("materials")); + matList.append(newMatNode.id()); + QString updatedExp = listToExp(matList); + qmlObjNode.setBindingProperty("materials", updatedExp); + } else { + qmlObjNode.setBindingProperty("materials", newMatNode.id()); + } + m_bundleMaterialDropTarget = {}; + } + + m_bundleMaterialAddToSelected = false; + }); + }); } return createWidgetInfo(m_widget.data(), @@ -185,24 +264,24 @@ void MaterialBrowserView::selectedNodesChanged(const QList &selectedN { Q_UNUSED(lastSelectedNodeList) - ModelNode selectedModel; + m_selectedModel = {}; for (const ModelNode &node : selectedNodeList) { if (node.isSubclassOf("QtQuick3D.Model")) { - selectedModel = node; + m_selectedModel = node; break; } } - m_widget->materialBrowserModel()->setHasModelSelection(selectedModel.isValid()); + m_widget->materialBrowserModel()->setHasModelSelection(m_selectedModel.isValid()); if (!m_autoSelectModelMaterial) return; - if (selectedNodeList.size() > 1 || !selectedModel.isValid()) + if (selectedNodeList.size() > 1 || !m_selectedModel.isValid()) return; - QmlObjectNode qmlObjNode(selectedModel); + QmlObjectNode qmlObjNode(m_selectedModel); QString matExp = qmlObjNode.expression("materials"); if (matExp.isEmpty()) return; @@ -339,6 +418,10 @@ void MaterialBrowserView::customNotification(const AbstractView *view, const QSt }); } else if (identifier == "delete_selected_material") { m_widget->materialBrowserModel()->deleteSelectedMaterial(); + } else if (identifier == "drop_bundle_material") { + m_bundleMaterialDropTarget = nodeList.first(); + m_widget->materialBrowserBundleModel()->addMaterial(m_draggedBundleMaterial); + m_draggedBundleMaterial = nullptr; } } diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h index 82ecfe593ed..09f2c99033c 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h @@ -31,6 +31,7 @@ namespace QmlDesigner { +class BundleMaterial; class MaterialBrowserWidget; class MaterialBrowserView : public AbstractView @@ -65,13 +66,17 @@ public: private: void refreshModel(bool updateImages); bool isMaterial(const ModelNode &node) const; + void loadPropertyGroups(); QPointer m_widget; + ModelNode m_bundleMaterialDropTarget; + ModelNode m_selectedModel; // first selected 3D model node + BundleMaterial *m_draggedBundleMaterial = nullptr; + bool m_bundleMaterialAddToSelected = false; bool m_hasQuick3DImport = false; bool m_autoSelectModelMaterial = false; // TODO: wire this to some action bool m_puppetResetPending = false; bool m_propertyGroupsLoaded = false; - void loadPropertyGroups(); }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index 71ddd1aca7f..99f063dfae4 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -24,25 +24,29 @@ ****************************************************************************/ #include "materialbrowserwidget.h" + +#include "bundlematerial.h" +#include "materialbrowserbundlemodel.h" #include "materialbrowsermodel.h" #include "materialbrowserview.h" -#include - #include #include #include #include #include -#include -#include -#include +#include + +#include +#include +#include -#include #include #include #include +#include +#include #include #include #include @@ -113,7 +117,7 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event) if (m_materialToDrag.isValid()) { QMouseEvent *me = static_cast(event); - if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 10) { + if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 20) { QByteArray data; QMimeData *mimeData = new QMimeData; QDataStream stream(&data, QIODevice::WriteOnly); @@ -125,6 +129,17 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event) QString::number(m_materialToDrag.internalId()), nullptr, {128, 128})); m_materialToDrag = {}; } + } else if (m_bundleMaterialToDrag != nullptr) { + QMouseEvent *me = static_cast(event); + if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 20) { + QMimeData *mimeData = new QMimeData; + mimeData->setData(Constants::MIME_TYPE_BUNDLE_MATERIAL, {}); + mimeData->removeFormat("text/plain"); + + model->startDrag(mimeData, m_bundleMaterialToDrag->icon().toLocalFile()); + emit bundleMaterialDragStarted(m_bundleMaterialToDrag); + m_bundleMaterialToDrag = {}; + } } } @@ -134,6 +149,7 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event) MaterialBrowserWidget::MaterialBrowserWidget(MaterialBrowserView *view) : m_materialBrowserView(view) , m_materialBrowserModel(new MaterialBrowserModel(this)) + , m_materialBrowserBundleModel(new MaterialBrowserBundleModel(this)) , m_quickWidget(new QQuickWidget(this)) , m_previewImageProvider(new PreviewImageProvider()) { @@ -152,6 +168,7 @@ MaterialBrowserWidget::MaterialBrowserWidget(MaterialBrowserView *view) m_quickWidget->rootContext()->setContextProperties({ {"rootView", QVariant::fromValue(this)}, {"materialBrowserModel", QVariant::fromValue(m_materialBrowserModel.data())}, + {"materialBrowserBundleModel", QVariant::fromValue(m_materialBrowserBundleModel.data())}, }); m_quickWidget->engine()->addImageProvider("materialBrowser", m_previewImageProvider); @@ -211,6 +228,12 @@ void MaterialBrowserWidget::startDragMaterial(int index, const QPointF &mousePos m_dragStartPoint = mousePos.toPoint(); } +void MaterialBrowserWidget::startDragBundleMaterial(QmlDesigner::BundleMaterial *bundleMat, const QPointF &mousePos) +{ + m_bundleMaterialToDrag = bundleMat; + m_dragStartPoint = mousePos.toPoint(); +} + QString MaterialBrowserWidget::qmlSourcesPath() { #ifdef SHARE_QML_PATH @@ -238,6 +261,7 @@ void MaterialBrowserWidget::reloadQmlSource() void MaterialBrowserWidget::updateSearch() { m_materialBrowserModel->setSearchText(m_filterText); + m_materialBrowserBundleModel->setSearchText(m_filterText); m_quickWidget->update(); } @@ -251,4 +275,10 @@ QPointer MaterialBrowserWidget::materialBrowserModel() con return m_materialBrowserModel; } +QPointer MaterialBrowserWidget::materialBrowserBundleModel() const +{ + return m_materialBrowserBundleModel; +} + + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h index 9c08f21f9af..93197225a11 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -48,8 +49,10 @@ QT_END_NAMESPACE namespace QmlDesigner { +class BundleMaterial; class MaterialBrowserView; class MaterialBrowserModel; +class MaterialBrowserBundleModel; class PreviewImageProvider; class MaterialBrowserWidget : public QFrame @@ -67,13 +70,18 @@ public: void clearSearchFilter(); QPointer materialBrowserModel() const; + QPointer materialBrowserBundleModel() const; void updateMaterialPreview(const ModelNode &node, const QPixmap &pixmap); Q_INVOKABLE void handleSearchfilterChanged(const QString &filterText); Q_INVOKABLE void startDragMaterial(int index, const QPointF &mousePos); + Q_INVOKABLE void startDragBundleMaterial(QmlDesigner::BundleMaterial *bundleMat, const QPointF &mousePos); QQuickWidget *quickWidget() const; +signals: + void bundleMaterialDragStarted(QmlDesigner::BundleMaterial *bundleMat); + protected: bool eventFilter(QObject *obj, QEvent *event) override; @@ -83,6 +91,7 @@ private: QPointer m_materialBrowserView; QPointer m_materialBrowserModel; + QPointer m_materialBrowserBundleModel; QScopedPointer m_quickWidget; QShortcut *m_qmlSourceUpdateShortcut = nullptr; @@ -92,6 +101,7 @@ private: QString m_filterText; ModelNode m_materialToDrag; + BundleMaterial *m_bundleMaterialToDrag = nullptr; QPoint m_dragStartPoint; }; diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h index 5d522dfbaf0..b042888cfb1 100644 --- a/src/plugins/qmldesigner/qmldesignerconstants.h +++ b/src/plugins/qmldesigner/qmldesignerconstants.h @@ -96,6 +96,7 @@ const char MATERIAL_LIB_ID[] = "__materialLibrary__"; const char MIME_TYPE_ITEM_LIBRARY_INFO[] = "application/vnd.qtdesignstudio.itemlibraryinfo"; const char MIME_TYPE_ASSETS[] = "application/vnd.qtdesignstudio.assets"; const char MIME_TYPE_MATERIAL[] = "application/vnd.qtdesignstudio.material"; +const char MIME_TYPE_BUNDLE_MATERIAL[] = "application/vnd.qtdesignstudio.bundlematerial"; const char MIME_TYPE_ASSET_IMAGE[] = "application/vnd.qtdesignstudio.asset.image"; const char MIME_TYPE_ASSET_FONT[] = "application/vnd.qtdesignstudio.asset.font"; const char MIME_TYPE_ASSET_SHADER[] = "application/vnd.qtdesignstudio.asset.shader"; diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index 1744520f5b0..51cd8b970aa 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -687,6 +687,12 @@ Project { "itemlibrary/itemlibraryiconimageprovider.h", "materialbrowser/materialbrowsermodel.cpp", "materialbrowser/materialbrowsermodel.h", + "materialbrowser/materialbrowserbundlemodel.cpp", + "materialbrowser/materialbrowserbundlemodel.h", + "materialbrowser/bundlematerial.cpp", + "materialbrowser/bundlematerial.h", + "materialbrowser/bundlematerialcategory.cpp", + "materialbrowser/bundlematerialcategory.h", "materialbrowser/materialbrowserview.cpp", "materialbrowser/materialbrowserview.h", "materialbrowser/materialbrowserwidget.cpp", From 145ecd040fb16531c76415fc1996ae85acd71ddf Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Fri, 16 Sep 2022 16:29:25 +0300 Subject: [PATCH 02/10] QmlDesigner: Add support for component unimporting from bundle Fixes: QDS-7706 Change-Id: Ib0ab71b650592b1796d3ef2f14e364431a82a268 Reviewed-by: Mahmoud Badri --- .../materialbrowser/bundleimporter.cpp | 170 ++++++++++++++---- .../materialbrowser/bundleimporter.h | 6 + .../qmldesigner/qmldesignerconstants.h | 1 + 3 files changed, 145 insertions(+), 32 deletions(-) diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp b/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp index 251ebabe6bb..046bd9f48ff 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp @@ -33,9 +33,9 @@ #include -#include #include -#include +#include +#include #include using namespace Utils; @@ -65,20 +65,14 @@ BundleImporter::BundleImporter(const QString &bundleDir, QString BundleImporter::importComponent(const QString &qmlFile, const QStringList &files) { - FilePath bundleImportPath = QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath(); + FilePath bundleImportPath = resolveBundleImportPath(); if (bundleImportPath.isEmpty()) - return "Failed to resolve current project path"; + return "Failed to resolve bundle import folder"; - const QString projectBundlePath = QStringLiteral("%1%2/%3").arg( - QLatin1String(Constants::DEFAULT_ASSET_IMPORT_FOLDER), - QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER), - m_bundleId).mid(1); // Chop leading slash - bundleImportPath = bundleImportPath.resolvePath(projectBundlePath); + bool bundleImportPathExists = bundleImportPath.exists(); - if (!bundleImportPath.exists()) { - if (!bundleImportPath.createDir()) - return QStringLiteral("Failed to create bundle import folder: '%1'").arg(bundleImportPath.toString()); - } + if (!bundleImportPathExists && !bundleImportPath.createDir()) + return QStringLiteral("Failed to create bundle import folder: '%1'").arg(bundleImportPath.toString()); for (const QString &file : qAsConst(m_sharedFiles)) { FilePath target = bundleImportPath.resolvePath(file); @@ -93,15 +87,8 @@ QString BundleImporter::importComponent(const QString &qmlFile, } FilePath qmldirPath = bundleImportPath.resolvePath(QStringLiteral("qmldir")); - QFile qmldirFile(qmldirPath.toString()); - - QString qmldirContent; - if (qmldirPath.exists()) { - if (!qmldirFile.open(QIODeviceBase::ReadOnly)) - return QStringLiteral("Failed to open qmldir file for reading: '%1'").arg(qmldirPath.toString()); - qmldirContent = QString::fromUtf8(qmldirFile.readAll()); - qmldirFile.close(); - } else { + QString qmldirContent = QString::fromUtf8(qmldirPath.fileContents()); + if (qmldirContent.isEmpty()) { qmldirContent.append("module "); qmldirContent.append(m_moduleName); qmldirContent.append('\n'); @@ -113,17 +100,11 @@ QString BundleImporter::importComponent(const QString &qmlFile, m_pendingTypes.append(QStringLiteral("%1.%2") .arg(QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1), qmlType)); if (!qmldirContent.contains(qmlFile)) { - QSaveFile qmldirSaveFile(qmldirPath.toString()); - if (!qmldirSaveFile.open(QIODeviceBase::WriteOnly | QIODeviceBase::Truncate)) - return QStringLiteral("Failed to open qmldir file for writing: '%1'").arg(qmldirPath.toString()); - qmldirContent.append(qmlType); qmldirContent.append(" 1.0 "); qmldirContent.append(qmlFile); qmldirContent.append('\n'); - - qmldirSaveFile.write(qmldirContent.toUtf8()); - qmldirSaveFile.commit(); + qmldirPath.writeFileContents(qmldirContent.toUtf8()); } QStringList allFiles; @@ -145,6 +126,19 @@ QString BundleImporter::importComponent(const QString &qmlFile, return QStringLiteral("Failed to copy file: '%1'").arg(source.toString()); } + QVariantHash assetRefMap = loadAssetRefMap(bundleImportPath); + bool writeAssetRefs = false; + for (const QString &assetFile : files) { + QStringList assets = assetRefMap[assetFile].toStringList(); + if (!assets.contains(qmlFile)) { + assets.append(qmlFile); + writeAssetRefs = true; + } + assetRefMap[assetFile] = assets; + } + if (writeAssetRefs) + writeAssetRefMap(bundleImportPath, assetRefMap); + m_fullReset = !qmlFileExists; auto doc = QmlDesignerPlugin::instance()->currentDesignDocument(); Model *model = doc ? doc->currentModel() : nullptr; @@ -163,10 +157,12 @@ QString BundleImporter::importComponent(const QString &qmlFile, } } else { // If import is not yet possible, import statement needs to be added asynchronously to - // avoid errors, as code model update takes a while. Full reset is not necessary - // in this case, as new import directory appearing will trigger scanning of it. + // avoid errors, as code model update takes a while. m_importAddPending = true; - m_fullReset = false; + + // Full reset is not necessary if new import directory appearing will trigger scanning, + // but if directory existed but was not valid possible import, we need to do a reset. + m_fullReset = bundleImportPathExists; } } m_importTimerCount = 0; @@ -232,4 +228,114 @@ void BundleImporter::handleImportTimer() } } +QVariantHash BundleImporter::loadAssetRefMap(const Utils::FilePath &bundlePath) +{ + FilePath assetRefPath = bundlePath.resolvePath(QLatin1String(Constants::COMPONENT_BUNDLES_ASSET_REF_FILE)); + QByteArray content = assetRefPath.fileContents(); + if (!content.isEmpty()) { + QJsonParseError error; + QJsonDocument bundleDataJsonDoc = QJsonDocument::fromJson(content, &error); + if (bundleDataJsonDoc.isNull()) { + // Failure to read asset refs is not considred fatal, so just print error + qWarning() << "Failed to parse bundle asset ref file:" << error.errorString(); + } else { + return bundleDataJsonDoc.object().toVariantHash(); + } + } + return {}; +} + +void BundleImporter::writeAssetRefMap(const Utils::FilePath &bundlePath, + const QVariantHash &assetRefMap) +{ + FilePath assetRefPath = bundlePath.resolvePath(QLatin1String(Constants::COMPONENT_BUNDLES_ASSET_REF_FILE)); + QJsonObject jsonObj = QJsonObject::fromVariantHash(assetRefMap); + if (!assetRefPath.writeFileContents(QJsonDocument{jsonObj}.toJson())) { + // Failure to write asset refs is not considred fatal, so just print error + qWarning() << QStringLiteral("Failed to save bundle asset ref file: '%1'").arg(assetRefPath.toString()) ; + } +} + +QString BundleImporter::unimportComponent(const QString &qmlFile) +{ + FilePath bundleImportPath = resolveBundleImportPath(); + if (bundleImportPath.isEmpty()) + return "Failed to resolve bundle import folder"; + + if (!bundleImportPath.exists()) + return {}; + + FilePath qmlFilePath = bundleImportPath.resolvePath(qmlFile); + if (!qmlFilePath.exists()) + return {}; + + QStringList removedFiles; + removedFiles.append(qmlFile); + + FilePath qmldirPath = bundleImportPath.resolvePath(QStringLiteral("qmldir")); + QByteArray qmldirContent = qmldirPath.fileContents(); + QByteArray newContent; + if (!qmldirContent.isEmpty()) { + QByteArray qmlType = qmlFilePath.baseName().toUtf8(); + int typeIndex = qmldirContent.indexOf(qmlType); + if (typeIndex != -1) { + int newLineIndex = qmldirContent.indexOf('\n', typeIndex); + newContent = qmldirContent.left(typeIndex); + if (newLineIndex != -1) + newContent.append(qmldirContent.mid(newLineIndex + 1)); + } + if (newContent != qmldirContent) { + if (!qmldirPath.writeFileContents(newContent)) + return QStringLiteral("Failed to write qmldir file: '%1'").arg(qmldirPath.toString()); + } + } + + QVariantHash assetRefMap = loadAssetRefMap(bundleImportPath); + bool writeAssetRefs = false; + const auto keys = assetRefMap.keys(); + for (const QString &assetFile : keys) { + QStringList assets = assetRefMap[assetFile].toStringList(); + if (assets.contains(qmlFile)) { + assets.removeAll(qmlFile); + writeAssetRefs = true; + } + if (!assets.isEmpty()) { + assetRefMap[assetFile] = assets; + } else { + removedFiles.append(assetFile); + assetRefMap.remove(assetFile); + writeAssetRefs = true; + } + } + + for (const QString &removedFile : removedFiles) { + FilePath removedFilePath = bundleImportPath.resolvePath(removedFile); + if (removedFilePath.exists()) + removedFilePath.removeFile(); + } + + if (writeAssetRefs) + writeAssetRefMap(bundleImportPath, assetRefMap); + + m_fullReset = true; + m_importTimerCount = 0; + m_importTimer.start(); + + return {}; +} + +FilePath BundleImporter::resolveBundleImportPath() +{ + FilePath bundleImportPath = QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath(); + if (bundleImportPath.isEmpty()) + return bundleImportPath; + + const QString projectBundlePath = QStringLiteral("%1%2/%3").arg( + QLatin1String(Constants::DEFAULT_ASSET_IMPORT_FOLDER), + QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER), + m_bundleId).mid(1); // Chop leading slash + + return bundleImportPath.resolvePath(projectBundlePath); +} + } // namespace QmlDesigner::Internal diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.h b/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.h index 840c4c672f5..b22d0edd591 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.h +++ b/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.h @@ -30,6 +30,7 @@ #include "nodemetainfo.h" #include +#include QT_BEGIN_NAMESPACE QT_END_NAMESPACE @@ -49,6 +50,8 @@ public: QString importComponent(const QString &qmlFile, const QStringList &files); + QString unimportComponent(const QString &qmlFile); + signals: // The metaInfo parameter will be invalid if an error was encountered during // asynchronous part of the import. In this case all remaining pending imports have been @@ -57,6 +60,9 @@ signals: private: void handleImportTimer(); + QVariantHash loadAssetRefMap(const Utils::FilePath &bundlePath); + void writeAssetRefMap(const Utils::FilePath &bundlePath, const QVariantHash &assetRefMap); + Utils::FilePath resolveBundleImportPath(); Utils::FilePath m_bundleDir; QString m_bundleId; diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h index b042888cfb1..45dbda6b961 100644 --- a/src/plugins/qmldesigner/qmldesignerconstants.h +++ b/src/plugins/qmldesigner/qmldesignerconstants.h @@ -84,6 +84,7 @@ const char EDIT3D_BACKGROUND_COLOR_ACTIONS[] = "QmlDesigner.Editor3D.BackgroundC const char QML_DESIGNER_SUBFOLDER[] = "/designer/"; const char COMPONENT_BUNDLES_FOLDER[] = "/ComponentBundles"; +const char COMPONENT_BUNDLES_ASSET_REF_FILE[] = "_asset_ref.json"; const char QUICK_3D_ASSETS_FOLDER[] = "/Quick3DAssets"; const char QUICK_3D_ASSET_LIBRARY_ICON_SUFFIX[] = "_libicon"; const char QUICK_3D_ASSET_ICON_DIR[] = "_icons"; From e936ef2fdbbe9e32ea701bc6d6a18463dc08e248 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 20 Sep 2022 16:14:51 +0300 Subject: [PATCH 03/10] QmlDesigner: Reduce vertical gap between vector fields This helps to differentiate which fields belong to which property when multiple vertor properties are added next to each other. Fixes: QDS-7704 Change-Id: I885e6d3d12a11851668e7c4d66f94f3356e5f082 Reviewed-by: Mahmoud Badri --- .../Vector2dEditorTemplate.template | 68 +++++++++---------- .../Vector3dEditorTemplate.template | 1 + .../Vector4dEditorTemplate.template | 1 + .../DynamicPropertiesSection.qml | 2 +- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector2dEditorTemplate.template b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector2dEditorTemplate.template index b339c254bac..ab2eb508d17 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector2dEditorTemplate.template +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector2dEditorTemplate.template @@ -3,40 +3,38 @@ PropertyLabel { tooltip: "%1" } -ColumnLayout { - SecondColumnLayout { - SpinBox { - minimumValue: -9999999 - maximumValue: 9999999 - decimals: 2 - backendValue: backendValues.%2_x - implicitWidth: StudioTheme.Values.twoControlColumnWidth - + StudioTheme.Values.actionIndicatorWidth - } - - Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } - - ControlLabel { - text: "X" - } - - Spacer { implicitWidth: StudioTheme.Values.controlGap } - - SpinBox { - minimumValue: -9999999 - maximumValue: 9999999 - decimals: 2 - backendValue: backendValues.%2_y - implicitWidth: StudioTheme.Values.twoControlColumnWidth - + StudioTheme.Values.actionIndicatorWidth - } - - Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } - - ControlLabel { - text: "Y" - } - - ExpandingSpacer {} +SecondColumnLayout { + SpinBox { + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + backendValue: backendValues.%2_x + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + text: "X" + } + + Spacer { implicitWidth: StudioTheme.Values.controlGap } + + SpinBox { + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + backendValue: backendValues.%2_y + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + text: "Y" + } + + ExpandingSpacer {} } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector3dEditorTemplate.template b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector3dEditorTemplate.template index 5caff585d73..c5be9374741 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector3dEditorTemplate.template +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector3dEditorTemplate.template @@ -4,6 +4,7 @@ PropertyLabel { } ColumnLayout { + spacing: StudioTheme.Values.sectionRowSpacing / 2 SecondColumnLayout { SpinBox { minimumValue: -9999999 diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector4dEditorTemplate.template b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector4dEditorTemplate.template index 619f51cebe3..0fd6cb9f7f1 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector4dEditorTemplate.template +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector4dEditorTemplate.template @@ -4,6 +4,7 @@ PropertyLabel { } ColumnLayout { + spacing: StudioTheme.Values.sectionRowSpacing / 2 SecondColumnLayout { SpinBox { minimumValue: -9999999 diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml index 4bd99e3f68e..2a0580edf14 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml @@ -297,7 +297,7 @@ Section { onVecSizeChanged: updateProxyValues() - spacing: StudioTheme.Values.sectionRowSpacing + spacing: StudioTheme.Values.sectionRowSpacing / 2 function isValidValue(v) { return !(v === undefined || isNaN(v)) From 6b8e2fbd49a5982fac84f8747c3d14b72feb4f0a Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Fri, 16 Sep 2022 17:21:03 +0200 Subject: [PATCH 04/10] QmlDesigner: Show proper error dialog if adding duplicate property Task-number: QDS-7702 Change-Id: I43f07afa95cef75e53bbc0259f6dd21b7c1a5363 Reviewed-by: Miikka Heikkinen --- .../propertyeditor/dynamicpropertiesproxymodel.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp index 3b45d7480f1..b5791ba2870 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp @@ -35,6 +35,7 @@ #include #include +#include #include #include @@ -150,6 +151,12 @@ void DynamicPropertiesProxyModel::createProperty(const QString &name, const QStr if (selectedNodes.count() == 1) { const ModelNode modelNode = selectedNodes.constFirst(); if (modelNode.isValid()) { + if (modelNode.hasProperty(name.toUtf8())) { + Core::AsynchronousMessageBox::warning(tr("Property already exists"), + tr("Property '%1' already exists") + .arg(name)); + return; + } try { if (Internal::DynamicPropertiesModel::isValueType(typeName)) { QVariant value = Internal::DynamicPropertiesModel::defaultValueForType(typeName); From b080376f70cfa729d2911730276ed943c40f9c96 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 20 Sep 2022 16:44:24 +0300 Subject: [PATCH 05/10] QmlDesigner: Fix renaming materials Check for id validity during id generation and try to fix invalid id. Fixes: QDS-7437 Change-Id: I9d7665cbebdce8ee3395e4abe9ee14b26866f761 Reviewed-by: Mahmoud Badri --- src/plugins/qmldesigner/designercore/model/model.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/plugins/qmldesigner/designercore/model/model.cpp b/src/plugins/qmldesigner/designercore/model/model.cpp index f6f852bf47d..ef4e3cb2f78 100644 --- a/src/plugins/qmldesigner/designercore/model/model.cpp +++ b/src/plugins/qmldesigner/designercore/model/model.cpp @@ -1577,6 +1577,10 @@ QString Model::generateIdFromName(const QString &name, const QString &fallbackId newId.prepend('_'); } + // If the new id is not valid (e.g. qml keyword match), try fixing it by prepending underscore + if (!ModelNode::isValidId(newId)) + newId.prepend("_"); + QRegularExpression rgx("\\d+$"); // matches a number at the end of a string while (hasId(newId)) { // id exists QRegularExpressionMatch match = rgx.match(newId); From 6d2806c6ed460ba5de1aba9961efe6bc4c0935a4 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Tue, 20 Sep 2022 16:45:26 +0300 Subject: [PATCH 06/10] QmlDesigner: Search for material bundle folder near executable Fixes: QDS-7717 Change-Id: I33405d0be6d55550d79fc371c6d82760ec6fa5b3 Reviewed-by: Miikka Heikkinen --- .../materialbrowserbundlemodel.cpp | 24 +++++++++++++------ .../materialbrowserbundlemodel.h | 1 + 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.cpp index cab5bc869d1..1632fc14fab 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.cpp @@ -30,6 +30,7 @@ #include "bundlematerialcategory.h" #include "utils/qtcassert.h" +#include #include #include #include @@ -82,15 +83,24 @@ QHash MaterialBrowserBundleModel::roleNames() const void MaterialBrowserBundleModel::loadMaterialBundle() { - if (m_matBundleExists) + if (m_matBundleExists || m_probeMatBundleDir) return; - QString bundlePath = qEnvironmentVariable("MATERIAL_BUNDLE_PATH"); + QDir matBundleDir(qEnvironmentVariable("MATERIAL_BUNDLE_PATH")); - if (bundlePath.isEmpty()) - return; + // search for matBundleDir from exec dir and up + if (matBundleDir.dirName() == ".") { + m_probeMatBundleDir = true; // probe only once - QString matBundlePath = bundlePath + "material_bundle.json"; + matBundleDir.setPath(QCoreApplication::applicationDirPath()); + while (!matBundleDir.cd("material_bundle") && matBundleDir.cdUp()) + ; // do nothing + + if (matBundleDir.dirName() != "material_bundle") // bundlePathDir not found + return; + } + + QString matBundlePath = matBundleDir.filePath("material_bundle.json"); if (m_matBundleObj.isEmpty()) { QFile matPropsFile(matBundlePath); @@ -127,7 +137,7 @@ void MaterialBrowserBundleModel::loadMaterialBundle() files.append(asset.toString()); auto bundleMat = new BundleMaterial(category, mat, matObj.value("qml").toString(), - QUrl::fromLocalFile(bundlePath + matObj.value("icon").toString()), files); + QUrl::fromLocalFile(matBundleDir.filePath(matObj.value("icon").toString())), files); category->addBundleMaterial(bundleMat); } @@ -139,7 +149,7 @@ void MaterialBrowserBundleModel::loadMaterialBundle() for (const QJsonValueRef &file : sharedFilesArr) sharedFiles.append(file.toString()); - m_importer = new Internal::BundleImporter(bundlePath, "MaterialBundle", sharedFiles); + m_importer = new Internal::BundleImporter(matBundleDir.path(), "MaterialBundle", sharedFiles); connect(m_importer, &Internal::BundleImporter::importFinished, this, [&](const QmlDesigner::NodeMetaInfo &metaInfo) { if (metaInfo.isValid()) emit addBundleMaterialToProjectRequested(metaInfo); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.h index 47d201a8293..60b48f1c9b9 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.h @@ -92,6 +92,7 @@ private: bool m_hasQuick3DImport = false; bool m_hasMaterialRoot = false; bool m_matBundleExists = false; + bool m_probeMatBundleDir = false; }; } // namespace QmlDesigner From 493c05a6c3970b50c57fce310471f03000389c3f Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Tue, 20 Sep 2022 16:47:13 +0200 Subject: [PATCH 07/10] QmlDesigner: Add ToolTip to StudioControls Change-Id: Ic65c654c1253cfbc04ef5863c9b3473b5d2fd670 Reviewed-by: Brook Cronin Reviewed-by: Thomas Hartmann --- .../imports/StudioControls/ToolTip.qml | 60 +++++++++++++++++++ .../imports/StudioControls/qmldir | 1 + 2 files changed, 61 insertions(+) create mode 100644 share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ToolTip.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ToolTip.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ToolTip.qml new file mode 100644 index 00000000000..0c99890e6b5 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ToolTip.qml @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick +import QtQuick.Templates as T +import StudioTheme 1.0 as StudioTheme + +T.ToolTip { + id: control + + x: parent ? (parent.width - implicitWidth) / 2 : 0 + y: -implicitHeight - 3 + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + contentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + contentHeight + topPadding + bottomPadding) + + margins: 6 + padding: 4 + delay: 1000 + timeout: 5000 + closePolicy: T.Popup.CloseOnEscape | T.Popup.CloseOnPressOutsideParent | T.Popup.CloseOnReleaseOutsideParent + + contentItem: Text { + text: control.text + wrapMode: Text.Wrap + font.pixelSize: StudioTheme.Values.myFontSize + color: StudioTheme.Values.themeToolTipText + } + + background: Rectangle { + color: StudioTheme.Values.themeToolTipBackground + border.width: StudioTheme.Values.border + border.color: StudioTheme.Values.themeToolTipOutline + } +} + diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir index 61c35a00490..878b2307c38 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir @@ -45,5 +45,6 @@ TabBar 1.0 TabBar.qml TabButton 1.0 TabButton.qml TextArea 1.0 TextArea.qml TextField 1.0 TextField.qml +ToolTip 1.0 ToolTip.qml TranslationIndicator 1.0 TranslationIndicator.qml VerticalScrollBar 1.0 VerticalScrollBar.qml From c23155cdd4cb63fe51ea2136984ab5b306d8f210 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Tue, 20 Sep 2022 18:12:52 +0200 Subject: [PATCH 08/10] QmlDesigner: use hasMultiSelection in property editor backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: I7499e278f80b72f9ffc61bc60b28145ea6fda685 Reviewed-by: Henning Gründl --- .../components/propertyeditor/propertyeditorqmlbackend.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp index f66e7fb72c5..c38136cdd75 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp @@ -461,6 +461,9 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q } ); + contextObject()->setHasMultiSelection( + !qmlObjectNode.view()->singleSelectedModelNode().isValid()); + qCInfo(propertyEditorBenchmark) << "anchors:" << time.elapsed(); qCInfo(propertyEditorBenchmark) << "context:" << time.elapsed(); From 720d07928b956f68def5725e2a84e02b3ddb8dd7 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Tue, 20 Sep 2022 15:28:58 +0200 Subject: [PATCH 09/10] ClangCodeModel: Add missing check in findLocalUsages() When trying to find out whether a symbol refers to a local variable, we first look up its definition. Afterwards, we need to check whether that definition is located in the same file. This was forgotten, which lead to seemingly random weirdness when trying to rename non-local symbols. Change-Id: Icdcfd5583bf33346d8f0e3caf2751fd16f7d9602 Reviewed-by: Qt CI Bot Reviewed-by: Reviewed-by: David Schulz --- src/plugins/clangcodemodel/clangdclient.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 228126ef215..5cb961a673d 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -1691,7 +1691,8 @@ void ClangdClient::findLocalUsages(TextDocument *document, const QTextCursor &cu << link.targetLine << (link.targetColumn + 1); if (!d->localRefsData || id != d->localRefsData->id) return; - if (!link.hasValidTarget()) { + if (!link.hasValidTarget() || !d->localRefsData->document + || d->localRefsData->document->filePath() != link.targetFilePath) { d->localRefsData.reset(); return; } From 5ebb467cc622a9dc91ff4ec0f02de257f19d69a0 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Wed, 14 Sep 2022 09:52:09 +0200 Subject: [PATCH 10/10] Deviceshell: Cleanup shellProcess destruction Change-Id: I71507d9e74979fca461784575257e7bafd1b4838 Reviewed-by: Jarek Kobus --- src/libs/utils/deviceshell.cpp | 24 ++++++++++++------------ src/libs/utils/deviceshell.h | 2 +- src/libs/utils/qtcprocess.cpp | 1 + src/plugins/docker/dockerdevice.cpp | 4 ++-- src/plugins/remotelinux/linuxdevice.cpp | 2 +- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/libs/utils/deviceshell.cpp b/src/libs/utils/deviceshell.cpp index cf9d2139550..46284178750 100644 --- a/src/libs/utils/deviceshell.cpp +++ b/src/libs/utils/deviceshell.cpp @@ -187,12 +187,12 @@ DeviceShell::DeviceShell() DeviceShell::~DeviceShell() { - m_shellProcess->deleteLater(); - if (m_thread.isRunning()) { m_thread.quit(); m_thread.wait(); } + + QTC_CHECK(!m_shellProcess); } /*! @@ -247,7 +247,7 @@ DeviceShell::RunResult DeviceShell::run(const CommandLine &cmd, const QByteArray const int id = ++m_currentId; const auto it = m_commandOutput.insert(id, CommandRun{{-1, {}, {}}, &waiter}); - QMetaObject::invokeMethod(m_shellProcess, [this, id, cmd, stdInData]() { + QMetaObject::invokeMethod(m_shellProcess.get(), [this, id, cmd, stdInData]() { const QString command = QString("%1 \"%2\" %3\n") .arg(id) .arg(QString::fromLatin1(stdInData.toBase64())) @@ -302,13 +302,12 @@ void DeviceShell::startupFailed(const CommandLine &cmdLine) */ bool DeviceShell::start() { - m_shellProcess = new QtcProcess(); - connect(m_shellProcess, &QtcProcess::done, m_shellProcess, + m_shellProcess = std::make_unique(); + connect(m_shellProcess.get(), &QtcProcess::done, m_shellProcess.get(), [this] { emit done(m_shellProcess->resultData()); }); - connect(m_shellProcess, &QObject::destroyed, this, [this] { m_shellProcess = nullptr; }); - connect(&m_thread, &QThread::finished, m_shellProcess, [this] { closeShellProcess(); }); + connect(&m_thread, &QThread::finished, m_shellProcess.get(), [this] { closeShellProcess(); }, Qt::DirectConnection); - setupShellProcess(m_shellProcess); + setupShellProcess(m_shellProcess.get()); m_shellProcess->setProcessMode(ProcessMode::Writer); @@ -317,7 +316,7 @@ bool DeviceShell::start() bool result = false; QMetaObject::invokeMethod( - m_shellProcess, + m_shellProcess.get(), [this] { m_shellProcess->start(); @@ -326,10 +325,10 @@ bool DeviceShell::start() return false; } - connect(m_shellProcess, &QtcProcess::readyReadStandardOutput, m_shellProcess, [this] { + connect(m_shellProcess.get(), &QtcProcess::readyReadStandardOutput, m_shellProcess.get(), [this] { onReadyRead(); }); - connect(m_shellProcess, &QtcProcess::readyReadStandardError, m_shellProcess, [this] { + connect(m_shellProcess.get(), &QtcProcess::readyReadStandardError, m_shellProcess.get(), [this] { const QByteArray stdErr = m_shellProcess->readAllStandardError(); if (m_shellScriptState == State::Unknown) { @@ -347,7 +346,7 @@ bool DeviceShell::start() qCWarning(deviceShellLog) << "Received unexpected output on stderr:" << stdErr; }); - connect(m_shellProcess, &QtcProcess::done, m_shellProcess, [this]() { + connect(m_shellProcess.get(), &QtcProcess::done, m_shellProcess.get(), [this] { if (m_shellProcess->resultData().m_exitCode != EXIT_SUCCESS || m_shellProcess->resultData().m_exitStatus != QProcess::NormalExit) { qCWarning(deviceShellLog) << "Shell exited with error code:" @@ -400,6 +399,7 @@ void DeviceShell::closeShellProcess() if (!m_shellProcess->waitForFinished(2000)) m_shellProcess->terminate(); } + m_shellProcess.reset(); } } diff --git a/src/libs/utils/deviceshell.h b/src/libs/utils/deviceshell.h index 48a20c4e4b2..31577ed8187 100644 --- a/src/libs/utils/deviceshell.h +++ b/src/libs/utils/deviceshell.h @@ -96,7 +96,7 @@ private: QWaitCondition *waiter; }; - QtcProcess *m_shellProcess = nullptr; + std::unique_ptr m_shellProcess; QThread m_thread; int m_currentId{0}; diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index 62a2435cd1b..45362d1ed71 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -1027,6 +1027,7 @@ QtcProcess::QtcProcess(QObject *parent) : QObject(parent), d(new QtcProcessPrivate(this)) { + qRegisterMetaType("ProcessResultData"); static int qProcessExitStatusMeta = qRegisterMetaType(); static int qProcessProcessErrorMeta = qRegisterMetaType(); Q_UNUSED(qProcessExitStatusMeta) diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 2b7b671ef09..7c6092eaa9f 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -454,7 +454,7 @@ void DockerDevicePrivate::startContainer() return; qCWarning(dockerDeviceLog) << "Container shell encountered error:" << resultData.m_error; - m_shell.reset(); + m_shell.release()->deleteLater(); DockerApi::recheckDockerDaemon(); MessageManager::writeFlashing(tr("Docker daemon appears to be not running. " @@ -479,7 +479,7 @@ void DockerDevicePrivate::updateContainerAccess() if (m_shell) return; - startContainer(); + startContainer(); } void DockerDevice::setMounts(const QStringList &mounts) const diff --git a/src/plugins/remotelinux/linuxdevice.cpp b/src/plugins/remotelinux/linuxdevice.cpp index b41a1f19b82..ad591122505 100644 --- a/src/plugins/remotelinux/linuxdevice.cpp +++ b/src/plugins/remotelinux/linuxdevice.cpp @@ -814,7 +814,7 @@ public: cmd.addArg("/bin/sh"); m_shell.reset(new LinuxDeviceShell(cmd)); - connect(m_shell.get(), &DeviceShell::done, this, [this] { m_shell.reset(); }); + connect(m_shell.get(), &DeviceShell::done, this, [this] { m_shell.release()->deleteLater(); }); return m_shell->start(); }