From 0667e600470f6682709ebb5b6903fd6cf69561a3 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Tue, 25 Feb 2025 16:06:11 +0100 Subject: [PATCH 01/54] QmlDesigner: Adapt comparisons to C++20 Change-Id: Ic4855335cdc1deda9520b7954dd3fd1584c30d3f Reviewed-by: Thomas Hartmann --- src/libs/utils/smallstring.h | 2 +- src/libs/utils/smallstringview.h | 26 +++---------------- .../components/componentcore/viewmanager.cpp | 2 +- .../libs/designercore/include/auxiliarydata.h | 20 ++------------ 4 files changed, 7 insertions(+), 43 deletions(-) diff --git a/src/libs/utils/smallstring.h b/src/libs/utils/smallstring.h index 56ab91a2f48..f502c7e7489 100644 --- a/src/libs/utils/smallstring.h +++ b/src/libs/utils/smallstring.h @@ -174,7 +174,7 @@ public: SmallStringView toStringView() const noexcept { return SmallStringView(data(), size()); } - operator SmallStringView() const noexcept { return SmallStringView(data(), size()); } + constexpr operator SmallStringView() const noexcept { return SmallStringView(data(), size()); } explicit operator QLatin1StringView() const noexcept { diff --git a/src/libs/utils/smallstringview.h b/src/libs/utils/smallstringview.h index 09b2a2abaf7..44940707696 100644 --- a/src/libs/utils/smallstringview.h +++ b/src/libs/utils/smallstringview.h @@ -110,36 +110,16 @@ public: } }; -constexpr bool operator!=(SmallStringView first, SmallStringView second) noexcept +inline constexpr auto operator<=>(const SmallStringView &first, const SmallStringView &second) { - return std::string_view{first} != std::string_view{second}; + return std::string_view{first} <=> std::string_view{second}; } -constexpr bool operator==(SmallStringView first, SmallStringView second) noexcept +inline constexpr bool operator==(const SmallStringView &first, const SmallStringView &second) { return std::string_view{first} == std::string_view{second}; } -constexpr bool operator<(SmallStringView first, SmallStringView second) noexcept -{ - return std::string_view{first} < std::string_view{second}; -} - -constexpr bool operator>(SmallStringView first, SmallStringView second) noexcept -{ - return std::string_view{first} > std::string_view{second}; -} - -constexpr bool operator<=(SmallStringView first, SmallStringView second) noexcept -{ - return std::string_view{first} <= std::string_view{second}; -} - -constexpr bool operator>=(SmallStringView first, SmallStringView second) noexcept -{ - return std::string_view{first} >= std::string_view{second}; -} - constexpr int compare(SmallStringView first, SmallStringView second) noexcept { return first.compare(second); diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp index c2feedd3453..0788e0e1121 100644 --- a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp @@ -85,7 +85,7 @@ public: QmlModelState savedState; Internal::DebugView debugView; Sqlite::Database auxiliaryDataDatabase{ - Utils::PathString{Core::ICore::userResourcePath("auxiliary_data.db").toUrlishString()}, + Utils::PathString{Core::ICore::userResourcePath("auxiliary_data.db").path()}, Sqlite::JournalMode::Wal, Sqlite::LockingMode::Normal}; AuxiliaryPropertyStorageView auxiliaryDataKeyView; diff --git a/src/plugins/qmldesigner/libs/designercore/include/auxiliarydata.h b/src/plugins/qmldesigner/libs/designercore/include/auxiliarydata.h index e5d9403d2cb..b61ec68a50b 100644 --- a/src/plugins/qmldesigner/libs/designercore/include/auxiliarydata.h +++ b/src/plugins/qmldesigner/libs/designercore/include/auxiliarydata.h @@ -34,29 +34,13 @@ public: operator BasicAuxiliaryDataKey() const { return {type, name}; } + constexpr auto operator<=>(const BasicAuxiliaryDataKey &) const = default; + public: AuxiliaryDataType type = AuxiliaryDataType::None; NameType name; }; -template -bool operator<(const BasicAuxiliaryDataKey &first, const BasicAuxiliaryDataKey &second) -{ - return std::tie(first.type, first.name) < std::tie(second.type, second.name); -} - -template -bool operator==(const BasicAuxiliaryDataKey &first, const BasicAuxiliaryDataKey &second) -{ - return first.type == second.type && first.name == second.name; -} - -template -bool operator!=(const BasicAuxiliaryDataKey &first, const BasicAuxiliaryDataKey &second) -{ - return !(first == second); -} - using AuxiliaryDataKey = BasicAuxiliaryDataKey; using AuxiliaryDataKeyView = BasicAuxiliaryDataKey; using AuxiliaryData = std::pair; From 88026e33b3b8a0cbf2a5c53db0030b9a87a7812c Mon Sep 17 00:00:00 2001 From: Shrief Gabr Date: Wed, 19 Feb 2025 11:32:38 +0200 Subject: [PATCH 02/54] QmlDesigner: Fix timeline playhead sync issue Fixes: QDS-14683 Change-Id: Ifdb103b9b31240a290a3d847cc1bfada06b38161 Reviewed-by: Thomas Hartmann --- .../qmldesigner/instances/nodeinstanceview.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/plugins/qmldesigner/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/instances/nodeinstanceview.cpp index 368872a8e99..cf90fbf1ddc 100644 --- a/src/plugins/qmldesigner/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/instances/nodeinstanceview.cpp @@ -679,21 +679,6 @@ void NodeInstanceView::auxiliaryDataChanged(const ModelNode &node, NodeInstance instance = instanceForModelNode(node); PropertyValueContainer container{instance.instanceId(), key.name, value, TypeName(), key.type}; m_nodeInstanceServer->changeAuxiliaryValues({{container}}); - const PropertyName name = key.name.toByteArray(); - if (node.hasVariantProperty(name)) { - PropertyValueContainer container(instance.instanceId(), - name, - node.variantProperty(name).value(), - TypeName()); - ChangeValuesCommand changeValueCommand({container}); - m_nodeInstanceServer->changePropertyValues(changeValueCommand); - } else if (node.hasBindingProperty(name)) { - PropertyBindingContainer container{instance.instanceId(), - name, - node.bindingProperty(name).expression(), - TypeName()}; - m_nodeInstanceServer->changePropertyBindings({{container}}); - } } break; From cd79ad68453b8c4994dcb9bd352d8ceb3d42b843 Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Tue, 18 Feb 2025 14:04:45 +0100 Subject: [PATCH 03/54] QmlDesigner: Change run target when shortcut used When a user uses a shortcut to run the application in either normal mode or live preview and the target selected in the run button, change it accordingly to the last ran target. Change-Id: Id398dde35121570e267cabdc81019d899a0e6b2f Reviewed-by: Burak Hancerli Reviewed-by: Thomas Hartmann --- src/plugins/qmldesigner/components/runmanager/runmanager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/qmldesigner/components/runmanager/runmanager.cpp b/src/plugins/qmldesigner/components/runmanager/runmanager.cpp index 451990166b1..fd49d9a2f0f 100644 --- a/src/plugins/qmldesigner/components/runmanager/runmanager.cpp +++ b/src/plugins/qmldesigner/components/runmanager/runmanager.cpp @@ -175,6 +175,9 @@ RunManager::RunManager(DeviceShare::DeviceManager &deviceManager) [this](ProjectExplorer::RunControl *runControl) { qCDebug(runManagerLog) << "Run Control started."; + if (m_currentTargetId != runControl->runMode()) + selectRunTarget(runControl->runMode()); + m_runningTargets.append(QPointer(runControl)); setState(TargetState::Running); From 0d92d5277fcf6efc10ab1d7e34c44db15f3c9f2e Mon Sep 17 00:00:00 2001 From: Pranta Dastider Date: Tue, 25 Feb 2025 16:52:03 +0100 Subject: [PATCH 04/54] QmlDesigner: Update Webinar Demo doc to remove Tutorial text This patch updates the Webinar Demo documentation and removes the text related to Tutorial, as it does not exist anymore. Fixes: QDS-14198 Change-Id: Idfd167084637d595d4bf43c561ec0222cdd4c755 Reviewed-by: Mats Honkamaa --- doc/qtdesignstudio/examples/doc/webinardemo.qdoc | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/qtdesignstudio/examples/doc/webinardemo.qdoc b/doc/qtdesignstudio/examples/doc/webinardemo.qdoc index 8ab2d061eeb..2ecd0e9c3da 100644 --- a/doc/qtdesignstudio/examples/doc/webinardemo.qdoc +++ b/doc/qtdesignstudio/examples/doc/webinardemo.qdoc @@ -14,8 +14,6 @@ to \QDS and to edit them to create a UI. The following sections describe some of the main points of the webinar. - Select the \uicontrol Tutorials tab to watch the webinar video for the - full details. \section1 Exporting from Adobe Photoshop From f8e71efc5a7f619eea9e5aad6701b04dd08ac99e Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Wed, 26 Feb 2025 10:43:07 +0100 Subject: [PATCH 05/54] QmlDesigner: Add emit for isBoundChanged if expression is set The value in some cases is not changing and therefore the icon based on isBound was not updated. Change-Id: I6b0ed8df6e0f5854660e54b4753a4c15107cda66 Reviewed-by: Ali Kianian --- .../components/propertyeditor/propertyeditorvalue.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp index b4e7f9a6eac..01b18a08709 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp @@ -160,6 +160,7 @@ void PropertyEditorValue::setExpressionWithEmit(const QString &expression) emit expressionChanged(nameAsQString()); emit expressionChangedQml();// Note that we set the name in this case } + emit isBoundChanged(); } void PropertyEditorValue::setExpression(const QString &expression) From de4b091deaee19c6563125507cc1fd97cec829ad Mon Sep 17 00:00:00 2001 From: Rafal Stawarski Date: Thu, 20 Feb 2025 16:37:24 +0100 Subject: [PATCH 06/54] Connection Editor: ConnectionModelBackendDelegate refactoring ConnectionModelBackendDelegate refactoring to extract logic responsible for generating scripts to a reusable backend component. ScriptEditorBackend can be used in the property editor to handle script creation in scripting components (such as ScriptAction or StateChangeScript) and as a base for ConnectionModelBackendDelegate. Task-number: QDS-10449 Change-Id: Ie83f413f42bc1134ebb866aa6e7ea9d6b37da49b Reviewed-by: Thomas Hartmann --- .../ConnectionsDialogForm.qml | 27 +- .../connectionseditor/StatementEditor.qml | 29 +- src/plugins/qmldesigner/CMakeLists.txt | 15 +- .../connectioneditor/bindingmodel.cpp | 3 +- .../connectioneditor/bindingmodelitem.cpp | 2 +- .../connectioneditorlogging.cpp | 10 + .../connectioneditorlogging.h | 12 + .../connectioneditor/connectionmodel.cpp | 1437 +---------------- .../connectioneditor/connectionmodel.h | 238 +-- .../connectioneditor/connectionview.cpp | 6 +- .../dynamicpropertiesitem.cpp | 2 +- .../dynamicpropertiesmodel.cpp | 13 +- .../dynamicpropertiesproxymodel.cpp | 2 +- .../propertytreemodel.cpp | 18 +- .../propertytreemodel.h | 7 +- .../scripteditor/scripteditorbackend.cpp | 1373 ++++++++++++++++ .../scripteditor/scripteditorbackend.h | 250 +++ .../scripteditorevaluator.cpp} | 179 +- .../scripteditorevaluator.h} | 16 +- .../scripteditorstatements.cpp} | 88 +- .../scripteditorstatements.h} | 29 +- .../scripteditorutils.cpp} | 48 +- .../scripteditorutils.h} | 4 +- 23 files changed, 1964 insertions(+), 1844 deletions(-) create mode 100644 src/plugins/qmldesigner/components/connectioneditor/connectioneditorlogging.cpp create mode 100644 src/plugins/qmldesigner/components/connectioneditor/connectioneditorlogging.h rename src/plugins/qmldesigner/components/{connectioneditor => scripteditor}/propertytreemodel.cpp (98%) rename src/plugins/qmldesigner/components/{connectioneditor => scripteditor}/propertytreemodel.h (97%) create mode 100644 src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.cpp create mode 100644 src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.h rename src/plugins/qmldesigner/components/{connectioneditor/connectioneditorevaluator.cpp => scripteditor/scripteditorevaluator.cpp} (80%) rename src/plugins/qmldesigner/components/{connectioneditor/connectioneditorevaluator.h => scripteditor/scripteditorevaluator.h} (77%) rename src/plugins/qmldesigner/components/{connectioneditor/connectioneditorstatements.cpp => scripteditor/scripteditorstatements.cpp} (71%) rename src/plugins/qmldesigner/components/{connectioneditor/connectioneditorstatements.h => scripteditor/scripteditorstatements.h} (77%) rename src/plugins/qmldesigner/components/{connectioneditor/connectioneditorutils.cpp => scripteditor/scripteditorutils.cpp} (92%) rename src/plugins/qmldesigner/components/{connectioneditor/connectioneditorutils.h => scripteditor/scripteditorutils.h} (93%) diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml index 5ca4ed28d25..5e40aaa446c 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml @@ -6,6 +6,7 @@ import QtQuick.Controls import HelperWidgets as HelperWidgets import StudioControls as StudioControls import StudioTheme as StudioTheme +import ScriptEditorBackend Column { id: root @@ -71,32 +72,32 @@ Column { model: ListModel { ListElement { - value: ConnectionModelStatementDelegate.CallFunction + value: StatementDelegate.CallFunction text: qsTr("Call Function") enabled: true } ListElement { - value: ConnectionModelStatementDelegate.Assign + value: StatementDelegate.Assign text: qsTr("Assign") enabled: true } ListElement { - value: ConnectionModelStatementDelegate.ChangeState + value: StatementDelegate.ChangeState text: qsTr("Change State") enabled: true } ListElement { - value: ConnectionModelStatementDelegate.SetProperty + value: StatementDelegate.SetProperty text: qsTr("Set Property") enabled: true } ListElement { - value: ConnectionModelStatementDelegate.PrintMessage + value: StatementDelegate.PrintMessage text: qsTr("Print Message") enabled: true } ListElement { - value: ConnectionModelStatementDelegate.Custom + value: StatementDelegate.Custom text: qsTr("Custom") enabled: false } @@ -106,7 +107,7 @@ Column { StatementEditor { width: root.width - actionType: action.currentValue ?? ConnectionModelStatementDelegate.Custom + actionType: action.currentValue ?? StatementDelegate.Custom horizontalSpacing: root.horizontalSpacing columnWidth: root.columnWidth statement: backend.okStatement @@ -122,7 +123,7 @@ Column { iconSize: StudioTheme.Values.baseFontSize iconFontFamily: StudioTheme.Constants.font.family anchors.horizontalCenter: parent.horizontalCenter - visible: action.currentValue !== ConnectionModelStatementDelegate.Custom && !backend.hasCondition + visible: action.currentValue !== StatementDelegate.Custom && !backend.hasCondition onClicked: backend.addCondition() } @@ -135,7 +136,7 @@ Column { iconSize: StudioTheme.Values.baseFontSize iconFontFamily: StudioTheme.Constants.font.family anchors.horizontalCenter: parent.horizontalCenter - visible: action.currentValue !== ConnectionModelStatementDelegate.Custom && backend.hasCondition + visible: action.currentValue !== StatementDelegate.Custom && backend.hasCondition onClicked: backend.removeCondition() } @@ -186,7 +187,7 @@ Column { iconSize: StudioTheme.Values.baseFontSize iconFontFamily: StudioTheme.Constants.font.family anchors.horizontalCenter: parent.horizontalCenter - visible: action.currentValue !== ConnectionModelStatementDelegate.Custom + visible: action.currentValue !== StatementDelegate.Custom && backend.hasCondition && !backend.hasElse onClicked: backend.addElse() @@ -200,7 +201,7 @@ Column { iconSize: StudioTheme.Values.baseFontSize iconFontFamily: StudioTheme.Constants.font.family anchors.horizontalCenter: parent.horizontalCenter - visible: action.currentValue !== ConnectionModelStatementDelegate.Custom + visible: action.currentValue !== StatementDelegate.Custom && backend.hasCondition && backend.hasElse onClicked: backend.removeElse() @@ -209,13 +210,13 @@ Column { //Else Statement StatementEditor { width: root.width - actionType: action.currentValue ?? ConnectionModelStatementDelegate.Custom + actionType: action.currentValue ?? StatementDelegate.Custom horizontalSpacing: root.horizontalSpacing columnWidth: root.columnWidth statement: backend.koStatement backend: root.backend spacing: root.verticalSpacing - visible: action.currentValue !== ConnectionModelStatementDelegate.Custom + visible: action.currentValue !== StatementDelegate.Custom && backend.hasCondition && backend.hasElse } diff --git a/share/qtcreator/qmldesigner/connectionseditor/StatementEditor.qml b/share/qtcreator/qmldesigner/connectionseditor/StatementEditor.qml index f198d84a103..0783f3cb7a0 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/StatementEditor.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/StatementEditor.qml @@ -7,6 +7,7 @@ import HelperWidgets as HelperWidgets import StudioControls as StudioControls import StudioTheme as StudioTheme import ConnectionsEditorEditorBackend +import ScriptEditorBackend Column { id: root @@ -22,7 +23,7 @@ Column { // Call Function Row { - visible: root.actionType === ConnectionModelStatementDelegate.CallFunction + visible: root.actionType === StatementDelegate.CallFunction spacing: root.horizontalSpacing PopupLabel { @@ -39,7 +40,7 @@ Column { } Row { - visible: root.actionType === ConnectionModelStatementDelegate.CallFunction + visible: root.actionType === StatementDelegate.CallFunction spacing: root.horizontalSpacing StudioControls.TopLevelComboBox { @@ -68,7 +69,7 @@ Column { // Assign Row { - visible: root.actionType === ConnectionModelStatementDelegate.Assign + visible: root.actionType === StatementDelegate.Assign spacing: root.horizontalSpacing PopupLabel { @@ -84,7 +85,7 @@ Column { } Row { - visible: root.actionType === ConnectionModelStatementDelegate.Assign + visible: root.actionType === StatementDelegate.Assign spacing: root.horizontalSpacing StudioControls.TopLevelComboBox { @@ -114,7 +115,7 @@ Column { } Row { - visible: root.actionType === ConnectionModelStatementDelegate.Assign + visible: root.actionType === StatementDelegate.Assign spacing: root.horizontalSpacing StudioControls.TopLevelComboBox { @@ -145,7 +146,7 @@ Column { // Change State Row { - visible: root.actionType === ConnectionModelStatementDelegate.ChangeState + visible: root.actionType === StatementDelegate.ChangeState spacing: root.horizontalSpacing PopupLabel { @@ -162,7 +163,7 @@ Column { } Row { - visible: root.actionType === ConnectionModelStatementDelegate.ChangeState + visible: root.actionType === StatementDelegate.ChangeState spacing: root.horizontalSpacing StudioControls.TopLevelComboBox { @@ -191,7 +192,7 @@ Column { // Set Property Row { - visible: root.actionType === ConnectionModelStatementDelegate.SetProperty + visible: root.actionType === StatementDelegate.SetProperty spacing: root.horizontalSpacing PopupLabel { @@ -208,7 +209,7 @@ Column { } Row { - visible: root.actionType === ConnectionModelStatementDelegate.SetProperty + visible: root.actionType === StatementDelegate.SetProperty spacing: root.horizontalSpacing StudioControls.TopLevelComboBox { @@ -238,14 +239,14 @@ Column { PopupLabel { width: root.columnWidth - visible: root.actionType === ConnectionModelStatementDelegate.SetProperty + visible: root.actionType === StatementDelegate.SetProperty text: qsTr("Value") tooltip: qsTr("Sets the value of the property of the component that is affected by the action of the Target component's Signal.") } StudioControls.TextField { id: setPropertyArgument - visible: root.actionType === ConnectionModelStatementDelegate.SetProperty + visible: root.actionType === StatementDelegate.SetProperty width: root.width actionIndicatorVisible: false translationIndicatorVisible: false @@ -259,14 +260,14 @@ Column { // Print Message PopupLabel { width: root.columnWidth - visible: root.actionType === ConnectionModelStatementDelegate.PrintMessage + visible: root.actionType === StatementDelegate.PrintMessage text: qsTr("Message") tooltip: qsTr("Sets a text that is printed when the Signal of the Target component initiates.") } StudioControls.TextField { id: messageString - visible: root.actionType === ConnectionModelStatementDelegate.PrintMessage + visible: root.actionType === StatementDelegate.PrintMessage width: root.width actionIndicatorVisible: false translationIndicatorVisible: false @@ -278,7 +279,7 @@ Column { // Custom PopupLabel { - visible: root.actionType === ConnectionModelStatementDelegate.Custom + visible: root.actionType === StatementDelegate.Custom text: qsTr("Custom Connections can only be edited with the binding editor") width: root.width horizontalAlignment: Text.AlignHCenter diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 0ba34894e2a..1c58e2ba88f 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -571,6 +571,16 @@ extend_qtc_plugin(QmlDesigner annotationeditor.qrc ) +extend_qtc_plugin(QmlDesigner + SOURCES_PREFIX components/scripteditor + SOURCES + scripteditorstatements.cpp scripteditorstatements.h + scripteditorevaluator.cpp scripteditorevaluator.h + scripteditorutils.cpp scripteditorutils.h + propertytreemodel.cpp propertytreemodel.h + scripteditorbackend.cpp scripteditorbackend.h +) + extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/connectioneditor SOURCES @@ -578,15 +588,12 @@ extend_qtc_plugin(QmlDesigner bindingmodel.cpp bindingmodel.h bindingmodelitem.cpp bindingmodelitem.h connectioneditor.qrc - connectioneditorevaluator.cpp connectioneditorevaluator.h - connectioneditorstatements.cpp connectioneditorstatements.h connectionmodel.cpp connectionmodel.h connectionview.cpp connectionview.h dynamicpropertiesmodel.cpp dynamicpropertiesmodel.h dynamicpropertiesitem.cpp dynamicpropertiesitem.h - connectioneditorutils.cpp connectioneditorutils.h selectiondynamicpropertiesproxymodel.cpp selectiondynamicpropertiesproxymodel.h - propertytreemodel.cpp propertytreemodel.h + connectioneditorlogging.cpp connectioneditorlogging.h ) extend_qtc_plugin(QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp index 4ef8a4ed378..4b4e59cfe00 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp @@ -3,10 +3,11 @@ #include "bindingmodel.h" #include "bindingmodelitem.h" -#include "connectioneditorutils.h" +#include "connectioneditorlogging.h" #include "connectionview.h" #include "modelfwd.h" +#include #include #include #include diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodelitem.cpp b/src/plugins/qmldesigner/components/connectioneditor/bindingmodelitem.cpp index e5e1372dd65..a0976564e90 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/bindingmodelitem.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodelitem.cpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "bindingmodelitem.h" -#include "connectioneditorutils.h" +#include #include #include diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorlogging.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorlogging.cpp new file mode 100644 index 00000000000..0ed91b33ab3 --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorlogging.cpp @@ -0,0 +1,10 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "connectioneditorlogging.h" + +namespace QmlDesigner { + +Q_LOGGING_CATEGORY(ConnectionEditorLog, "qtc.qtquickdesigner.connectioneditor", QtWarningMsg) + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorlogging.h b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorlogging.h new file mode 100644 index 00000000000..673a5b1a32f --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorlogging.h @@ -0,0 +1,12 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace QmlDesigner { + +Q_DECLARE_LOGGING_CATEGORY(ConnectionEditorLog) + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp index f4462f911f5..c0a13a788e8 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp @@ -2,41 +2,22 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "connectionmodel.h" -#include "connectioneditorutils.h" +#include "connectioneditorlogging.h" #include "connectionview.h" -#include "utils/algorithm.h" -#include -#include -#include -#include -#include #include -#include #include -#include #include #include #include #include -#include +#include +#include #include -#include - -#include #include -#include -#include -#include -#include -#include -#include namespace { - -const char defaultCondition[] = "condition"; - QStringList propertyNameListToStringList(const QmlDesigner::PropertyNameList &propertyNameList) { QStringList stringList; @@ -308,8 +289,7 @@ void ConnectionModel::updateCustomData(QStandardItem *item, const SignalHandlerP //anything else is assignment // e.g. foo.bal = foo2.bula ; foo.bal = "literal" ; goo.gal = true - item->setData(tr(ConnectionEditorEvaluator::getDisplayStringForType( - signalHandlerProperty.source()) + item->setData(tr(ScriptEditorEvaluator::getDisplayStringForType(signalHandlerProperty.source()) .toLatin1()), UserRoles::ActionTypeRole); } @@ -344,21 +324,6 @@ ModelNode ConnectionModel::getTargetNodeForConnection(const ModelNode &connectio return result; } -static QString addOnToSignalName(const QString &signal) -{ - if (signal.isEmpty()) - return {}; - - static const QRegularExpression rx("^on[A-Z]"); - if (rx.match(signal).hasMatch()) - return signal; - - QString ret = signal; - ret[0] = ret.at(0).toUpper(); - ret.prepend("on"); - return ret; -} - static PropertyName getFirstSignalForTarget(const NodeMetaInfo &target) { PropertyName ret = "clicked"; @@ -738,208 +703,22 @@ QHash ConnectionModel::roleNames() const return roleNames; } -ConnectionModelBackendDelegate::ConnectionModelBackendDelegate(ConnectionModel *model) - : m_signalDelegate(model->connectionView()) - , m_okStatementDelegate(model) - , m_koStatementDelegate(model) - , m_conditionListModel(model) - , m_propertyTreeModel(model->connectionView()) - , m_propertyListProxyModel(&m_propertyTreeModel) - , m_model(model) -{ - connect(&m_signalDelegate, &PropertyTreeModelDelegate::commitData, this, [this] { - handleTargetChanged(); - }); - - connect(&m_okStatementDelegate, - &ConnectionModelStatementDelegate::statementChanged, - this, - [this] { handleOkStatementChanged(); }); - - connect(&m_koStatementDelegate, - &ConnectionModelStatementDelegate::statementChanged, - this, - [this] { handleKOStatementChanged(); }); - - connect(&m_conditionListModel, &ConditionListModel::conditionChanged, this, [this] { - handleConditionChanged(); - }); - - m_signalDelegate.setPropertyType(PropertyTreeModel::SignalType); -} - -QString generateDefaultStatement(ConnectionModelBackendDelegate::ActionType actionType, - const QString &rootId) -{ - switch (actionType) { - case ConnectionModelStatementDelegate::CallFunction: - return "Qt.quit()"; - case ConnectionModelStatementDelegate::Assign: - return QString("%1.visible = %1.visible").arg(rootId); - case ConnectionModelStatementDelegate::ChangeState: - return QString("%1.state = \"\"").arg(rootId); - case ConnectionModelStatementDelegate::SetProperty: - return QString("%1.visible = true").arg(rootId); - case ConnectionModelStatementDelegate::PrintMessage: - return QString("console.log(\"test\")").arg(rootId); - case ConnectionModelStatementDelegate::Custom: - return {}; - }; - - //Qt.quit() - //console.log("test") - //root.state = "" - //root.visible = root.visible - //root.visible = true - - return {}; -} - -void ConnectionModelBackendDelegate::changeActionType(ActionType actionType) -{ - QTC_ASSERT(actionType != ConnectionModelStatementDelegate::Custom, return ); - - ConnectionModel *model = m_model; - - QTC_ASSERT(model, return ); - QTC_ASSERT(model->connectionView()->isAttached(), return ); - - const QString validId = model->connectionView()->rootModelNode().validId(); - - SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); - - // Do not take ko into account for now - - model->connectionView() - ->executeInTransaction("ConnectionModelBackendDelegate::removeCondition", [&]() { - ConnectionEditorStatements::MatchedStatement &okStatement - = ConnectionEditorStatements::okStatement(m_handler); - - ConnectionEditorStatements::MatchedStatement &koStatement - = ConnectionEditorStatements::koStatement(m_handler); - - koStatement = ConnectionEditorStatements::EmptyBlock(); - - //We expect a valid id on the root node - const QString validId = model->connectionView()->rootModelNode().validId(); - QString statementSource = generateDefaultStatement(actionType, validId); - - auto tempHandler = ConnectionEditorEvaluator::parseStatement(statementSource); - - auto newOkStatement = ConnectionEditorStatements::okStatement(tempHandler); - - QTC_ASSERT(!ConnectionEditorStatements::isEmptyStatement(newOkStatement), return ); - - okStatement = newOkStatement; - - QString newSource = ConnectionEditorStatements::toJavascript(m_handler); - - signalHandlerProperty.setSource(newSource); - }); - - setSource(signalHandlerProperty.source()); - - setupHandlerAndStatements(); - setupCondition(); -} - -void ConnectionModelBackendDelegate::addCondition() -{ - ConnectionEditorStatements::MatchedStatement okStatement - = ConnectionEditorStatements::okStatement(m_handler); - - ConnectionEditorStatements::MatchedCondition newCondition; - - ConnectionEditorStatements::Variable variable; - variable.nodeId = defaultCondition; - newCondition.statements.append(variable); - - ConnectionEditorStatements::ConditionalStatement conditionalStatement; - - conditionalStatement.ok = okStatement; - conditionalStatement.condition = newCondition; - - m_handler = conditionalStatement; - - QString newSource = ConnectionEditorStatements::toJavascript(m_handler); - - commitNewSource(newSource); - - setupHandlerAndStatements(); - setupCondition(); -} - -void ConnectionModelBackendDelegate::removeCondition() -{ - ConnectionEditorStatements::MatchedStatement okStatement - = ConnectionEditorStatements::okStatement(m_handler); - - m_handler = okStatement; - - QString newSource = ConnectionEditorStatements::toJavascript(m_handler); - - commitNewSource(newSource); - - setupHandlerAndStatements(); - setupCondition(); -} - -void ConnectionModelBackendDelegate::addElse() -{ - ConnectionEditorStatements::MatchedStatement okStatement - = ConnectionEditorStatements::okStatement(m_handler); - - auto &condition = ConnectionEditorStatements::conditionalStatement(m_handler); - condition.ko = condition.ok; - - QString newSource = ConnectionEditorStatements::toJavascript(m_handler); - - - commitNewSource(newSource); - setupHandlerAndStatements(); -} - -void ConnectionModelBackendDelegate::removeElse() -{ - ConnectionEditorStatements::MatchedStatement okStatement - = ConnectionEditorStatements::okStatement(m_handler); - - auto &condition = ConnectionEditorStatements::conditionalStatement(m_handler); - condition.ko = ConnectionEditorStatements::EmptyBlock(); - - QString newSource = ConnectionEditorStatements::toJavascript(m_handler); - - - commitNewSource(newSource); - setupHandlerAndStatements(); -} - -void ConnectionModelBackendDelegate::setNewSource(const QString &newSource) -{ - setSource(newSource); - commitNewSource(newSource); - setupHandlerAndStatements(); - setupCondition(); -} - int ConnectionModelBackendDelegate::currentRow() const { return m_currentRow; } -static QString removeOnFromSignalName(const QString &signal) +ConnectionModelBackendDelegate::ConnectionModelBackendDelegate(ConnectionModel *model) + : ScriptEditorBackend(model->connectionView()) + , m_signalDelegate(model->connectionView()) + , m_model(model) { - if (signal.isEmpty()) - return {}; + connect(&m_signalDelegate, + &PropertyTreeModelDelegate::commitData, + this, + &ConnectionModelBackendDelegate::handleTargetChanged); - static const QRegularExpression rx("^on[A-Z]"); - if (!rx.match(signal).hasMatch()) - return signal; - - QString ret = signal; - ret.remove(0, 2); - ret[0] = ret.at(0).toLower(); - return ret; + m_signalDelegate.setPropertyType(PropertyTreeModel::SignalType); } void ConnectionModelBackendDelegate::setCurrentRow(int i) @@ -954,22 +733,22 @@ void ConnectionModelBackendDelegate::setCurrentRow(int i) void ConnectionModelBackendDelegate::update() { - if (m_blockReflection) - return; - if (m_currentRow == -1) return; - m_propertyTreeModel.resetModel(); - m_propertyListProxyModel.setRowAndInternalId(0, internalRootIndex); + if (blockReflection()) { + return; + } + + ScriptEditorBackend::update(); ConnectionModel *model = m_model; - QTC_ASSERT(model, return ); + QTC_ASSERT(model, return); if (!model->connectionView()->isAttached()) return; - SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); + auto signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); QStringList targetNodes; @@ -988,65 +767,8 @@ void ConnectionModelBackendDelegate::update() if (!targetNodes.contains(targetNodeName)) targetNodes.append(targetNodeName); - setSource(signalHandlerProperty.source()); - m_signalDelegate.setup(targetNodeName, removeOnFromSignalName(QString::fromUtf8(signalHandlerProperty.name()))); - - setupHandlerAndStatements(); - - setupCondition(); - - QTC_ASSERT(model, return ); -} - -void ConnectionModelBackendDelegate::jumpToCode() -{ - ConnectionModel *model = m_model; - - QTC_ASSERT(model, return ); - QTC_ASSERT(model->connectionView()->isAttached(), return ); - SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); - - ModelNodeOperations::jumpToCode(signalHandlerProperty.parentModelNode()); -} - -void ConnectionModelBackendDelegate::handleException() -{ - QMessageBox::warning(nullptr, tr("Error"), m_exceptionError); -} - -bool ConnectionModelBackendDelegate::hasCondition() const -{ - return m_hasCondition; -} - -bool ConnectionModelBackendDelegate::hasElse() const -{ - return m_hasElse; -} - -void ConnectionModelBackendDelegate::setHasCondition(bool b) -{ - if (b == m_hasCondition) - return; - - m_hasCondition = b; - emit hasConditionChanged(); -} - -void ConnectionModelBackendDelegate::setHasElse(bool b) -{ - if (b == m_hasElse) - return; - - m_hasElse = b; - emit hasElseChanged(); -} - -ConnectionModelBackendDelegate::ActionType ConnectionModelBackendDelegate::actionType() const -{ - return m_actionType; } PropertyTreeModelDelegate *ConnectionModelBackendDelegate::signal() @@ -1054,135 +776,21 @@ PropertyTreeModelDelegate *ConnectionModelBackendDelegate::signal() return &m_signalDelegate; } -ConnectionModelStatementDelegate *ConnectionModelBackendDelegate::okStatement() -{ - return &m_okStatementDelegate; -} - -ConnectionModelStatementDelegate *ConnectionModelBackendDelegate::koStatement() -{ - return &m_koStatementDelegate; -} - -ConditionListModel *ConnectionModelBackendDelegate::conditionListModel() -{ - return &m_conditionListModel; -} - -QString ConnectionModelBackendDelegate::indentedSource() const -{ - if (m_source.isEmpty()) - return {}; - - QTextDocument doc(m_source); - IndentingTextEditModifier mod(&doc); - - mod.indent(0, m_source.length() - 1); - return mod.text(); -} - -QString ConnectionModelBackendDelegate::source() const -{ - return m_source; -} - -void ConnectionModelBackendDelegate::setSource(const QString &source) -{ - if (source == m_source) - return; - - m_source = source; - emit sourceChanged(); -} - -PropertyTreeModel *ConnectionModelBackendDelegate::propertyTreeModel() -{ - return &m_propertyTreeModel; -} - -PropertyListProxyModel *ConnectionModelBackendDelegate::propertyListProxyModel() -{ - return &m_propertyListProxyModel; -} - -void ConnectionModelBackendDelegate::setupCondition() -{ - auto &condition = ConnectionEditorStatements::matchedCondition(m_handler); - m_conditionListModel.setCondition(ConnectionEditorStatements::matchedCondition(m_handler)); - setHasCondition(!condition.statements.isEmpty()); -} - -void ConnectionModelBackendDelegate::setupHandlerAndStatements() -{ - ConnectionModel *model = m_model; - QTC_ASSERT(model, return ); - SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); - - if (signalHandlerProperty.source().isEmpty()) { - m_actionType = ConnectionModelStatementDelegate::Custom; - m_handler = ConnectionEditorStatements::EmptyBlock(); - } else { - m_handler = ConnectionEditorEvaluator::parseStatement(signalHandlerProperty.source()); - - const QString statementType = QmlDesigner::ConnectionEditorStatements::toDisplayName( - m_handler); - - if (statementType == ConnectionEditorStatements::EMPTY_DISPLAY_NAME) { - m_actionType = ConnectionModelStatementDelegate::Custom; - } else if (statementType == ConnectionEditorStatements::ASSIGNMENT_DISPLAY_NAME) { - m_actionType = ConnectionModelStatementDelegate::Assign; - //setupAssignment(); - } else if (statementType == ConnectionEditorStatements::SETPROPERTY_DISPLAY_NAME) { - m_actionType = ConnectionModelStatementDelegate::SetProperty; - //setupSetProperty(); - } else if (statementType == ConnectionEditorStatements::FUNCTION_DISPLAY_NAME) { - m_actionType = ConnectionModelStatementDelegate::CallFunction; - //setupCallFunction(); - } else if (statementType == ConnectionEditorStatements::SETSTATE_DISPLAY_NAME) { - m_actionType = ConnectionModelStatementDelegate::ChangeState; - //setupChangeState(); - } else if (statementType == ConnectionEditorStatements::LOG_DISPLAY_NAME) { - m_actionType = ConnectionModelStatementDelegate::PrintMessage; - //setupPrintMessage(); - } else { - m_actionType = ConnectionModelStatementDelegate::Custom; - } - } - - ConnectionEditorStatements::MatchedStatement &okStatement - = ConnectionEditorStatements::okStatement(m_handler); - m_okStatementDelegate.setStatement(okStatement); - m_okStatementDelegate.setActionType(m_actionType); - - ConnectionEditorStatements::MatchedStatement &koStatement - = ConnectionEditorStatements::koStatement(m_handler); - - if (!ConnectionEditorStatements::isEmptyStatement(koStatement)) { - m_koStatementDelegate.setStatement(koStatement); - m_koStatementDelegate.setActionType(m_actionType); - } - - ConnectionEditorStatements::isEmptyStatement(koStatement); - setHasElse(!ConnectionEditorStatements::isEmptyStatement(koStatement)); - - emit actionTypeChanged(); -} - void ConnectionModelBackendDelegate::handleTargetChanged() { ConnectionModel *model = m_model; - QTC_ASSERT(model, return ); + QTC_ASSERT(model, return); - QTC_ASSERT(model->connectionView()->isAttached(), return ); + QTC_ASSERT(model->connectionView()->isAttached(), return); - SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); + SignalHandlerProperty signalHandlerProperty = getSignalHandlerProperty(); const PropertyName handlerName = addOnToSignalName(m_signalDelegate.name()).toUtf8(); const auto parentModelNode = signalHandlerProperty.parentModelNode(); - QTC_ASSERT(parentModelNode.isValid(), return ); + QTC_ASSERT(parentModelNode.isValid(), return); const auto newId = m_signalDelegate.id(); @@ -1190,8 +798,9 @@ void ConnectionModelBackendDelegate::handleTargetChanged() model->connectionView() ->executeInTransaction("ConnectionModelBackendDelegate::handleTargetChanged", [&]() { - const auto oldTargetNodeName - = parentModelNode.bindingProperty("target").resolveToModelNode().id(); + const auto oldTargetNodeName = parentModelNode.bindingProperty("target") + .resolveToModelNode() + .id(); if (signalHandlerProperty.name() != handlerName) { const auto expression = signalHandlerProperty.source(); @@ -1212,996 +821,38 @@ void ConnectionModelBackendDelegate::handleTargetChanged() } }); - model->selectProperty(model->connectionView() - ->modelNodeForInternalId(internalId) - .signalHandlerProperty(handlerName)); + model->selectProperty( + model->connectionView()->modelNodeForInternalId(internalId).signalHandlerProperty(handlerName)); } -void ConnectionModelBackendDelegate::handleOkStatementChanged() +AbstractProperty ConnectionModelBackendDelegate::getSourceProperty() const { - ConnectionEditorStatements::MatchedStatement &okStatement - = ConnectionEditorStatements::okStatement(m_handler); - - okStatement = m_okStatementDelegate.statement(); //TODO why? - - QString newSource = ConnectionEditorStatements::toJavascript(m_handler); - - commitNewSource(newSource); + return getSignalHandlerProperty(); } -void ConnectionModelBackendDelegate::handleKOStatementChanged() +void ConnectionModelBackendDelegate::setPropertySource(const QString &source) { - ConnectionEditorStatements::MatchedStatement &koStatement - = ConnectionEditorStatements::koStatement(m_handler); + auto property = getSourceProperty(); - koStatement = m_koStatementDelegate.statement(); //TODO why? + QTC_ASSERT(property.isValid(), return); - QString newSource = ConnectionEditorStatements::toJavascript(m_handler); - - commitNewSource(newSource); -} - -void ConnectionModelBackendDelegate::handleConditionChanged() -{ - ConnectionModel *model = m_model; - - QTC_ASSERT(model, return ); - QTC_ASSERT(model->connectionView()->isAttached(), return ); - - ConnectionEditorStatements::MatchedCondition &condition - = ConnectionEditorStatements::matchedCondition(m_handler); - condition = m_conditionListModel.condition(); //why? - QString newSource = ConnectionEditorStatements::toJavascript(m_handler); - - commitNewSource(newSource); -} - -void ConnectionModelBackendDelegate::commitNewSource(const QString &source) -{ - ConnectionModel *model = m_model; - - QTC_ASSERT(model, return ); - - QTC_ASSERT(model->connectionView()->isAttached(), return ); - - SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); - - m_blockReflection = true; - model->connectionView()->executeInTransaction("ConnectionModelBackendDelegate::commitNewSource", - [&]() { - signalHandlerProperty.setSource(source); - }); - - setSource(signalHandlerProperty.source()); - m_blockReflection = false; -} - -static ConnectionEditorStatements::MatchedStatement emptyStatement; - -ConnectionModelStatementDelegate::ConnectionModelStatementDelegate(ConnectionModel *model) - : m_functionDelegate(model->connectionView()) - , m_lhsDelegate(model->connectionView()) - , m_rhsAssignmentDelegate(model->connectionView()) - , m_statement(emptyStatement) - , m_model(model) -{ - m_functionDelegate.setPropertyType(PropertyTreeModel::SlotType); - - connect(&m_functionDelegate, &PropertyTreeModelDelegate::commitData, this, [this] { - handleFunctionChanged(); - }); - - connect(&m_rhsAssignmentDelegate, &PropertyTreeModelDelegate::commitData, this, [this] { - handleRhsAssignmentChanged(); - }); - - connect(&m_lhsDelegate, &PropertyTreeModelDelegate::commitData, this, [this] { - handleLhsChanged(); - }); - - connect(&m_stringArgument, &StudioQmlTextBackend::activated, this, [this] { - handleStringArgumentChanged(); - }); - - connect(&m_states, &StudioQmlComboBoxBackend::activated, this, [this] { - handleStateChanged(); - }); - - connect(&m_stateTargets, &StudioQmlComboBoxBackend::activated, this, [this] { - handleStateTargetsChanged(); - }); -} - -void ConnectionModelStatementDelegate::setActionType(ActionType type) -{ - if (m_actionType == type) + if (source.isEmpty()) return; - m_actionType = type; - emit actionTypeChanged(); - setup(); -} - -void ConnectionModelStatementDelegate::setup() -{ - switch (m_actionType) { - case CallFunction: - setupCallFunction(); - break; - case Assign: - setupAssignment(); - break; - case ChangeState: - setupChangeState(); - break; - case SetProperty: - setupSetProperty(); - break; - case PrintMessage: - setupPrintMessage(); - break; - case Custom: - break; - }; -} - -void ConnectionModelStatementDelegate::setStatement( - ConnectionEditorStatements::MatchedStatement &statement) -{ - m_statement = statement; - setup(); -} - -ConnectionEditorStatements::MatchedStatement &ConnectionModelStatementDelegate::statement() -{ - return m_statement; -} - -ConnectionModelStatementDelegate::ActionType ConnectionModelStatementDelegate::actionType() const -{ - return m_actionType; -} - -PropertyTreeModelDelegate *ConnectionModelStatementDelegate::function() -{ - return &m_functionDelegate; -} - -PropertyTreeModelDelegate *ConnectionModelStatementDelegate::lhs() -{ - return &m_lhsDelegate; -} - -PropertyTreeModelDelegate *ConnectionModelStatementDelegate::rhsAssignment() -{ - return &m_rhsAssignmentDelegate; -} - -StudioQmlTextBackend *ConnectionModelStatementDelegate::stringArgument() -{ - return &m_stringArgument; -} - -StudioQmlComboBoxBackend *ConnectionModelStatementDelegate::stateTargets() -{ - return &m_stateTargets; -} - -StudioQmlComboBoxBackend *ConnectionModelStatementDelegate::states() -{ - return &m_states; -} - -void ConnectionModelStatementDelegate::handleFunctionChanged() -{ - QTC_ASSERT(std::holds_alternative(m_statement), - return ); - - ConnectionEditorStatements::MatchedFunction &functionStatement - = std::get(m_statement); - - functionStatement.functionName = m_functionDelegate.name(); - functionStatement.nodeId = m_functionDelegate.id(); - - emit statementChanged(); -} - -void ConnectionModelStatementDelegate::handleLhsChanged() -{ - if (m_actionType == Assign) { - QTC_ASSERT(std::holds_alternative(m_statement), - return ); - - ConnectionEditorStatements::Assignment &assignmentStatement - = std::get(m_statement); - - assignmentStatement.lhs.nodeId = m_lhsDelegate.id(); - assignmentStatement.lhs.propertyName = m_lhsDelegate.name(); - - } else if (m_actionType == SetProperty) { - QTC_ASSERT(std::holds_alternative(m_statement), - return ); - - ConnectionEditorStatements::PropertySet &setPropertyStatement - = std::get(m_statement); - - setPropertyStatement.lhs.nodeId = m_lhsDelegate.id(); - setPropertyStatement.lhs.propertyName = m_lhsDelegate.name(); - } else { - QTC_ASSERT(false, return ); - } - - emit statementChanged(); -} - -void ConnectionModelStatementDelegate::handleRhsAssignmentChanged() -{ - QTC_ASSERT(std::holds_alternative(m_statement), return ); - - ConnectionEditorStatements::Assignment &assignmentStatement - = std::get(m_statement); - - assignmentStatement.rhs.nodeId = m_rhsAssignmentDelegate.id(); - assignmentStatement.rhs.propertyName = m_rhsAssignmentDelegate.name(); - - setupPropertyType(); - - emit statementChanged(); -} - -static ConnectionEditorStatements::Literal parseTextArgument(const QString &text) -{ - if (text.startsWith("\"") && text.endsWith("\"")) { - QString ret = text; - ret.remove(0, 1); - ret.chop(1); - return ret; - } - - if (text == "true") - return true; - - if (text == "false") - return false; - - bool ok = true; - double d = text.toDouble(&ok); - if (ok) - return d; - - return text; -} - -static ConnectionEditorStatements::ComparativeStatement parseTextArgumentComparativeStatement( - const QString &text) -{ - if (text.startsWith("\"") && text.endsWith("\"")) { - QString ret = text; - ret.remove(0, 1); - ret.chop(1); - return ret; - } - - if (text == "true") - return true; - - if (text == "false") - return false; - - bool ok = true; - double d = text.toDouble(&ok); - if (ok) - return d; - - return text; -} - -static ConnectionEditorStatements::RightHandSide parseLogTextArgument(const QString &text) -{ - if (text.startsWith("\"") && text.endsWith("\"")) { - QString ret = text; - ret.remove(0, 1); - ret.chop(1); - return ret; - } - - if (text == "true") - return true; - - if (text == "false") - return true; - - bool ok = true; - double d = text.toDouble(&ok); - if (ok) - return d; - - //TODO variables and function calls - return text; -} - -void ConnectionModelStatementDelegate::handleStringArgumentChanged() -{ - if (m_actionType == SetProperty) { - QTC_ASSERT(std::holds_alternative(m_statement), - return ); - - ConnectionEditorStatements::PropertySet &propertySet - = std::get(m_statement); - - propertySet.rhs = parseTextArgument(m_stringArgument.text()); - - } else if (m_actionType == PrintMessage) { - QTC_ASSERT(std::holds_alternative(m_statement), - return ); - - ConnectionEditorStatements::ConsoleLog &consoleLog - = std::get(m_statement); - - consoleLog.argument = parseLogTextArgument(m_stringArgument.text()); - } else { - QTC_ASSERT(false, return ); - } - - emit statementChanged(); -} - -void ConnectionModelStatementDelegate::handleStateChanged() -{ - QTC_ASSERT(std::holds_alternative(m_statement), return ); - - ConnectionEditorStatements::StateSet &stateSet = std::get( - m_statement); - - QString stateName = m_states.currentText(); - if (stateName == baseStateName()) - stateName = ""; - stateSet.stateName = "\"" + stateName + "\""; - - emit statementChanged(); -} - -void ConnectionModelStatementDelegate::handleStateTargetsChanged() -{ - QTC_ASSERT(std::holds_alternative(m_statement), return ); - - ConnectionEditorStatements::StateSet &stateSet = std::get( - m_statement); - - stateSet.nodeId = m_stateTargets.currentText(); - stateSet.stateName = "\"\""; - - setupStates(); - - emit statementChanged(); -} - -void ConnectionModelStatementDelegate::setupAssignment() -{ - QTC_ASSERT(std::holds_alternative(m_statement), return ); - - const auto assignment = std::get(m_statement); - m_lhsDelegate.setup(assignment.lhs.nodeId, assignment.lhs.propertyName); - m_rhsAssignmentDelegate.setup(assignment.rhs.nodeId, assignment.rhs.propertyName); - setupPropertyType(); -} - -void ConnectionModelStatementDelegate::setupSetProperty() -{ - QTC_ASSERT(std::holds_alternative(m_statement), - return ); - - const auto propertySet = std::get(m_statement); - m_lhsDelegate.setup(propertySet.lhs.nodeId, propertySet.lhs.propertyName); - m_stringArgument.setText(ConnectionEditorStatements::toString(propertySet.rhs)); -} - -void ConnectionModelStatementDelegate::setupCallFunction() -{ - QTC_ASSERT(std::holds_alternative(m_statement), - return ); - - const auto functionStatement = std::get( - m_statement); - m_functionDelegate.setup(functionStatement.nodeId, functionStatement.functionName); -} - -void ConnectionModelStatementDelegate::setupChangeState() -{ - QTC_ASSERT(std::holds_alternative(m_statement), return ); - - QTC_ASSERT(m_model->connectionView()->isAttached(), return ); - - auto model = m_model->connectionView()->model(); - const auto items = Utils::filtered(m_model->connectionView()->allModelNodesOfType( - model->qtQuickItemMetaInfo()), - [](const ModelNode &node) { - QmlItemNode item(node); - return node.hasId() && item.isValid() - && !item.allStateNames().isEmpty(); - }); - - QStringList itemIds = Utils::transform(items, &ModelNode::id); - const auto groups = m_model->connectionView()->allModelNodesOfType( - model->qtQuickStateGroupMetaInfo()); - - const auto rootId = m_model->connectionView()->rootModelNode().id(); - itemIds.removeAll(rootId); - - QStringList groupIds = Utils::transform(groups, &ModelNode::id); - - Utils::sort(itemIds); - Utils::sort(groupIds); - - if (!rootId.isEmpty()) - groupIds.prepend(rootId); - - const QStringList stateGroupModel = groupIds + itemIds; - m_stateTargets.setModel(stateGroupModel); - - const auto stateSet = std::get(m_statement); - - m_stateTargets.setCurrentText(stateSet.nodeId); - setupStates(); -} -QString stripQuotesFromState(const QString &input) -{ - if (input.startsWith("\"") && input.endsWith("\"")) { - QString ret = input; - ret.remove(0, 1); - ret.chop(1); - return ret; - } - return input; -} -void ConnectionModelStatementDelegate::setupStates() -{ - QTC_ASSERT(std::holds_alternative(m_statement), return ); - QTC_ASSERT(m_model->connectionView()->isAttached(), return ); - - const auto stateSet = std::get(m_statement); - - const QString nodeId = m_stateTargets.currentText(); - - const ModelNode node = m_model->connectionView()->modelNodeForId(nodeId); - - QStringList states; - if (node.metaInfo().isQtQuickItem()) { - QmlItemNode item(node); - QTC_ASSERT(item.isValid(), return ); - if (item.isRootNode()) - states = item.states().names(); //model - else - states = item.allStateNames(); //instances - } else { - QmlModelStateGroup group(node); - states = group.names(); //model - } - - const QString stateName = stripQuotesFromState(stateSet.stateName); - - states.prepend(baseStateName()); - m_states.setModel(states); - if (stateName.isEmpty()) - m_states.setCurrentText(baseStateName()); + auto normalizedSource = QmlDesigner::SignalHandlerProperty::normalizedSourceWithBraces(source); + if (property.exists()) + property.toSignalHandlerProperty().setSource(normalizedSource); else - m_states.setCurrentText(stateName); + property.parentModelNode().signalHandlerProperty(property.name()).setSource(normalizedSource); } -void ConnectionModelStatementDelegate::setupPrintMessage() +SignalHandlerProperty ConnectionModelBackendDelegate::getSignalHandlerProperty() const { - QTC_ASSERT(std::holds_alternative(m_statement), return ); + ConnectionModel *model = m_model; + QTC_ASSERT(model, return {}); + QTC_ASSERT(model->connectionView()->isAttached(), return {}); - const auto consoleLog = std::get(m_statement); - m_stringArgument.setText(ConnectionEditorStatements::toString(consoleLog.argument)); -} - -void ConnectionModelStatementDelegate::setupPropertyType() -{ - PropertyTreeModel::PropertyTypes type = PropertyTreeModel::AllTypes; - - const NodeMetaInfo metaInfo = m_rhsAssignmentDelegate.propertyMetaInfo(); - - if (metaInfo.isBool()) - type = PropertyTreeModel::BoolType; - else if (metaInfo.isNumber()) - type = PropertyTreeModel::NumberType; - else if (metaInfo.isColor()) - type = PropertyTreeModel::ColorType; - else if (metaInfo.isString()) - type = PropertyTreeModel::StringType; - else if (metaInfo.isUrl()) - type = PropertyTreeModel::UrlType; - - m_lhsDelegate.setPropertyType(type); -} - -QString ConnectionModelStatementDelegate::baseStateName() const -{ - return tr("Base State"); -} - -static ConnectionEditorStatements::MatchedCondition emptyCondition; - -ConditionListModel::ConditionListModel(ConnectionModel *model) - : m_connectionModel(model) - , m_condition(emptyCondition) -{} - -int ConditionListModel::rowCount(const QModelIndex & /*parent*/) const -{ - return m_tokens.size(); -} - -QHash ConditionListModel::roleNames() const -{ - static QHash roleNames{{Qt::UserRole + 1, "type"}, {Qt::UserRole + 2, "value"}}; - return roleNames; -} - -QVariant ConditionListModel::data(const QModelIndex &index, int role) const -{ - if (index.isValid() && index.row() < rowCount()) { - if (role == Qt::UserRole + 1) { - return m_tokens.at(index.row()).type; - } else if (role == Qt::UserRole + 2) { - return m_tokens.at(index.row()).value; - } - - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "invalid role"; - } else { - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "invalid index"; - } - - return QVariant(); -} - -void ConditionListModel::setup() -{ - m_tokens.clear(); - - internalSetup(); - - emit validChanged(); - emit emptyChanged(); - - beginResetModel(); - endResetModel(); -} - -void ConditionListModel::setCondition(ConnectionEditorStatements::MatchedCondition &condition) -{ - m_condition = condition; - setup(); -} - -ConnectionEditorStatements::MatchedCondition &ConditionListModel::condition() -{ - return m_condition; -} - -ConditionListModel::ConditionToken ConditionListModel::tokenFromConditionToken( - const ConnectionEditorStatements::ConditionToken &token) -{ - ConditionToken ret; - ret.type = Operator; - ret.value = ConnectionEditorStatements::toJavascript(token); - - return ret; -} - -ConditionListModel::ConditionToken ConditionListModel::tokenFromComparativeStatement( - const ConnectionEditorStatements::ComparativeStatement &token) -{ - ConditionToken ret; - - if (auto *variable = std::get_if(&token)) { - ret.type = Variable; - ret.value = variable->expression(); - return ret; - } else if (auto *literal = std::get_if(&token)) { - ret.type = Literal; - ret.value = "\"" + *literal + "\""; - return ret; - } else if (auto *literal = std::get_if(&token)) { - ret.type = Literal; - if (*literal) - ret.value = "true"; - else - ret.value = "false"; - return ret; - } else if (auto *literal = std::get_if(&token)) { - ret.type = Literal; - ret.value = QString::number(*literal); - return ret; - } - - ret.type = Invalid; - ret.value = "invalid"; - return {}; -} - -void ConditionListModel::insertToken(int index, const QString &value) -{ - beginInsertRows({}, index, index); - - m_tokens.insert(index, valueToToken(value)); - validateAndRebuildTokens(); - - endInsertRows(); - //resetModel(); -} - -void ConditionListModel::updateToken(int index, const QString &value) -{ - m_tokens[index] = valueToToken(value); - validateAndRebuildTokens(); - - dataChanged(createIndex(index, 0), createIndex(index, 0)); - //resetModel(); -} - -void ConditionListModel::appendToken(const QString &value) -{ - beginInsertRows({}, rowCount() - 1, rowCount() - 1); - - insertToken(rowCount(), value); - validateAndRebuildTokens(); - - endInsertRows(); - //resetModel(); -} - -void ConditionListModel::removeToken(int index) -{ - QTC_ASSERT(index < m_tokens.count(), return ); - beginRemoveRows({}, index, index); - - m_tokens.remove(index, 1); - validateAndRebuildTokens(); - - endRemoveRows(); - - //resetModel(); -} - -void ConditionListModel::insertIntermediateToken(int index, const QString &value) -{ - beginInsertRows({}, index, index); - - ConditionToken token; - token.type = Intermediate; - token.value = value; - - m_tokens.insert(index, token); - - endInsertRows(); - //resetModel(); -} - -void ConditionListModel::insertShadowToken(int index, const QString &value) -{ - beginInsertRows({}, index, index); - - ConditionToken token; - token.type = Shadow; - token.value = value; - - m_tokens.insert(index, token); - - endInsertRows(); - - //resetModel(); -} - -void ConditionListModel::setShadowToken(int index, const QString &value) -{ - m_tokens[index].type = Shadow; - m_tokens[index].value = value; - - dataChanged(createIndex(index, 0), createIndex(index, 0)); - //resetModel(); -} - -bool ConditionListModel::valid() const -{ - return m_valid; -} - -bool ConditionListModel::empty() const -{ - return m_tokens.isEmpty(); -} - -void ConditionListModel::command(const QString &string) -{ - //TODO remove from prodcution code - QStringList list = string.split("%", Qt::SkipEmptyParts); - - if (list.size() < 2) - return; - - if (list.size() == 2) { - if (list.first() == "A") { - appendToken(list.last()); - } else if (list.first() == "R") { - bool ok = true; - int index = list.last().toInt(&ok); - - if (ok) - removeToken(index); - } - } - - if (list.size() == 3) { - if (list.first() == "U") { - bool ok = true; - int index = list.at(1).toInt(&ok); - - if (ok) - updateToken(index, list.last()); - } else if (list.first() == "I") { - bool ok = true; - int index = list.at(1).toInt(&ok); - - if (ok) - insertToken(index, list.last()); - } - } -} - -void ConditionListModel::setInvalid(const QString &errorMessage, int index) -{ - m_valid = false; - m_errorMessage = errorMessage; - - emit errorChanged(); - emit validChanged(); - - if (index != -1) { - m_errorIndex = index; - emit errorIndexChanged(); - } -} - -void ConditionListModel::setValid() -{ - m_valid = true; - m_errorMessage.clear(); - m_errorIndex = -1; - - emit errorChanged(); - emit validChanged(); - emit errorIndexChanged(); -} - -QString ConditionListModel::error() const -{ - return m_errorMessage; -} - -int ConditionListModel::errorIndex() const -{ - return m_errorIndex; -} - -bool ConditionListModel::operatorAllowed(int cursorPosition) -{ - if (m_tokens.empty()) - return false; - - int tokenIdx = cursorPosition - 1; - - if (tokenIdx >= 0 && tokenIdx < m_tokens.length() && m_tokens[tokenIdx].type != Operator) - return true; - - return false; -} - -void ConditionListModel::internalSetup() -{ - setInvalid(tr("No Valid Condition")); - if (!m_condition.statements.size() && !m_condition.tokens.size()) - return; - - if (m_condition.statements.size() != m_condition.tokens.size() + 1) - return; - - if (m_condition.statements.size() == 1 && m_condition.tokens.isEmpty()) { - auto token = tokenFromComparativeStatement(m_condition.statements.first()); - if (token.value == defaultCondition) - return; - } - - auto s_it = m_condition.statements.begin(); - auto o_it = m_condition.tokens.begin(); - - while (o_it != m_condition.tokens.end()) { - m_tokens.append(tokenFromComparativeStatement(*s_it)); - m_tokens.append(tokenFromConditionToken(*o_it)); - - s_it++; - o_it++; - } - m_tokens.append(tokenFromComparativeStatement(*s_it)); - - setValid(); -} - -ConditionListModel::ConditionToken ConditionListModel::valueToToken(const QString &value) -{ - const QStringList operators = {"&&", "||", "===", "!==", ">", ">=", "<", "<="}; - - if (operators.contains(value)) { - ConditionToken token; - token.type = Operator; - token.value = value; - return token; - } - - bool ok = false; - value.toDouble(&ok); - - if (value == "true" || value == "false" || ok - || (value.startsWith("\"") && value.endsWith("\""))) { - ConditionToken token; - token.type = Literal; - token.value = value; - return token; - } - - static QRegularExpression regexp("^[a-z_]\\w*|^[A-Z]\\w*\\.{1}([a-z_]\\w*\\.?)+"); - QRegularExpressionMatch match = regexp.match(value); - - if (match.hasMatch()) { //variable - ConditionToken token; - token.type = Variable; - token.value = value; - return token; - } - - ConditionToken token; - token.type = Invalid; - token.value = value; - - return token; -} - -void ConditionListModel::resetModel() -{ - beginResetModel(); - endResetModel(); -} - -int ConditionListModel::checkOrder() const -{ - auto it = m_tokens.begin(); - - bool wasOperator = true; - - int ret = 0; - while (it != m_tokens.end()) { - if (wasOperator && it->type == Operator) - return ret; - if (!wasOperator && it->type == Literal) - return ret; - if (!wasOperator && it->type == Variable) - return ret; - wasOperator = it->type == Operator; - it++; - ret++; - } - - if (wasOperator) - return ret; - - return -1; -} - -void ConditionListModel::validateAndRebuildTokens() -{ - /// NEW - auto it = m_tokens.begin(); - - while (it != m_tokens.end()) { - if (it->type == Intermediate) - *it = valueToToken(it->value); - - it++; - } - // NEW - - QString invalidValue; - const bool invalidToken = Utils::contains(m_tokens, - [&invalidValue](const ConditionToken &token) { - if (token.type == Invalid) - invalidValue = token.value; - return token.type == Invalid; - }); - - if (invalidToken) { - setInvalid(tr("Invalid token %1").arg(invalidValue)); - return; - } - - if (int firstError = checkOrder() != -1) { - setInvalid(tr("Invalid order at %1").arg(firstError), firstError); - return; - } - - setValid(); - - rebuildTokens(); -} - -void ConditionListModel::rebuildTokens() -{ - QTC_ASSERT(m_valid, return ); - - m_condition.statements.clear(); - m_condition.tokens.clear(); - - auto it = m_tokens.begin(); - - while (it != m_tokens.end()) { - QTC_ASSERT(it->type != Invalid, return ); - if (it->type == Operator) - m_condition.tokens.append(toOperatorStatement(*it)); - else if (it->type == Literal || it->type == Variable) - m_condition.statements.append(toStatement(*it)); - - it++; - } - - emit conditionChanged(); -} - -ConnectionEditorStatements::ConditionToken ConditionListModel::toOperatorStatement( - const ConditionToken &token) -{ - if (token.value == "&&") - return ConnectionEditorStatements::ConditionToken::And; - - if (token.value == "||") - return ConnectionEditorStatements::ConditionToken::Or; - - if (token.value == "===") - return ConnectionEditorStatements::ConditionToken::Equals; - - if (token.value == "!==") - return ConnectionEditorStatements::ConditionToken::Not; - - if (token.value == ">") - return ConnectionEditorStatements::ConditionToken::LargerThan; - - if (token.value == ">=") - return ConnectionEditorStatements::ConditionToken::LargerEqualsThan; - - if (token.value == "<") - return ConnectionEditorStatements::ConditionToken::SmallerThan; - - if (token.value == "<=") - return ConnectionEditorStatements::ConditionToken::SmallerEqualsThan; - - return ConnectionEditorStatements::ConditionToken::Unknown; -} - -ConnectionEditorStatements::ComparativeStatement ConditionListModel::toStatement( - const ConditionToken &token) -{ - if (token.type == Variable) { - QStringList list = token.value.split("."); - ConnectionEditorStatements::Variable variable; - - variable.nodeId = list.first(); - if (list.count() > 1) - variable.propertyName = list.last(); - return variable; - } else if (token.type == Literal) { - return parseTextArgumentComparativeStatement(token.value); - } - - return {}; + return model->signalHandlerPropertyForRow(currentRow()); } void QmlDesigner::ConnectionModel::modelAboutToBeDetached() diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h index 017cf676843..8767dac4134 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h @@ -3,15 +3,11 @@ #pragma once -#include -#include +#include #include -#include #include -#include - namespace QmlDesigner { class AbstractProperty; @@ -23,247 +19,35 @@ class VariantProperty; class ConnectionView; class ConnectionModel; -class ConditionListModel : public QAbstractListModel +class ConnectionModelBackendDelegate : public ScriptEditorBackend { Q_OBJECT - - Q_PROPERTY(bool valid READ valid NOTIFY validChanged) - Q_PROPERTY(bool empty READ empty NOTIFY emptyChanged) - Q_PROPERTY(QString error READ error NOTIFY errorChanged) - Q_PROPERTY(int errorIndex READ errorIndex NOTIFY errorIndexChanged) - -public: - enum ConditionType { Intermediate, Invalid, Operator, Literal, Variable, Shadow }; - Q_ENUM(ConditionType) - - struct ConditionToken - { - ConditionType type; - QString value; - }; - - ConditionListModel(ConnectionModel *model); - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - - QHash roleNames() const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - - void setup(); - void setCondition(ConnectionEditorStatements::MatchedCondition &condition); - ConnectionEditorStatements::MatchedCondition &condition(); - - static ConditionToken tokenFromConditionToken(const ConnectionEditorStatements::ConditionToken &token); - - static ConditionToken tokenFromComparativeStatement( - const ConnectionEditorStatements::ComparativeStatement &token); - - Q_INVOKABLE void insertToken(int index, const QString &value); - Q_INVOKABLE void updateToken(int index, const QString &value); - Q_INVOKABLE void appendToken(const QString &value); - Q_INVOKABLE void removeToken(int index); - - Q_INVOKABLE void insertIntermediateToken(int index, const QString &value); - Q_INVOKABLE void insertShadowToken(int index, const QString &value); - Q_INVOKABLE void setShadowToken(int index, const QString &value); - - bool valid() const; - bool empty() const; - - //for debugging - Q_INVOKABLE void command(const QString &string); - - void setInvalid(const QString &errorMessage, int index = -1); - void setValid(); - - QString error() const; - int errorIndex() const; - - Q_INVOKABLE bool operatorAllowed(int cursorPosition); - -signals: - void validChanged(); - void emptyChanged(); - void conditionChanged(); - void errorChanged(); - void errorIndexChanged(); - -private: - void internalSetup(); - ConditionToken valueToToken(const QString &value); - void resetModel(); - int checkOrder() const; - void validateAndRebuildTokens(); - void rebuildTokens(); - - ConnectionEditorStatements::ConditionToken toOperatorStatement(const ConditionToken &token); - ConnectionEditorStatements::ComparativeStatement toStatement(const ConditionToken &token); - - ConnectionModel *m_connectionModel = nullptr; - ConnectionEditorStatements::MatchedCondition &m_condition; - QList m_tokens; - bool m_valid = false; - QString m_errorMessage; - int m_errorIndex = -1; -}; - -class ConnectionModelStatementDelegate : public QObject -{ - Q_OBJECT - -public: - explicit ConnectionModelStatementDelegate(ConnectionModel *model); - - enum ActionType { CallFunction, Assign, ChangeState, SetProperty, PrintMessage, Custom }; - - Q_ENUM(ActionType) - - Q_PROPERTY(ActionType actionType READ actionType NOTIFY actionTypeChanged) - - Q_PROPERTY(PropertyTreeModelDelegate *function READ function CONSTANT) - Q_PROPERTY(PropertyTreeModelDelegate *lhs READ lhs CONSTANT) - Q_PROPERTY(PropertyTreeModelDelegate *rhsAssignment READ rhsAssignment CONSTANT) - Q_PROPERTY(StudioQmlTextBackend *stringArgument READ stringArgument CONSTANT) - Q_PROPERTY(StudioQmlComboBoxBackend *states READ states CONSTANT) - Q_PROPERTY(StudioQmlComboBoxBackend *stateTargets READ stateTargets CONSTANT) - - void setActionType(ActionType type); - void setup(); - void setStatement(ConnectionEditorStatements::MatchedStatement &statement); - ConnectionEditorStatements::MatchedStatement &statement(); - -signals: - void actionTypeChanged(); - void statementChanged(); - -private: - ActionType actionType() const; - PropertyTreeModelDelegate *signal(); - PropertyTreeModelDelegate *function(); - PropertyTreeModelDelegate *lhs(); - PropertyTreeModelDelegate *rhsAssignment(); - StudioQmlTextBackend *stringArgument(); - StudioQmlComboBoxBackend *stateTargets(); - StudioQmlComboBoxBackend *states(); - - void handleFunctionChanged(); - void handleLhsChanged(); - void handleRhsAssignmentChanged(); - void handleStringArgumentChanged(); - void handleStateChanged(); - void handleStateTargetsChanged(); - - void setupAssignment(); - void setupSetProperty(); - void setupCallFunction(); - void setupChangeState(); - void setupStates(); - void setupPrintMessage(); - void setupPropertyType(); - QString baseStateName() const; - - ActionType m_actionType; - PropertyTreeModelDelegate m_functionDelegate; - PropertyTreeModelDelegate m_lhsDelegate; - PropertyTreeModelDelegate m_rhsAssignmentDelegate; - ConnectionEditorStatements::MatchedStatement &m_statement; - ConnectionModel *m_model = nullptr; - StudioQmlTextBackend m_stringArgument; - StudioQmlComboBoxBackend m_stateTargets; - StudioQmlComboBoxBackend m_states; -}; - -class ConnectionModelBackendDelegate : public QObject -{ - Q_OBJECT - Q_PROPERTY(int currentRow READ currentRow WRITE setCurrentRow NOTIFY currentRowChanged) - - Q_PROPERTY(ActionType actionType READ actionType NOTIFY actionTypeChanged) Q_PROPERTY(PropertyTreeModelDelegate *signal READ signal CONSTANT) - Q_PROPERTY(ConnectionModelStatementDelegate *okStatement READ okStatement CONSTANT) - Q_PROPERTY(ConnectionModelStatementDelegate *koStatement READ koStatement CONSTANT) - Q_PROPERTY(ConditionListModel *conditionListModel READ conditionListModel CONSTANT) - Q_PROPERTY(bool hasCondition READ hasCondition NOTIFY hasConditionChanged) - Q_PROPERTY(bool hasElse READ hasElse NOTIFY hasElseChanged) - Q_PROPERTY(QString source READ source NOTIFY sourceChanged) - Q_PROPERTY(QString indentedSource READ indentedSource NOTIFY sourceChanged) - - Q_PROPERTY(PropertyTreeModel *propertyTreeModel READ propertyTreeModel CONSTANT) - Q_PROPERTY(PropertyListProxyModel *propertyListProxyModel READ propertyListProxyModel CONSTANT) - public: explicit ConnectionModelBackendDelegate(ConnectionModel *model); - using ActionType = ConnectionModelStatementDelegate::ActionType; - - Q_INVOKABLE void changeActionType(QmlDesigner::ConnectionModelStatementDelegate::ActionType actionType); - - Q_INVOKABLE void addCondition(); - Q_INVOKABLE void removeCondition(); - - Q_INVOKABLE void addElse(); - Q_INVOKABLE void removeElse(); - - Q_INVOKABLE void setNewSource(const QString &newSource); + void update() override; void setCurrentRow(int i); - void update(); - Q_INVOKABLE void jumpToCode(); + PropertyTreeModelDelegate *signal(); signals: void currentRowChanged(); - void actionTypeChanged(); - void hasConditionChanged(); - void hasElseChanged(); - void sourceChanged(); - void popupShouldClose(); - void popupShouldOpen(); + +private slots: + void handleTargetChanged(); private: + AbstractProperty getSourceProperty() const override; + void setPropertySource(const QString &source) override; + + SignalHandlerProperty getSignalHandlerProperty() const; int currentRow() const; - void handleException(); - bool hasCondition() const; - bool hasElse() const; - void setHasCondition(bool b); - void setHasElse(bool b); - ActionType actionType() const; - PropertyTreeModelDelegate *signal(); - ConnectionModelStatementDelegate *okStatement(); - ConnectionModelStatementDelegate *koStatement(); - ConditionListModel *conditionListModel(); - QString indentedSource() const; - QString source() const; - void setSource(const QString &source); - PropertyTreeModel *propertyTreeModel(); - PropertyListProxyModel *propertyListProxyModel(); - - void setupCondition(); - void setupHandlerAndStatements(); - - void handleTargetChanged(); - void handleOkStatementChanged(); - void handleKOStatementChanged(); - void handleConditionChanged(); - - void commitNewSource(const QString &source); - - ActionType m_actionType; - QString m_exceptionError; int m_currentRow = -1; - ConnectionEditorStatements::Handler m_handler; PropertyTreeModelDelegate m_signalDelegate; - ConnectionModelStatementDelegate m_okStatementDelegate; - ConnectionModelStatementDelegate m_koStatementDelegate; - ConditionListModel m_conditionListModel; - bool m_hasCondition = false; - bool m_hasElse = false; - QString m_source; - PropertyTreeModel m_propertyTreeModel; - PropertyListProxyModel m_propertyListProxyModel; - bool m_blockReflection = false; QPointer m_model = nullptr; }; diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp index a60edca67b9..007116105b0 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp @@ -83,10 +83,8 @@ public: 0, "DynamicPropertiesModelBackendDelegate"); - qmlRegisterType("ConnectionsEditorEditorBackend", - 1, - 0, - "ConnectionModelStatementDelegate"); + // TODO: register in the property editor along with the ScriptEditorBackend + qmlRegisterType("ScriptEditorBackend", 1, 0, "StatementDelegate"); qmlRegisterType("ConnectionsEditorEditorBackend", 1, 0, "ConditionListModel"); diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesitem.cpp b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesitem.cpp index a45b0e115d8..1694231630c 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesitem.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesitem.cpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "dynamicpropertiesitem.h" -#include "connectioneditorutils.h" +#include #include #include diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp index 687752b8c98..c4307a07182 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp @@ -2,22 +2,23 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "dynamicpropertiesmodel.h" +#include "connectioneditorlogging.h" #include "dynamicpropertiesitem.h" -#include "connectioneditorutils.h" #include #include #include -#include -#include -#include -#include -#include #include #include #include #include #include +#include +#include +#include +#include +#include +#include #include diff --git a/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp index 8ba6b22ac0f..8c6f272fa91 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp @@ -27,7 +27,7 @@ #include "bindingproperty.h" #include "propertyeditorvalue.h" -#include "connectioneditorutils.h" +#include #include diff --git a/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.cpp b/src/plugins/qmldesigner/components/scripteditor/propertytreemodel.cpp similarity index 98% rename from src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.cpp rename to src/plugins/qmldesigner/components/scripteditor/propertytreemodel.cpp index 463286e6956..b1892c9b557 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.cpp +++ b/src/plugins/qmldesigner/components/scripteditor/propertytreemodel.cpp @@ -163,8 +163,8 @@ std::vector properityLists() return result; } -PropertyTreeModel::PropertyTreeModel(ConnectionView *view) - : m_connectionView(view) +PropertyTreeModel::PropertyTreeModel(AbstractView *view) + : m_view(view) {} void PropertyTreeModel::resetModel() @@ -257,7 +257,7 @@ Qt::ItemFlags PropertyTreeModel::flags(const QModelIndex &) const QModelIndex PropertyTreeModel::index(int row, int column, const QModelIndex &parent) const { auto internalId = parent.internalId(); - if (!m_connectionView->isAttached()) + if (!m_view->isAttached()) return {}; if (!parent.isValid()) @@ -337,7 +337,7 @@ QPersistentModelIndex PropertyTreeModel::indexForInternalIdAndRow(quintptr inter int PropertyTreeModel::rowCount(const QModelIndex &parent) const { - if (!m_connectionView->isAttached() || parent.column() > 0) + if (!m_view->isAttached() || parent.column() > 0) return 0; if (!parent.isValid()) @@ -404,10 +404,10 @@ const std::vector PropertyTreeModel::getProperties(const ModelNode ModelNode PropertyTreeModel::getModelNodeForId(const QString &id) const { - if (!m_connectionView->isAttached()) + if (!m_view->isAttached()) return {}; - return m_connectionView->modelNodeForId(id); + return m_view->modelNodeForId(id); } QModelIndex PropertyTreeModel::ensureModelIndex(const ModelNode &node, int row) const @@ -473,10 +473,10 @@ void PropertyTreeModel::testModel() const QList PropertyTreeModel::allModelNodesWithIdsSortedByDisplayName() const { - if (!m_connectionView->isAttached()) + if (!m_view->isAttached()) return {}; - return Utils::sorted(ModelUtils::allModelNodesWithId(m_connectionView), + return Utils::sorted(ModelUtils::allModelNodesWithId(m_view), [](const ModelNode &lhs, const ModelNode &rhs) { return lhs.displayName() < rhs.displayName(); }); @@ -882,7 +882,7 @@ QString PropertyListProxyModel::parentName() const return m_treeModel->data(m_parentIndex, PropertyTreeModel::UserRoles::PropertyNameRole).toString(); } -PropertyTreeModelDelegate::PropertyTreeModelDelegate(ConnectionView *view) +PropertyTreeModelDelegate::PropertyTreeModelDelegate(AbstractView *view) : m_model(view) { connect(&m_nameCombboBox, &StudioQmlComboBoxBackend::activated, this, [this] { diff --git a/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.h b/src/plugins/qmldesigner/components/scripteditor/propertytreemodel.h similarity index 97% rename from src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.h rename to src/plugins/qmldesigner/components/scripteditor/propertytreemodel.h index 39af4857654..bb7dab533db 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.h +++ b/src/plugins/qmldesigner/components/scripteditor/propertytreemodel.h @@ -49,7 +49,8 @@ public: BoolType }; - PropertyTreeModel(ConnectionView *view); + // PropertyTreeModel(ConnectionView *view); + PropertyTreeModel(AbstractView *view); void resetModel(); @@ -120,7 +121,7 @@ private: const PropertyMetaInfo &metaInfo, bool recursive) const; - ConnectionView *m_connectionView; + AbstractView *m_view; mutable std::set m_indexCache; mutable std::vector m_indexHash; @@ -187,7 +188,7 @@ class PropertyTreeModelDelegate : public QObject Q_PROPERTY(StudioQmlComboBoxBackend *id READ idCombboBox CONSTANT) public: - explicit PropertyTreeModelDelegate(ConnectionView *view); + explicit PropertyTreeModelDelegate(AbstractView *view); void setPropertyType(PropertyTreeModel::PropertyTypes type); void setup(const QString &id, const QString &name, bool *nameExists = nullptr); void setupNameComboBox(const QString &id, const QString &name, bool *nameExists); diff --git a/src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.cpp b/src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.cpp new file mode 100644 index 00000000000..4846f149ddf --- /dev/null +++ b/src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.cpp @@ -0,0 +1,1373 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "scripteditorbackend.h" +#include "scripteditorevaluator.h" +#include "scripteditorutils.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace { +const char defaultCondition[] = "condition"; + +QString getSourceFromProperty(const QmlDesigner::AbstractProperty &property) +{ + QTC_ASSERT(property.isValid(), return {}); + + if (!property.exists()) + return {}; + + if (property.isSignalHandlerProperty()) + return property.toSignalHandlerProperty().source(); + if (property.isBindingProperty()) + return property.toBindingProperty().expression(); + + return {}; +} +} // namespace + +namespace QmlDesigner { +static ScriptEditorStatements::MatchedCondition emptyCondition; + +ConditionListModel::ConditionListModel(AbstractView *view) + : m_view(view) + , m_condition(emptyCondition) +{} + +int ConditionListModel::rowCount(const QModelIndex & /*parent*/) const +{ + return m_tokens.size(); +} + +QHash ConditionListModel::roleNames() const +{ + static QHash roleNames{{Qt::UserRole + 1, "type"}, {Qt::UserRole + 2, "value"}}; + return roleNames; +} + +QVariant ConditionListModel::data(const QModelIndex &index, int role) const +{ + if (index.isValid() && index.row() < rowCount()) { + if (role == Qt::UserRole + 1) { + return m_tokens.at(index.row()).type; + } else if (role == Qt::UserRole + 2) { + return m_tokens.at(index.row()).value; + } + + qCWarning(ScriptEditorLog) << __FUNCTION__ << "invalid role"; + } else { + qCWarning(ScriptEditorLog) << __FUNCTION__ << "invalid index"; + } + + return QVariant(); +} + +void ConditionListModel::setup() +{ + m_tokens.clear(); + + internalSetup(); + + emit validChanged(); + emit emptyChanged(); + + beginResetModel(); + endResetModel(); +} + +void ConditionListModel::setCondition(ScriptEditorStatements::MatchedCondition &condition) +{ + m_condition = condition; + setup(); +} + +ScriptEditorStatements::MatchedCondition &ConditionListModel::condition() +{ + return m_condition; +} + +ConditionListModel::ConditionToken ConditionListModel::tokenFromConditionToken( + const ScriptEditorStatements::ConditionToken &token) +{ + ConditionToken ret; + ret.type = Operator; + ret.value = ScriptEditorStatements::toJavascript(token); + + return ret; +} + +ConditionListModel::ConditionToken ConditionListModel::tokenFromComparativeStatement( + const ScriptEditorStatements::ComparativeStatement &token) +{ + ConditionToken ret; + + if (auto *variable = std::get_if(&token)) { + ret.type = Variable; + ret.value = variable->expression(); + return ret; + } else if (auto *literal = std::get_if(&token)) { + ret.type = Literal; + ret.value = "\"" + *literal + "\""; + return ret; + } else if (auto *literal = std::get_if(&token)) { + ret.type = Literal; + if (*literal) + ret.value = "true"; + else + ret.value = "false"; + return ret; + } else if (auto *literal = std::get_if(&token)) { + ret.type = Literal; + ret.value = QString::number(*literal); + return ret; + } + + ret.type = Invalid; + ret.value = "invalid"; + return {}; +} + +void ConditionListModel::insertToken(int index, const QString &value) +{ + beginInsertRows({}, index, index); + + m_tokens.insert(index, valueToToken(value)); + validateAndRebuildTokens(); + + endInsertRows(); +} + +void ConditionListModel::updateToken(int index, const QString &value) +{ + m_tokens[index] = valueToToken(value); + validateAndRebuildTokens(); + + emit dataChanged(createIndex(index, 0), createIndex(index, 0)); +} + +void ConditionListModel::appendToken(const QString &value) +{ + beginInsertRows({}, rowCount() - 1, rowCount() - 1); + + insertToken(rowCount(), value); + validateAndRebuildTokens(); + + endInsertRows(); +} + +void ConditionListModel::removeToken(int index) +{ + QTC_ASSERT(index < m_tokens.count(), return); + beginRemoveRows({}, index, index); + + m_tokens.remove(index, 1); + validateAndRebuildTokens(); + + endRemoveRows(); +} + +void ConditionListModel::insertIntermediateToken(int index, const QString &value) +{ + beginInsertRows({}, index, index); + + ConditionToken token; + token.type = Intermediate; + token.value = value; + + m_tokens.insert(index, token); + + endInsertRows(); +} + +void ConditionListModel::insertShadowToken(int index, const QString &value) +{ + beginInsertRows({}, index, index); + + ConditionToken token; + token.type = Shadow; + token.value = value; + + m_tokens.insert(index, token); + + endInsertRows(); +} + +void ConditionListModel::setShadowToken(int index, const QString &value) +{ + m_tokens[index].type = Shadow; + m_tokens[index].value = value; + + emit dataChanged(createIndex(index, 0), createIndex(index, 0)); +} + +bool ConditionListModel::valid() const +{ + return m_valid; +} + +bool ConditionListModel::empty() const +{ + return m_tokens.isEmpty(); +} + +void ConditionListModel::command(const QString &string) +{ + //TODO remove from prodcution code + QStringList list = string.split("%", Qt::SkipEmptyParts); + + if (list.size() < 2) + return; + + if (list.size() == 2) { + if (list.first() == "A") { + appendToken(list.last()); + } else if (list.first() == "R") { + bool ok = true; + int index = list.last().toInt(&ok); + + if (ok) + removeToken(index); + } + } + + if (list.size() == 3) { + if (list.first() == "U") { + bool ok = true; + int index = list.at(1).toInt(&ok); + + if (ok) + updateToken(index, list.last()); + } else if (list.first() == "I") { + bool ok = true; + int index = list.at(1).toInt(&ok); + + if (ok) + insertToken(index, list.last()); + } + } +} + +void ConditionListModel::setInvalid(const QString &errorMessage, int index) +{ + m_valid = false; + m_errorMessage = errorMessage; + + emit errorChanged(); + emit validChanged(); + + if (index != -1) { + m_errorIndex = index; + emit errorIndexChanged(); + } +} + +void ConditionListModel::setValid() +{ + m_valid = true; + m_errorMessage.clear(); + m_errorIndex = -1; + + emit errorChanged(); + emit validChanged(); + emit errorIndexChanged(); +} + +QString ConditionListModel::error() const +{ + return m_errorMessage; +} + +int ConditionListModel::errorIndex() const +{ + return m_errorIndex; +} + +bool ConditionListModel::operatorAllowed(int cursorPosition) +{ + if (m_tokens.empty()) + return false; + + int tokenIdx = cursorPosition - 1; + + if (tokenIdx >= 0 && tokenIdx < m_tokens.length() && m_tokens[tokenIdx].type != Operator) + return true; + + return false; +} + +void ConditionListModel::internalSetup() +{ + setInvalid(tr("No Valid Condition")); + if (!m_condition.statements.size() && !m_condition.tokens.size()) + return; + + if (m_condition.statements.size() != m_condition.tokens.size() + 1) + return; + + if (m_condition.statements.size() == 1 && m_condition.tokens.isEmpty()) { + auto token = tokenFromComparativeStatement(m_condition.statements.first()); + if (token.value == defaultCondition) + return; + } + + auto s_it = m_condition.statements.begin(); + auto o_it = m_condition.tokens.begin(); + + while (o_it != m_condition.tokens.end()) { + m_tokens.append(tokenFromComparativeStatement(*s_it)); + m_tokens.append(tokenFromConditionToken(*o_it)); + + s_it++; + o_it++; + } + m_tokens.append(tokenFromComparativeStatement(*s_it)); + + setValid(); +} + +ConditionListModel::ConditionToken ConditionListModel::valueToToken(const QString &value) +{ + const QStringList operators = {"&&", "||", "===", "!==", ">", ">=", "<", "<="}; + + if (operators.contains(value)) { + ConditionToken token; + token.type = Operator; + token.value = value; + return token; + } + + bool ok = false; + value.toDouble(&ok); + + if (value == "true" || value == "false" || ok || (value.startsWith("\"") && value.endsWith("\""))) { + ConditionToken token; + token.type = Literal; + token.value = value; + return token; + } + + static QRegularExpression regexp("^[a-z_]\\w*|^[A-Z]\\w*\\.{1}([a-z_]\\w*\\.?)+"); + QRegularExpressionMatch match = regexp.match(value); + + if (match.hasMatch()) { //variable + ConditionToken token; + token.type = Variable; + token.value = value; + return token; + } + + ConditionToken token; + token.type = Invalid; + token.value = value; + + return token; +} + +void ConditionListModel::resetModel() +{ + beginResetModel(); + endResetModel(); +} + +int ConditionListModel::checkOrder() const +{ + auto it = m_tokens.begin(); + + bool wasOperator = true; + + int ret = 0; + while (it != m_tokens.end()) { + if (wasOperator && it->type == Operator) + return ret; + if (!wasOperator && it->type == Literal) + return ret; + if (!wasOperator && it->type == Variable) + return ret; + wasOperator = it->type == Operator; + it++; + ret++; + } + + if (wasOperator) + return ret; + + return -1; +} + +void ConditionListModel::validateAndRebuildTokens() +{ + /// NEW + auto it = m_tokens.begin(); + + while (it != m_tokens.end()) { + if (it->type == Intermediate) + *it = valueToToken(it->value); + + it++; + } + // NEW + + QString invalidValue; + const bool invalidToken = Utils::contains(m_tokens, [&invalidValue](const ConditionToken &token) { + if (token.type == Invalid) + invalidValue = token.value; + return token.type == Invalid; + }); + + if (invalidToken) { + setInvalid(tr("Invalid token %1").arg(invalidValue)); + return; + } + + if (int firstError = checkOrder() != -1) { + setInvalid(tr("Invalid order at %1").arg(firstError), firstError); + return; + } + + setValid(); + + rebuildTokens(); +} + +ScriptEditorStatements::ConditionToken ConditionListModel::toOperatorStatement(const ConditionToken &token) +{ + if (token.value == "&&") + return ScriptEditorStatements::ConditionToken::And; + + if (token.value == "||") + return ScriptEditorStatements::ConditionToken::Or; + + if (token.value == "===") + return ScriptEditorStatements::ConditionToken::Equals; + + if (token.value == "!==") + return ScriptEditorStatements::ConditionToken::Not; + + if (token.value == ">") + return ScriptEditorStatements::ConditionToken::LargerThan; + + if (token.value == ">=") + return ScriptEditorStatements::ConditionToken::LargerEqualsThan; + + if (token.value == "<") + return ScriptEditorStatements::ConditionToken::SmallerThan; + + if (token.value == "<=") + return ScriptEditorStatements::ConditionToken::SmallerEqualsThan; + + return ScriptEditorStatements::ConditionToken::Unknown; +} + +static ScriptEditorStatements::ComparativeStatement parseTextArgumentComparativeStatement( + const QString &text) +{ + if (text.startsWith("\"") && text.endsWith("\"")) { + QString ret = text; + ret.remove(0, 1); + ret.chop(1); + return ret; + } + + if (text == "true") + return true; + + if (text == "false") + return false; + + bool ok = true; + double d = text.toDouble(&ok); + if (ok) + return d; + + return text; +} + +ScriptEditorStatements::ComparativeStatement ConditionListModel::toStatement(const ConditionToken &token) +{ + if (token.type == Variable) { + QStringList list = token.value.split("."); + ScriptEditorStatements::Variable variable; + + variable.nodeId = list.first(); + if (list.count() > 1) + variable.propertyName = list.last(); + return variable; + } else if (token.type == Literal) { + return parseTextArgumentComparativeStatement(token.value); + } + + return {}; +} + +void ConditionListModel::rebuildTokens() +{ + QTC_ASSERT(m_valid, return); + + m_condition.statements.clear(); + m_condition.tokens.clear(); + + auto it = m_tokens.begin(); + + while (it != m_tokens.end()) { + QTC_ASSERT(it->type != Invalid, return); + if (it->type == Operator) + m_condition.tokens.append(toOperatorStatement(*it)); + else if (it->type == Literal || it->type == Variable) + m_condition.statements.append(toStatement(*it)); + + it++; + } + + emit conditionChanged(); +} + +static ScriptEditorStatements::MatchedStatement emptyStatement; + +StatementDelegate::StatementDelegate(AbstractView *view) + : m_functionDelegate(view) + , m_lhsDelegate(view) + , m_rhsAssignmentDelegate(view) + , m_statement(emptyStatement) + , m_view(view) +{ + m_functionDelegate.setPropertyType(PropertyTreeModel::SlotType); + + connect(&m_functionDelegate, &PropertyTreeModelDelegate::commitData, this, [this] { + handleFunctionChanged(); + }); + + connect(&m_rhsAssignmentDelegate, &PropertyTreeModelDelegate::commitData, this, [this] { + handleRhsAssignmentChanged(); + }); + + connect(&m_lhsDelegate, &PropertyTreeModelDelegate::commitData, this, [this] { + handleLhsChanged(); + }); + + connect(&m_stringArgument, &StudioQmlTextBackend::activated, this, [this] { + handleStringArgumentChanged(); + }); + + connect(&m_states, &StudioQmlComboBoxBackend::activated, this, [this] { handleStateChanged(); }); + + connect(&m_stateTargets, &StudioQmlComboBoxBackend::activated, this, [this] { + handleStateTargetsChanged(); + }); +} + +void StatementDelegate::setActionType(ActionType type) +{ + if (m_actionType == type) + return; + + m_actionType = type; + emit actionTypeChanged(); + setup(); +} + +void StatementDelegate::setup() +{ + switch (m_actionType) { + case CallFunction: + setupCallFunction(); + break; + case Assign: + setupAssignment(); + break; + case ChangeState: + setupChangeState(); + break; + case SetProperty: + setupSetProperty(); + break; + case PrintMessage: + setupPrintMessage(); + break; + case Custom: + break; + }; +} + +void StatementDelegate::setStatement(ScriptEditorStatements::MatchedStatement &statement) +{ + m_statement = statement; + setup(); +} + +ScriptEditorStatements::MatchedStatement &StatementDelegate::statement() +{ + return m_statement; +} + +StatementDelegate::ActionType StatementDelegate::actionType() const +{ + return m_actionType; +} + +PropertyTreeModelDelegate *StatementDelegate::function() +{ + return &m_functionDelegate; +} + +PropertyTreeModelDelegate *StatementDelegate::lhs() +{ + return &m_lhsDelegate; +} + +PropertyTreeModelDelegate *StatementDelegate::rhsAssignment() +{ + return &m_rhsAssignmentDelegate; +} + +StudioQmlTextBackend *StatementDelegate::stringArgument() +{ + return &m_stringArgument; +} + +StudioQmlComboBoxBackend *StatementDelegate::stateTargets() +{ + return &m_stateTargets; +} + +StudioQmlComboBoxBackend *StatementDelegate::states() +{ + return &m_states; +} + +void StatementDelegate::handleFunctionChanged() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return); + + ScriptEditorStatements::MatchedFunction &functionStatement = std::get( + m_statement); + + functionStatement.functionName = m_functionDelegate.name(); + functionStatement.nodeId = m_functionDelegate.id(); + + emit statementChanged(); +} + +void StatementDelegate::handleLhsChanged() +{ + if (m_actionType == Assign) { + QTC_ASSERT(std::holds_alternative(m_statement), return); + + ScriptEditorStatements::Assignment &assignmentStatement = std::get( + m_statement); + + assignmentStatement.lhs.nodeId = m_lhsDelegate.id(); + assignmentStatement.lhs.propertyName = m_lhsDelegate.name(); + + } else if (m_actionType == SetProperty) { + QTC_ASSERT(std::holds_alternative(m_statement), return); + + ScriptEditorStatements::PropertySet &setPropertyStatement = std::get( + m_statement); + + setPropertyStatement.lhs.nodeId = m_lhsDelegate.id(); + setPropertyStatement.lhs.propertyName = m_lhsDelegate.name(); + } else { + QTC_ASSERT(false, return); + } + + emit statementChanged(); +} + +void StatementDelegate::handleRhsAssignmentChanged() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return); + + ScriptEditorStatements::Assignment &assignmentStatement = std::get( + m_statement); + + assignmentStatement.rhs.nodeId = m_rhsAssignmentDelegate.id(); + assignmentStatement.rhs.propertyName = m_rhsAssignmentDelegate.name(); + + setupPropertyType(); + + emit statementChanged(); +} + +static ScriptEditorStatements::Literal parseTextArgument(const QString &text) +{ + if (text.startsWith("\"") && text.endsWith("\"")) { + QString ret = text; + ret.remove(0, 1); + ret.chop(1); + return ret; + } + + if (text == "true") + return true; + + if (text == "false") + return false; + + bool ok = true; + double d = text.toDouble(&ok); + if (ok) + return d; + + return text; +} + +static ScriptEditorStatements::RightHandSide parseLogTextArgument(const QString &text) +{ + if (text.startsWith("\"") && text.endsWith("\"")) { + QString ret = text; + ret.remove(0, 1); + ret.chop(1); + return ret; + } + + if (text == "true") + return true; + + if (text == "false") + return true; + + bool ok = true; + double d = text.toDouble(&ok); + if (ok) + return d; + + //TODO variables and function calls + return text; +} + +void StatementDelegate::handleStringArgumentChanged() +{ + if (m_actionType == SetProperty) { + QTC_ASSERT(std::holds_alternative(m_statement), return); + + ScriptEditorStatements::PropertySet &propertySet = std::get( + m_statement); + + propertySet.rhs = parseTextArgument(m_stringArgument.text()); + + } else if (m_actionType == PrintMessage) { + QTC_ASSERT(std::holds_alternative(m_statement), return); + + ScriptEditorStatements::ConsoleLog &consoleLog = std::get( + m_statement); + + consoleLog.argument = parseLogTextArgument(m_stringArgument.text()); + } else { + QTC_ASSERT(false, return); + } + + emit statementChanged(); +} + +void StatementDelegate::handleStateChanged() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return); + + ScriptEditorStatements::StateSet &stateSet = std::get( + m_statement); + + QString stateName = m_states.currentText(); + if (stateName == baseStateName()) + stateName = ""; + stateSet.stateName = "\"" + stateName + "\""; + + emit statementChanged(); +} + +void StatementDelegate::handleStateTargetsChanged() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return); + + ScriptEditorStatements::StateSet &stateSet = std::get( + m_statement); + + stateSet.nodeId = m_stateTargets.currentText(); + stateSet.stateName = "\"\""; + + setupStates(); + + emit statementChanged(); +} + +void StatementDelegate::setupAssignment() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return); + + const auto assignment = std::get(m_statement); + m_lhsDelegate.setup(assignment.lhs.nodeId, assignment.lhs.propertyName); + m_rhsAssignmentDelegate.setup(assignment.rhs.nodeId, assignment.rhs.propertyName); + setupPropertyType(); +} + +void StatementDelegate::setupSetProperty() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return); + + const auto propertySet = std::get(m_statement); + m_lhsDelegate.setup(propertySet.lhs.nodeId, propertySet.lhs.propertyName); + m_stringArgument.setText(ScriptEditorStatements::toString(propertySet.rhs)); +} + +void StatementDelegate::setupCallFunction() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return); + + const auto functionStatement = std::get(m_statement); + m_functionDelegate.setup(functionStatement.nodeId, functionStatement.functionName); +} + +void StatementDelegate::setupChangeState() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return); + + QTC_ASSERT(m_view->isAttached(), return); + + auto model = m_view->model(); + const auto items = Utils::filtered(m_view->allModelNodesOfType(model->qtQuickItemMetaInfo()), + [](const ModelNode &node) { + QmlItemNode item(node); + return node.hasId() && item.isValid() + && !item.allStateNames().isEmpty(); + }); + + QStringList itemIds = Utils::transform(items, &ModelNode::id); + const auto groups = m_view->allModelNodesOfType(model->qtQuickStateGroupMetaInfo()); + + const auto rootId = m_view->rootModelNode().id(); + itemIds.removeAll(rootId); + + QStringList groupIds = Utils::transform(groups, &ModelNode::id); + + Utils::sort(itemIds); + Utils::sort(groupIds); + + if (!rootId.isEmpty()) + groupIds.prepend(rootId); + + const QStringList stateGroupModel = groupIds + itemIds; + m_stateTargets.setModel(stateGroupModel); + + const auto stateSet = std::get(m_statement); + + m_stateTargets.setCurrentText(stateSet.nodeId); + setupStates(); +} + +static QString stripQuotesFromState(const QString &input) +{ + if (input.startsWith("\"") && input.endsWith("\"")) { + QString ret = input; + ret.remove(0, 1); + ret.chop(1); + return ret; + } + return input; +} + +void StatementDelegate::setupStates() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return); + QTC_ASSERT(m_view->isAttached(), return); + + const auto stateSet = std::get(m_statement); + + const QString nodeId = m_stateTargets.currentText(); + + const ModelNode node = m_view->modelNodeForId(nodeId); + + QStringList states; + if (node.metaInfo().isQtQuickItem()) { + QmlItemNode item(node); + QTC_ASSERT(item.isValid(), return); + if (item.isRootNode()) + states = item.states().names(); //model + else + states = item.allStateNames(); //instances + } else { + QmlModelStateGroup group(node); + states = group.names(); //model + } + + const QString stateName = stripQuotesFromState(stateSet.stateName); + + states.prepend(baseStateName()); + m_states.setModel(states); + if (stateName.isEmpty()) + m_states.setCurrentText(baseStateName()); + else + m_states.setCurrentText(stateName); +} + +void StatementDelegate::setupPrintMessage() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return); + + const auto consoleLog = std::get(m_statement); + m_stringArgument.setText(ScriptEditorStatements::toString(consoleLog.argument)); +} + +void StatementDelegate::setupPropertyType() +{ + PropertyTreeModel::PropertyTypes type = PropertyTreeModel::AllTypes; + + const NodeMetaInfo metaInfo = m_rhsAssignmentDelegate.propertyMetaInfo(); + + if (metaInfo.isBool()) + type = PropertyTreeModel::BoolType; + else if (metaInfo.isNumber()) + type = PropertyTreeModel::NumberType; + else if (metaInfo.isColor()) + type = PropertyTreeModel::ColorType; + else if (metaInfo.isString()) + type = PropertyTreeModel::StringType; + else if (metaInfo.isUrl()) + type = PropertyTreeModel::UrlType; + + m_lhsDelegate.setPropertyType(type); +} + +QString StatementDelegate::baseStateName() const +{ + return tr("Base State"); +} + +ScriptEditorBackend::ScriptEditorBackend(AbstractView *view) + : m_okStatementDelegate(view) + , m_koStatementDelegate(view) + , m_conditionListModel(view) + , m_propertyTreeModel(view) + , m_propertyListProxyModel(&m_propertyTreeModel) + , m_view(view) +{ + connect(&m_okStatementDelegate, &StatementDelegate::statementChanged, this, [this] { + handleOkStatementChanged(); + }); + + connect(&m_koStatementDelegate, &StatementDelegate::statementChanged, this, [this] { + handleKOStatementChanged(); + }); + + connect(&m_conditionListModel, &ConditionListModel::conditionChanged, this, [this] { + handleConditionChanged(); + }); +} + +static QString generateDefaultStatement(ScriptEditorBackend::ActionType actionType, + const QString &rootId) +{ + switch (actionType) { + case StatementDelegate::CallFunction: + return "Qt.quit()"; + case StatementDelegate::Assign: + return QString("%1.visible = %1.visible").arg(rootId); + case StatementDelegate::ChangeState: + return QString("%1.state = \"\"").arg(rootId); + case StatementDelegate::SetProperty: + return QString("%1.visible = true").arg(rootId); + case StatementDelegate::PrintMessage: + return QString("console.log(\"test\")").arg(rootId); + case StatementDelegate::Custom: + return {}; + }; + + return {}; +} + +void ScriptEditorBackend::changeActionType(ActionType actionType) +{ + QTC_ASSERT(actionType != StatementDelegate::Custom, return); + + AbstractView *view = m_view; + + QTC_ASSERT(view, return); + QTC_ASSERT(view->isAttached(), return); + + view->executeInTransaction("ScriptEditorBackend::removeCondition", [&]() { + ScriptEditorStatements::MatchedStatement &okStatement = ScriptEditorStatements::okStatement( + m_handler); + + ScriptEditorStatements::MatchedStatement &koStatement = ScriptEditorStatements::koStatement( + m_handler); + + koStatement = ScriptEditorStatements::EmptyBlock(); + + //We expect a valid id on the root node + const QString validId = view->rootModelNode().validId(); + QString statementSource = generateDefaultStatement(actionType, validId); + + auto tempHandler = ScriptEditorEvaluator::parseStatement(statementSource); //what's that? + + auto newOkStatement = ScriptEditorStatements::okStatement(tempHandler); + + QTC_ASSERT(!ScriptEditorStatements::isEmptyStatement(newOkStatement), return); + + okStatement = newOkStatement; + + QString newSource = ScriptEditorStatements::toJavascript(m_handler); + + setPropertySource(newSource); + }); + + setSource(getSourceFromProperty(getSourceProperty())); + + setupHandlerAndStatements(); + setupCondition(); +} + +void ScriptEditorBackend::addCondition() +{ + ScriptEditorStatements::MatchedStatement okStatement = ScriptEditorStatements::okStatement( + m_handler); + + ScriptEditorStatements::MatchedCondition newCondition; + + ScriptEditorStatements::Variable variable; + variable.nodeId = defaultCondition; + newCondition.statements.append(variable); + + ScriptEditorStatements::ConditionalStatement conditionalStatement; + + conditionalStatement.ok = okStatement; + conditionalStatement.condition = newCondition; + + m_handler = conditionalStatement; + + QString newSource = ScriptEditorStatements::toJavascript(m_handler); + + commitNewSource(newSource); + + setupHandlerAndStatements(); + setupCondition(); +} + +void ScriptEditorBackend::removeCondition() +{ + ScriptEditorStatements::MatchedStatement okStatement = ScriptEditorStatements::okStatement( + m_handler); + + m_handler = okStatement; + + QString newSource = ScriptEditorStatements::toJavascript(m_handler); + + commitNewSource(newSource); + + setupHandlerAndStatements(); + setupCondition(); +} + +void ScriptEditorBackend::addElse() +{ + ScriptEditorStatements::MatchedStatement okStatement = ScriptEditorStatements::okStatement( + m_handler); + + auto &condition = ScriptEditorStatements::conditionalStatement(m_handler); + condition.ko = condition.ok; + + QString newSource = ScriptEditorStatements::toJavascript(m_handler); + + commitNewSource(newSource); + setupHandlerAndStatements(); +} + +void ScriptEditorBackend::removeElse() +{ + ScriptEditorStatements::MatchedStatement okStatement = ScriptEditorStatements::okStatement( + m_handler); + + auto &condition = ScriptEditorStatements::conditionalStatement(m_handler); + condition.ko = ScriptEditorStatements::EmptyBlock(); + + QString newSource = ScriptEditorStatements::toJavascript(m_handler); + + commitNewSource(newSource); + setupHandlerAndStatements(); +} + +void ScriptEditorBackend::setNewSource(const QString &newSource) +{ + setSource(newSource); + commitNewSource(newSource); + setupHandlerAndStatements(); + setupCondition(); +} + +void ScriptEditorBackend::update() +{ + if (m_blockReflection) + return; + + m_propertyTreeModel.resetModel(); + m_propertyListProxyModel.setRowAndInternalId(0, internalRootIndex); + + AbstractView *view = m_view; + + QTC_ASSERT(view, return); + if (!view->isAttached()) + return; + + // setup print message as default action if property does not exists + auto sourceProperty = getSourceProperty(); + if (sourceProperty.exists()) + setSource(getSourceFromProperty(sourceProperty)); + else + setSource(QStringLiteral("console.log(\"%1\")").arg(sourceProperty.parentModelNode().id())); + + setupHandlerAndStatements(); + + setupCondition(); +} + +void ScriptEditorBackend::jumpToCode() +{ + AbstractView *view = m_view; + + QTC_ASSERT(view, return); + QTC_ASSERT(view->isAttached(), return); + auto sourceProperty = getSourceProperty(); + + ModelNodeOperations::jumpToCode(sourceProperty.parentModelNode()); +} + +bool ScriptEditorBackend::blockReflection() const +{ + return m_blockReflection; +} + +BindingProperty ScriptEditorBackend::getBindingProperty() const +{ + AbstractView *view = m_view; + + QTC_ASSERT(view, return {}); + QTC_ASSERT(view->isAttached(), return {}); + + return SelectionContext{view}.currentSingleSelectedNode().bindingProperty("script"); +} + +void ScriptEditorBackend::handleException() +{ + QMessageBox::warning(nullptr, tr("Error"), m_exceptionError); +} + +bool ScriptEditorBackend::hasCondition() const +{ + return m_hasCondition; +} + +bool ScriptEditorBackend::hasElse() const +{ + return m_hasElse; +} + +void ScriptEditorBackend::setHasCondition(bool b) +{ + if (b == m_hasCondition) + return; + + m_hasCondition = b; + emit hasConditionChanged(); +} + +void ScriptEditorBackend::setHasElse(bool b) +{ + if (b == m_hasElse) + return; + + m_hasElse = b; + emit hasElseChanged(); +} + +ScriptEditorBackend::ActionType ScriptEditorBackend::actionType() const +{ + return m_actionType; +} + +AbstractProperty ScriptEditorBackend::getSourceProperty() const +{ + return getBindingProperty(); +} + +StatementDelegate *ScriptEditorBackend::okStatement() +{ + return &m_okStatementDelegate; +} + +StatementDelegate *ScriptEditorBackend::koStatement() +{ + return &m_koStatementDelegate; +} + +ConditionListModel *ScriptEditorBackend::conditionListModel() +{ + return &m_conditionListModel; +} + +QString ScriptEditorBackend::indentedSource() const +{ + if (m_source.isEmpty()) + return {}; + + QTextDocument doc(m_source); + IndentingTextEditModifier mod(&doc); + + mod.indent(0, m_source.length() - 1); + return mod.text(); +} + +QString ScriptEditorBackend::source() const +{ + return m_source; +} + +void ScriptEditorBackend::setSource(const QString &source) +{ + if (source == m_source) + return; + + m_source = source; + emit sourceChanged(); +} + +PropertyTreeModel *ScriptEditorBackend::propertyTreeModel() +{ + return &m_propertyTreeModel; +} + +PropertyListProxyModel *ScriptEditorBackend::propertyListProxyModel() +{ + return &m_propertyListProxyModel; +} + +void ScriptEditorBackend::setupCondition() +{ + auto &condition = ScriptEditorStatements::matchedCondition(m_handler); + m_conditionListModel.setCondition(ScriptEditorStatements::matchedCondition(m_handler)); + setHasCondition(!condition.statements.isEmpty()); +} + +void ScriptEditorBackend::setupHandlerAndStatements() +{ + AbstractView *view = m_view; + QTC_ASSERT(view, return); + + if (m_source.isEmpty()) { + m_actionType = StatementDelegate::Custom; + m_handler = ScriptEditorStatements::EmptyBlock(); + } else { + m_handler = ScriptEditorEvaluator::parseStatement(m_source); + const QString statementType = QmlDesigner::ScriptEditorStatements::toDisplayName(m_handler); + if (statementType == ScriptEditorStatements::EMPTY_DISPLAY_NAME) { + m_actionType = StatementDelegate::Custom; + } else if (statementType == ScriptEditorStatements::ASSIGNMENT_DISPLAY_NAME) { + m_actionType = StatementDelegate::Assign; + } else if (statementType == ScriptEditorStatements::SETPROPERTY_DISPLAY_NAME) { + m_actionType = StatementDelegate::SetProperty; + } else if (statementType == ScriptEditorStatements::FUNCTION_DISPLAY_NAME) { + m_actionType = StatementDelegate::CallFunction; + } else if (statementType == ScriptEditorStatements::SETSTATE_DISPLAY_NAME) { + m_actionType = StatementDelegate::ChangeState; + } else if (statementType == ScriptEditorStatements::LOG_DISPLAY_NAME) { + m_actionType = StatementDelegate::PrintMessage; + } else { + m_actionType = StatementDelegate::Custom; + } + } + + ScriptEditorStatements::MatchedStatement &okStatement = ScriptEditorStatements::okStatement( + m_handler); + m_okStatementDelegate.setStatement(okStatement); + m_okStatementDelegate.setActionType(m_actionType); + + ScriptEditorStatements::MatchedStatement &koStatement = ScriptEditorStatements::koStatement( + m_handler); + + if (!ScriptEditorStatements::isEmptyStatement(koStatement)) { + m_koStatementDelegate.setStatement(koStatement); + m_koStatementDelegate.setActionType(m_actionType); + } + + setHasElse(!ScriptEditorStatements::isEmptyStatement(koStatement)); + + emit actionTypeChanged(); +} + +void ScriptEditorBackend::handleOkStatementChanged() +{ + ScriptEditorStatements::MatchedStatement &okStatement = ScriptEditorStatements::okStatement( + m_handler); + + okStatement = m_okStatementDelegate.statement(); //TODO why? + + QString newSource = ScriptEditorStatements::toJavascript(m_handler); + + commitNewSource(newSource); +} + +void ScriptEditorBackend::handleKOStatementChanged() +{ + ScriptEditorStatements::MatchedStatement &koStatement = ScriptEditorStatements::koStatement( + m_handler); + + koStatement = m_koStatementDelegate.statement(); //TODO why? + + QString newSource = ScriptEditorStatements::toJavascript(m_handler); + + commitNewSource(newSource); +} + +void ScriptEditorBackend::handleConditionChanged() +{ + AbstractView *view = m_view; + + QTC_ASSERT(view, return); + QTC_ASSERT(view->isAttached(), return); + + ScriptEditorStatements::MatchedCondition &condition = ScriptEditorStatements::matchedCondition( + m_handler); + condition = m_conditionListModel.condition(); //why? + QString newSource = ScriptEditorStatements::toJavascript(m_handler); + + commitNewSource(newSource); +} + +void ScriptEditorBackend::setPropertySource(const QString &source) +{ + auto property = getSourceProperty(); + + QTC_ASSERT(property.isValid(), return); + + if (source.isEmpty()) + return; + + auto normalizedSource = QmlDesigner::SignalHandlerProperty::normalizedSourceWithBraces(source); + if (property.exists()) + property.toBindingProperty().setExpression(normalizedSource); + else + property.parentModelNode().bindingProperty(property.name()).setExpression(normalizedSource); +} + +void ScriptEditorBackend::commitNewSource(const QString &source) +{ + AbstractView *view = m_view; + + QTC_ASSERT(view, return); + QTC_ASSERT(view->isAttached(), return); + + m_blockReflection = true; + view->executeInTransaction("ScriptEditorBackend::commitNewSource", + [&]() { setPropertySource(source); }); + setSource(getSourceFromProperty(getSourceProperty())); + m_blockReflection = false; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.h b/src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.h new file mode 100644 index 00000000000..496c4d198a0 --- /dev/null +++ b/src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.h @@ -0,0 +1,250 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "propertytreemodel.h" +#include "scripteditorstatements.h" + +namespace QmlDesigner { + +class ConditionListModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(bool valid READ valid NOTIFY validChanged) + Q_PROPERTY(bool empty READ empty NOTIFY emptyChanged) + Q_PROPERTY(QString error READ error NOTIFY errorChanged) + Q_PROPERTY(int errorIndex READ errorIndex NOTIFY errorIndexChanged) + +public: + enum ConditionType { Intermediate, Invalid, Operator, Literal, Variable, Shadow }; + Q_ENUM(ConditionType) + + struct ConditionToken + { + ConditionType type; + QString value; + }; + + ConditionListModel(AbstractView *view); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QHash roleNames() const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + void setup(); + void setCondition(ScriptEditorStatements::MatchedCondition &condition); + ScriptEditorStatements::MatchedCondition &condition(); + + static ConditionToken tokenFromConditionToken(const ScriptEditorStatements::ConditionToken &token); + + static ConditionToken tokenFromComparativeStatement( + const ScriptEditorStatements::ComparativeStatement &token); + + Q_INVOKABLE void insertToken(int index, const QString &value); + Q_INVOKABLE void updateToken(int index, const QString &value); + Q_INVOKABLE void appendToken(const QString &value); + Q_INVOKABLE void removeToken(int index); + + Q_INVOKABLE void insertIntermediateToken(int index, const QString &value); + Q_INVOKABLE void insertShadowToken(int index, const QString &value); + Q_INVOKABLE void setShadowToken(int index, const QString &value); + + bool valid() const; + bool empty() const; + + //for debugging + Q_INVOKABLE void command(const QString &string); + + void setInvalid(const QString &errorMessage, int index = -1); + void setValid(); + + QString error() const; + int errorIndex() const; + + Q_INVOKABLE bool operatorAllowed(int cursorPosition); + +signals: + void validChanged(); + void emptyChanged(); + void conditionChanged(); + void errorChanged(); + void errorIndexChanged(); + +private: + void internalSetup(); + ConditionToken valueToToken(const QString &value); + void resetModel(); + int checkOrder() const; + void validateAndRebuildTokens(); + void rebuildTokens(); + + ScriptEditorStatements::ConditionToken toOperatorStatement(const ConditionToken &token); + ScriptEditorStatements::ComparativeStatement toStatement(const ConditionToken &token); + + AbstractView *m_view = nullptr; + ScriptEditorStatements::MatchedCondition &m_condition; + QList m_tokens; + bool m_valid = false; + QString m_errorMessage; + int m_errorIndex = -1; +}; + +class StatementDelegate : public QObject +{ + Q_OBJECT + +public: + explicit StatementDelegate(AbstractView *view); + + enum ActionType { CallFunction, Assign, ChangeState, SetProperty, PrintMessage, Custom }; + + Q_ENUM(ActionType) + + Q_PROPERTY(ActionType actionType READ actionType NOTIFY actionTypeChanged) + + Q_PROPERTY(PropertyTreeModelDelegate *function READ function CONSTANT) + Q_PROPERTY(PropertyTreeModelDelegate *lhs READ lhs CONSTANT) + Q_PROPERTY(PropertyTreeModelDelegate *rhsAssignment READ rhsAssignment CONSTANT) + Q_PROPERTY(StudioQmlTextBackend *stringArgument READ stringArgument CONSTANT) + Q_PROPERTY(StudioQmlComboBoxBackend *states READ states CONSTANT) + Q_PROPERTY(StudioQmlComboBoxBackend *stateTargets READ stateTargets CONSTANT) + + void setActionType(ActionType type); + void setup(); + void setStatement(ScriptEditorStatements::MatchedStatement &statement); + ScriptEditorStatements::MatchedStatement &statement(); + +signals: + void actionTypeChanged(); + void statementChanged(); + +private: + ActionType actionType() const; + PropertyTreeModelDelegate *function(); + PropertyTreeModelDelegate *lhs(); + PropertyTreeModelDelegate *rhsAssignment(); + StudioQmlTextBackend *stringArgument(); + StudioQmlComboBoxBackend *stateTargets(); + StudioQmlComboBoxBackend *states(); + + void handleFunctionChanged(); + void handleLhsChanged(); + void handleRhsAssignmentChanged(); + void handleStringArgumentChanged(); + void handleStateChanged(); + void handleStateTargetsChanged(); + + void setupAssignment(); + void setupSetProperty(); + void setupCallFunction(); + void setupChangeState(); + void setupStates(); + void setupPrintMessage(); + void setupPropertyType(); + QString baseStateName() const; + + ActionType m_actionType; + PropertyTreeModelDelegate m_functionDelegate; + PropertyTreeModelDelegate m_lhsDelegate; + PropertyTreeModelDelegate m_rhsAssignmentDelegate; + ScriptEditorStatements::MatchedStatement &m_statement; + AbstractView *m_view = nullptr; + StudioQmlTextBackend m_stringArgument; + StudioQmlComboBoxBackend m_stateTargets; + StudioQmlComboBoxBackend m_states; +}; + +class ScriptEditorBackend : public QObject +{ + Q_OBJECT + Q_PROPERTY(ActionType actionType READ actionType NOTIFY actionTypeChanged) + Q_PROPERTY(StatementDelegate *okStatement READ okStatement CONSTANT) + Q_PROPERTY(StatementDelegate *koStatement READ koStatement CONSTANT) + Q_PROPERTY(ConditionListModel *conditionListModel READ conditionListModel CONSTANT) + Q_PROPERTY(bool hasCondition READ hasCondition NOTIFY hasConditionChanged) + Q_PROPERTY(bool hasElse READ hasElse NOTIFY hasElseChanged) + Q_PROPERTY(QString source READ source NOTIFY sourceChanged) + Q_PROPERTY(QString indentedSource READ indentedSource NOTIFY sourceChanged) + + Q_PROPERTY(PropertyTreeModel *propertyTreeModel READ propertyTreeModel CONSTANT) + Q_PROPERTY(PropertyListProxyModel *propertyListProxyModel READ propertyListProxyModel CONSTANT) + +public: + explicit ScriptEditorBackend(AbstractView *view); + + using ActionType = StatementDelegate::ActionType; + + Q_INVOKABLE void changeActionType(QmlDesigner::StatementDelegate::ActionType actionType); + + Q_INVOKABLE void addCondition(); + Q_INVOKABLE void removeCondition(); + + Q_INVOKABLE void addElse(); + Q_INVOKABLE void removeElse(); + + Q_INVOKABLE void setNewSource(const QString &newSource); + + Q_INVOKABLE virtual void update(); + + Q_INVOKABLE void jumpToCode(); + +signals: + void actionTypeChanged(); + void hasConditionChanged(); + void hasElseChanged(); + void sourceChanged(); + void popupShouldClose(); + void popupShouldOpen(); + +protected: + bool blockReflection() const; + ; + +private: + virtual AbstractProperty getSourceProperty() const; + virtual void setPropertySource(const QString &source); + + BindingProperty getBindingProperty() const; + void handleException(); + bool hasCondition() const; + bool hasElse() const; + void setHasCondition(bool b); + void setHasElse(bool b); + ActionType actionType() const; + StatementDelegate *okStatement(); + StatementDelegate *koStatement(); + ConditionListModel *conditionListModel(); + QString indentedSource() const; + QString source() const; + void setSource(const QString &source); + + PropertyTreeModel *propertyTreeModel(); + PropertyListProxyModel *propertyListProxyModel(); + + void setupCondition(); + void setupHandlerAndStatements(); + + void handleOkStatementChanged(); + void handleKOStatementChanged(); + void handleConditionChanged(); + + void commitNewSource(const QString &source); + + ActionType m_actionType; + QString m_exceptionError; + ScriptEditorStatements::Handler m_handler; + StatementDelegate m_okStatementDelegate; + StatementDelegate m_koStatementDelegate; + ConditionListModel m_conditionListModel; + bool m_hasCondition = false; + bool m_hasElse = false; + QString m_source; + PropertyTreeModel m_propertyTreeModel; + PropertyListProxyModel m_propertyListProxyModel; + bool m_blockReflection = false; + QPointer m_view = nullptr; +}; +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorevaluator.cpp b/src/plugins/qmldesigner/components/scripteditor/scripteditorevaluator.cpp similarity index 80% rename from src/plugins/qmldesigner/components/connectioneditor/connectioneditorevaluator.cpp rename to src/plugins/qmldesigner/components/scripteditor/scripteditorevaluator.cpp index 7bea5cd2cdd..2f6d8ba6c2f 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorevaluator.cpp +++ b/src/plugins/qmldesigner/components/scripteditor/scripteditorevaluator.cpp @@ -1,10 +1,10 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "connectioneditorevaluator.h" -#include "connectioneditorutils.h" +#include "scripteditorevaluator.h" #include "qmljs/parser/qmljsast_p.h" #include "qmljs/qmljsdocument.h" +#include "scripteditorutils.h" #include @@ -14,10 +14,10 @@ using namespace QmlDesigner; using QmlJS::AST::Node; using Kind = Node::Kind; -using ConnectionEditorStatements::ConditionalStatement; -using ConnectionEditorStatements::ConditionToken; -using ConnectionEditorStatements::MatchedCondition; -using ConnectionEditorStatements::MatchedStatement; +using ScriptEditorStatements::ConditionalStatement; +using ScriptEditorStatements::ConditionToken; +using ScriptEditorStatements::MatchedCondition; +using ScriptEditorStatements::MatchedStatement; namespace { enum class TrackingArea { No, Condition, Ok, Ko }; @@ -255,14 +255,14 @@ protected: void throwRecursionDepthError() override { checkValidityAndReturn(false, "Recursion depth problem"); - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "Recursion depth error"; + qCWarning(ScriptEditorLog) << __FUNCTION__ << "Recursion depth error"; } void checkAndResetVariable() { if (--m_depth == 0) { m_condition.statements.push_back( - ConnectionEditorStatements::Variable{m_identifier, m_fields.join(".")}); + ScriptEditorStatements::Variable{m_identifier, m_fields.join(".")}); m_identifier.clear(); m_fields.clear(); } @@ -300,7 +300,7 @@ private: class RightHandVisitor : public QmlJS::AST::Visitor { public: - ConnectionEditorStatements::RightHandSide rhs() const { return m_rhs; } + ScriptEditorStatements::RightHandSide rhs() const { return m_rhs; } void reset() { @@ -318,48 +318,48 @@ public: if (!isValid()) return false; - return ConnectionEditorStatements::isLiteralType(rhs()); + return ScriptEditorStatements::isLiteralType(rhs()); } bool couldBeLHS() const { if (!isValid()) return false; - return std::holds_alternative(m_rhs); + return std::holds_alternative(m_rhs); } inline bool couldBeVariable() const { return couldBeLHS(); } - ConnectionEditorStatements::Literal literal() const + ScriptEditorStatements::Literal literal() const { if (!isLiteralType()) return {}; return std::visit( - Overload{[](const bool &var) -> ConnectionEditorStatements::Literal { return var; }, - [](const double &var) -> ConnectionEditorStatements::Literal { return var; }, - [](const QString &var) -> ConnectionEditorStatements::Literal { return var; }, - [](const auto &) -> ConnectionEditorStatements::Literal { return false; }}, + Overload{[](const bool &var) -> ScriptEditorStatements::Literal { return var; }, + [](const double &var) -> ScriptEditorStatements::Literal { return var; }, + [](const QString &var) -> ScriptEditorStatements::Literal { return var; }, + [](const auto &) -> ScriptEditorStatements::Literal { return false; }}, m_rhs); } - ConnectionEditorStatements::Variable lhs() const + ScriptEditorStatements::Variable lhs() const { if (!isValid()) return {}; - if (auto rhs = std::get_if(&m_rhs)) + if (auto rhs = std::get_if(&m_rhs)) return *rhs; return {}; } - ConnectionEditorStatements::Variable variable() const + ScriptEditorStatements::Variable variable() const { if (!isValid()) return {}; - if (auto rhs = std::get_if(&m_rhs)) + if (auto rhs = std::get_if(&m_rhs)) return *rhs; return {}; @@ -478,13 +478,13 @@ protected: void throwRecursionDepthError() override { setFailed(); - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "Recursion depth error"; + qCWarning(ScriptEditorLog) << __FUNCTION__ << "Recursion depth error"; } void checkAndResetCal() { if (--m_depth == 0) { - m_rhs = ConnectionEditorStatements::MatchedFunction{m_identifier, m_fields.join(".")}; + m_rhs = ScriptEditorStatements::MatchedFunction{m_identifier, m_fields.join(".")}; m_specified = true; m_identifier.clear(); @@ -495,7 +495,7 @@ protected: void checkAndResetNonCal() { if (--m_depth == 0) { - m_rhs = ConnectionEditorStatements::Variable{m_identifier, m_fields.join(".")}; + m_rhs = ScriptEditorStatements::Variable{m_identifier, m_fields.join(".")}; m_specified = true; m_identifier.clear(); @@ -513,22 +513,23 @@ private: int m_depth = 0; QString m_identifier; QStringList m_fields; - ConnectionEditorStatements::RightHandSide m_rhs; + ScriptEditorStatements::RightHandSide m_rhs; }; MatchedStatement checkForStateSet(const MatchedStatement ¤tState) { - using namespace ConnectionEditorStatements; - return std::visit( - Overload{[](const PropertySet &propertySet) -> MatchedStatement { - if (propertySet.lhs.nodeId.size() && propertySet.lhs.propertyName == u"state" - && std::holds_alternative(propertySet.rhs)) - return StateSet{propertySet.lhs.nodeId, - ConnectionEditorStatements::toString(propertySet.rhs)}; - return propertySet; - }, - [](const auto &pSet) -> MatchedStatement { return pSet; }}, - currentState); + using namespace ScriptEditorStatements; + return std::visit(Overload{[](const PropertySet &propertySet) -> MatchedStatement { + if (propertySet.lhs.nodeId.size() + && propertySet.lhs.propertyName == u"state" + && std::holds_alternative(propertySet.rhs)) + return StateSet{propertySet.lhs.nodeId, + ScriptEditorStatements::toString( + propertySet.rhs)}; + return propertySet; + }, + [](const auto &pSet) -> MatchedStatement { return pSet; }}, + currentState); } class ConsoleLogEvaluator : public QmlJS::AST::Visitor @@ -536,9 +537,9 @@ class ConsoleLogEvaluator : public QmlJS::AST::Visitor public: bool isValid() { return m_completed; } - ConnectionEditorStatements::ConsoleLog expression() + ScriptEditorStatements::ConsoleLog expression() { - return ConnectionEditorStatements::ConsoleLog{m_arg}; + return ScriptEditorStatements::ConsoleLog{m_arg}; } protected: @@ -618,14 +619,14 @@ protected: void throwRecursionDepthError() override { m_failed = true; - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "Recursion depth error"; + qCWarning(ScriptEditorLog) << __FUNCTION__ << "Recursion depth error"; } private: bool m_failed = false; bool m_completed = false; int m_stepId = 0; - ConnectionEditorStatements::RightHandSide m_arg; + ScriptEditorStatements::RightHandSide m_arg; }; struct StatementReply @@ -650,10 +651,10 @@ struct StatementReply } // namespace -class QmlDesigner::ConnectionEditorEvaluatorPrivate +class QmlDesigner::ScriptEditorEvaluatorPrivate { - friend class ConnectionEditorEvaluator; - using Status = ConnectionEditorEvaluator::Status; + friend class ScriptEditorEvaluator; + using Status = ScriptEditorEvaluator::Status; public: bool checkValidityAndReturn(bool valid, const QString &parseError = {}); @@ -731,28 +732,28 @@ private: QString m_errorString; Status m_checkStatus = Status::UnStarted; QList m_nodeHierarchy; - ConnectionEditorStatements::Handler m_handler; + ScriptEditorStatements::Handler m_handler; }; -ConnectionEditorEvaluator::ConnectionEditorEvaluator() - : d(std::make_unique()) +ScriptEditorEvaluator::ScriptEditorEvaluator() + : d(std::make_unique()) {} -ConnectionEditorEvaluator::~ConnectionEditorEvaluator() {} +ScriptEditorEvaluator::~ScriptEditorEvaluator() {} -ConnectionEditorEvaluator::Status ConnectionEditorEvaluator::status() const +ScriptEditorEvaluator::Status ScriptEditorEvaluator::status() const { return d->m_checkStatus; } -ConnectionEditorStatements::Handler ConnectionEditorEvaluator::resultNode() const +ScriptEditorStatements::Handler ScriptEditorEvaluator::resultNode() const { - return (d->m_checkStatus == Succeeded) ? d->m_handler : ConnectionEditorStatements::EmptyBlock{}; + return (d->m_checkStatus == Succeeded) ? d->m_handler : ScriptEditorStatements::EmptyBlock{}; } -QString ConnectionEditorEvaluator::getDisplayStringForType(const QString &statement) +QString ScriptEditorEvaluator::getDisplayStringForType(const QString &statement) { - ConnectionEditorEvaluator evaluator; + ScriptEditorEvaluator evaluator; QmlJS::Document::MutablePtr newDoc = QmlJS::Document::create(Utils::FilePath::fromString( ""), QmlJS::Dialect::JavaScript); @@ -761,23 +762,23 @@ QString ConnectionEditorEvaluator::getDisplayStringForType(const QString &statem newDoc->parseJavaScript(); if (!newDoc->isParsedCorrectly()) - return ConnectionEditorStatements::CUSTOM_DISPLAY_NAME; + return ScriptEditorStatements::CUSTOM_DISPLAY_NAME; newDoc->ast()->accept(&evaluator); - const bool valid = evaluator.status() == ConnectionEditorEvaluator::Succeeded; + const bool valid = evaluator.status() == ScriptEditorEvaluator::Succeeded; if (!valid) - return ConnectionEditorStatements::CUSTOM_DISPLAY_NAME; + return ScriptEditorStatements::CUSTOM_DISPLAY_NAME; auto result = evaluator.resultNode(); - return QmlDesigner::ConnectionEditorStatements::toDisplayName(result); + return QmlDesigner::ScriptEditorStatements::toDisplayName(result); } -ConnectionEditorStatements::Handler ConnectionEditorEvaluator::parseStatement(const QString &statement) +ScriptEditorStatements::Handler ScriptEditorEvaluator::parseStatement(const QString &statement) { - ConnectionEditorEvaluator evaluator; + ScriptEditorEvaluator evaluator; QmlJS::Document::MutablePtr newDoc = QmlJS::Document::create(Utils::FilePath::fromString( ""), QmlJS::Dialect::JavaScript); @@ -786,19 +787,19 @@ ConnectionEditorStatements::Handler ConnectionEditorEvaluator::parseStatement(co newDoc->parseJavaScript(); if (!newDoc->isParsedCorrectly()) - return ConnectionEditorStatements::EmptyBlock{}; + return ScriptEditorStatements::EmptyBlock{}; newDoc->ast()->accept(&evaluator); - const bool valid = evaluator.status() == ConnectionEditorEvaluator::Succeeded; + const bool valid = evaluator.status() == ScriptEditorEvaluator::Succeeded; if (!valid) - return ConnectionEditorStatements::EmptyBlock{}; + return ScriptEditorStatements::EmptyBlock{}; return evaluator.resultNode(); } -bool ConnectionEditorEvaluator::preVisit(Node *node) +bool ScriptEditorEvaluator::preVisit(Node *node) { if (d->m_nodeHierarchy.size()) { NodeStatus &parentNode = d->m_nodeHierarchy.last(); @@ -832,7 +833,7 @@ bool ConnectionEditorEvaluator::preVisit(Node *node) } } -void ConnectionEditorEvaluator::postVisit(QmlJS::AST::Node *node) +void ScriptEditorEvaluator::postVisit(QmlJS::AST::Node *node) { if (d->m_nodeHierarchy.isEmpty()) { d->checkValidityAndReturn(false, "Unexpected post visiting"); @@ -859,18 +860,18 @@ void ConnectionEditorEvaluator::postVisit(QmlJS::AST::Node *node) } } -bool ConnectionEditorEvaluator::visit([[maybe_unused]] QmlJS::AST::Program *program) +bool ScriptEditorEvaluator::visit([[maybe_unused]] QmlJS::AST::Program *program) { d->setStatus(UnFinished); d->setTrackingArea(false, 0); d->m_ifStatement = 0; d->m_consoleLogCount = 0; d->m_consoleIdentifierCount = 0; - d->m_handler = ConnectionEditorStatements::EmptyBlock{}; + d->m_handler = ScriptEditorStatements::EmptyBlock{}; return true; } -bool ConnectionEditorEvaluator::visit([[maybe_unused]] QmlJS::AST::IfStatement *ifStatement) +bool ScriptEditorEvaluator::visit([[maybe_unused]] QmlJS::AST::IfStatement *ifStatement) { if (d->m_ifStatement++) return d->checkValidityAndReturn(false, "Nested if conditions are not supported"); @@ -885,7 +886,7 @@ bool ConnectionEditorEvaluator::visit([[maybe_unused]] QmlJS::AST::IfStatement * return d->checkValidityAndReturn(true); } -bool ConnectionEditorEvaluator::visit(QmlJS::AST::IdentifierExpression *identifier) +bool ScriptEditorEvaluator::visit(QmlJS::AST::IdentifierExpression *identifier) { if (d->parentNodeStatus() == Kind::Kind_FieldMemberExpression) if (d->m_consoleLogCount) @@ -896,7 +897,7 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::IdentifierExpression *identifi return d->checkValidityAndReturn(true); } -bool ConnectionEditorEvaluator::visit(QmlJS::AST::BinaryExpression *binaryExpression) +bool ScriptEditorEvaluator::visit(QmlJS::AST::BinaryExpression *binaryExpression) { if (d->isInIfCondition()) { if (binaryExpression->op == QSOperator::Assign) @@ -923,7 +924,7 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::BinaryExpression *binaryExpres return false; } else { MatchedStatement *currentStatement = d->currentStatement(); - if (currentStatement && ConnectionEditorStatements::isEmptyStatement(*currentStatement) + if (currentStatement && ScriptEditorStatements::isEmptyStatement(*currentStatement) && d->parentNodeStatus().childId() == 0) { if (binaryExpression->op == QSOperator::Assign) { RightHandVisitor variableVisitor; @@ -932,16 +933,16 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::BinaryExpression *binaryExpres if (!variableVisitor.couldBeLHS()) return d->checkValidityAndReturn(false, "Invalid left hand."); - ConnectionEditorStatements::Variable lhs = variableVisitor.lhs(); + ScriptEditorStatements::Variable lhs = variableVisitor.lhs(); variableVisitor.reset(); binaryExpression->right->accept(&variableVisitor); if (variableVisitor.couldBeLHS()) { - ConnectionEditorStatements::Assignment assignment{lhs, variableVisitor.variable()}; + ScriptEditorStatements::Assignment assignment{lhs, variableVisitor.variable()}; *currentStatement = assignment; } else if (variableVisitor.isLiteralType()) { - ConnectionEditorStatements::PropertySet propSet{lhs, variableVisitor.literal()}; + ScriptEditorStatements::PropertySet propSet{lhs, variableVisitor.literal()}; *currentStatement = propSet; } else { return d->checkValidityAndReturn(false, "Invalid RHS"); @@ -955,7 +956,7 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::BinaryExpression *binaryExpres return d->checkValidityAndReturn(true); } -bool ConnectionEditorEvaluator::visit(QmlJS::AST::FieldMemberExpression *fieldExpression) +bool ScriptEditorEvaluator::visit(QmlJS::AST::FieldMemberExpression *fieldExpression) { if (d->parentNodeStatus() == Kind::Kind_CallExpression && fieldExpression->name == u"log") d->m_consoleLogCount++; @@ -965,7 +966,7 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::FieldMemberExpression *fieldEx return d->checkValidityAndReturn(true); } -bool ConnectionEditorEvaluator::visit(QmlJS::AST::CallExpression *callExpression) +bool ScriptEditorEvaluator::visit(QmlJS::AST::CallExpression *callExpression) { if (d->isInIfCondition()) return d->checkValidityAndReturn(false, "Functions are not allowd in the expressions"); @@ -974,7 +975,7 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::CallExpression *callExpression if (!currentStatement) return d->checkValidityAndReturn(false, "Invalid place to call an expression"); - if (ConnectionEditorStatements::isEmptyStatement(*currentStatement)) { + if (ScriptEditorStatements::isEmptyStatement(*currentStatement)) { if (d->parentNodeStatus().childId() == 0) { ConsoleLogEvaluator logEvaluator; callExpression->accept(&logEvaluator); @@ -985,8 +986,8 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::CallExpression *callExpression callExpression->accept(&callVisitor); if (callVisitor.isValid()) { - ConnectionEditorStatements::RightHandSide rhs = callVisitor.rhs(); - if (auto rhs_ = std::get_if(&rhs)) + ScriptEditorStatements::RightHandSide rhs = callVisitor.rhs(); + if (auto rhs_ = std::get_if(&rhs)) *currentStatement = *rhs_; else return d->checkValidityAndReturn(false, "Invalid Matched Function type."); @@ -999,7 +1000,7 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::CallExpression *callExpression return d->checkValidityAndReturn(true); } -bool ConnectionEditorEvaluator::visit([[maybe_unused]] QmlJS::AST::Block *block) +bool ScriptEditorEvaluator::visit([[maybe_unused]] QmlJS::AST::Block *block) { Kind parentKind = d->parentNodeStatus(); @@ -1013,7 +1014,7 @@ bool ConnectionEditorEvaluator::visit([[maybe_unused]] QmlJS::AST::Block *block) return d->checkValidityAndReturn(false, "Block count ptoblem"); } -bool ConnectionEditorEvaluator::visit(QmlJS::AST::ArgumentList *arguments) +bool ScriptEditorEvaluator::visit(QmlJS::AST::ArgumentList *arguments) { if (d->trackingArea() == TrackingArea::Condition) return d->checkValidityAndReturn(false, "Arguments are not supported in if condition"); @@ -1022,7 +1023,7 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::ArgumentList *arguments) if (!currentStatement) return d->checkValidityAndReturn(false, "No statement found for argument"); - if (!ConnectionEditorStatements::isConsoleLog(*currentStatement)) + if (!ScriptEditorStatements::isConsoleLog(*currentStatement)) return d->checkValidityAndReturn(false, "Arguments are only supported for console.log"); if (d->m_acceptLogArgument && !arguments->next) @@ -1031,13 +1032,13 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::ArgumentList *arguments) return d->checkValidityAndReturn(false, "The only supported argument is in console.log"); } -void ConnectionEditorEvaluator::endVisit([[maybe_unused]] QmlJS::AST::Program *program) +void ScriptEditorEvaluator::endVisit([[maybe_unused]] QmlJS::AST::Program *program) { if (status() == UnFinished) d->setStatus(Succeeded); } -void ConnectionEditorEvaluator::endVisit(QmlJS::AST::FieldMemberExpression *fieldExpression) +void ScriptEditorEvaluator::endVisit(QmlJS::AST::FieldMemberExpression *fieldExpression) { if (status() != UnFinished) return; @@ -1053,12 +1054,12 @@ void ConnectionEditorEvaluator::endVisit(QmlJS::AST::FieldMemberExpression *fiel } } -void ConnectionEditorEvaluator::endVisit([[maybe_unused]] QmlJS::AST::CallExpression *callExpression) +void ScriptEditorEvaluator::endVisit([[maybe_unused]] QmlJS::AST::CallExpression *callExpression) { d->m_acceptLogArgument = false; } -void ConnectionEditorEvaluator::endVisit(QmlJS::AST::IfStatement * /*ifStatement*/) +void ScriptEditorEvaluator::endVisit(QmlJS::AST::IfStatement * /*ifStatement*/) { if (status() != UnFinished) return; @@ -1071,7 +1072,7 @@ void ConnectionEditorEvaluator::endVisit(QmlJS::AST::IfStatement * /*ifStatement } } -void ConnectionEditorEvaluator::endVisit(QmlJS::AST::StatementList * /*statementList*/) +void ScriptEditorEvaluator::endVisit(QmlJS::AST::StatementList * /*statementList*/) { if (status() != UnFinished) return; @@ -1080,26 +1081,26 @@ void ConnectionEditorEvaluator::endVisit(QmlJS::AST::StatementList * /*statement d->checkValidityAndReturn(false, "More than one statements are available."); } -void ConnectionEditorEvaluator::throwRecursionDepthError() +void ScriptEditorEvaluator::throwRecursionDepthError() { d->checkValidityAndReturn(false, "Recursion depth problem"); - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "Recursion depth error"; + qCWarning(ScriptEditorLog) << __FUNCTION__ << "Recursion depth error"; } -bool ConnectionEditorEvaluatorPrivate::checkValidityAndReturn(bool valid, const QString &parseError) +bool ScriptEditorEvaluatorPrivate::checkValidityAndReturn(bool valid, const QString &parseError) { if (!valid) { if (m_checkStatus != Status::Failed) { setStatus(Status::Failed); m_errorString = parseError; - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "Parse error" << parseError; + qCWarning(ScriptEditorLog) << __FUNCTION__ << "Parse error" << parseError; } } return m_checkStatus; } -NodeStatus ConnectionEditorEvaluatorPrivate::nodeStatus(int reverseLevel) const +NodeStatus ScriptEditorEvaluatorPrivate::nodeStatus(int reverseLevel) const { if (m_nodeHierarchy.size() > reverseLevel) return m_nodeHierarchy.at(m_nodeHierarchy.size() - reverseLevel - 1); diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorevaluator.h b/src/plugins/qmldesigner/components/scripteditor/scripteditorevaluator.h similarity index 77% rename from src/plugins/qmldesigner/components/connectioneditor/connectioneditorevaluator.h rename to src/plugins/qmldesigner/components/scripteditor/scripteditorevaluator.h index 2cc285ed6c5..33cae4b7e6d 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorevaluator.h +++ b/src/plugins/qmldesigner/components/scripteditor/scripteditorevaluator.h @@ -3,7 +3,7 @@ #pragma once -#include "connectioneditorstatements.h" +#include "scripteditorstatements.h" #include "qmldesigner_global.h" @@ -11,21 +11,21 @@ namespace QmlDesigner { -class ConnectionEditorEvaluatorPrivate; +class ScriptEditorEvaluatorPrivate; -class QMLDESIGNER_EXPORT ConnectionEditorEvaluator : public QmlJS::AST::Visitor +class QMLDESIGNER_EXPORT ScriptEditorEvaluator : public QmlJS::AST::Visitor { public: enum Status { UnStarted, UnFinished, Succeeded, Failed }; - ConnectionEditorEvaluator(); - virtual ~ConnectionEditorEvaluator(); + ScriptEditorEvaluator(); + virtual ~ScriptEditorEvaluator(); Status status() const; - ConnectionEditorStatements::Handler resultNode() const; + ScriptEditorStatements::Handler resultNode() const; static QString getDisplayStringForType(const QString &statement); - static ConnectionEditorStatements::Handler parseStatement(const QString &statement); + static ScriptEditorStatements::Handler parseStatement(const QString &statement); protected: bool preVisit(QmlJS::AST::Node *node) override; @@ -49,7 +49,7 @@ protected: void throwRecursionDepthError() override; private: - std::unique_ptr d; + std::unique_ptr d; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.cpp b/src/plugins/qmldesigner/components/scripteditor/scripteditorstatements.cpp similarity index 71% rename from src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.cpp rename to src/plugins/qmldesigner/components/scripteditor/scripteditorstatements.cpp index 5fbad9acadb..cc8c2872b98 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.cpp +++ b/src/plugins/qmldesigner/components/scripteditor/scripteditorstatements.cpp @@ -1,11 +1,11 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "connectioneditorstatements.h" +#include "scripteditorstatements.h" #include using namespace QmlDesigner; -using namespace ConnectionEditorStatements; +using namespace ScriptEditorStatements; namespace { template @@ -39,31 +39,31 @@ struct StringVisitor return "Variable{" + var.nodeId + propertyName + "}"; } - QString operator()(const ConnectionEditorStatements::MatchedFunction &func) + QString operator()(const ScriptEditorStatements::MatchedFunction &func) { return "MatchedFunction{" + func.nodeId + "." + func.functionName + "}"; } - QString operator()(const ConnectionEditorStatements::Assignment &assignment) + QString operator()(const ScriptEditorStatements::Assignment &assignment) { return "Assignment{" + assignment.lhs.expression() + " = " + StringVisitor()(assignment.rhs) + "}"; } - QString operator()(const ConnectionEditorStatements::PropertySet &propertySet) + QString operator()(const ScriptEditorStatements::PropertySet &propertySet) { return "PropertySet{" + propertySet.lhs.expression() + " = " + std::visit(StringVisitor{}, propertySet.rhs) + "}"; } - QString operator()(const ConnectionEditorStatements::StateSet &stateSet) + QString operator()(const ScriptEditorStatements::StateSet &stateSet) { return "StateSet{" + stateSet.nodeId + ".state = " + stateSet.stateName + "}"; } - QString operator()(const ConnectionEditorStatements::EmptyBlock &) { return "EmptyBlock{}"; } + QString operator()(const ScriptEditorStatements::EmptyBlock &) { return "EmptyBlock{}"; } - QString operator()(const ConnectionEditorStatements::ConsoleLog &consoleLog) + QString operator()(const ScriptEditorStatements::ConsoleLog &consoleLog) { return "ConsoleLog{" + std::visit(StringVisitor{}, consoleLog.argument) + "}"; } @@ -92,7 +92,7 @@ struct StringVisitor } } - QString operator()(const ConnectionEditorStatements::MatchedCondition &matched) + QString operator()(const ScriptEditorStatements::MatchedCondition &matched) { if (!matched.statements.size() && !matched.tokens.size()) return "MatchedCondition{}"; @@ -113,7 +113,7 @@ struct StringVisitor return value; } - QString operator()(const ConnectionEditorStatements::ConditionalStatement &conditional) + QString operator()(const ScriptEditorStatements::ConditionalStatement &conditional) { QString value; value.reserve(200); @@ -130,7 +130,7 @@ struct StringVisitor return value; } - QString operator()(const ConnectionEditorStatements::MatchedStatement &conditional) + QString operator()(const ScriptEditorStatements::MatchedStatement &conditional) { return std::visit(StringVisitor{}, conditional); } @@ -156,7 +156,7 @@ struct JSOverload return var.nodeId + propertyName; } - QString operator()(const ConnectionEditorStatements::MatchedFunction &func) + QString operator()(const ScriptEditorStatements::MatchedFunction &func) { QString funcName; if (func.functionName.size()) @@ -165,31 +165,31 @@ struct JSOverload return func.nodeId + funcName + "()"; } - QString operator()(const ConnectionEditorStatements::Assignment &assignment) + QString operator()(const ScriptEditorStatements::Assignment &assignment) { return JSOverload()(assignment.lhs) + " = " + JSOverload()(assignment.rhs); } - QString operator()(const ConnectionEditorStatements::PropertySet &propertySet) + QString operator()(const ScriptEditorStatements::PropertySet &propertySet) { return JSOverload()(propertySet.lhs) + " = " + std::visit(JSOverload{}, propertySet.rhs); } - QString operator()(const ConnectionEditorStatements::StateSet &stateSet) + QString operator()(const ScriptEditorStatements::StateSet &stateSet) { return stateSet.nodeId + ".state = " + stateSet.stateName; } - QString operator()(const ConnectionEditorStatements::EmptyBlock &) { return "{}"; } + QString operator()(const ScriptEditorStatements::EmptyBlock &) { return "{}"; } - QString operator()(const ConnectionEditorStatements::ConsoleLog &consoleLog) + QString operator()(const ScriptEditorStatements::ConsoleLog &consoleLog) { return "console.log(" + std::visit(JSOverload{}, consoleLog.argument) + ")"; } QString operator()(const ConditionToken &token) { return toJavascript(token); } - QString operator()(const ConnectionEditorStatements::MatchedCondition &matched) + QString operator()(const ScriptEditorStatements::MatchedCondition &matched) { if (!matched.statements.size() && !matched.tokens.size()) return {}; @@ -209,7 +209,7 @@ struct JSOverload return value; } - QString operator()(const ConnectionEditorStatements::MatchedStatement &statement) + QString operator()(const ScriptEditorStatements::MatchedStatement &statement) { if (isEmptyStatement(statement)) return {}; @@ -217,7 +217,7 @@ struct JSOverload return std::visit(JSOverload{}, statement); } - QString operator()(const ConnectionEditorStatements::ConditionalStatement &conditional) + QString operator()(const ScriptEditorStatements::ConditionalStatement &conditional) { QString value; value.reserve(200); @@ -240,47 +240,47 @@ struct JSOverload } // namespace -bool ConnectionEditorStatements::isEmptyStatement(const MatchedStatement &stat) +bool ScriptEditorStatements::isEmptyStatement(const MatchedStatement &stat) { return std::holds_alternative(stat); } -QString ConnectionEditorStatements::toString(const ComparativeStatement &stat) +QString ScriptEditorStatements::toString(const ComparativeStatement &stat) { return std::visit(StringVisitor{}, stat); } -QString ConnectionEditorStatements::toString(const RightHandSide &rhs) +QString ScriptEditorStatements::toString(const RightHandSide &rhs) { return std::visit(StringVisitor{}, rhs); } -QString ConnectionEditorStatements::toString(const Literal &literal) +QString ScriptEditorStatements::toString(const Literal &literal) { return std::visit(StringVisitor{}, literal); } -QString ConnectionEditorStatements::toString(const MatchedStatement &statement) +QString ScriptEditorStatements::toString(const MatchedStatement &statement) { return std::visit(StringVisitor{}, statement); } -QString ConnectionEditorStatements::toString(const Handler &handler) +QString ScriptEditorStatements::toString(const Handler &handler) { return std::visit(StringVisitor{}, handler); } -QString ConnectionEditorStatements::toJavascript(const Handler &handler) +QString ScriptEditorStatements::toJavascript(const Handler &handler) { return std::visit(JSOverload{}, handler); } -bool ConnectionEditorStatements::isConsoleLog(const MatchedStatement &curState) +bool ScriptEditorStatements::isConsoleLog(const MatchedStatement &curState) { return std::holds_alternative(curState); } -bool ConnectionEditorStatements::isLiteralType(const RightHandSide &var) +bool ScriptEditorStatements::isLiteralType(const RightHandSide &var) { return std::visit(Overload{[](const double &) { return true; }, [](const bool &) { return true; }, @@ -289,7 +289,7 @@ bool ConnectionEditorStatements::isLiteralType(const RightHandSide &var) var); } -QString ConnectionEditorStatements::toDisplayName(const MatchedStatement &statement) +QString ScriptEditorStatements::toDisplayName(const MatchedStatement &statement) { const char *displayName = std::visit( Overload{[](const MatchedFunction &) { return FUNCTION_DISPLAY_NAME; }, @@ -303,7 +303,7 @@ QString ConnectionEditorStatements::toDisplayName(const MatchedStatement &statem return QString::fromLatin1(displayName); } -QString ConnectionEditorStatements::toDisplayName(const Handler &handler) +QString ScriptEditorStatements::toDisplayName(const Handler &handler) { const MatchedStatement &statement = std::visit( Overload{[](const MatchedStatement &statement) { return statement; }, @@ -312,51 +312,49 @@ QString ConnectionEditorStatements::toDisplayName(const Handler &handler) return toDisplayName(statement); } -MatchedStatement &ConnectionEditorStatements::okStatement( - ConnectionEditorStatements::Handler &handler) +MatchedStatement &ScriptEditorStatements::okStatement(ScriptEditorStatements::Handler &handler) { MatchedStatement statement; - return std::visit(Overload{[](ConnectionEditorStatements::MatchedStatement &var) - -> MatchedStatement & { return var; }, - [](ConnectionEditorStatements::ConditionalStatement &statement) + return std::visit(Overload{[](ScriptEditorStatements::MatchedStatement &var) -> MatchedStatement & { + return var; + }, + [](ScriptEditorStatements::ConditionalStatement &statement) -> MatchedStatement & { return statement.ok; }}, handler); } -MatchedStatement &ConnectionEditorStatements::koStatement( - ConnectionEditorStatements::Handler &handler) +MatchedStatement &ScriptEditorStatements::koStatement(ScriptEditorStatements::Handler &handler) { static MatchedStatement block; - if (auto *statement = std::get_if(&handler)) + if (auto *statement = std::get_if(&handler)) return statement->ko; return block; } -MatchedCondition &ConnectionEditorStatements::matchedCondition(Handler &handler) +MatchedCondition &ScriptEditorStatements::matchedCondition(Handler &handler) { static MatchedCondition block; - if (auto *statement = std::get_if(&handler)) + if (auto *statement = std::get_if(&handler)) return statement->condition; return block; } -ConditionalStatement &ConnectionEditorStatements::conditionalStatement( - ConnectionEditorStatements::Handler &handler) +ConditionalStatement &ScriptEditorStatements::conditionalStatement(ScriptEditorStatements::Handler &handler) { static ConditionalStatement block; - if (auto *statement = std::get_if(&handler)) + if (auto *statement = std::get_if(&handler)) return *statement; return block; } -QString ConnectionEditorStatements::toJavascript(const ConditionToken &token) +QString ScriptEditorStatements::toJavascript(const ConditionToken &token) { switch (token) { case ConditionToken::Not: diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.h b/src/plugins/qmldesigner/components/scripteditor/scripteditorstatements.h similarity index 77% rename from src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.h rename to src/plugins/qmldesigner/components/scripteditor/scripteditorstatements.h index 06e6434b5ab..34f046b117f 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.h +++ b/src/plugins/qmldesigner/components/scripteditor/scripteditorstatements.h @@ -7,22 +7,22 @@ #include namespace QmlDesigner { -namespace ConnectionEditorStatements { +namespace ScriptEditorStatements { inline constexpr char FUNCTION_DISPLAY_NAME[] = QT_TRANSLATE_NOOP( - "QmlDesigner::ConnectionEditorStatements", "Function"); + "QmlDesigner::ScriptEditorStatements", "Function"); inline constexpr char ASSIGNMENT_DISPLAY_NAME[] = QT_TRANSLATE_NOOP( - "QmlDesigner::ConnectionEditorStatements", "Assignment"); + "QmlDesigner::ScriptEditorStatements", "Assignment"); inline constexpr char SETPROPERTY_DISPLAY_NAME[] = QT_TRANSLATE_NOOP( - "QmlDesigner::ConnectionEditorStatements", "Set Property"); + "QmlDesigner::ScriptEditorStatements", "Set Property"); inline constexpr char SETSTATE_DISPLAY_NAME[] = QT_TRANSLATE_NOOP( - "QmlDesigner::ConnectionEditorStatements", "Set State"); -inline constexpr char LOG_DISPLAY_NAME[] = QT_TRANSLATE_NOOP( - "QmlDesigner::ConnectionEditorStatements", "Print"); + "QmlDesigner::ScriptEditorStatements", "Set State"); +inline constexpr char LOG_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("QmlDesigner::ScriptEditorStatements", + "Print"); inline constexpr char EMPTY_DISPLAY_NAME[] = QT_TRANSLATE_NOOP( - "QmlDesigner::ConnectionEditorStatements", "Empty"); + "QmlDesigner::ScriptEditorStatements", "Empty"); inline constexpr char CUSTOM_DISPLAY_NAME[] = QT_TRANSLATE_NOOP( - "QmlDesigner::ConnectionEditorStatements", "Custom"); + "QmlDesigner::ScriptEditorStatements", "Custom"); struct Variable; struct MatchedFunction; @@ -122,13 +122,12 @@ QMLDESIGNER_EXPORT QString toDisplayName(const MatchedStatement &statement); QMLDESIGNER_EXPORT QString toDisplayName(const Handler &handler); QMLDESIGNER_EXPORT QString toJavascript(const ConditionToken &token); -QMLDESIGNER_EXPORT MatchedStatement &okStatement(ConnectionEditorStatements::Handler &handler); -QMLDESIGNER_EXPORT MatchedStatement &koStatement(ConnectionEditorStatements::Handler &handler); +QMLDESIGNER_EXPORT MatchedStatement &okStatement(ScriptEditorStatements::Handler &handler); +QMLDESIGNER_EXPORT MatchedStatement &koStatement(ScriptEditorStatements::Handler &handler); -QMLDESIGNER_EXPORT MatchedCondition &matchedCondition(ConnectionEditorStatements::Handler &handler); -QMLDESIGNER_EXPORT ConditionalStatement &conditionalStatement( - ConnectionEditorStatements::Handler &handler); +QMLDESIGNER_EXPORT MatchedCondition &matchedCondition(ScriptEditorStatements::Handler &handler); +QMLDESIGNER_EXPORT ConditionalStatement &conditionalStatement(ScriptEditorStatements::Handler &handler); -} // namespace ConnectionEditorStatements +} // namespace ScriptEditorStatements } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp b/src/plugins/qmldesigner/components/scripteditor/scripteditorutils.cpp similarity index 92% rename from src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp rename to src/plugins/qmldesigner/components/scripteditor/scripteditorutils.cpp index fcedbc9876c..36c5ed54d7c 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp +++ b/src/plugins/qmldesigner/components/scripteditor/scripteditorutils.cpp @@ -1,6 +1,6 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "connectioneditorutils.h" +#include "scripteditorutils.h" #include #include @@ -8,11 +8,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include @@ -24,7 +24,7 @@ namespace QmlDesigner { -Q_LOGGING_CATEGORY(ConnectionEditorLog, "qtc.qtquickdesigner.connectioneditor", QtWarningMsg) +Q_LOGGING_CATEGORY(ScriptEditorLog, "qtc.qtquickdesigner.scripteditor", QtWarningMsg) void callLater(const std::function &fun) { @@ -62,7 +62,7 @@ PropertyName uniquePropertyName(const PropertyName &suggestion, const ModelNode NodeMetaInfo dynamicTypeNameToNodeMetaInfo(const TypeName &typeName, Model *model) { // Note: Uses old mechanism to create the NodeMetaInfo and supports - // only types we care about in the connection editor. + // only types we care about in the script editor. // TODO: Support all possible AbstractProperty types and move to the // AbstractProperty class. if (typeName == "bool") @@ -80,7 +80,7 @@ NodeMetaInfo dynamicTypeNameToNodeMetaInfo(const TypeName &typeName, Model *mode else if (typeName == "var" || typeName == "variant") return model->metaInfo("QML.variant"); else - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "type" << typeName << "not found"; + qCWarning(ScriptEditorLog) << __FUNCTION__ << "type" << typeName << "not found"; return {}; } @@ -355,7 +355,7 @@ QStringList availableTargetProperties(const BindingProperty &bindingProperty) { const ModelNode modelNode = bindingProperty.parentModelNode(); if (!modelNode.isValid()) { - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "invalid model node"; + qCWarning(ScriptEditorLog) << __FUNCTION__ << "invalid model node"; return {}; } @@ -428,7 +428,7 @@ QStringList availableSourceProperties(const QString &id, } else if (auto metaInfo = targetProperty.parentModelNode().metaInfo(); metaInfo.isValid()) { targetType = metaInfo.property(targetProperty.name()).propertyType(); } else - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "no meta info for target node"; + qCWarning(ScriptEditorLog) << __FUNCTION__ << "no meta info for target node"; QStringList possibleProperties; if (!modelNode.isValid()) { @@ -450,7 +450,7 @@ QStringList availableSourceProperties(const QString &id, return possibleProperties; } #endif - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "invalid model node:" << id; + qCWarning(ScriptEditorLog) << __FUNCTION__ << "invalid model node:" << id; return {}; } @@ -476,10 +476,40 @@ QStringList availableSourceProperties(const QString &id, possibleProperties.push_back(QString::fromUtf8(property.name())); } } else { - qCWarning(ConnectionEditorLog) << __FUNCTION__ << "no meta info for source node"; + qCWarning(ScriptEditorLog) << __FUNCTION__ << "no meta info for source node"; } return possibleProperties; } +QString addOnToSignalName(const QString &signal) +{ + if (signal.isEmpty()) + return {}; + + static const QRegularExpression rx("^on[A-Z]"); + if (rx.match(signal).hasMatch()) + return signal; + + QString ret = signal; + ret[0] = ret.at(0).toUpper(); + ret.prepend("on"); + return ret; +} + +QString removeOnFromSignalName(const QString &signal) +{ + if (signal.isEmpty()) + return {}; + + static const QRegularExpression rx("^on[A-Z]"); + if (!rx.match(signal).hasMatch()) + return signal; + + QString ret = signal; + ret.remove(0, 2); + ret[0] = ret.at(0).toLower(); + return ret; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.h b/src/plugins/qmldesigner/components/scripteditor/scripteditorutils.h similarity index 93% rename from src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.h rename to src/plugins/qmldesigner/components/scripteditor/scripteditorutils.h index 220fdc9e11d..9d5d170381c 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.h +++ b/src/plugins/qmldesigner/components/scripteditor/scripteditorutils.h @@ -13,7 +13,7 @@ namespace QmlDesigner { -Q_DECLARE_LOGGING_CATEGORY(ConnectionEditorLog) +Q_DECLARE_LOGGING_CATEGORY(ScriptEditorLog) class AbstractView; class AbstractProperty; @@ -26,6 +26,8 @@ void showErrorMessage(const QString &text); QString idOrTypeName(const ModelNode &modelNode); PropertyName uniquePropertyName(const PropertyName &suggestion, const ModelNode &modelNode); +QString addOnToSignalName(const QString &signal); +QString removeOnFromSignalName(const QString &signal); NodeMetaInfo dynamicTypeMetaInfo(const AbstractProperty &property); NodeMetaInfo dynamicTypeNameToNodeMetaInfo(const TypeName &typeName, Model *model); From ea53994ff009f3b35ed1fe0c64f872df2415b746 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Thu, 27 Feb 2025 10:02:50 +0100 Subject: [PATCH 07/54] QmlDesigner: Add module statement to qmldir Change-Id: I2d4a6dd08973b88dccd8ba72139ebc7b0d266030 Reviewed-by: Marco Bubke --- .../propertyEditorQmlSources/imports/HelperWidgets/qmldir | 1 + .../propertyEditorQmlSources/imports/StudioControls/qmldir | 1 + .../propertyEditorQmlSources/imports/StudioTheme/qmldir | 1 + 3 files changed, 3 insertions(+) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir index ddade5606a1..8429c162a15 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir @@ -1,3 +1,4 @@ +module HelperWidgets AbstractButton 2.0 AbstractButton.qml ActionIndicator 2.0 ActionIndicator.qml AlignmentHorizontalButtons 2.0 AlignmentHorizontalButtons.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir index d187fcfa14e..92fa4a2093c 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir @@ -1,3 +1,4 @@ +module StudioControls AbstractButton 1.0 AbstractButton.qml ActionIndicator 1.0 ActionIndicator.qml Button 1.0 Button.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/qmldir index e22b7d9a583..f52634892e2 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/qmldir @@ -1,3 +1,4 @@ +module StudioTheme singleton Values 1.0 Values.qml singleton Constants 1.0 Constants.qml ControlStyle 1.0 ControlStyle.qml From 5db5a5bd0d34573cd7dc4dc339027e3e2f417ece Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Thu, 27 Feb 2025 12:31:54 +0200 Subject: [PATCH 08/54] QmlDesigner: Allow exporting multiple items to a bundle Fixes: QDS-13201 Change-Id: Id9a1981b91d9cbc3e98bd21fc01b76d89126c167 Reviewed-by: Shrief Gabr Reviewed-by: Miikka Heikkinen --- .../components/componentcore/bundlehelper.cpp | 148 +++++++++--------- .../components/componentcore/bundlehelper.h | 10 +- .../componentcore/designeractionmanager.cpp | 6 +- .../modelnodecontextmenu_helper.h | 10 +- .../components/edit3d/edit3dwidget.cpp | 2 +- .../materialbrowser/materialbrowserwidget.cpp | 3 +- 6 files changed, 94 insertions(+), 85 deletions(-) diff --git a/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp b/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp index 42074fe126d..671cbcc8f52 100644 --- a/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp +++ b/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp @@ -182,7 +182,6 @@ void BundleHelper::importBundleToProject() // TODO: before overwriting remove old item's dependencies (not harmful but for cleanup) } - // add entry to model QStringList files = itemObj.value("files").toVariant().toStringList(); QString icon = itemObj.value("icon").toString(); @@ -207,28 +206,57 @@ void BundleHelper::importBundleToProject() zipReader.close(); } -void BundleHelper::exportBundle(const ModelNode &node, const QPixmap &iconPixmap) +void BundleHelper::exportBundle(const QList &nodes, const QPixmap &iconPixmap) { - if (node.isComponent()) - exportComponent(node); - else - exportNode(node, iconPixmap); -} + QTC_ASSERT(!nodes.isEmpty(), return); -void BundleHelper::exportComponent(const ModelNode &node) -{ - QString exportPath = getExportPath(node); + QString exportPath = getExportPath(nodes.at(0)); if (exportPath.isEmpty()) return; m_zipWriter = std::make_unique(exportPath); - Utils::FilePath compFilePath = componentPath(node); + m_tempDir = std::make_unique(); + QTC_ASSERT(m_tempDir->isValid(), return); + + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + QJsonObject jsonObj; + jsonObj["id"] = compUtils.user3DBundleId(); + jsonObj["version"] = BUNDLE_VERSION; + QJsonArray itemsArr; + + // remove nested nodes (they will be exported anyway as dependency of the parent) + QList nodesToExport; + for (const ModelNode &node : nodes) { + bool isChild = std::ranges::any_of(nodes, [&](const ModelNode &possibleParent) { + return &node != &possibleParent && possibleParent.isAncestorOf(node); + }); + + if (!isChild) + nodesToExport.append(node); + } + + m_remainingIcons = nodesToExport.size(); + + for (const ModelNode &node : std::as_const(nodesToExport)) { + if (node.isComponent()) + itemsArr.append(exportComponent(node)); + else + itemsArr.append(exportNode(node, iconPixmap)); + } + + jsonObj["items"] = itemsArr; + m_zipWriter->addFile(Constants::BUNDLE_JSON_FILENAME, QJsonDocument(jsonObj).toJson()); +} + +QJsonObject BundleHelper::exportComponent(const ModelNode &node) +{ + Utils::FilePath compFilePath = Utils::FilePath::fromString(ModelUtils::componentFilePath(node)); Utils::FilePath compDir = compFilePath.parentDir(); QString compBaseName = compFilePath.completeBaseName(); QString compFileName = compFilePath.fileName(); - m_iconPath = QLatin1String("icons/%1").arg(UniqueName::generateId(compBaseName) + ".png"); + QString iconPath = QLatin1String("icons/%1").arg(UniqueName::generateId(compBaseName) + ".png"); const QSet compDependencies = getComponentDependencies(compFilePath, compDir); @@ -250,43 +278,25 @@ void BundleHelper::exportComponent(const ModelNode &node) filesList.append(asset.relativePath); } - // add the item to the bundle json - QJsonObject jsonObj; - QJsonArray itemsArr; - itemsArr.append(QJsonObject { + // add icon + QString filePath = compFilePath.path(); + getImageFromCache(filePath, [this, iconPath](const QImage &image) { + addIconAndCloseZip(iconPath, image); + }); + + return { {"name", node.simplifiedTypeName()}, {"qml", compFileName}, - {"icon", m_iconPath}, + {"icon", iconPath}, {"files", QJsonArray::fromStringList(filesList)} - }); - - jsonObj["items"] = itemsArr; - - auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); - jsonObj["id"] = compUtils.user3DBundleId(); - jsonObj["version"] = BUNDLE_VERSION; - - m_zipWriter->addFile(Constants::BUNDLE_JSON_FILENAME, QJsonDocument(jsonObj).toJson()); - - // add icon - getImageFromCache(compFilePath.path(), [&](const QImage &image) { - addIconAndCloseZip(image); - }); + }; } -void BundleHelper::exportNode(const ModelNode &node, const QPixmap &iconPixmap) +QJsonObject BundleHelper::exportNode(const ModelNode &node, const QPixmap &iconPixmap) { - QString exportPath = getExportPath(node); - if (exportPath.isEmpty()) - return; - - // targetPath is a temp path for collecting and zipping assets, actual export target is where + // tempPath is a temp path for collecting and zipping assets, actual export target is where // the user chose to export (i.e. exportPath) - m_tempDir = std::make_unique(); - QTC_ASSERT(m_tempDir->isValid(), return); - auto targetPath = Utils::FilePath::fromString(m_tempDir->path()); - - m_zipWriter = std::make_unique(exportPath); + auto tempPath = Utils::FilePath::fromString(m_tempDir->path()); QString name = node.variantProperty("objectName").value().toString(); if (name.isEmpty()) @@ -294,7 +304,7 @@ void BundleHelper::exportNode(const ModelNode &node, const QPixmap &iconPixmap) QString qml = nodeNameToComponentFileName(name); QString iconBaseName = UniqueName::generateId(name); - m_iconPath = QLatin1String("icons/%1.png").arg(iconBaseName); + QString iconPath = QLatin1String("icons/%1.png").arg(iconBaseName); // generate and save Qml file auto [qmlString, depAssets] = modelNodeToQmlString(node); @@ -304,37 +314,17 @@ void BundleHelper::exportNode(const ModelNode &node, const QPixmap &iconPixmap) for (const AssetPath &assetPath : depAssetsList) depAssetsRelativePaths.append(assetPath.relativePath); - auto qmlFilePath = targetPath.pathAppended(qml); + auto qmlFilePath = tempPath.pathAppended(qml); auto result = qmlFilePath.writeFileContents(qmlString.toUtf8()); - QTC_ASSERT_EXPECTED(result, return); + QTC_ASSERT_EXPECTED(result, return {}); m_zipWriter->addFile(qmlFilePath.fileName(), qmlString.toUtf8()); - // add the item to the bundle json - QJsonObject jsonObj; - QJsonArray itemsArr; - itemsArr.append(QJsonObject { - {"name", name}, - {"qml", qml}, - {"icon", m_iconPath}, - {"files", QJsonArray::fromStringList(depAssetsRelativePaths)} - }); - - jsonObj["items"] = itemsArr; - - auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); - jsonObj["id"] = node.metaInfo().isQtQuick3DMaterial() ? compUtils.userMaterialsBundleId() - : compUtils.user3DBundleId(); - jsonObj["version"] = BUNDLE_VERSION; - - Utils::FilePath jsonFilePath = targetPath.pathAppended(Constants::BUNDLE_JSON_FILENAME); - m_zipWriter->addFile(jsonFilePath.fileName(), QJsonDocument(jsonObj).toJson()); - // add item's dependency assets to the bundle zip and target path (for icon generation) for (const AssetPath &assetPath : depAssetsList) { QByteArray assetContent = assetPath.fileContent(); m_zipWriter->addFile(assetPath.relativePath, assetContent); - Utils::FilePath assetTargetPath = targetPath.pathAppended(assetPath.relativePath); + Utils::FilePath assetTargetPath = tempPath.pathAppended(assetPath.relativePath); assetTargetPath.parentDir().ensureWritableDir(); assetTargetPath.writeFileContents(assetContent); } @@ -353,12 +343,19 @@ void BundleHelper::exportNode(const ModelNode &node, const QPixmap &iconPixmap) } if (iconPixmapToSave.isNull()) { - getImageFromCache(qmlFilePath.toFSPathString(), [&](const QImage &image) { - addIconAndCloseZip(image); + getImageFromCache(qmlFilePath.toFSPathString(), [this, iconPath](const QImage &image) { + addIconAndCloseZip(iconPath, image); }); } else { - addIconAndCloseZip(iconPixmapToSave); + addIconAndCloseZip(iconPath, iconPixmapToSave); } + + return { + {"name", name}, + {"qml", qml}, + {"icon", iconPath}, + {"files", QJsonArray::fromStringList(depAssetsRelativePaths)} + }; } QPair> BundleHelper::modelNodeToQmlString(const ModelNode &node, int depth) @@ -544,14 +541,16 @@ void BundleHelper::getImageFromCache(const QString &qmlPath, }); } -void BundleHelper::addIconAndCloseZip(const auto &image) { // auto: QImage or QPixmap +void BundleHelper::addIconAndCloseZip(const QString &iconPath, const auto &image) { // auto: QImage or QPixmap QByteArray iconByteArray; QBuffer buffer(&iconByteArray); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "PNG"); - m_zipWriter->addFile(m_iconPath, iconByteArray); - m_zipWriter->close(); + m_zipWriter->addFile(iconPath, iconByteArray); + + if (--m_remainingIcons <= 0) + m_zipWriter->close(); }; QString BundleHelper::getImportPath() const @@ -689,6 +688,10 @@ QSet BundleHelper::getComponentDependencies(const Utils::FilePath &fi parseNode = [&](const ModelNode &node) { // workaround node.isComponent() as it is not working here QString nodeType = QString::fromLatin1(node.type()); + +#ifdef QDS_USE_PROJECTSTORAGE + // TODO +#else if (!nodeType.startsWith("QtQuick")) { Utils::FilePath compFilPath = getComponentFilePath(nodeType, mainCompDir); if (!compFilPath.isEmpty()) { @@ -705,6 +708,7 @@ QSet BundleHelper::getComponentDependencies(const Utils::FilePath &fi return; } } +#endif const QList nodeProps = node.properties(); for (const AbstractProperty &p : nodeProps) { diff --git a/src/plugins/qmldesigner/components/componentcore/bundlehelper.h b/src/plugins/qmldesigner/components/componentcore/bundlehelper.h index 14d90ef951e..ddd79f33158 100644 --- a/src/plugins/qmldesigner/components/componentcore/bundlehelper.h +++ b/src/plugins/qmldesigner/components/componentcore/bundlehelper.h @@ -57,7 +57,7 @@ public: ~BundleHelper(); void importBundleToProject(); - void exportBundle(const ModelNode &node, const QPixmap &iconPixmap = QPixmap()); + void exportBundle(const QList &nodes, const QPixmap &iconPixmap = QPixmap()); void getImageFromCache(const QString &qmlPath, std::function successCallback); QString nodeNameToComponentFileName(const QString &name) const; @@ -71,18 +71,18 @@ private: QString getExportPath(const ModelNode &node) const; bool isMaterialBundle(const QString &bundleId) const; bool isItemBundle(const QString &bundleId) const; - void addIconAndCloseZip(const auto &image); + void addIconAndCloseZip(const QString &iconPath, const auto &image); Utils::FilePath componentPath(const ModelNode &node) const; QSet getBundleComponentDependencies(const ModelNode &node) const; - void exportComponent(const ModelNode &node); - void exportNode(const ModelNode &node, const QPixmap &iconPixmap = QPixmap()); + QJsonObject exportComponent(const ModelNode &node); + QJsonObject exportNode(const ModelNode &node, const QPixmap &iconPixmap = QPixmap()); QPointer m_view; QPointer m_widget; Utils::UniqueObjectPtr m_importer; std::unique_ptr m_zipWriter; std::unique_ptr m_tempDir; - QString m_iconPath; + int m_remainingIcons = 0; static constexpr char BUNDLE_VERSION[] = "1.0"; }; diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index fe687a99ce1..6cb88e5f411 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -2033,10 +2033,10 @@ void DesignerActionManager::createDefaultDesignerActions() QKeySequence(), Priorities::ExportComponent, [&](const SelectionContext &context) { - m_bundleHelper->exportBundle(context.currentSingleSelectedNode()); + m_bundleHelper->exportBundle(context.selectedModelNodes()); }, - &is3DNode, - &is3DNode)); + &are3DNodes, + &are3DNodes)); addDesignerAction(new ModelNodeContextMenuAction( editMaterialCommandId, diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h index 95cf541340b..fbf27083127 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h +++ b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h @@ -80,11 +80,15 @@ inline bool enableAddToContentLib(const SelectionContext &selectionState) return isNode3D && !isInBundle; } -inline bool is3DNode(const SelectionContext &selectionState) +inline bool are3DNodes(const SelectionContext &selectionState) { - ModelNode modelNode = selectionState.currentSingleSelectedNode(); + const QList nodes = selectionState.selectedModelNodes(); + if (nodes.isEmpty()) + return false; - return modelNode.metaInfo().isQtQuick3DNode(); + return std::all_of(nodes.cbegin(), nodes.cend(), [](const ModelNode &node) { + return node.metaInfo().isQtQuick3DNode(); + }); } inline bool hasEditableMaterial(const SelectionContext &selectionState) diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index 9029001c5de..219da704d36 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -385,7 +385,7 @@ void Edit3DWidget::createContextMenu() m_exportBundleAction = m_contextMenu->addAction( contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon tr("Export Component"), [&] { - m_bundleHelper->exportBundle(m_contextMenuTarget); + m_bundleHelper->exportBundle(m_view->selectedModelNodes()); }); m_contextMenu->addSeparator(); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index 9ac95dba432..3e93a0922b4 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -382,10 +382,11 @@ void MaterialBrowserWidget::importMaterial() { m_bundleHelper->importBundleToProject(); } + void MaterialBrowserWidget::exportMaterial() { ModelNode mat = m_materialBrowserModel->selectedMaterial(); - m_bundleHelper->exportBundle(mat, m_previewImageProvider->getPixmap(mat)); + m_bundleHelper->exportBundle({mat}, m_previewImageProvider->getPixmap(mat)); } QString MaterialBrowserWidget::qmlSourcesPath() From 14b1b5b63332831a3b61495181614410b2739b00 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Thu, 27 Feb 2025 15:05:15 +0200 Subject: [PATCH 09/54] QmlDesigner: Rename "Component" to "Bundle" in import/export bundles Change-Id: I728d496f9ac47f53ca601850c043c10daef47810 Reviewed-by: Miikka Heikkinen Reviewed-by: Shrief Gabr Reviewed-by: Mats Honkamaa --- .../components/componentcore/componentcore_constants.h | 4 ++-- src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h index 291fb9a1224..4d55e0a977f 100644 --- a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h +++ b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h @@ -167,9 +167,9 @@ inline constexpr char editMaterialDisplayName[] = QT_TRANSLATE_NOOP("QmlDesigner inline constexpr char addToContentLibraryDisplayName[] = QT_TRANSLATE_NOOP( "QmlDesignerContextMenu", "Add to Content Library"); inline constexpr char importComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", - "Import Component"); + "Import Bundle"); inline constexpr char exportComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", - "Export Component"); + "Export Bundle"); inline constexpr char editAnnotationsDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Annotations"); inline constexpr char addMouseAreaFillDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index 219da704d36..78a610ffefc 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -378,13 +378,13 @@ void Edit3DWidget::createContextMenu() m_importBundleAction = m_contextMenu->addAction( contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon - tr("Import Component"), [&] { + tr("Import Bundle"), [&] { m_bundleHelper->importBundleToProject(); }); m_exportBundleAction = m_contextMenu->addAction( contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon - tr("Export Component"), [&] { + tr("Export Bundle"), [&] { m_bundleHelper->exportBundle(m_view->selectedModelNodes()); }); From c8f31558f711669b6371346a8cc73c2f739bb35c Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Thu, 27 Feb 2025 14:54:56 +0200 Subject: [PATCH 10/54] QmlDesigner: Hide current open component from item library Fixes: QDS-14800 Change-Id: I11bb292c1db167dc5ae862073f0b90637dfa63cc Reviewed-by: Mahmoud Badri --- .../qmldesigner/components/itemlibrary/itemlibrarymodel.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp index 64f2c384caa..e8e065d12fe 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -362,6 +363,9 @@ void ItemLibraryModel::update(Model *model) DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument(); const bool blockNewImports = document->inFileComponentModelActive(); #endif + + TypeName currentFileType = QFileInfo(model->fileUrl().toLocalFile()).baseName().toUtf8(); + const QList itemLibEntries = model->itemLibraryEntries(); for (const ItemLibraryEntry &entry : itemLibEntries) { NodeMetaInfo metaInfo; @@ -421,6 +425,8 @@ void ItemLibraryModel::update(Model *model) if (isUsable) { if (catName == ItemLibraryImport::userComponentsTitle()) { if (entry.requiredImport().isEmpty()) { // user components + if (currentFileType == entry.typeName()) + continue; importSection = importHash[ItemLibraryImport::userComponentsTitle()]; if (!importSection) { importSection = new ItemLibraryImport( From 2ed2c3b48cebb075416c265a0f6ab347afe738d0 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Thu, 27 Feb 2025 14:12:38 +0100 Subject: [PATCH 11/54] QmlJS:: Fix values in pragma statements when reformatting Task-number: QDS-9732 Change-Id: Ia3ca22b4a26aef8ac8ffef4fb50a45bdb0e072f8 Reviewed-by: Fabian Kosmale --- src/libs/qmljs/qmljsreformatter.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libs/qmljs/qmljsreformatter.cpp b/src/libs/qmljs/qmljsreformatter.cpp index 008968afc46..b29d4fd7827 100644 --- a/src/libs/qmljs/qmljsreformatter.cpp +++ b/src/libs/qmljs/qmljsreformatter.cpp @@ -526,6 +526,10 @@ protected: { out("pragma ", ast->pragmaToken); out(ast->name.toString()); + if (!ast->value.isEmpty()) { + out(": "); + out(ast->value.toString()); + } newLine(); return false; } From cd1ac1336cc1cf9b12549c74d71962129fe03b81 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Thu, 27 Feb 2025 15:37:15 +0200 Subject: [PATCH 12/54] QmlDesigner: Fix a navigator code warning Change-Id: Ie063a1b884a84c9aa413e43bb8258f219d0e5999 Reviewed-by: Miikka Heikkinen --- .../qmldesigner/components/navigator/navigatorview.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp index fa5efd65346..bf5a2dfc7e3 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp @@ -183,11 +183,11 @@ void NavigatorView::clearExplorerWarnings() QList allNodes; allNodes.append(rootModelNode()); allNodes.append(rootModelNode().allSubModelNodes()); - for (ModelNode node : allNodes) { + for (const ModelNode &node : std::as_const(allNodes)) { if (node.metaInfo().isFileComponent()) { - const ProjectExplorer::FileNode *fnode = fileNodeForModelNode(node); - if (fnode) - fnode->setHasError(false); + const ProjectExplorer::FileNode *fNode = fileNodeForModelNode(node); + if (fNode) + fNode->setHasError(false); } } } From af43b0c0ac1574dc00d3034d59c9334016e09320 Mon Sep 17 00:00:00 2001 From: Mats Honkamaa Date: Thu, 27 Feb 2025 15:30:08 +0200 Subject: [PATCH 13/54] Doc: Rename component to bundle According to https://codereview.qt-project.org/c/qt-creator/qt-creator/+/627795 Change-Id: Icc71409773f7861c7edb462c595fd2eda94805fc Reviewed-by: Mahmoud Badri Reviewed-by: Johanna Vanhatapio --- doc/qtdesignstudio/src/qtdesignstudio-sharing-assets.qdoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/qtdesignstudio/src/qtdesignstudio-sharing-assets.qdoc b/doc/qtdesignstudio/src/qtdesignstudio-sharing-assets.qdoc index 52ab0eb0015..0f2f4bf732e 100644 --- a/doc/qtdesignstudio/src/qtdesignstudio-sharing-assets.qdoc +++ b/doc/qtdesignstudio/src/qtdesignstudio-sharing-assets.qdoc @@ -16,7 +16,7 @@ \list \li In the \uicontrol 3D or \uicontrol Navigator view, right-click a 3D component and select - \uicontrol {Export Component}. + \uicontrol {Export Bundle}. \li In the \uicontrol {Material Browser} view, right-click a material and select \uicontrol {Export Material}. \endlist @@ -25,7 +25,7 @@ To import a 3D component or material bundle, do one of the following: \list \li In the \uicontrol {3D}, \uicontrol {2D}, or \uicontrol {Navigator} view, right-click - and select \uicontrol {Import Component}. If you use this method to import a bundle of 3D + and select \uicontrol {Import Bundle}. If you use this method to import a bundle of 3D components, the 3D components are added to the 3D scene. \li In \uicontrol {Content Library} > \uicontrol {User Assets}, right-click and select \uicontrol {Import Bundle}. If you use this method to import a bundle of 3D From 39eebaf0779bea1f11311ea899c159924d755e62 Mon Sep 17 00:00:00 2001 From: Rafal Stawarski Date: Tue, 25 Feb 2025 12:44:45 +0100 Subject: [PATCH 14/54] Connection Editor: ConnectionsDialogForm refactoring Extract a part responsible for creating scripts to ScriptEditorForm, which can be used in both: Connection Editor and Property Editor. Task-number: QDS-10449 Change-Id: Ie6b8b4becfee4ed05760e31b742d8d0a9b79a113 Reviewed-by: Thomas Hartmann --- .../connectionseditor/BindingsDialogForm.qml | 7 +- .../connectionseditor/ConnectionsDialog.qml | 2 - .../ConnectionsDialogForm.qml | 275 ++---------------- .../PropertiesDialogForm.qml | 7 +- .../QtQuick/ScriptActionSpecifics.qml | 5 + .../QtQuick/ScriptSection.qml | 102 +++++++ .../imports/HelperWidgets}/PopupLabel.qml | 0 .../imports/HelperWidgets/qmldir | 1 + .../imports/ScriptsEditor/ActionsComboBox.qml | 54 ++++ .../ScriptsEditor}/ExpressionBuilder.qml | 5 +- .../ScriptsEditor}/MyListViewDelegate.qml | 0 .../ScriptsEditor}/MyTreeViewDelegate.qml | 0 .../imports/ScriptsEditor}/Pill.qml | 1 + .../ScriptsEditor/ScriptEditorForm.qml | 262 +++++++++++++++++ .../ScriptsEditor}/StatementEditor.qml | 54 ++-- .../ScriptsEditor}/SuggestionPopup.qml | 6 +- .../imports/ScriptsEditor/qmldir | 2 + .../bindingeditor/actioneditordialog.cpp | 2 +- .../connectioneditor/connectionview.cpp | 54 ++-- .../connectioneditor/connectionview.h | 1 - .../propertyeditorqmlbackend.cpp | 22 +- .../propertyeditor/propertyeditorqmlbackend.h | 2 + .../quick2propertyeditorview.cpp | 2 + .../scripteditor/scripteditorbackend.cpp | 20 ++ .../scripteditor/scripteditorbackend.h | 2 + 25 files changed, 570 insertions(+), 318 deletions(-) create mode 100644 share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ScriptSection.qml rename share/qtcreator/qmldesigner/{connectionseditor => propertyEditorQmlSources/imports/HelperWidgets}/PopupLabel.qml (100%) create mode 100644 share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/ActionsComboBox.qml rename share/qtcreator/qmldesigner/{connectionseditor => scriptseditor/imports/ScriptsEditor}/ExpressionBuilder.qml (98%) rename share/qtcreator/qmldesigner/{connectionseditor => scriptseditor/imports/ScriptsEditor}/MyListViewDelegate.qml (100%) rename share/qtcreator/qmldesigner/{connectionseditor => scriptseditor/imports/ScriptsEditor}/MyTreeViewDelegate.qml (100%) rename share/qtcreator/qmldesigner/{connectionseditor => scriptseditor/imports/ScriptsEditor}/Pill.qml (99%) create mode 100644 share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/ScriptEditorForm.qml rename share/qtcreator/qmldesigner/{connectionseditor => scriptseditor/imports/ScriptsEditor}/StatementEditor.qml (84%) rename share/qtcreator/qmldesigner/{connectionseditor => scriptseditor/imports/ScriptsEditor}/SuggestionPopup.qml (97%) create mode 100644 share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/qmldir diff --git a/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml index 9d1d74901e9..14398b7299a 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml @@ -5,6 +5,7 @@ import QtQuick import QtQuick.Controls import StudioControls as StudioControls import StudioTheme as StudioTheme +import HelperWidgets as HelperWidgets Column { id: root @@ -21,13 +22,13 @@ Column { Row { spacing: root.horizontalSpacing - PopupLabel { + HelperWidgets.PopupLabel { width: root.columnWidth text: qsTr("From") tooltip: qsTr("Sets the component and its property from which the value is copied.") } - PopupLabel { + HelperWidgets.PopupLabel { width: root.columnWidth text: qsTr("To") tooltip: qsTr("Sets the property of the selected component to which the copied value is assigned.") @@ -49,7 +50,7 @@ Column { onCurrentTypeIndexChanged: sourceNode.currentIndex = sourceNode.currentTypeIndex } - PopupLabel { + HelperWidgets.PopupLabel { width: root.columnWidth text: backend.targetNode anchors.verticalCenter: parent.verticalCenter diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml index 81f26f6a090..5e1aacee6ee 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml @@ -49,8 +49,6 @@ StudioControls.PopupDialog { ConnectionsDialogForm { id: form - parentWindow: root.window - Connections { target: root.backend function onPopupShouldClose() { diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml index 5e40aaa446c..7e1330cb2f5 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml @@ -6,6 +6,7 @@ import QtQuick.Controls import HelperWidgets as HelperWidgets import StudioControls as StudioControls import StudioTheme as StudioTheme +import ScriptsEditor as ScriptsEditor import ScriptEditorBackend Column { @@ -17,8 +18,7 @@ Column { property var backend - property bool keepOpen: expressionDialogLoader.visible - property Window parentWindow: null + property bool keepOpen: scriptEditor.keepOpen width: parent.width spacing: root.verticalSpacing @@ -30,13 +30,13 @@ Column { Row { spacing: root.horizontalSpacing - PopupLabel { + HelperWidgets.PopupLabel { width: root.columnWidth text: qsTr("Signal") tooltip: qsTr("Sets an interaction method that connects to the Target component.") } - PopupLabel { + HelperWidgets.PopupLabel { width: root.columnWidth text: qsTr("Action") tooltip: qsTr("Sets an action that is associated with the selected Target component's Signal.") @@ -59,259 +59,40 @@ Column { onCurrentTypeIndexChanged: signal.currentIndex = signal.currentTypeIndex } - StudioControls.TopLevelComboBox { + ScriptsEditor.ActionsComboBox { id: action - style: StudioTheme.Values.connectionPopupControlStyle + width: root.columnWidth - textRole: "text" - valueRole: "value" - ///model.getData(currentIndex, "role") - property int indexFromBackend: indexOfValue(backend.actionType) - onIndexFromBackendChanged: action.currentIndex = action.indexFromBackend - onActivated: backend.changeActionType(action.currentValue) - - model: ListModel { - ListElement { - value: StatementDelegate.CallFunction - text: qsTr("Call Function") - enabled: true - } - ListElement { - value: StatementDelegate.Assign - text: qsTr("Assign") - enabled: true - } - ListElement { - value: StatementDelegate.ChangeState - text: qsTr("Change State") - enabled: true - } - ListElement { - value: StatementDelegate.SetProperty - text: qsTr("Set Property") - enabled: true - } - ListElement { - value: StatementDelegate.PrintMessage - text: qsTr("Print Message") - enabled: true - } - ListElement { - value: StatementDelegate.Custom - text: qsTr("Custom") - enabled: false - } - } + backend: root.backend } } - StatementEditor { - width: root.width - actionType: action.currentValue ?? StatementDelegate.Custom + ScriptsEditor.ScriptEditorForm { + id: scriptEditor + + anchors.left: parent.left + anchors.right: parent.right + horizontalSpacing: root.horizontalSpacing + verticalSpacing: root.verticalSpacing columnWidth: root.columnWidth - statement: backend.okStatement + spacing: root.spacing + backend: root.backend - spacing: root.verticalSpacing - } - HelperWidgets.AbstractButton { - style: StudioTheme.Values.connectionPopupButtonStyle - width: 160 - buttonIcon: qsTr("Add Condition") - tooltip: qsTr("Sets a logical condition for the selected Signal. It works with the properties of the Target component.") - iconSize: StudioTheme.Values.baseFontSize - iconFontFamily: StudioTheme.Constants.font.family - anchors.horizontalCenter: parent.horizontalCenter - visible: action.currentValue !== StatementDelegate.Custom && !backend.hasCondition + currentAction: action.currentValue ?? StatementDelegate.Custom - onClicked: backend.addCondition() - } - - HelperWidgets.AbstractButton { - style: StudioTheme.Values.connectionPopupButtonStyle - width: 160 - buttonIcon: qsTr("Remove Condition") - tooltip: qsTr("Removes the logical condition for the Target component.") - iconSize: StudioTheme.Values.baseFontSize - iconFontFamily: StudioTheme.Constants.font.family - anchors.horizontalCenter: parent.horizontalCenter - visible: action.currentValue !== StatementDelegate.Custom && backend.hasCondition - - onClicked: backend.removeCondition() - } - - ExpressionBuilder { - style: StudioTheme.Values.connectionPopupControlStyle - width: root.width - - visible: backend.hasCondition - model: backend.conditionListModel - - onRemove: function(index) { - //console.log("remove", index) - backend.conditionListModel.removeToken(index) - } - - onUpdate: function(index, value) { - //console.log("update", index, value) - backend.conditionListModel.updateToken(index, value) - } - - onAdd: function(value) { - //console.log("add", value) - backend.conditionListModel.appendToken(value) - } - - onInsert: function(index, value, type) { - //console.log("insert", index, value, type) - if (type === ConditionListModel.Intermediate) - backend.conditionListModel.insertIntermediateToken(index, value) - else if (type === ConditionListModel.Shadow) - backend.conditionListModel.insertShadowToken(index, value) - else - backend.conditionListModel.insertToken(index, value) - } - - onSetValue: function(index, value) { - //console.log("setValue", index, value) - backend.conditionListModel.setShadowToken(index, value) - } - } - - HelperWidgets.AbstractButton { - style: StudioTheme.Values.connectionPopupButtonStyle - width: 160 - buttonIcon: qsTr("Add Else Statement") - tooltip: qsTr("Sets an alternate condition for the previously defined logical condition.") - iconSize: StudioTheme.Values.baseFontSize - iconFontFamily: StudioTheme.Constants.font.family - anchors.horizontalCenter: parent.horizontalCenter - visible: action.currentValue !== StatementDelegate.Custom - && backend.hasCondition && !backend.hasElse - - onClicked: backend.addElse() - } - - HelperWidgets.AbstractButton { - style: StudioTheme.Values.connectionPopupButtonStyle - width: 160 - buttonIcon: qsTr("Remove Else Statement") - tooltip: qsTr("Removes the alternate logical condition for the previously defined logical condition.") - iconSize: StudioTheme.Values.baseFontSize - iconFontFamily: StudioTheme.Constants.font.family - anchors.horizontalCenter: parent.horizontalCenter - visible: action.currentValue !== StatementDelegate.Custom - && backend.hasCondition && backend.hasElse - - onClicked: backend.removeElse() - } - - //Else Statement - StatementEditor { - width: root.width - actionType: action.currentValue ?? StatementDelegate.Custom - horizontalSpacing: root.horizontalSpacing - columnWidth: root.columnWidth - statement: backend.koStatement - backend: root.backend - spacing: root.verticalSpacing - visible: action.currentValue !== StatementDelegate.Custom - && backend.hasCondition && backend.hasElse - } - - // code preview toolbar - Column { - id: miniToolbarEditor - width: parent.width - spacing: -2 - - Rectangle { - id: miniToolbar - width: parent.width - height: editorButton.height + 2 - radius: 4 - z: -1 - color: StudioTheme.Values.themeConnectionEditorMicroToolbar - - Row { - spacing: 2 - HelperWidgets.AbstractButton { - id: editorButton - style: StudioTheme.Values.microToolbarButtonStyle - buttonIcon: StudioTheme.Constants.codeEditor_medium - tooltip: qsTr("Write the conditions for the components and the signals manually.") - onClicked: expressionDialogLoader.show() - } - HelperWidgets.AbstractButton { - id: jumpToCodeButton - style: StudioTheme.Values.microToolbarButtonStyle - buttonIcon: StudioTheme.Constants.jumpToCode_medium - tooltip: qsTr("Jump to the code.") - onClicked: backend.jumpToCode() - } - } - } - - // Editor - Rectangle { - id: editor - width: parent.width - height: 150 - color: StudioTheme.Values.themeConnectionCodeEditor - - Text { - id: code - anchors.fill: parent - anchors.margins: 4 - text: backend.indentedSource - color: StudioTheme.Values.themeTextColor - font.pixelSize: StudioTheme.Values.myFontSize - wrapMode: Text.Wrap - horizontalAlignment: code.lineCount === 1 ? Text.AlignHCenter : Text.AlignLeft - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - } - - Loader { - id: expressionDialogLoader - parent: editor - anchors.fill: parent - visible: false - active: visible - - function show() { - expressionDialogLoader.visible = true - } - - sourceComponent: Item { - id: bindingEditorParent - - Component.onCompleted: { - bindingEditor.showWidget() - bindingEditor.text = backend.source - bindingEditor.showControls(false) - bindingEditor.setMultilne(true) - bindingEditor.updateWindowName() - } - - ActionEditor { - id: bindingEditor - - onRejected: { - bindingEditor.hideWidget() - expressionDialogLoader.visible = false - } - - onAccepted: { - backend.setNewSource(bindingEditor.text) - bindingEditor.hideWidget() - expressionDialogLoader.visible = false - } - } - } - } - } + itemTooltip: qsTr("Sets the component that is affected by the action of the Target component's Signal.") + methodTooltip: qsTr("Sets the item component's method that is affected by the Target component's Signal.") + fromTooltip: qsTr("Sets the component and its property from which the value is copied when the Target component initiates the Signal.") + toTooltip: qsTr("Sets the component and its property to which the copied value is assigned when the Target component initiates the Signal.") + addConditionTooltip: qsTr("Sets a logical condition for the selected Signal. It works with the properties of the Target component.") + removeConditionTooltip: qsTr("Removes the logical condition for the Target component.") + stateGroupTooltip: qsTr("Sets a State Group that is accessed when the Target component initiates the Signal.") + stateTooltip: qsTr("Sets a State within the assigned State Group that is accessed when the Target component initiates the Signal.") + propertyTooltip: qsTr("Sets the property of the component that is affected by the action of the Target component's Signal.") + valueTooltip: qsTr("Sets the value of the property of the component that is affected by the action of the Target component's Signal.") + messageTooltip: qsTr("Sets a text that is printed when the Signal of the Target component initiates.") } } diff --git a/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml index eee45df8605..7111fbb40e9 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml @@ -5,6 +5,7 @@ import QtQuick import QtQuick.Controls import StudioControls as StudioControls import StudioTheme as StudioTheme +import HelperWidgets as HelperWidgets Column { id: root @@ -18,7 +19,7 @@ Column { width: parent.width spacing: root.verticalSpacing - PopupLabel { + HelperWidgets.PopupLabel { text: qsTr("Type") tooltip: qsTr("Sets the category of the Local Custom Property.") } @@ -37,13 +38,13 @@ Column { Row { spacing: root.horizontalSpacing - PopupLabel { + HelperWidgets.PopupLabel { width: root.columnWidth text: qsTr("Name") tooltip: qsTr("Sets a name for the Local Custom Property.") } - PopupLabel { + HelperWidgets.PopupLabel { width: root.columnWidth text: qsTr("Value") tooltip: qsTr("Sets a valid Local Custom Property value.") diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ScriptActionSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ScriptActionSpecifics.qml index 5f063879a72..4ff2d990104 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ScriptActionSpecifics.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ScriptActionSpecifics.qml @@ -12,4 +12,9 @@ Column { AnimationSection { showDuration: false } + + ScriptSection { + anchors.left: parent.left + anchors.right: parent.right + } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ScriptSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ScriptSection.qml new file mode 100644 index 00000000000..422cdc533a9 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ScriptSection.qml @@ -0,0 +1,102 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme +import ScriptEditorBackend +import ScriptsEditor as ScriptsEditor + +Section { + id: root + caption: qsTr("Script") + + SectionLayout { + PropertyLabel { + text: "" + tooltip: "" + } + + SecondColumnLayout { + Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth } + + AbstractButton { + id: editScriptButton + implicitWidth: StudioTheme.Values.singleControlColumnWidth + width: StudioTheme.Values.singleControlColumnWidth + buttonIcon: qsTr("Edit script") + iconFontFamily: StudioTheme.Constants.font.family + onClicked: dialog.show(root) + } + + ExpandingSpacer {} + } + } + + data: [ + StudioControls.PopupDialog { + id: dialog + + readonly property real horizontalSpacing: 10 + readonly property real verticalSpacing: 12 + readonly property real columnWidth: (dialog.width - dialog.horizontalSpacing - (2 * dialog.anchorGap)) / 2 + + property var backend: ScriptEditorBackend + + titleBar: Row { + id: titleBarRow + spacing: 30 + anchors.fill: parent + + Text { + color: StudioTheme.Values.themeTextColor + text: qsTr("Action") + font.pixelSize: StudioTheme.Values.myFontSize + anchors.verticalCenter: parent.verticalCenter + + ToolTipArea { + anchors.fill: parent + tooltip: qsTr("Sets an action that is associated with the selected ScriptAction.") + } + } + + ScriptsEditor.ActionsComboBox { + id: action + style: StudioTheme.Values.connectionPopupControlStyle + width: 180 + anchors.verticalCenter: parent.verticalCenter + backend: dialog.backend + } + } + + ScriptsEditor.ScriptEditorForm { + id: scriptEditor + + anchors.left: parent.left + anchors.right: parent.right + + horizontalSpacing: dialog.horizontalSpacing + verticalSpacing: dialog.verticalSpacing + columnWidth: dialog.columnWidth + spacing: dialog.verticalSpacing + + backend: dialog.backend + + currentAction: action.currentValue ?? StatementDelegate.Custom + } + }, + + Connections { + target: modelNodeBackend + function onSelectionChanged() { + dialog.backend.update() + } + }, + + TapHandler { + onTapped: scriptEditor.forceActiveFocus() + } + ] +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/PopupLabel.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PopupLabel.qml similarity index 100% rename from share/qtcreator/qmldesigner/connectionseditor/PopupLabel.qml rename to share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PopupLabel.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir index 8429c162a15..f363bab3bda 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir @@ -52,6 +52,7 @@ MultiIconLabel 2.0 MultiIconLabel.qml OriginControl 2.0 OriginControl.qml OriginIndicator 2.0 OriginIndicator.qml OriginSelector 2.0 OriginSelector.qml +PopupLabel 2.0 PopupLabel.qml PropertyEditorPane 2.0 PropertyEditorPane.qml PropertyLabel 2.0 PropertyLabel.qml PaddingSection 2.0 PaddingSection.qml diff --git a/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/ActionsComboBox.qml b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/ActionsComboBox.qml new file mode 100644 index 00000000000..15f0222fd24 --- /dev/null +++ b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/ActionsComboBox.qml @@ -0,0 +1,54 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import StudioControls as StudioControls +import StudioTheme as StudioTheme +import ScriptEditorBackend + +StudioControls.TopLevelComboBox { + id: action + + style: StudioTheme.Values.connectionPopupControlStyle + textRole: "text" + valueRole: "value" + + property var backend + property int indexFromBackend: indexOfValue(backend.actionType) + + onIndexFromBackendChanged: action.currentIndex = action.indexFromBackend + onActivated: backend.changeActionType(action.currentValue) + + model: ListModel { + ListElement { + value: StatementDelegate.CallFunction + text: qsTr("Call Function") + enabled: true + } + ListElement { + value: StatementDelegate.Assign + text: qsTr("Assign") + enabled: true + } + ListElement { + value: StatementDelegate.ChangeState + text: qsTr("Change State") + enabled: true + } + ListElement { + value: StatementDelegate.SetProperty + text: qsTr("Set Property") + enabled: true + } + ListElement { + value: StatementDelegate.PrintMessage + text: qsTr("Print Message") + enabled: true + } + ListElement { + value: StatementDelegate.Custom + text: qsTr("Custom") + enabled: false + } + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/ExpressionBuilder.qml b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/ExpressionBuilder.qml similarity index 98% rename from share/qtcreator/qmldesigner/connectionseditor/ExpressionBuilder.qml rename to share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/ExpressionBuilder.qml index 54d8ee937f3..9e02424f692 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/ExpressionBuilder.qml +++ b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/ExpressionBuilder.qml @@ -4,13 +4,16 @@ import QtQuick import StudioControls as StudioControls import StudioTheme as StudioTheme +import ScriptEditorBackend Rectangle { id: root property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle - property var conditionListModel: ConnectionsEditorEditorBackend.connectionModel.delegate.conditionListModel + property var conditionListModel: ScriptEditorBackend.conditionListModel // connect a valid model here + property alias popupListModel: popup.listModel + property alias popupTreeModel: popup.treeModel property alias model: repeater.model property int shadowPillIndex: -1 diff --git a/share/qtcreator/qmldesigner/connectionseditor/MyListViewDelegate.qml b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/MyListViewDelegate.qml similarity index 100% rename from share/qtcreator/qmldesigner/connectionseditor/MyListViewDelegate.qml rename to share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/MyListViewDelegate.qml diff --git a/share/qtcreator/qmldesigner/connectionseditor/MyTreeViewDelegate.qml b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/MyTreeViewDelegate.qml similarity index 100% rename from share/qtcreator/qmldesigner/connectionseditor/MyTreeViewDelegate.qml rename to share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/MyTreeViewDelegate.qml diff --git a/share/qtcreator/qmldesigner/connectionseditor/Pill.qml b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/Pill.qml similarity index 99% rename from share/qtcreator/qmldesigner/connectionseditor/Pill.qml rename to share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/Pill.qml index aa6cba03639..43f2789cb3b 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/Pill.qml +++ b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/Pill.qml @@ -5,6 +5,7 @@ import QtQuick import StudioControls as StudioControls import StudioTheme as StudioTheme import HelperWidgets as HelperWidgets +import ScriptEditorBackend FocusScope { id: root diff --git a/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/ScriptEditorForm.qml b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/ScriptEditorForm.qml new file mode 100644 index 00000000000..e7fc3eb8a8b --- /dev/null +++ b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/ScriptEditorForm.qml @@ -0,0 +1,262 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import HelperWidgets as HelperWidgets +import StudioTheme as StudioTheme +import ScriptEditorBackend + +Column { + id: root + + property real horizontalSpacing + property real verticalSpacing + property real columnWidth + + property var backend + + property alias keepOpen: expressionDialogLoader.visible + + property int currentAction: StatementDelegate.Custom + + property string itemTooltip: qsTr("Sets the component that is affected by the action.") + property string methodTooltip: qsTr("Sets the item component's method.") + property string fromTooltip: qsTr("Sets the component and its property from which the value is copied.") + property string toTooltip: qsTr("Sets the component and its property to which the copied value is assigned.") + property string addConditionTooltip: qsTr("Sets a logical condition for the selected action.") + property string removeConditionTooltip: qsTr("Removes the logical condition for the action.") + property string stateGroupTooltip: qsTr("Sets a State Group that is accessed when the action is initiated.") + property string stateTooltip: qsTr("Sets a State within the assigned State Group that is accessed when the action is initiated.") + property string propertyTooltip: qsTr("Sets the property of the component that is affected by the action.") + property string valueTooltip: qsTr("Sets the value of the property of the component that is affected by the action.") + property string messageTooltip: qsTr("Sets a text that is printed when the action is initiated.") + + + StatementEditor { + width: root.width + actionType: root.currentAction + horizontalSpacing: root.horizontalSpacing + columnWidth: root.columnWidth + statement: root.backend.okStatement + backend: root.backend + spacing: root.verticalSpacing + itemTooltip: root.itemTooltip + methodTooltip: root.methodTooltip + fromTooltip: root.fromTooltip + toTooltip: root.toTooltip + stateGroupTooltip: root.stateGroupTooltip + stateTooltip: root.stateTooltip + propertyTooltip: root.propertyTooltip + valueTooltip: root.valueTooltip + messageTooltip: root.messageTooltip + } + + + HelperWidgets.AbstractButton { + id: addConditionButton + style: StudioTheme.Values.connectionPopupButtonStyle + width: 160 + buttonIcon: qsTr("Add Condition") + tooltip: root.addConditionTooltip + iconSize: StudioTheme.Values.baseFontSize + iconFontFamily: StudioTheme.Constants.font.family + anchors.horizontalCenter: parent.horizontalCenter + visible: root.currentAction !== StatementDelegate.Custom && !backend.hasCondition + + onClicked: backend.addCondition() + } + + HelperWidgets.AbstractButton { + style: StudioTheme.Values.connectionPopupButtonStyle + width: 160 + buttonIcon: qsTr("Remove Condition") + tooltip: root.removeConditionTooltip + iconSize: StudioTheme.Values.baseFontSize + iconFontFamily: StudioTheme.Constants.font.family + anchors.horizontalCenter: parent.horizontalCenter + visible: root.currentAction !== StatementDelegate.Custom && backend.hasCondition + + onClicked: backend.removeCondition() + } + + ExpressionBuilder { + style: StudioTheme.Values.connectionPopupControlStyle + width: root.width + + visible: backend.hasCondition + model: backend.conditionListModel + conditionListModel: backend.conditionListModel + popupListModel: backend.propertyListProxyModel + popupTreeModel: backend.propertyTreeModel + + onRemove: function(index) { + backend.conditionListModel.removeToken(index) + } + + onUpdate: function(index, value) { + backend.conditionListModel.updateToken(index, value) + } + + onAdd: function(value) { + backend.conditionListModel.appendToken(value) + } + + onInsert: function(index, value, type) { + if (type === ConditionListModel.Intermediate) + backend.conditionListModel.insertIntermediateToken(index, value) + else if (type === ConditionListModel.Shadow) + backend.conditionListModel.insertShadowToken(index, value) + else + backend.conditionListModel.insertToken(index, value) + } + + onSetValue: function(index, value) { + backend.conditionListModel.setShadowToken(index, value) + } + } + + HelperWidgets.AbstractButton { + style: StudioTheme.Values.connectionPopupButtonStyle + width: 160 + buttonIcon: qsTr("Add Else Statement") + tooltip: qsTr("Sets an alternate condition for the previously defined logical condition.") + iconSize: StudioTheme.Values.baseFontSize + iconFontFamily: StudioTheme.Constants.font.family + anchors.horizontalCenter: parent.horizontalCenter + visible: root.currentAcion !== StatementDelegate.Custom + && backend.hasCondition && !backend.hasElse + + onClicked: backend.addElse() + } + + HelperWidgets.AbstractButton { + style: StudioTheme.Values.connectionPopupButtonStyle + width: 160 + buttonIcon: qsTr("Remove Else Statement") + tooltip: qsTr("Removes the alternate logical condition for the previously defined logical condition.") + iconSize: StudioTheme.Values.baseFontSize + iconFontFamily: StudioTheme.Constants.font.family + anchors.horizontalCenter: parent.horizontalCenter + visible: root.currentAction !== StatementDelegate.Custom + && backend.hasCondition && backend.hasElse + + onClicked: backend.removeElse() + } + + //Else Statement + StatementEditor { + width: root.width + actionType: root.currentAction + horizontalSpacing: root.horizontalSpacing + columnWidth: root.columnWidth + statement: backend.koStatement + backend: root.backend + spacing: root.verticalSpacing + visible: action.currentValue !== StatementDelegate.Custom + && backend.hasCondition && backend.hasElse + itemTooltip: root.itemTooltip + methodTooltip: root.methodTooltip + fromTooltip: root.fromTooltip + toTooltip: root.toTooltip + stateGroupTooltip: root.stateGroupTooltip + stateTooltip: root.stateTooltip + propertyTooltip: root.propertyTooltip + valueTooltip: root.valueTooltip + messageTooltip: root.messageTooltip + } + + // code preview toolbar + Column { + id: miniToolbarEditor + width: parent.width + spacing: -2 + + Rectangle { + id: miniToolbar + width: parent.width + height: editorButton.height + 2 + radius: 4 + z: -1 + color: StudioTheme.Values.themeConnectionEditorMicroToolbar + + Row { + spacing: 2 + HelperWidgets.AbstractButton { + id: editorButton + style: StudioTheme.Values.microToolbarButtonStyle + buttonIcon: StudioTheme.Constants.codeEditor_medium + tooltip: qsTr("Write the conditions manually.") + onClicked: expressionDialogLoader.show() + } + HelperWidgets.AbstractButton { + id: jumpToCodeButton + style: StudioTheme.Values.microToolbarButtonStyle + buttonIcon: StudioTheme.Constants.jumpToCode_medium + tooltip: qsTr("Jump to the code.") + onClicked: backend.jumpToCode() + } + } + } + + // Editor + Rectangle { + id: editor + width: parent.width + height: 150 + color: StudioTheme.Values.themeConnectionCodeEditor + + Text { + id: code + anchors.fill: parent + anchors.margins: 4 + text: backend.indentedSource + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.myFontSize + wrapMode: Text.Wrap + horizontalAlignment: code.lineCount === 1 ? Text.AlignHCenter : Text.AlignLeft + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + + Loader { + id: expressionDialogLoader + parent: editor + anchors.fill: parent + visible: false + active: visible + + function show() { + expressionDialogLoader.visible = true + } + + sourceComponent: Item { + id: bindingEditorParent + + Component.onCompleted: { + bindingEditor.showWidget() + bindingEditor.text = backend.source + bindingEditor.showControls(false) + bindingEditor.setMultilne(true) + bindingEditor.updateWindowName() + } + + HelperWidgets.ActionEditor { + id: bindingEditor + + onRejected: { + bindingEditor.hideWidget() + expressionDialogLoader.visible = false + } + + onAccepted: { + backend.setNewSource(bindingEditor.text) + bindingEditor.hideWidget() + expressionDialogLoader.visible = false + } + } + } + } + } + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/StatementEditor.qml b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/StatementEditor.qml similarity index 84% rename from share/qtcreator/qmldesigner/connectionseditor/StatementEditor.qml rename to share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/StatementEditor.qml index 0783f3cb7a0..47a89d6553b 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/StatementEditor.qml +++ b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/StatementEditor.qml @@ -6,7 +6,6 @@ import QtQuick.Controls import HelperWidgets as HelperWidgets import StudioControls as StudioControls import StudioTheme as StudioTheme -import ConnectionsEditorEditorBackend import ScriptEditorBackend Column { @@ -21,21 +20,32 @@ Column { property var backend + property string itemTooltip + property alias methodTooltip: methodPopupLabel.tooltip + property alias fromTooltip: fromPopupLabel.tooltip + property alias toTooltip: toPopupLabel.tooltip + property alias stateGroupTooltip: stateGroupPopupLabel.tooltip + property alias stateTooltip: statePopupLabel.tooltip + property alias propertyTooltip: propertyPopupLabel.tooltip + property alias valueTooltip: valuePopupLabel.tooltip + property alias messageTooltip: messagePopupLabel.tooltip + // Call Function Row { visible: root.actionType === StatementDelegate.CallFunction spacing: root.horizontalSpacing - PopupLabel { + HelperWidgets.PopupLabel { + id: itemPopupLabel width: root.columnWidth text: qsTr("Item") - tooltip: qsTr("Sets the component that is affected by the action of the Target component's Signal.") + tooltip: root.itemTooltip } - PopupLabel { + HelperWidgets.PopupLabel { + id: methodPopupLabel width: root.columnWidth text: qsTr("Method") - tooltip: qsTr("Sets the item component's method that is affected by the Target component's Signal.") } } @@ -72,15 +82,15 @@ Column { visible: root.actionType === StatementDelegate.Assign spacing: root.horizontalSpacing - PopupLabel { + HelperWidgets.PopupLabel { + id: fromPopupLabel width: root.columnWidth text: qsTr("From") - tooltip: qsTr("Sets the component and its property from which the value is copied when the Target component initiates the Signal.") } - PopupLabel { + HelperWidgets.PopupLabel { + id: toPopupLabel width: root.columnWidth text: qsTr("To") - tooltip: qsTr("Sets the component and its property to which the copied value is assigned when the Target component initiates the Signal.") } } @@ -149,16 +159,16 @@ Column { visible: root.actionType === StatementDelegate.ChangeState spacing: root.horizontalSpacing - PopupLabel { + HelperWidgets.PopupLabel { + id: stateGroupPopupLabel width: root.columnWidth text: qsTr("State Group") - tooltip: qsTr("Sets a State Group that is accessed when the Target component initiates the Signal.") } - PopupLabel { + HelperWidgets.PopupLabel { + id: statePopupLabel width: root.columnWidth text: qsTr("State") - tooltip: qsTr("Sets a State within the assigned State Group that is accessed when the Target component initiates the Signal.") } } @@ -195,16 +205,16 @@ Column { visible: root.actionType === StatementDelegate.SetProperty spacing: root.horizontalSpacing - PopupLabel { + HelperWidgets.PopupLabel { width: root.columnWidth text: qsTr("Item") - tooltip: qsTr("Sets the component that is affected by the action of the Target component's Signal.") + tooltip: root.itemTooltip } - PopupLabel { + HelperWidgets.PopupLabel { + id: propertyPopupLabel width: root.columnWidth text: qsTr("Property") - tooltip: qsTr("Sets the property of the component that is affected by the action of the Target component's Signal.") } } @@ -237,11 +247,11 @@ Column { } } - PopupLabel { + HelperWidgets.PopupLabel { + id: valuePopupLabel width: root.columnWidth visible: root.actionType === StatementDelegate.SetProperty text: qsTr("Value") - tooltip: qsTr("Sets the value of the property of the component that is affected by the action of the Target component's Signal.") } StudioControls.TextField { @@ -258,11 +268,11 @@ Column { } // Print Message - PopupLabel { + HelperWidgets.PopupLabel { + id: messagePopupLabel width: root.columnWidth visible: root.actionType === StatementDelegate.PrintMessage text: qsTr("Message") - tooltip: qsTr("Sets a text that is printed when the Signal of the Target component initiates.") } StudioControls.TextField { @@ -278,7 +288,7 @@ Column { } // Custom - PopupLabel { + HelperWidgets.PopupLabel { visible: root.actionType === StatementDelegate.Custom text: qsTr("Custom Connections can only be edited with the binding editor") width: root.width diff --git a/share/qtcreator/qmldesigner/connectionseditor/SuggestionPopup.qml b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/SuggestionPopup.qml similarity index 97% rename from share/qtcreator/qmldesigner/connectionseditor/SuggestionPopup.qml rename to share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/SuggestionPopup.qml index fad01c32c4d..fc40100ede8 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/SuggestionPopup.qml +++ b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/SuggestionPopup.qml @@ -6,15 +6,15 @@ import QtQuick.Controls as Controls import HelperWidgets as HelperWidgets import StudioTheme as StudioTheme import StudioControls as StudioControls -import ConnectionsEditorEditorBackend +import ScriptEditorBackend Controls.Popup { id: root property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle - property var listModel: ConnectionsEditorEditorBackend.connectionModel.delegate.propertyListProxyModel - property var treeModel: ConnectionsEditorEditorBackend.connectionModel.delegate.propertyTreeModel + property var listModel: ScriptEditorBackend.propertyListProxyModel // connect a valid model here + property var treeModel: ScriptEditorBackend.propertyTreeModel // connect a valid model here signal select(var value) signal entered(var value) diff --git a/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/qmldir b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/qmldir new file mode 100644 index 00000000000..18ffd98cad6 --- /dev/null +++ b/share/qtcreator/qmldesigner/scriptseditor/imports/ScriptsEditor/qmldir @@ -0,0 +1,2 @@ +ScriptEditorForm 1.0 ScriptEditorForm.qml +ActionsComboBox 1.0 ActionsComboBox.qml diff --git a/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.cpp b/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.cpp index 86824ac2656..7361826d88f 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.cpp @@ -23,7 +23,7 @@ static Q_LOGGING_CATEGORY(ceLog, "qtc.qmldesigner.connectioneditor", QtWarningMs namespace QmlDesigner { ActionEditorDialog::ActionEditorDialog(QWidget *parent) - : AbstractEditorDialog(parent, tr("Connection Editor")) + : AbstractEditorDialog(parent, tr("Action Editor")) { setupUIComponents(); diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp index 007116105b0..aa81e7c06de 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp @@ -6,7 +6,6 @@ #include "bindingmodel.h" #include "connectionmodel.h" #include "dynamicpropertiesmodel.h" -#include "propertytreemodel.h" #include "theme.h" #include @@ -32,13 +31,28 @@ namespace QmlDesigner { -static QString propertyEditorResourcesPath() +static QString resourcesPath(const QString &dir) { #ifdef SHARE_QML_PATH if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) - return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources"; + return QLatin1String(SHARE_QML_PATH) + "/" + dir; #endif - return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toUrlishString(); + return Core::ICore::resourcePath("qmldesigner/" + dir).toUrlishString(); +} + +static QString propertyEditorResourcesPath() +{ + return resourcesPath("propertyEditorQmlSources"); +} + +static QString scriptsEditorResourcesPath() +{ + return resourcesPath("scriptseditor"); +} + +static QString connectionsEditorResourcesPath() +{ + return resourcesPath("connectionseditor"); } class ConnectionViewQuickWidget : public StudioQuickWidget @@ -53,9 +67,10 @@ public: : m_connectionEditorView(connectionEditorView) { - engine()->addImportPath(qmlSourcesPath()); + engine()->addImportPath(connectionsEditorResourcesPath()); engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); - engine()->addImportPath(qmlSourcesPath() + "/imports"); + engine()->addImportPath(scriptsEditorResourcesPath() + "/imports"); + engine()->addImportPath(connectionsEditorResourcesPath() + "/imports"); m_qmlSourceUpdateShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_F12), this); connect(m_qmlSourceUpdateShortcut, @@ -83,17 +98,6 @@ public: 0, "DynamicPropertiesModelBackendDelegate"); - // TODO: register in the property editor along with the ScriptEditorBackend - qmlRegisterType("ScriptEditorBackend", 1, 0, "StatementDelegate"); - - qmlRegisterType("ConnectionsEditorEditorBackend", 1, 0, "ConditionListModel"); - - qmlRegisterType("ConnectionsEditorEditorBackend", 1, 0, "PropertyTreeModel"); - qmlRegisterType("ConnectionsEditorEditorBackend", - 1, - 0, - "PropertyListProxyModel"); - Theme::setupTheme(engine()); setMinimumSize(QSize(195, 195)); @@ -103,19 +107,11 @@ public: } ~ConnectionViewQuickWidget() = default; - static QString qmlSourcesPath() - { -#ifdef SHARE_QML_PATH - if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) - return QLatin1String(SHARE_QML_PATH) + "/connectionseditor"; -#endif - return Core::ICore::resourcePath("qmldesigner/connectionseditor").toUrlishString(); - } - private: void reloadQmlSource() { - QString connectionEditorQmlFilePath = qmlSourcesPath() + QStringLiteral("/Main.qml"); + QString connectionEditorQmlFilePath = connectionsEditorResourcesPath() + + QStringLiteral("/Main.qml"); QTC_ASSERT(QFileInfo::exists(connectionEditorQmlFilePath), return ); setSource(QUrl::fromLocalFile(connectionEditorQmlFilePath)); @@ -127,7 +123,7 @@ private: Core::AsynchronousMessageBox::warning( Tr::tr("Cannot Create QtQuick View"), Tr::tr("ConnectionsEditorWidget: %1 cannot be created.%2") - .arg(qmlSourcesPath(), errorString)); + .arg(connectionsEditorResourcesPath(), errorString)); return; } } @@ -143,7 +139,6 @@ struct ConnectionView::ConnectionViewData : connectionModel{view} , bindingModel{view} , dynamicPropertiesModel{false, view} - , propertyTreeModel{view} , connectionViewQuickWidget{Utils::makeUniqueObjectPtr( view, &connectionModel, &bindingModel, &dynamicPropertiesModel)} {} @@ -151,7 +146,6 @@ struct ConnectionView::ConnectionViewData ConnectionModel connectionModel; BindingModel bindingModel; DynamicPropertiesModel dynamicPropertiesModel; - PropertyTreeModel propertyTreeModel; int currentIndex = 0; // Ensure that QML is deleted first to avoid calling back to C++. diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h index debf78ae2e7..e06f1702892 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h @@ -22,7 +22,6 @@ class BindingModel; class ConnectionModel; class DynamicPropertiesModel; class ConnectionViewQuickWidget; -class PropertyTreeModel; class PropertyListProxyModel; class ConnectionView : public AbstractView diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp index 92992a2ad62..24621182e14 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp @@ -100,6 +100,8 @@ PropertyEditorQmlBackend::PropertyEditorQmlBackend(PropertyEditorView *propertyE ->settings().value(DesignerSettingsKey::SHOW_PROPERTYEDITOR_WARNINGS).toBool()); m_view->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); + m_view->engine()->addImportPath(scriptsEditorResourcesPath() + "/imports"); + m_dummyPropertyEditorValue->setValue(QLatin1String("#000000")); context()->setContextProperty(QLatin1String("dummyBackendValue"), m_dummyPropertyEditorValue.get()); @@ -642,11 +644,12 @@ void PropertyEditorQmlBackend::initialSetup(const TypeName &typeName, const QUrl QString PropertyEditorQmlBackend::propertyEditorResourcesPath() { -#ifdef SHARE_QML_PATH - if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) - return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources"; -#endif - return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toUrlishString(); + return resourcesPath("propertyEditorQmlSources"); +} + +QString PropertyEditorQmlBackend::scriptsEditorResourcesPath() +{ + return resourcesPath("scriptseditor"); } inline bool dotPropertyHeuristic(const QmlObjectNode &node, @@ -920,6 +923,15 @@ TypeName PropertyEditorQmlBackend::fixTypeNameForPanes(const TypeName &typeName) return fixedTypeName; } +QString PropertyEditorQmlBackend::resourcesPath(const QString &dir) +{ +#ifdef SHARE_QML_PATH + if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/" + dir; +#endif + return Core::ICore::resourcePath("qmldesigner/" + dir).toUrlishString(); +} + static NodeMetaInfo findCommonSuperClass(const NodeMetaInfo &first, const NodeMetaInfo &second) { auto commonBase = first.commonBase(second); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h index 062153289da..1ba93bdc8bb 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h @@ -52,6 +52,7 @@ public: PropertyEditorValue *propertyValueForName(const QString &propertyName); static QString propertyEditorResourcesPath(); + static QString scriptsEditorResourcesPath(); static QUrl emptyPaneUrl(); #ifndef QDS_USE_PROJECTSTORAGE static QString templateGeneration(const NodeMetaInfo &type, @@ -109,6 +110,7 @@ private: static QString locateQmlFile(const NodeMetaInfo &info, const QString &relativePath); #endif static TypeName fixTypeNameForPanes(const TypeName &typeName); + static QString resourcesPath(const QString &dir); private: // to avoid a crash while destructing DesignerPropertyMap in the QQmlData diff --git a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp index 78514fc91a0..9c403300763 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp @@ -4,6 +4,7 @@ #include "quick2propertyeditorview.h" #include +#include #include "aligndistribute.h" #include "annotationeditor/annotationeditor.h" @@ -67,6 +68,7 @@ void Quick2PropertyEditorView::registerQmlTypes() PropertyChangesModel::registerDeclarativeType(); PropertyModel::registerDeclarativeType(); PropertyNameValidator::registerDeclarativeType(); + ScriptEditorBackend::registerDeclarativeType(); const QString resourcePath = PropertyEditorQmlBackend::propertyEditorResourcesPath(); diff --git a/src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.cpp b/src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.cpp index 4846f149ddf..3bf324336b6 100644 --- a/src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.cpp +++ b/src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -1139,6 +1140,25 @@ void ScriptEditorBackend::jumpToCode() ModelNodeOperations::jumpToCode(sourceProperty.parentModelNode()); } +void ScriptEditorBackend::registerDeclarativeType() +{ + qmlRegisterType("ScriptEditorBackend", 1, 0, "StatementDelegate"); + qmlRegisterType("ScriptEditorBackend", 1, 0, "PropertyTreeModel"); + qmlRegisterType("ScriptEditorBackend", 1, 0, "ConditionListModel"); + qmlRegisterType("ScriptEditorBackend", 1, 0, "PropertyListProxyModel"); + + QTC_ASSERT(!QmlDesignerPlugin::viewManager().views().isEmpty(), return); + AbstractView *view = QmlDesignerPlugin::viewManager().views().first(); + + qmlRegisterSingletonType("ScriptEditorBackend", + 1, + 0, + "ScriptEditorBackend", + [view](QQmlEngine *, QJSEngine *) { + return new ScriptEditorBackend(view); + }); +} + bool ScriptEditorBackend::blockReflection() const { return m_blockReflection; diff --git a/src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.h b/src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.h index 496c4d198a0..64012c195b4 100644 --- a/src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.h +++ b/src/plugins/qmldesigner/components/scripteditor/scripteditorbackend.h @@ -191,6 +191,8 @@ public: Q_INVOKABLE void jumpToCode(); + static void registerDeclarativeType(); + signals: void actionTypeChanged(); void hasConditionChanged(); From c4eeee49834b9737aa8779e0becc671d468e12f2 Mon Sep 17 00:00:00 2001 From: Rafal Stawarski Date: Thu, 27 Feb 2025 17:15:05 +0100 Subject: [PATCH 15/54] Bindings editor: filter out script properties Script properties are currently not handled properly (QDS-13510). They won't be shown in the Bindings editor. Task-number: QDS-10449 Change-Id: I3a1ab916aabc1bb18852e5c723a48e68f7ba755b Reviewed-by: Thomas Hartmann --- .../connectioneditor/bindingmodel.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp index 4b4e59cfe00..e7996ba0a10 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp @@ -65,12 +65,20 @@ BindingProperty BindingModel::propertyForRow(int row) const return {}; } +// TODO: add support for scripts in the binding editor (QDS-13510) +static bool isSupportedProperty(const PropertyNameView &propertyName) +{ + return propertyName != QLatin1String("script"); +} + static PropertyName unusedProperty(const ModelNode &modelNode) { if (modelNode.metaInfo().isValid()) { for (const auto &property : modelNode.metaInfo().properties()) { - if (property.isWritable() && !modelNode.hasProperty(property.name())) + if (property.isWritable() && !modelNode.hasProperty(property.name()) + && isSupportedProperty(property.name())) { return property.name(); + } } } return "none"; @@ -148,7 +156,7 @@ void BindingModel::updateItem(const BindingProperty &property) item->updateProperty(property); } else { ModelNode node = property.parentModelNode(); - if (connectionView()->isSelectedModelNode(node)) { + if (connectionView()->isSelectedModelNode(node) && isSupportedProperty(property.name())) { appendRow(new BindingModelItem(property)); setCurrentProperty(property); } @@ -244,8 +252,10 @@ void BindingModel::addModelNode(const ModelNode &node) return; const QList bindingProperties = node.bindingProperties(); - for (const BindingProperty &property : bindingProperties) - appendRow(new BindingModelItem(property)); + for (const BindingProperty &property : bindingProperties) { + if (isSupportedProperty(property.name())) + appendRow(new BindingModelItem(property)); + } } BindingModelBackendDelegate::BindingModelBackendDelegate(BindingModel &model) From 20fe6e1e608d2731e2e00815d745836a07edfcf1 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Fri, 21 Feb 2025 17:14:43 +0200 Subject: [PATCH 16/54] QmlDesigner: Enable updating 3D assets from asset view context menu Fixes: QDS-14790 Change-Id: I5e88c5a1c2de1dc581c19259f5771b15a65b2ecc Reviewed-by: Mahmoud Badri Reviewed-by: Ali Kianian --- .../AssetsContextMenu.qml | 9 + .../assetslibrary/assetslibrarywidget.cpp | 8 + .../assetslibrary/assetslibrarywidget.h | 1 + .../componentcore/componentcore_constants.h | 2 +- .../components/import3d/import3ddialog.cpp | 181 +++++++++--------- .../components/import3d/import3ddialog.h | 2 +- .../components/integration/componentview.cpp | 18 +- 7 files changed, 123 insertions(+), 98 deletions(-) diff --git a/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetsContextMenu.qml b/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetsContextMenu.qml index cc4b0c17fce..3fe07f47a89 100644 --- a/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetsContextMenu.qml +++ b/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetsContextMenu.qml @@ -141,6 +141,15 @@ StudioControls.Menu { onTriggered: root.rootView.editAssetComponent(root.__selectedAssetPathsList[0]) } + StudioControls.MenuItem { + id: updateComponent + text: qsTr("Reimport 3D Asset") + visible: root.__fileIndex && root.__selectedAssetPathsList.length === 1 + && root.rootView.assetIsImported3d(root.__selectedAssetPathsList[0]) + height: editComponent.visible ? editComponent.implicitHeight : 0 + onTriggered: root.rootView.updateAssetComponent(root.__selectedAssetPathsList[0]) + } + StudioControls.MenuItem { id: addTexturesItem text: qsTr("Add Texture") diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp index 2191b1c981d..baf5826b622 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp @@ -617,6 +617,14 @@ void AssetsLibraryWidget::editAssetComponent(const QString &filePath) DocumentManager::goIntoComponent(fullPath.toFSPathString()); } +void AssetsLibraryWidget::updateAssetComponent(const QString &filePath) +{ + Utils::FilePath qml = QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().getImported3dQml(filePath); + if (qml.exists()) + m_assetsView->emitCustomNotification("UpdateImported3DAsset", {}, {qml.toFSPathString()}); +} + QString AssetsLibraryWidget::qmlSourcesPath() { #ifdef SHARE_QML_PATH diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h index 29952579af6..e4a457253c0 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h @@ -85,6 +85,7 @@ public: Q_INVOKABLE QSet supportedAssetSuffixes(bool complex); Q_INVOKABLE void openEffectComposer(const QString &filePath); Q_INVOKABLE void editAssetComponent(const QString &filePath); + Q_INVOKABLE void updateAssetComponent(const QString &filePath); Q_INVOKABLE int qtVersion() const; Q_INVOKABLE void invalidateThumbnail(const QString &id); Q_INVOKABLE QSize imageSize(const QString &id); diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h index 4d55e0a977f..54729d9075a 100644 --- a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h +++ b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h @@ -182,7 +182,7 @@ inline constexpr char editInEffectComposerDisplayName[] = QT_TRANSLATE_NOOP("Qml inline constexpr char openSignalDialogDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Open Signal Dialog"); inline constexpr char update3DAssetDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", - "Update 3D Asset"); + "Reimport 3D Asset"); inline constexpr char setIdDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Set Id"); diff --git a/src/plugins/qmldesigner/components/import3d/import3ddialog.cpp b/src/plugins/qmldesigner/components/import3d/import3ddialog.cpp index 68e704fcb7d..b2b7adda751 100644 --- a/src/plugins/qmldesigner/components/import3d/import3ddialog.cpp +++ b/src/plugins/qmldesigner/components/import3d/import3ddialog.cpp @@ -92,7 +92,7 @@ constexpr QStringView expandValuesKey{u"expandValueComponents"}; } // namespace Import3dDialog::Import3dDialog( - const QStringList &importFiles, const QString &defaulTargetDirectory, + const QStringList &importFiles, const QVariantMap &supportedExts, const QVariantMap &supportedOpts, const QJsonObject &defaultOpts, const QSet &preselectedFilesForOverwrite, AbstractView *view, QWidget *parent) @@ -152,10 +152,6 @@ Import3dDialog::Import3dDialog( importPaths = model->importPaths(); } - QString targetDir = QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath().toUrlishString(); - if (targetDir.isEmpty()) - targetDir = defaulTargetDirectory; - m_quick3DImportPath = QmlDesignerPlugin::instance()->documentManager() .generatedComponentUtils().import3dBasePath().toUrlishString(); @@ -282,111 +278,114 @@ Import3dDialog::~Import3dDialog() } void Import3dDialog::updateImport(AbstractView *view, + const Utils::FilePath &import3dQml, const ModelNode &updateNode, const QVariantMap &supportedExts, const QVariantMap &supportedOpts) { QString errorMsg; - const ModelNode &node = updateNode; - if (node.hasMetaInfo()) { - QString compFileName = ModelUtils::componentFilePath(node); // absolute path - bool preselectNodeSource = false; + QString compFileName; // absolute path to imported 3D qml + bool preselectNodeSource = false; + if (import3dQml.exists()) { + compFileName = import3dQml.toFSPathString(); + } else if (updateNode.isValid()) { + compFileName = ModelUtils::componentFilePath(updateNode); if (compFileName.isEmpty()) { // Node is not a file component, so we have to check if the current doc itself is - compFileName = node.model()->fileUrl().toLocalFile(); + compFileName = updateNode.model()->fileUrl().toLocalFile(); preselectNodeSource = true; } - QFileInfo compFileInfo{compFileName}; + } - // Find to top asset folder - const QString oldAssetFolder = Constants::oldQuick3dAssetsFolder; - QString assetFolder = Constants::quick3DComponentsFolder; - const QStringList parts = compFileName.split('/'); - int i = parts.size() - 1; - int previousSize = 0; - for (; i >= 0; --i) { - if (parts[i] == oldAssetFolder) - assetFolder = oldAssetFolder; - if (parts[i] == assetFolder) - break; - previousSize = parts[i].size(); - } - if (i >= 0) { - const QString assetPath = compFileName.left(compFileName.lastIndexOf(assetFolder) - + assetFolder.size() + previousSize + 1); - const QDir assetDir(assetPath); + QFileInfo compFileInfo{compFileName}; - // Find import options and the original source scene - const QString jsonFileName = assetDir.absoluteFilePath( - Constants::QUICK_3D_ASSET_IMPORT_DATA_NAME); - QFile jsonFile{jsonFileName}; - if (jsonFile.open(QIODevice::ReadOnly)) { - QJsonParseError jsonError; - const QByteArray fileData = jsonFile.readAll(); - auto jsonDocument = QJsonDocument::fromJson(fileData, &jsonError); - jsonFile.close(); - if (jsonError.error == QJsonParseError::NoError) { - QJsonObject jsonObj = jsonDocument.object(); - const QJsonObject options = jsonObj.value( - Constants::QUICK_3D_ASSET_IMPORT_DATA_OPTIONS_KEY).toObject(); - QString sourcePath = jsonObj.value( - Constants::QUICK_3D_ASSET_IMPORT_DATA_SOURCE_KEY).toString(); - if (options.isEmpty() || sourcePath.isEmpty()) { - errorMsg = Tr::tr("Asset import data file \"%1\" is invalid.").arg(jsonFileName); - } else { - QFileInfo sourceInfo{sourcePath}; - if (!sourceInfo.exists()) { - // Unable to find original scene source, launch file dialog to locate it - QString initialPath; - ProjectExplorer::Project *currentProject - = ProjectExplorer::ProjectManager::projectForFile( - Utils::FilePath::fromString(compFileName)); - if (currentProject) - initialPath = currentProject->projectDirectory().toUrlishString(); - else - initialPath = compFileInfo.absolutePath(); - QStringList selectedFiles = QFileDialog::getOpenFileNames( - Core::ICore::dialogParent(), - tr("Locate 3D Asset \"%1\"").arg(sourceInfo.fileName()), - initialPath, sourceInfo.fileName()); - if (!selectedFiles.isEmpty() - && QFileInfo{selectedFiles[0]}.fileName() == sourceInfo.fileName()) { - sourcePath = selectedFiles[0]; - sourceInfo.setFile(sourcePath); - } - } - if (sourceInfo.exists()) { - // In case of a selected node inside an imported component, preselect - // any file pointed to by a "source" property of the node. - QSet preselectedFiles; - if (preselectNodeSource && updateNode.hasProperty("source")) { - QString source = updateNode.variantProperty("source").value().toString(); - if (QFileInfo{source}.isRelative()) - source = QDir{compFileInfo.absolutePath()}.absoluteFilePath(source); - preselectedFiles.insert(source); - } - auto importDlg = new Import3dDialog( - {sourceInfo.absoluteFilePath()}, - node.model()->fileUrl().toLocalFile(), - supportedExts, supportedOpts, options, - preselectedFiles, view, - Core::ICore::dialogParent()); - importDlg->show(); + // Find to top asset folder + const QString oldAssetFolder = Constants::oldQuick3dAssetsFolder; + QString assetFolder = Constants::quick3DComponentsFolder; + const QStringList parts = compFileName.split('/'); + int i = parts.size() - 1; + int previousSize = 0; + for (; i >= 0; --i) { + if (parts[i] == oldAssetFolder) + assetFolder = oldAssetFolder; + if (parts[i] == assetFolder) + break; + previousSize = parts[i].size(); + } + if (i >= 0) { + const QString assetPath = compFileName.left(compFileName.lastIndexOf(assetFolder) + + assetFolder.size() + previousSize + 1); + const QDir assetDir(assetPath); - } else { - errorMsg = Tr::tr("Unable to locate source scene \"%1\".") - .arg(sourceInfo.fileName()); + // Find import options and the original source scene + const QString jsonFileName = assetDir.absoluteFilePath( + Constants::QUICK_3D_ASSET_IMPORT_DATA_NAME); + QFile jsonFile{jsonFileName}; + if (jsonFile.open(QIODevice::ReadOnly)) { + QJsonParseError jsonError; + const QByteArray fileData = jsonFile.readAll(); + auto jsonDocument = QJsonDocument::fromJson(fileData, &jsonError); + jsonFile.close(); + if (jsonError.error == QJsonParseError::NoError) { + QJsonObject jsonObj = jsonDocument.object(); + const QJsonObject options = jsonObj.value( + Constants::QUICK_3D_ASSET_IMPORT_DATA_OPTIONS_KEY).toObject(); + QString sourcePath = jsonObj.value( + Constants::QUICK_3D_ASSET_IMPORT_DATA_SOURCE_KEY).toString(); + if (options.isEmpty() || sourcePath.isEmpty()) { + errorMsg = Tr::tr("Asset import data file \"%1\" is invalid.").arg(jsonFileName); + } else { + QFileInfo sourceInfo{sourcePath}; + if (!sourceInfo.exists()) { + // Unable to find original scene source, launch file dialog to locate it + QString initialPath; + ProjectExplorer::Project *currentProject + = ProjectExplorer::ProjectManager::projectForFile( + Utils::FilePath::fromString(compFileName)); + if (currentProject) + initialPath = currentProject->projectDirectory().toUrlishString(); + else + initialPath = compFileInfo.absolutePath(); + QStringList selectedFiles = QFileDialog::getOpenFileNames( + Core::ICore::dialogParent(), + tr("Locate 3D Asset \"%1\"").arg(sourceInfo.fileName()), + initialPath, sourceInfo.fileName()); + if (!selectedFiles.isEmpty() + && QFileInfo{selectedFiles[0]}.fileName() == sourceInfo.fileName()) { + sourcePath = selectedFiles[0]; + sourceInfo.setFile(sourcePath); } } - } else { - errorMsg = jsonError.errorString(); + if (sourceInfo.exists()) { + // In case of a selected node inside an imported component, preselect + // any file pointed to by a "source" property of the node. + QSet preselectedFiles; + if (preselectNodeSource && updateNode.hasProperty("source")) { + QString source = updateNode.variantProperty("source").value().toString(); + if (QFileInfo{source}.isRelative()) + source = QDir{compFileInfo.absolutePath()}.absoluteFilePath(source); + preselectedFiles.insert(source); + } + auto importDlg = new Import3dDialog( + {sourceInfo.absoluteFilePath()}, + supportedExts, supportedOpts, options, + preselectedFiles, view, + Core::ICore::dialogParent()); + importDlg->show(); + + } else { + errorMsg = Tr::tr("Unable to locate source scene \"%1\".") + .arg(sourceInfo.fileName()); + } } } else { - errorMsg = Tr::tr("Opening asset import data file \"%1\" failed.").arg(jsonFileName); + errorMsg = jsonError.errorString(); } } else { - errorMsg = Tr::tr("Unable to resolve asset import path."); + errorMsg = Tr::tr("Opening asset import data file \"%1\" failed.").arg(jsonFileName); } + } else { + errorMsg = Tr::tr("Unable to resolve asset import path."); } if (!errorMsg.isEmpty()) { diff --git a/src/plugins/qmldesigner/components/import3d/import3ddialog.h b/src/plugins/qmldesigner/components/import3d/import3ddialog.h index df7dd7c27d5..98cdf937cc5 100644 --- a/src/plugins/qmldesigner/components/import3d/import3ddialog.h +++ b/src/plugins/qmldesigner/components/import3d/import3ddialog.h @@ -41,7 +41,6 @@ class Import3dDialog : public QDialog public: explicit Import3dDialog(const QStringList &importFiles, - const QString &defaulTargetDirectory, const QVariantMap &supportedExts, const QVariantMap &supportedOpts, const QJsonObject &defaultOpts, @@ -51,6 +50,7 @@ public: ~Import3dDialog(); static void updateImport(AbstractView *view, + const Utils::FilePath &import3dQml, const ModelNode &updateNode, const QVariantMap &supportedExts, const QVariantMap &supportedOpts); diff --git a/src/plugins/qmldesigner/components/integration/componentview.cpp b/src/plugins/qmldesigner/components/integration/componentview.cpp index 0e3f0243724..806bdb4ee31 100644 --- a/src/plugins/qmldesigner/components/integration/componentview.cpp +++ b/src/plugins/qmldesigner/components/integration/componentview.cpp @@ -316,10 +316,18 @@ void ComponentView::nodeSourceChanged(const ModelNode &node, const QString &/*ne void ComponentView::customNotification(const AbstractView *, const QString &identifier, const QList &nodeList, - const QList &) + const QList &data) { - if (identifier == "UpdateImported3DAsset" && nodeList.size() > 0) { - Import3dDialog::updateImport(this, nodeList[0], + if (identifier == "UpdateImported3DAsset") { + Utils::FilePath import3dQml; + if (!data.isEmpty()) + import3dQml = Utils::FilePath::fromString(data[0].toString()); + + ModelNode node; + if (!nodeList.isEmpty()) + node = nodeList[0]; + + Import3dDialog::updateImport(this, import3dQml, node, m_importableExtensions3DMap, m_importOptions3DMap); } @@ -338,11 +346,11 @@ void ComponentView::updateImport3DSupport(const QVariantMap &supportMap) m_importableExtensions3DMap = extMap; AddResourceOperation import3DModelOperation = [this](const QStringList &fileNames, - const QString &defaultDir, + const QString &, bool showDialog) -> AddFilesResult { Q_UNUSED(showDialog) - auto importDlg = new Import3dDialog(fileNames, defaultDir, + auto importDlg = new Import3dDialog(fileNames, m_importableExtensions3DMap, m_importOptions3DMap, {}, {}, this, Core::ICore::dialogParent()); From c9c0c0838d0905451de7be2fafd7c68b9b7e3f21 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Fri, 28 Feb 2025 14:54:08 +0200 Subject: [PATCH 17/54] QmlDesigner: Links that add QtQuick3D import now also add default View3D Links in 3D view and Content Library that add QtQuick3D import now also add a View3D with extended scene environment, if View3D doesn't already exist in the scene. Fixes: QDS-14821 Change-Id: I9df6fd832e8bd0721ae2a451e7d2f3613af884fb Reviewed-by: Mahmoud Badri Reviewed-by: Ali Kianian --- src/plugins/qmldesigner/CMakeLists.txt | 1 - .../componentcore/modelnodeoperations.cpp | 3 +- .../components/componentcore/utils3d.cpp | 112 ++++++++++++++++++ .../components/componentcore/utils3d.h | 5 + .../contentlibrary/contentlibraryview.cpp | 20 +--- .../components/edit3d/edit3dview.cpp | 27 +---- .../components/edit3d/edit3dview.h | 1 - .../components/edit3d/edit3dwidget.cpp | 6 +- .../components/formeditor/dragtool.cpp | 4 +- .../materialbrowser/materialutils.cpp | 76 ------------ .../materialbrowser/materialutils.h | 20 ---- .../navigator/navigatortreemodel.cpp | 7 +- 12 files changed, 128 insertions(+), 154 deletions(-) delete mode 100644 src/plugins/qmldesigner/components/materialbrowser/materialutils.cpp delete mode 100644 src/plugins/qmldesigner/components/materialbrowser/materialutils.h diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 1c58e2ba88f..78f4e908a54 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -477,7 +477,6 @@ extend_qtc_plugin(QmlDesigner materialbrowserwidget.cpp materialbrowserwidget.h materialbrowsermodel.cpp materialbrowsermodel.h materialbrowsertexturesmodel.cpp materialbrowsertexturesmodel.h - materialutils.cpp materialutils.h ) extend_qtc_plugin(QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index d59b69f63ae..afd2ae12b31 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -2093,7 +2092,7 @@ void handleMaterialDrop(const QMimeData *mimeData, const ModelNode &targetNode) ModelNode matNode = view->modelNodeForInternalId(internalId); view->executeInTransaction(__FUNCTION__, [&] { - MaterialUtils::assignMaterialTo3dModel(view, targetNode, matNode); + Utils3D::assignMaterialTo3dModel(view, targetNode, matNode); }); } diff --git a/src/plugins/qmldesigner/components/componentcore/utils3d.cpp b/src/plugins/qmldesigner/components/componentcore/utils3d.cpp index 465514bbb6c..1673c854d6b 100644 --- a/src/plugins/qmldesigner/components/componentcore/utils3d.cpp +++ b/src/plugins/qmldesigner/components/componentcore/utils3d.cpp @@ -3,14 +3,20 @@ #include "utils3d.h" +#include #include #include #include #include #include +#include +#include +#include #include #include +#include + #include #include @@ -342,5 +348,111 @@ ModelNode createMaterial(AbstractView *view, const NodeMetaInfo &metaInfo) } #endif +void addQuick3DImportAndView3D(AbstractView *view) +{ + DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument(); + if (!view || !view->model() || !document || document->inFileComponentModelActive()) { + Core::AsynchronousMessageBox::warning(Tr::tr("Failed to Add Import"), + Tr::tr("Could not add QtQuick3D import to the document.")); + return; + } + + QString importName{"QtQuick3D"}; + if (view->model()->hasImport(importName)) + return; + + view->executeInTransaction(__FUNCTION__, [&] { + Import import = Import::createLibraryImport(importName); + view->model()->changeImports({import}, {}); + + if (!view->rootModelNode().metaInfo().isQtQuickItem()) + return; + + ensureMaterialLibraryNode(view); +#ifndef QDS_USE_PROJECTSTORAGE + }); + view->executeInTransaction(__FUNCTION__, [&] { +#endif + NodeMetaInfo view3dInfo = view->model()->qtQuick3DView3DMetaInfo(); + if (!view->allModelNodesOfType(view3dInfo).isEmpty()) + return; + + const QList entries = view->model()->itemLibraryEntries(); + // Use template file to identify correct entry, as name could be localized in the future + const QString view3dSource{"extendedview3D_template.qml"}; + auto templateMatch = [&view3dSource](const ItemLibraryEntry &entry) -> bool { + return entry.templatePath().endsWith(view3dSource); + }; + auto iter = std::ranges::find_if(entries, templateMatch); + if (iter == entries.end()) + return; + + NodeAbstractProperty targetProp = view->rootModelNode().defaultNodeAbstractProperty(); + QmlObjectNode newQmlObjectNode = QmlItemNode::createQmlObjectNode( + view, *iter, QPointF(), targetProp, false); + + const QList models = newQmlObjectNode.modelNode().subModelNodesOfType( + view->model()->qtQuick3DModelMetaInfo()); + if (!models.isEmpty()) + assignMaterialTo3dModel(view, models.at(0)); + }); +} + +// Assigns given material to a 3D model. +// The assigned material is also inserted into material library if not already there. +// If given material is not valid, first existing material from material library is used, +// or if material library is empty, a new material is created. +// This function should be called only from inside a transaction, as it potentially does many +// changes to model. +void assignMaterialTo3dModel(AbstractView *view, const ModelNode &modelNode, + const ModelNode &materialNode) +{ + QTC_ASSERT(modelNode.metaInfo().isQtQuick3DModel(), return); + + ModelNode matLib = Utils3D::materialLibraryNode(view); + + if (!matLib) + return; + + ModelNode newMaterialNode; + + if (materialNode.metaInfo().isQtQuick3DMaterial()) { + newMaterialNode = materialNode; + } else { + const QList materials = matLib.directSubModelNodes(); + auto isMaterial = [](const ModelNode &node) -> bool { + return node.metaInfo().isQtQuick3DMaterial(); + }; + if (auto iter = std::ranges::find_if(materials, isMaterial); iter != materials.end()) + newMaterialNode = *iter; + + // if no valid material, create a new default material + if (!newMaterialNode) { +#ifdef QDS_USE_PROJECTSTORAGE + newMaterialNode = view->createModelNode("PrincipledMaterial"); +#else + NodeMetaInfo metaInfo = view->model()->qtQuick3DPrincipledMaterialMetaInfo(); + newMaterialNode = view->createModelNode("QtQuick3D.PrincipledMaterial", + metaInfo.majorVersion(), + metaInfo.minorVersion()); +#endif + newMaterialNode.ensureIdExists(); + } + } + + QTC_ASSERT(newMaterialNode, return); + + VariantProperty matNameProp = newMaterialNode.variantProperty("objectName"); + if (matNameProp.value().isNull()) + matNameProp.setValue("New Material"); + + if (!newMaterialNode.hasParentProperty() + || newMaterialNode.parentProperty() != matLib.defaultNodeListProperty()) { + matLib.defaultNodeListProperty().reparentHere(newMaterialNode); + } + + QmlObjectNode(modelNode).setBindingProperty("materials", newMaterialNode.id()); +} + } // namespace Utils3D } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/utils3d.h b/src/plugins/qmldesigner/components/componentcore/utils3d.h index 6a7899589c3..b14784d0291 100644 --- a/src/plugins/qmldesigner/components/componentcore/utils3d.h +++ b/src/plugins/qmldesigner/components/componentcore/utils3d.h @@ -54,5 +54,10 @@ ModelNode createMaterial(AbstractView *view, const TypeName &typeName); ModelNode createMaterial(AbstractView *view, const NodeMetaInfo &metaInfo); #endif +void addQuick3DImportAndView3D(AbstractView *view); +void assignMaterialTo3dModel(AbstractView *view, const ModelNode &modelNode, + const ModelNode &materialNode = {}); + + } // namespace Utils3D } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp index 83415587fdc..7ec0ff221bd 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp @@ -35,8 +35,6 @@ #include -#include - #ifndef QMLDESIGNER_TEST #include #include @@ -77,23 +75,7 @@ WidgetInfo ContentLibraryView::widgetInfo() m_bundleHelper = std::make_unique(this, m_widget); connect(m_widget, &ContentLibraryWidget::importQtQuick3D, this, [&] { - DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument(); - if (document && !document->inFileComponentModelActive() && model()) { -#ifdef QDS_USE_PROJECTSTORAGE - Import import = Import::createLibraryImport("QtQuick3D"); - model()->changeImports({import}, {}); - return; -#else - if (ModelUtils::addImportWithCheck( - "QtQuick3D", - [](const Import &import) { return !import.hasVersion() || import.majorVersion() >= 6; }, - model())) { - return; - } -#endif - } - Core::AsynchronousMessageBox::warning(tr("Failed to Add Import"), - tr("Could not add QtQuick3D import to project.")); + Utils3D::addQuick3DImportAndView3D(this); }); connect(m_widget, &ContentLibraryWidget::bundleMaterialDragStarted, this, [&] (QmlDesigner::ContentLibraryMaterial *mat) { diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index e23c926fec7..8887b8f176d 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -33,7 +32,6 @@ #include #include -#include #include @@ -481,7 +479,7 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos createdNode = QmlVisualNode::createQml3DNode( this, m_droppedEntry, edit3DWidget()->canvas()->activeScene(), pos3d).modelNode(); if (createdNode.metaInfo().isQtQuick3DModel()) - MaterialUtils::assignMaterialTo3dModel(this, createdNode); + Utils3D::assignMaterialTo3dModel(this, createdNode); }); if (createdNode.isValid()) setSelectedModelNode(createdNode); @@ -489,7 +487,7 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos bool isModel = modelNode.metaInfo().isQtQuick3DModel(); if (m_droppedModelNode.isValid() && isModel) { executeInTransaction(__FUNCTION__, [&] { - MaterialUtils::assignMaterialTo3dModel(this, modelNode, m_droppedModelNode); + Utils3D::assignMaterialTo3dModel(this, modelNode, m_droppedModelNode); }); } } else if (m_nodeAtPosReqType == NodeAtPosReqType::BundleMaterialDrop) { @@ -1400,27 +1398,6 @@ Edit3DBakeLightsAction *Edit3DView::bakeLightsAction() const return m_bakeLightsAction.get(); } -void Edit3DView::addQuick3DImport() -{ - DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument(); - if (document && !document->inFileComponentModelActive() && model()) { -#ifdef QDS_USE_PROJECTSTORAGE - Import import = Import::createLibraryImport("QtQuick3D"); - model()->changeImports({import}, {}); - return; -#else - if (ModelUtils::addImportWithCheck( - "QtQuick3D", - [](const Import &import) { return !import.hasVersion() || import.majorVersion() >= 6; }, - model())) { - return; - } -#endif - } - Core::AsynchronousMessageBox::warning(tr("Failed to Add Import"), - tr("Could not add QtQuick3D import to project.")); -} - // This method is called upon right-clicking the view to prepare for context-menu creation. The actual // context menu is created when nodeAtPosReady() is received from puppet void Edit3DView::startContextMenu(const QPoint &pos) diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index 5e45ee80889..e6dd069b0cf 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -93,7 +93,6 @@ public: Edit3DAction *edit3DAction(View3DActionType type) const; Edit3DBakeLightsAction *bakeLightsAction() const; - void addQuick3DImport(); void startContextMenu(const QPoint &pos); void showContextMenu(); void dropMaterial(const ModelNode &matNode, const QPointF &pos); diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index 78a610ffefc..e3553ff5a36 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -566,7 +565,7 @@ void Edit3DWidget::onCreateAction(QAction *action) // if added node is a Model, assign it a material if (modelNode.metaInfo().isQtQuick3DModel()) - MaterialUtils::assignMaterialTo3dModel(m_view, modelNode); + Utils3D::assignMaterialTo3dModel(m_view, modelNode); }); } @@ -732,8 +731,7 @@ void Edit3DWidget::showContextMenu(const QPoint &pos, const ModelNode &modelNode void Edit3DWidget::linkActivated([[maybe_unused]] const QString &link) { - if (m_view) - m_view->addQuick3DImport(); + Utils3D::addQuick3DImportAndView3D(m_view); } Edit3DCanvas *Edit3DWidget::canvas() const diff --git a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp index ec9897aeeac..9ea9c9a8feb 100644 --- a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp @@ -7,13 +7,13 @@ #include "assetslibrarywidget.h" #include "formeditorscene.h" #include "formeditorview.h" -#include "materialutils.h" #include "qmldesignerconstants.h" #include #include #include #include #include +#include #include @@ -455,7 +455,7 @@ void DragTool::handleView3dDrop() const QList models = dragNode.modelNode().subModelNodesOfType( model->qtQuick3DModelMetaInfo()); QTC_ASSERT(models.size() == 1, return); - MaterialUtils::assignMaterialTo3dModel(view(), models.at(0)); + Utils3D::assignMaterialTo3dModel(view(), models.at(0)); } } } diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialutils.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialutils.cpp deleted file mode 100644 index 1a1071de347..00000000000 --- a/src/plugins/qmldesigner/components/materialbrowser/materialutils.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 - -#include "materialutils.h" - -#include "abstractview.h" -#include "nodelistproperty.h" -#include "nodemetainfo.h" -#include "qmlobjectnode.h" -#include "variantproperty.h" -#include - -#include - -namespace QmlDesigner { - -// Assigns given material to a 3D model. -// The assigned material is also inserted into material library if not already there. -// If given material is not valid, first existing material from material library is used, -// or if material library is empty, a new material is created. -// This function should be called only from inside a transaction, as it potentially does many -// changes to model. -void MaterialUtils::assignMaterialTo3dModel(AbstractView *view, const ModelNode &modelNode, - const ModelNode &materialNode) -{ - QTC_ASSERT(modelNode.isValid() && modelNode.metaInfo().isQtQuick3DModel(), return); - - ModelNode matLib = Utils3D::materialLibraryNode(view); - - if (!matLib.isValid()) - return; - - ModelNode newMaterialNode; - - if (materialNode.isValid() && materialNode.metaInfo().isQtQuick3DMaterial()) { - newMaterialNode = materialNode; - } else { - const QList materials = matLib.directSubModelNodes(); - if (materials.size() > 0) { - for (const ModelNode &mat : materials) { - if (mat.metaInfo().isQtQuick3DMaterial()) { - newMaterialNode = mat; - break; - } - } - } - - // if no valid material, create a new default material - if (!newMaterialNode.isValid()) { -#ifdef QDS_USE_PROJECTSTORAGE - newMaterialNode = view->createModelNode("PrincipledMaterial"); -#else - NodeMetaInfo metaInfo = view->model()->qtQuick3DPrincipledMaterialMetaInfo(); - newMaterialNode = view->createModelNode("QtQuick3D.PrincipledMaterial", - metaInfo.majorVersion(), - metaInfo.minorVersion()); -#endif - newMaterialNode.ensureIdExists(); - } - } - - QTC_ASSERT(newMaterialNode.isValid(), return); - - VariantProperty matNameProp = newMaterialNode.variantProperty("objectName"); - if (matNameProp.value().isNull()) - matNameProp.setValue("New Material"); - - if (!newMaterialNode.hasParentProperty() - || newMaterialNode.parentProperty() != matLib.defaultNodeListProperty()) { - matLib.defaultNodeListProperty().reparentHere(newMaterialNode); - } - - QmlObjectNode(modelNode).setBindingProperty("materials", newMaterialNode.id()); -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialutils.h b/src/plugins/qmldesigner/components/materialbrowser/materialutils.h deleted file mode 100644 index e273ef74a44..00000000000 --- a/src/plugins/qmldesigner/components/materialbrowser/materialutils.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 -#pragma once - -#include "modelnode.h" - -namespace QmlDesigner { - -class AbstractView; - -class MaterialUtils -{ -public: - MaterialUtils(); - - static void assignMaterialTo3dModel(AbstractView *view, const ModelNode &modelNode, - const ModelNode &materialNode = {}); -}; - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp index 7787e0ef829..4e045375347 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -773,7 +772,7 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in newQmlObjectNode.destroy(); return; } - MaterialUtils::assignMaterialTo3dModel(m_view, targetNode, newModelNode); + Utils3D::assignMaterialTo3dModel(m_view, targetNode, newModelNode); } else { ChooseFromPropertyListDialog *dialog = ChooseFromPropertyListDialog::createIfNeeded( targetNode, newModelNode, Core::ICore::dialogParent()); @@ -814,9 +813,9 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in const QList models = newModelNode.subModelNodesOfType( m_view->model()->qtQuick3DModelMetaInfo()); QTC_ASSERT(models.size() == 1, return); - MaterialUtils::assignMaterialTo3dModel(m_view, models.at(0)); + Utils3D::assignMaterialTo3dModel(m_view, models.at(0)); } else if (newModelNode.metaInfo().isQtQuick3DModel()) { - MaterialUtils::assignMaterialTo3dModel(m_view, newModelNode); + Utils3D::assignMaterialTo3dModel(m_view, newModelNode); } if (!validContainer) { From f473b652bd572c62106e5a430d0da6e6103afa57 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Fri, 28 Feb 2025 17:36:55 +0200 Subject: [PATCH 18/54] QmlDesigner: Hide generated components when searching item library Fixes: QDS-14843 Change-Id: Iddff469e14b1151d1c7ca6e7ee00916bdb5de8dc Reviewed-by: Mahmoud Badri --- .../components/itemlibrary/itemlibrarymodel.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp index e8e065d12fe..8024ccf2816 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp @@ -326,11 +326,10 @@ void ItemLibraryModel::update(Model *model) // create import sections const Imports usedImports = model->usedImports(); QHash importHash; + const QString generatedPrefix = compUtils.generatedComponentTypePrefix(); for (const Import &import : model->imports()) { - if (excludedImports.contains(import.url()) - || import.url().startsWith(compUtils.generatedComponentTypePrefix())) { + if (excludedImports.contains(import.url()) || import.url().startsWith(generatedPrefix)) continue; - } bool addNew = true; QString importUrl = import.url(); @@ -450,6 +449,8 @@ void ItemLibraryModel::update(Model *model) : entry.requiredImport()]; } } else { + if (entry.requiredImport().startsWith(generatedPrefix)) + continue; catName = ItemLibraryImport::unimportedComponentsTitle(); importSection = importHash[catName]; if (!importSection) { From 037b32ef33b1e4d7201126e7091363aee6dd1808 Mon Sep 17 00:00:00 2001 From: Ali Kianian Date: Mon, 17 Feb 2025 18:42:44 +0200 Subject: [PATCH 19/54] QmlDesigner: Add InstanceImageProvider to PropertyEditor Task-number: QDS-14768 Change-Id: I76bb54aa75e7a8a6828e7d7b75a077042301d2d2 Reviewed-by: Mahmoud Badri --- src/plugins/qmldesigner/CMakeLists.txt | 1 + .../materialeditor/materialeditorview.cpp | 5 +- .../propertyeditor/instanceimageprovider.cpp | 150 ++++++++++++++++++ .../propertyeditor/instanceimageprovider.h | 53 +++++++ .../propertyeditorqmlbackend.cpp | 100 +++++------- .../propertyeditor/propertyeditorqmlbackend.h | 7 +- .../propertyeditor/propertyeditorview.cpp | 15 +- .../propertyeditor/propertyeditorview.h | 4 + .../quick2propertyeditorview.cpp | 9 ++ .../propertyeditor/quick2propertyeditorview.h | 8 +- .../instances/nodeinstanceview.cpp | 10 +- .../include/customnotificationpackage.h | 12 +- 12 files changed, 306 insertions(+), 68 deletions(-) create mode 100644 src/plugins/qmldesigner/components/propertyeditor/instanceimageprovider.cpp create mode 100644 src/plugins/qmldesigner/components/propertyeditor/instanceimageprovider.h diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 78f4e908a54..2140f15dfe5 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -412,6 +412,7 @@ extend_qtc_plugin(QmlDesigner gradientpresetdefaultlistmodel.cpp gradientpresetdefaultlistmodel.h gradientpresetitem.cpp gradientpresetitem.h gradientpresetlistmodel.cpp gradientpresetlistmodel.h + instanceimageprovider.cpp instanceimageprovider.h propertyeditorcontextobject.cpp propertyeditorcontextobject.h propertyeditorqmlbackend.cpp propertyeditorqmlbackend.h propertyeditortransaction.cpp propertyeditortransaction.h diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index 328e62dff0f..4d67afd7985 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -905,8 +905,9 @@ void MaterialEditorView::requestPreviewRender() static int requestId = 0; m_previewRequestId = QByteArray(MATERIAL_EDITOR_IMAGE_REQUEST_ID) + QByteArray::number(++requestId); - static_cast(model()->nodeInstanceView()) - ->previewImageDataForGenericNode(m_selectedMaterial, {}, m_previewSize, m_previewRequestId); + + model()->sendCustomNotificationToNodeInstanceView( + NodePreviewImage{m_selectedMaterial, {}, m_previewSize, m_previewRequestId}); } } diff --git a/src/plugins/qmldesigner/components/propertyeditor/instanceimageprovider.cpp b/src/plugins/qmldesigner/components/propertyeditor/instanceimageprovider.cpp new file mode 100644 index 00000000000..eba1d90b0d1 --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/instanceimageprovider.cpp @@ -0,0 +1,150 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "instanceimageprovider.h" + +#include +#include + +static const char INSTANCE_IMAGE_REQUEST_ID[] = "PropertyEditor.InstanceImage"; + +namespace QmlDesigner { + +InstanceImageProvider::InstanceImageProvider() + : QQuickImageProvider(Pixmap) + , m_delayTimer(new QTimer(this)) +{ + m_delayTimer->setInterval(500); + m_delayTimer->setSingleShot(true); + m_delayTimer->callOnTimeout([this] { requestOne(); }); +} + +/*! + * \internal + * \brief If a fresh image for the node is available, it returns it. + * Otherwise, if the requested node matches the received node, it loads a rescaled image of the + * most recent received image. + * But since it's been rescaled, and probably doesn't have a good resolution, + * it requests one more to get a new image. + * \return The most recent image received for the node from NodeInstanceView. + */ +QPixmap InstanceImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) +{ + using namespace Qt::StringLiterals; + static const QPixmap defaultImage = QPixmap::fromImage( + QImage(":/materialeditor/images/defaultmaterialpreview.png")); + + if (id != "preview") + return defaultImage; + + if (!m_requestedNode) + return defaultImage; + + QPixmap result; + if (dataAvailable(m_requestedNode, requestedSize)) { + result = m_receivedImage; + } else { + result = (m_requestedNode == m_receivedNode) ? getScaledImage(requestedSize) : defaultImage; + + // Here we should request one more image since the dataAvailable was false, and it means + // that we've temporarily returned a scaled image. But we need another fresh image with + // the correct size. + + if (hasPendingRequest()) + postponeRequest(requestedSize); + else + requestOne(requestedSize); + } + + if (result.isNull()) + result = defaultImage; + + if (size) + *size = result.size(); + + return result; +} + +bool InstanceImageProvider::feedImage(const ModelNode &node, + const QPixmap &pixmap, + const QByteArray &requestId) +{ + if (!requestId.startsWith(INSTANCE_IMAGE_REQUEST_ID)) + return false; + + if (m_pendingRequest == requestId) + m_pendingRequest.clear(); + + m_receivedImage = pixmap; + m_receivedNode = node; + + return true; +} + +void InstanceImageProvider::setModelNode(const ModelNode &node) +{ + m_requestedNode = node; +} + +bool InstanceImageProvider::hasPendingRequest() const +{ + return !m_pendingRequest.isEmpty(); +} + +void InstanceImageProvider::requestOne() +{ + if (!m_requestedNode) + return; + + static int requestId = 0; + QByteArray previewRequestId = QByteArray(INSTANCE_IMAGE_REQUEST_ID) + + QByteArray::number(++requestId); + m_pendingRequest = previewRequestId; + + m_resetRequest = false; + + m_requestedNode.model()->sendCustomNotificationToNodeInstanceView( + NodePreviewImage{m_requestedNode, {}, m_requestedSize, previewRequestId}); +} + +void InstanceImageProvider::requestOne(QSize size) +{ + prepareRequest(size); + requestOne(); +} + +void InstanceImageProvider::postponeRequest(QSize size) +{ + prepareRequest(size); + QMetaObject::invokeMethod(m_delayTimer, static_cast(&QTimer::start)); +} + +void InstanceImageProvider::prepareRequest(QSize size) +{ + m_requestedSize = size; +} + +/*! + * \internal + * \return true if the node matches the most recent node received from NodeInstanceView, and the + * size is the same as the received one, and data is not invalidated after the image is received. + */ +bool InstanceImageProvider::dataAvailable(const ModelNode &node, QSize size) +{ + return !m_resetRequest && node == m_receivedNode && size == m_receivedImage.size(); +} + +void InstanceImageProvider::invalidate() +{ + m_resetRequest = true; +} + +QPixmap InstanceImageProvider::getScaledImage(QSize size) +{ + if (size == m_receivedImage.size()) + return m_receivedImage; + else + return m_receivedImage.scaled(size, Qt::KeepAspectRatioByExpanding); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/instanceimageprovider.h b/src/plugins/qmldesigner/components/propertyeditor/instanceimageprovider.h new file mode 100644 index 00000000000..c25b0b383fd --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/instanceimageprovider.h @@ -0,0 +1,53 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include +#include + +#include +#include + +QT_FORWARD_DECLARE_CLASS(QTimer) + +namespace QmlDesigner { + +class InstanceImageProvider : public QQuickImageProvider +{ + Q_OBJECT + +public: + explicit InstanceImageProvider(); + + QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override; + + bool feedImage(const ModelNode &node, const QPixmap &pixmap, const QByteArray &requestId); + void setModelNode(const ModelNode &node); + + bool hasPendingRequest() const; + void invalidate(); + +protected: + void requestOne(); + void requestOne(QSize size); + void postponeRequest(QSize size); + void prepareRequest(QSize size); + bool dataAvailable(const ModelNode &node, QSize size); + + QPixmap getScaledImage(QSize size); + +private: + QByteArray m_pendingRequest; + bool m_resetRequest = false; + + ModelNode m_requestedNode; + ModelNode m_receivedNode; + QSize m_requestedSize; + + QPixmap m_receivedImage; + + QTimer *m_delayTimer = nullptr; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp index 24621182e14..110dbf4cf11 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp @@ -3,6 +3,7 @@ #include "propertyeditorqmlbackend.h" +#include "instanceimageprovider.h" #include "propertyeditortransaction.h" #include "propertyeditorutils.h" #include "propertyeditorvalue.h" @@ -31,12 +32,12 @@ #include #include #include -#include #include #include #include #include +#include #include #include #include @@ -321,6 +322,20 @@ void PropertyEditorQmlBackend::handlePropertiesRemovedInModelNodeProxy(const Abs m_backendModelNode.handlePropertiesRemoved(property); } +void PropertyEditorQmlBackend::handleModelNodePreviewPixmapChanged(const ModelNode &node, + const QPixmap &pixmap, + const QByteArray &requestId) +{ + InstanceImageProvider *imageProvider = m_view->instanceImageProvider(); + + if (!imageProvider) + return; + + bool imageFed = imageProvider->feedImage(node, pixmap, requestId); + if (imageFed && !imageProvider->hasPendingRequest()) + refreshPreview(); +} + void PropertyEditorQmlBackend::createPropertyEditorValue(const QmlObjectNode &qmlObjectNode, PropertyNameView name, const QVariant &value, @@ -591,57 +606,14 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q contextObject()->setSelectedNode(qmlObjectNode); contextObject()->setHasQuick3DImport(propertyEditor->model()->hasImport("QtQuick3D")); + m_view->instanceImageProvider()->setModelNode(propertyEditor->firstSelectedModelNode()); + qCInfo(propertyEditorBenchmark) << "final:" << time.elapsed(); } else { qWarning() << "PropertyEditor: invalid node for setup"; } } -void PropertyEditorQmlBackend::initialSetup(const TypeName &typeName, const QUrl &qmlSpecificsFile, PropertyEditorView *propertyEditor) -{ - NodeMetaInfo metaInfo = propertyEditor->model()->metaInfo(typeName); - - for (const auto &property : PropertyEditorUtils::filteredProperties(metaInfo)) { - setupPropertyEditorValue(property.name(), propertyEditor, property.propertyType()); - } - - auto valueObject = qobject_cast(variantToQObject( - m_backendValuesPropertyMap.value(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY))); - if (!valueObject) - valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap); - valueObject->setName(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY); - - valueObject->setValue(typeName); - QObject::connect(valueObject, - &PropertyEditorValue::valueChanged, - &backendValuesPropertyMap(), - &DesignerPropertyMap::valueChanged); - m_backendValuesPropertyMap.insert(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY, - QVariant::fromValue(valueObject)); - - // id - valueObject = qobject_cast(variantToQObject(m_backendValuesPropertyMap.value(QLatin1String("id")))); - if (!valueObject) - valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap); - valueObject->setName("id"); - valueObject->setValue("id"); - QObject::connect(valueObject, &PropertyEditorValue::valueChanged, &backendValuesPropertyMap(), &DesignerPropertyMap::valueChanged); - m_backendValuesPropertyMap.insert(QLatin1String("id"), QVariant::fromValue(valueObject)); - - context()->setContextProperties(QVector{ - {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)}, - {{"modelNodeBackend"}, QVariant::fromValue(&m_backendModelNode)}, - {{"transaction"}, QVariant::fromValue(m_propertyEditorTransaction.get())}}); - - contextObject()->setSpecificsUrl(qmlSpecificsFile); - - contextObject()->setStateName(QStringLiteral("basestate")); - - contextObject()->setIsBaseState(true); - - contextObject()->setSpecificQmlData(QStringLiteral("")); -} - QString PropertyEditorQmlBackend::propertyEditorResourcesPath() { return resourcesPath("propertyEditorQmlSources"); @@ -946,11 +918,14 @@ NodeMetaInfo PropertyEditorQmlBackend::findCommonAncestor(const ModelNode &node) AbstractView *view = node.view(); - if (view->selectedModelNodes().size() > 1) { + const QList &selectedNodes = view->selectedModelNodes(); + if (selectedNodes.size() > 1) { NodeMetaInfo commonClass = node.metaInfo(); - for (const ModelNode ¤tNode : view->selectedModelNodes()) { - if (currentNode.metaInfo().isValid() && !currentNode.metaInfo().isBasedOn(commonClass)) - commonClass = findCommonSuperClass(currentNode.metaInfo(), commonClass); + + for (const ModelNode &selectedNode : selectedNodes) { + const NodeMetaInfo &nodeMetaInfo = selectedNode.metaInfo(); + if (nodeMetaInfo.isValid() && !nodeMetaInfo.isBasedOn(commonClass)) + commonClass = findCommonSuperClass(nodeMetaInfo, commonClass); } return commonClass; } @@ -963,12 +938,22 @@ void PropertyEditorQmlBackend::refreshBackendModel() m_backendModelNode.refresh(); } +void PropertyEditorQmlBackend::refreshPreview() +{ + auto qmlPreview = widget()->rootObject(); + + if (qmlPreview && qmlPreview->metaObject()->indexOfMethod("refreshPreview()") > -1) + QMetaObject::invokeMethod(qmlPreview, "refreshPreview"); +} + void PropertyEditorQmlBackend::setupContextProperties() { - context()->setContextProperty("modelNodeBackend", &m_backendModelNode); - context()->setContextProperties(QVector{ - {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)}, - {{"transaction"}, QVariant::fromValue(m_propertyEditorTransaction.get())}}); + context()->setContextProperties({ + {"modelNodeBackend", QVariant::fromValue(&m_backendModelNode)}, + {"anchorBackend", QVariant::fromValue(&m_backendAnchorBinding)}, + {"transaction", QVariant::fromValue(m_propertyEditorTransaction.get())}, + {"dummyBackendValue", QVariant::fromValue(m_dummyPropertyEditorValue.get())}, + }); } #ifndef QDS_USE_PROJECTSTORAGE @@ -1062,14 +1047,12 @@ void PropertyEditorQmlBackend::setValueforAuxiliaryProperties(const QmlObjectNod #ifndef QDS_USE_PROJECTSTORAGE std::tuple PropertyEditorQmlBackend::getQmlUrlForMetaInfo(const NodeMetaInfo &metaInfo) { - QString className; if (metaInfo.isValid()) { - const NodeMetaInfos hierarchy = metaInfo.selfAndPrototypes(); + const NodeMetaInfos &hierarchy = metaInfo.selfAndPrototypes(); for (const NodeMetaInfo &info : hierarchy) { QUrl fileUrl = fileToUrl(locateQmlFile(info, QString::fromUtf8(qmlFileName(info)))); - if (fileUrl.isValid()) { + if (fileUrl.isValid()) return {fileUrl, info}; - } } } @@ -1126,4 +1109,3 @@ QString PropertyEditorQmlBackend::locateQmlFile(const NodeMetaInfo &info, const #endif // QDS_USE_PROJECTSTORAGE } //QmlDesigner - diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h index 1ba93bdc8bb..cf464282717 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h @@ -18,6 +18,8 @@ #include +QT_FORWARD_DECLARE_CLASS(QQuickImageProvider) + class PropertyEditorValue; namespace QmlDesigner { @@ -37,7 +39,6 @@ public: ~PropertyEditorQmlBackend(); void setup(const QmlObjectNode &fxObjectNode, const QString &stateName, const QUrl &qmlSpecificsFile, PropertyEditorView *propertyEditor); - void initialSetup(const TypeName &typeName, const QUrl &qmlSpecificsFile, PropertyEditorView *propertyEditor); void setValue(const QmlObjectNode &fxObjectNode, PropertyNameView name, const QVariant &value); void setExpression(PropertyNameView propName, const QString &exp); @@ -86,10 +87,14 @@ public: void handleVariantPropertyChangedInModelNodeProxy(const VariantProperty &property); void handleBindingPropertyChangedInModelNodeProxy(const BindingProperty &property); void handlePropertiesRemovedInModelNodeProxy(const AbstractProperty &property); + void handleModelNodePreviewPixmapChanged(const ModelNode &node, + const QPixmap &pixmap, + const QByteArray &requestId); static NodeMetaInfo findCommonAncestor(const ModelNode &node); void refreshBackendModel(); + void refreshPreview(); void setupContextProperties(); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp index 2b4b348a978..d1d08cb5c7f 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp @@ -67,8 +67,7 @@ PropertyEditorView::PropertyEditorView(AsynchronousImageCache &imageCache, , m_timerId(0) , m_stackedWidget(new PropertyEditorWidget()) , m_qmlBackEndForCurrentType(nullptr) - , m_propertyComponentGenerator{QmlDesigner::PropertyEditorQmlBackend::propertyEditorResourcesPath(), - model()} + , m_propertyComponentGenerator{PropertyEditorQmlBackend::propertyEditorResourcesPath(), model()} , m_locked(false) { @@ -602,6 +601,7 @@ void PropertyEditorView::setupQmlBackend() #else const NodeMetaInfo commonAncestor = PropertyEditorQmlBackend::findCommonAncestor(m_selectedNode); + // qmlFileUrl is panel url. and specifics is its metainfo const auto [qmlFileUrl, specificsClassMetaInfo] = PropertyEditorQmlBackend::getQmlUrlForMetaInfo( commonAncestor); @@ -1045,6 +1045,17 @@ void PropertyEditorView::importsChanged(const Imports &addedImports, const Impor m_qmlBackEndForCurrentType->contextObject()->setHasQuick3DImport(true); } +void PropertyEditorView::modelNodePreviewPixmapChanged(const ModelNode &node, + const QPixmap &pixmap, + const QByteArray &requestId) +{ + if (node != m_selectedNode) + return; + + if (m_qmlBackEndForCurrentType) + m_qmlBackEndForCurrentType->handleModelNodePreviewPixmapChanged(node, pixmap, requestId); +} + void PropertyEditorView::highlightTextureProperties(bool highlight) { NodeMetaInfo metaInfo = m_selectedNode.metaInfo(); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h index 7ac545b6a83..a8a6d525342 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h @@ -73,6 +73,10 @@ public: const NodeAbstractProperty &oldPropertyParent, AbstractView::PropertyChangeFlags propertyChange) override; + void modelNodePreviewPixmapChanged(const ModelNode &node, + const QPixmap &pixmap, + const QByteArray &requestId) override; + void importsChanged(const Imports &addedImports, const Imports &removedImports) override; void dragStarted(QMimeData *mimeData) override; diff --git a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp index 9c403300763..c5b4039540f 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp @@ -16,6 +16,7 @@ #include "gradientmodel.h" #include "gradientpresetcustomlistmodel.h" #include "gradientpresetdefaultlistmodel.h" +#include "instanceimageprovider.h" #include "itemfiltermodel.h" #include "listvalidator.h" #include "propertychangesmodel.h" @@ -40,6 +41,14 @@ Quick2PropertyEditorView::Quick2PropertyEditorView(AsynchronousImageCache &image Theme::setupTheme(engine()); engine()->addImageProvider("qmldesigner_thumbnails", new AssetImageProvider(imageCache)); + + m_instanceImageProvider = new InstanceImageProvider(); + engine()->addImageProvider("nodeInstance", m_instanceImageProvider); +} + +InstanceImageProvider *Quick2PropertyEditorView::instanceImageProvider() const +{ + return m_instanceImageProvider; } void Quick2PropertyEditorView::registerQmlTypes() diff --git a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.h b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.h index 3b0872df482..c3f4d3ffbaf 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.h +++ b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.h @@ -5,9 +5,10 @@ #include - namespace QmlDesigner { +class InstanceImageProvider; + class Quick2PropertyEditorView : public QQuickWidget { Q_OBJECT @@ -15,7 +16,12 @@ class Quick2PropertyEditorView : public QQuickWidget public: explicit Quick2PropertyEditorView(class AsynchronousImageCache &imageCache); + InstanceImageProvider *instanceImageProvider() const; + static void registerQmlTypes(); + +private: + InstanceImageProvider *m_instanceImageProvider = nullptr; }; } //QmlDesigner diff --git a/src/plugins/qmldesigner/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/instances/nodeinstanceview.cpp index cf90fbf1ddc..6a16f65d83a 100644 --- a/src/plugins/qmldesigner/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/instances/nodeinstanceview.cpp @@ -712,10 +712,16 @@ void NodeInstanceView::customNotification(const AbstractView *view, void NodeInstanceView::customNotification(const CustomNotificationPackage &package) { - if (auto inputEvent = std::get_if(&package)) + if (auto inputEvent = std::get_if(&package)) { sendInputEvent(inputEvent->event); - else if (auto resize3DCanvas = std::get_if(&package)) + } else if (auto resize3DCanvas = std::get_if(&package)) { edit3DViewResized(resize3DCanvas->size); + } else if (auto preview = std::get_if(&package)) { + previewImageDataForGenericNode(preview->modelNode, + preview->renderNode, + preview->size, + preview->requestId); + } } void NodeInstanceView::nodeSourceChanged(const ModelNode &node, const QString & newNodeSource) diff --git a/src/plugins/qmldesigner/libs/designercore/include/customnotificationpackage.h b/src/plugins/qmldesigner/libs/designercore/include/customnotificationpackage.h index 07782f8014a..c0969d4d621 100644 --- a/src/plugins/qmldesigner/libs/designercore/include/customnotificationpackage.h +++ b/src/plugins/qmldesigner/libs/designercore/include/customnotificationpackage.h @@ -3,6 +3,8 @@ #pragma once +#include "modelnode.h" + #include #include @@ -18,6 +20,14 @@ struct Resize3DCanvas QSize size; }; -using CustomNotificationPackage = std::variant; +struct NodePreviewImage +{ + ModelNode modelNode; + ModelNode renderNode; + QSize size; + QByteArray requestId; +}; + +using CustomNotificationPackage = std::variant; } // namespace QmlDesigner From 7222a14808fbaf9c324db6fda39c4896101ca40d Mon Sep 17 00:00:00 2001 From: Ali Kianian Date: Thu, 20 Feb 2025 11:30:17 +0200 Subject: [PATCH 20/54] QmlDesigner: Add backend for materials in PropertyEditor Task-number: QDS-14784 Change-Id: I531124231545971c5ceb171da8685efeb3eaf0fb Reviewed-by: Mahmoud Badri Reviewed-by: Miikka Heikkinen --- src/plugins/qmldesigner/CMakeLists.txt | 1 + .../propertyeditorqmlbackend.cpp | 47 ++- .../propertyeditor/propertyeditorqmlbackend.h | 5 + .../propertyeditor/propertyeditorview.cpp | 70 ++++- .../propertyeditor/qmlmaterialnodeproxy.cpp | 285 ++++++++++++++++++ .../propertyeditor/qmlmaterialnodeproxy.h | 89 ++++++ .../quick2propertyeditorview.cpp | 2 + 7 files changed, 486 insertions(+), 13 deletions(-) create mode 100644 src/plugins/qmldesigner/components/propertyeditor/qmlmaterialnodeproxy.cpp create mode 100644 src/plugins/qmldesigner/components/propertyeditor/qmlmaterialnodeproxy.h diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 2140f15dfe5..baa53432110 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -422,6 +422,7 @@ extend_qtc_plugin(QmlDesigner propertynamevalidator.cpp propertynamevalidator.h tooltip.cpp tooltip.h qmlanchorbindingproxy.cpp qmlanchorbindingproxy.h + qmlmaterialnodeproxy.cpp qmlmaterialnodeproxy.h qmlmodelnodeproxy.cpp qmlmodelnodeproxy.h quick2propertyeditorview.cpp quick2propertyeditorview.h propertyeditorutils.cpp propertyeditorutils.h diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp index 110dbf4cf11..c9297694fd0 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +91,19 @@ namespace QmlDesigner { using namespace Qt::StringLiterals; +static bool isMaterialAuxiliaryKey(AuxiliaryDataKeyView key) +{ + static constexpr auto previewKeys = Utils::to_array( + materialPreviewEnvDocProperty, + materialPreviewEnvValueDocProperty, + materialPreviewModelDocProperty, + materialPreviewEnvProperty, + materialPreviewEnvValueProperty, + materialPreviewModelProperty); + + return std::ranges::find(previewKeys, key) != std::ranges::end(previewKeys); +} + PropertyEditorQmlBackend::PropertyEditorQmlBackend(PropertyEditorView *propertyEditor, AsynchronousImageCache &imageCache) : m_view(Utils::makeUniqueObjectPtr(imageCache)) @@ -307,19 +321,41 @@ void PropertyEditorQmlBackend::handleInstancePropertyChangedInModelNodeProxy( m_backendModelNode.handleInstancePropertyChanged(modelNode, propertyName); } +void PropertyEditorQmlBackend::handleAuxiliaryDataChanges(const QmlObjectNode &qmlObjectNode, + AuxiliaryDataKeyView key) +{ + if (qmlObjectNode.isRootModelNode() && isMaterialAuxiliaryKey(key)) { + m_backendMaterialNode.handleAuxiliaryPropertyChanges(); + m_view->instanceImageProvider()->invalidate(); + } +} + void PropertyEditorQmlBackend::handleVariantPropertyChangedInModelNodeProxy(const VariantProperty &property) { m_backendModelNode.handleVariantPropertyChanged(property); + updateInstanceImage(); } void PropertyEditorQmlBackend::handleBindingPropertyChangedInModelNodeProxy(const BindingProperty &property) { m_backendModelNode.handleBindingPropertyChanged(property); + updateInstanceImage(); +} + +void PropertyEditorQmlBackend::handleBindingPropertyInModelNodeProxyAboutToChange( + const BindingProperty &property) +{ + if (m_backendMaterialNode.materialNode()) { + ModelNode expressionNode = property.resolveToModelNode(); + if (expressionNode.metaInfo().isQtQuick3DTexture()) + updateInstanceImage(); + } } void PropertyEditorQmlBackend::handlePropertiesRemovedInModelNodeProxy(const AbstractProperty &property) { m_backendModelNode.handlePropertiesRemoved(property); + updateInstanceImage(); } void PropertyEditorQmlBackend::handleModelNodePreviewPixmapChanged(const ModelNode &node, @@ -496,10 +532,15 @@ void QmlDesigner::PropertyEditorQmlBackend::createPropertyEditorValues(const Qml #endif } +void PropertyEditorQmlBackend::updateInstanceImage() +{ + m_view->instanceImageProvider()->invalidate(); + refreshPreview(); +} + void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const QString &stateName, const QUrl &qmlSpecificsFile, PropertyEditorView *propertyEditor) { if (qmlObjectNode.isValid()) { - m_contextObject->setModel(propertyEditor->model()); qCInfo(propertyEditorBenchmark) << Q_FUNC_INFO; @@ -517,6 +558,8 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q m_backendModelNode.setup(qmlObjectNode.modelNode()); context()->setContextProperty("modelNodeBackend", &m_backendModelNode); + m_backendMaterialNode.setup(qmlObjectNode); + // className auto valueObject = qobject_cast(variantToQObject( m_backendValuesPropertyMap.value(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY))); @@ -607,6 +650,7 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q contextObject()->setHasQuick3DImport(propertyEditor->model()->hasImport("QtQuick3D")); m_view->instanceImageProvider()->setModelNode(propertyEditor->firstSelectedModelNode()); + updateInstanceImage(); qCInfo(propertyEditorBenchmark) << "final:" << time.elapsed(); } else { @@ -950,6 +994,7 @@ void PropertyEditorQmlBackend::setupContextProperties() { context()->setContextProperties({ {"modelNodeBackend", QVariant::fromValue(&m_backendModelNode)}, + {"materialNodeBackend", QVariant::fromValue(&m_backendMaterialNode)}, {"anchorBackend", QVariant::fromValue(&m_backendAnchorBinding)}, {"transaction", QVariant::fromValue(m_propertyEditorTransaction.get())}, {"dummyBackendValue", QVariant::fromValue(m_dummyPropertyEditorValue.get())}, diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h index cf464282717..8e735232870 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h @@ -7,6 +7,7 @@ #include "propertyeditorcontextobject.h" #include "propertyeditorvalue.h" #include "qmlanchorbindingproxy.h" +#include "qmlmaterialnodeproxy.h" #include "qmlmodelnodeproxy.h" #include "quick2propertyeditorview.h" @@ -84,8 +85,10 @@ public: void handleInstancePropertyChangedInModelNodeProxy(const ModelNode &modelNode, PropertyNameView propertyName); + void handleAuxiliaryDataChanges(const QmlObjectNode &qmlObjectNode, AuxiliaryDataKeyView key); void handleVariantPropertyChangedInModelNodeProxy(const VariantProperty &property); void handleBindingPropertyChangedInModelNodeProxy(const BindingProperty &property); + void handleBindingPropertyInModelNodeProxyAboutToChange(const BindingProperty &property); void handlePropertiesRemovedInModelNodeProxy(const AbstractProperty &property); void handleModelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap, @@ -95,6 +98,7 @@ public: void refreshBackendModel(); void refreshPreview(); + void updateInstanceImage(); void setupContextProperties(); @@ -124,6 +128,7 @@ private: Utils::UniqueObjectPtr m_view = nullptr; QmlAnchorBindingProxy m_backendAnchorBinding; + QmlMaterialNodeProxy m_backendMaterialNode; QmlModelNodeProxy m_backendModelNode; std::unique_ptr m_propertyEditorTransaction; std::unique_ptr m_dummyPropertyEditorValue; diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp index d1d08cb5c7f..3eb0c0c8d4e 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp @@ -755,6 +755,7 @@ void PropertyEditorView::propertiesRemoved(const QList &proper QTC_ASSERT(m_qmlBackEndForCurrentType, return ); + bool changed = false; for (const AbstractProperty &property : propertyList) { m_qmlBackEndForCurrentType->handlePropertiesRemovedInModelNodeProxy(property); @@ -765,6 +766,7 @@ void PropertyEditorView::propertiesRemoved(const QList &proper if (node == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == node) { m_locked = true; + changed = true; const PropertyName propertyName = property.name().toByteArray(); PropertyName convertedpropertyName = propertyName; @@ -814,6 +816,8 @@ void PropertyEditorView::propertiesRemoved(const QList &proper m_qmlBackEndForCurrentType->backendAnchorBinding().invalidate(m_selectedNode); } } + if (changed) + m_qmlBackEndForCurrentType->updateInstanceImage(); } void PropertyEditorView::variantPropertiesChanged(const QList& propertyList, PropertyChangeFlags /*propertyChange*/) @@ -823,6 +827,11 @@ void PropertyEditorView::variantPropertiesChanged(const QList& QTC_ASSERT(m_qmlBackEndForCurrentType, return ); + bool changed = false; + + bool selectedNodeIsMaterial = m_selectedNode.metaInfo().isQtQuick3DMaterial(); + bool selectedNodeHasBindingProperties = !m_selectedNode.bindingProperties().isEmpty(); + for (const VariantProperty &property : propertyList) { m_qmlBackEndForCurrentType->handleVariantPropertyChangedInModelNodeProxy(property); @@ -841,17 +850,38 @@ void PropertyEditorView::variantPropertiesChanged(const QList& setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name())); else setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name())); + changed = true; + } + + if (!changed) { + // Check if property changes affects the selected node preview + + if (selectedNodeIsMaterial && selectedNodeHasBindingProperties + && node.metaInfo().isQtQuick3DTexture()) { + changed = true; + } } } + + if (changed) + m_qmlBackEndForCurrentType->updateInstanceImage(); } -void PropertyEditorView::bindingPropertiesChanged(const QList &propertyList, PropertyChangeFlags /*propertyChange*/) +void PropertyEditorView::bindingPropertiesChanged(const QList &propertyList, + PropertyChangeFlags /*propertyChange*/) { - if (locked() || noValidSelection()) + if (noValidSelection()) return; - QTC_ASSERT(m_qmlBackEndForCurrentType, return ); + QTC_ASSERT(m_qmlBackEndForCurrentType, return); + if (locked()) { + for (const BindingProperty &property : propertyList) + m_qmlBackEndForCurrentType->handleBindingPropertyInModelNodeProxyAboutToChange(property); + return; + } + + bool changed = false; for (const BindingProperty &property : propertyList) { m_qmlBackEndForCurrentType->handleBindingPropertyChangedInModelNodeProxy(property); @@ -868,8 +898,12 @@ void PropertyEditorView::bindingPropertiesChanged(const QList & QString exp = QmlObjectNode(m_selectedNode).bindingProperty(property.name()).expression(); m_qmlBackEndForCurrentType->setExpression(property.name(), exp); m_locked = false; + changed = true; } } + + if (changed) + m_qmlBackEndForCurrentType->updateInstanceImage(); } void PropertyEditorView::auxiliaryDataChanged(const ModelNode &node, @@ -879,10 +913,21 @@ void PropertyEditorView::auxiliaryDataChanged(const ModelNode &node, if (noValidSelection()) return; + bool saved = false; + + QScopeGuard rootGuard([this, node, key, &saved] { + if (node.isRootNode()) { + if (!saved) + m_qmlBackEndForCurrentType->setValueforAuxiliaryProperties(m_selectedNode, key); + m_qmlBackEndForCurrentType->handleAuxiliaryDataChanges(node, key); + } + }); + if (!node.isSelected()) return; m_qmlBackEndForCurrentType->setValueforAuxiliaryProperties(m_selectedNode, key); + saved = true; if (key == insightEnabledProperty) m_qmlBackEndForCurrentType->contextObject()->setInsightEnabled(data.toBool()); @@ -979,7 +1024,7 @@ void PropertyEditorView::instancePropertyChanged(const QList; for (const ModelNodePropertyPair &propertyPair : propertyList) { const ModelNode modelNode = propertyPair.first; @@ -989,20 +1034,21 @@ void PropertyEditorView::instancePropertyChanged(const QListhandleInstancePropertyChangedInModelNodeProxy(modelNode, propertyName); - if (qmlObjectNode.isValid() && m_qmlBackEndForCurrentType && modelNode == m_selectedNode + if (qmlObjectNode.isValid() && modelNode == m_selectedNode && qmlObjectNode.currentState().isValid()) { const AbstractProperty property = modelNode.property(propertyName); - if (modelNode == m_selectedNode || qmlObjectNode.propertyChangeForCurrentState() == qmlObjectNode) { - if ( !modelNode.hasProperty(propertyName) || modelNode.property(property.name()).isBindingProperty() ) - setValue(modelNode, property.name(), qmlObjectNode.instanceValue(property.name())); - else - setValue(modelNode, property.name(), qmlObjectNode.modelValue(property.name())); - } + if (!modelNode.hasProperty(propertyName) || property.isBindingProperty()) + setValue(modelNode, property.name(), qmlObjectNode.instanceValue(property.name())); + else + setValue(modelNode, property.name(), qmlObjectNode.modelValue(property.name())); + changed = true; } } - m_locked = false; + if (changed) + m_qmlBackEndForCurrentType->updateInstanceImage(); + m_locked = false; } void PropertyEditorView::rootNodeTypeChanged(const QString &/*type*/, int /*majorVersion*/, int /*minorVersion*/) diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmlmaterialnodeproxy.cpp b/src/plugins/qmldesigner/components/propertyeditor/qmlmaterialnodeproxy.cpp new file mode 100644 index 00000000000..6cac8b471c7 --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/qmlmaterialnodeproxy.cpp @@ -0,0 +1,285 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qmlmaterialnodeproxy.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace QmlDesigner { + +using namespace Qt::StringLiterals; + +static void renameMaterial(ModelNode &material, const QString &newName) +{ + QTC_ASSERT(material.isValid(), return); + QmlObjectNode(material).setNameAndId(newName, "material"); +} + +QmlMaterialNodeProxy::QmlMaterialNodeProxy() + : QObject() + , m_previewUpdateTimer(this) +{ + m_previewUpdateTimer.setInterval(200); + m_previewUpdateTimer.setSingleShot(true); + m_previewUpdateTimer.callOnTimeout( + std::bind_front(&QmlMaterialNodeProxy::updatePreviewModel, this)); +} + +QmlMaterialNodeProxy::~QmlMaterialNodeProxy() = default; + +void QmlMaterialNodeProxy::setup(const QmlObjectNode &objectNode) +{ + const QmlObjectNode material = objectNode.metaInfo().isQtQuick3DMaterial() ? objectNode + : QmlObjectNode{}; + setMaterialNode(material); + updatePossibleTypes(); + updatePreviewModel(); +} + +ModelNode QmlMaterialNodeProxy::materialNode() const +{ + return m_materialNode; +} + +void QmlMaterialNodeProxy::setPossibleTypes(const QStringList &types) +{ + if (types == m_possibleTypes) + return; + + m_possibleTypes = types; + emit possibleTypesChanged(); + + updatePossibleTypeIndex(); +} + +void QmlMaterialNodeProxy::updatePossibleTypes() +{ + static const QStringList basicTypes{ + "CustomMaterial", + "DefaultMaterial", + "PrincipledMaterial", + "SpecularGlossyMaterial", + }; + + const QString &matType = materialNode().simplifiedTypeName(); + setPossibleTypes(basicTypes.contains(matType) ? basicTypes : QStringList{matType}); + setCurrentType(matType); +} + +void QmlMaterialNodeProxy::setCurrentType(const QString &type) +{ + m_currentType = type.split('.').last(); + updatePossibleTypeIndex(); +} + +void QmlMaterialNodeProxy::toolBarAction(int action) +{ + QTC_ASSERT(hasQuick3DImport(), return); + + switch (ToolBarAction(action)) { + case ToolBarAction::ApplyToSelected: { + Utils3D::applyMaterialToModels(materialView(), + materialNode(), + Utils3D::getSelectedModels(materialView())); + break; + } + + case ToolBarAction::ApplyToSelectedAdd: { + Utils3D::applyMaterialToModels(materialView(), + materialNode(), + Utils3D::getSelectedModels(materialView()), + true); + break; + } + + case ToolBarAction::AddNewMaterial: { + if (!materialNode()) + break; + + ModelNode newMatNode; + AbstractView *view = materialView(); + view->executeInTransaction(__FUNCTION__, [&] { + ModelNode matLib = Utils3D::materialLibraryNode(view); + if (!matLib.isValid()) + return; +#ifdef QDS_USE_PROJECTSTORAGE + ModelNode newMatNode = view->createModelNode("PrincipledMaterial"); +#else + NodeMetaInfo metaInfo = materialView()->model()->qtQuick3DPrincipledMaterialMetaInfo(); + newMatNode = materialView()->createModelNode("QtQuick3D.PrincipledMaterial", + metaInfo.majorVersion(), + metaInfo.minorVersion()); +#endif + renameMaterial(newMatNode, "New Material"); + Utils3D::materialLibraryNode(view).defaultNodeListProperty().reparentHere(newMatNode); + }); + QTimer::singleShot(0, this, [newMatNode]() { + newMatNode.model()->setSelectedModelNodes({newMatNode}); + }); + break; + } + + case ToolBarAction::DeleteCurrentMaterial: { + if (materialNode().isValid()) + materialView()->executeInTransaction(__FUNCTION__, [&] { materialNode().destroy(); }); + break; + } + + case ToolBarAction::OpenMaterialBrowser: { + QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialBrowser", true); + break; + } + } +} + +void QmlMaterialNodeProxy::setPreviewEnv(const QString &envAndValue) +{ + if (envAndValue.isEmpty()) + return; + + if (!hasQuick3DImport()) + return; + + AbstractView *view = m_materialNode.modelNode().view(); + ModelNode rootModelNode = view->rootModelNode(); + + QStringList parts = envAndValue.split('='); + QString env = parts[0]; + QString value; + if (parts.size() > 1) + value = parts[1]; + + if (env == "Color" && value.isEmpty()) + value = rootModelNode.auxiliaryDataWithDefault(materialPreviewColorDocProperty).toString(); + + auto renderPreviews = [rootModelNode](const QString &auxEnv, const QString &auxValue) { + if (!rootModelNode) + return; + rootModelNode.setAuxiliaryData(materialPreviewEnvDocProperty, auxEnv); + rootModelNode.setAuxiliaryData(materialPreviewEnvProperty, auxEnv); + rootModelNode.setAuxiliaryData(materialPreviewEnvValueDocProperty, auxValue); + rootModelNode.setAuxiliaryData(materialPreviewEnvValueProperty, auxValue); + + if (auxEnv == "Color" && !auxValue.isEmpty()) + rootModelNode.setAuxiliaryData(materialPreviewColorDocProperty, auxEnv); + + rootModelNode.view()->emitCustomNotification("refresh_material_browser", {}); + }; + + QMetaObject::invokeMethod(view, renderPreviews, env, value); +} + +void QmlMaterialNodeProxy::setPreviewModel(const QString &modelStr) +{ + if (modelStr.isEmpty()) + return; + + if (!hasQuick3DImport()) + return; + + AbstractView *view = m_materialNode.modelNode().view(); + ModelNode rootModelNode = view->rootModelNode(); + + auto renderPreviews = [rootModelNode](const QString &modelStr) { + if (!rootModelNode) + return; + + rootModelNode.setAuxiliaryData(materialPreviewModelDocProperty, modelStr); + rootModelNode.setAuxiliaryData(materialPreviewModelProperty, modelStr); + + rootModelNode.view()->emitCustomNotification("refresh_material_browser", {}); + }; + + QMetaObject::invokeMethod(view, renderPreviews, modelStr); +} + +void QmlMaterialNodeProxy::handleAuxiliaryPropertyChanges() +{ + if (!hasQuick3DImport()) + return; + + m_previewUpdateTimer.start(); +} + +void QmlMaterialNodeProxy::registerDeclarativeType() +{ + qmlRegisterType("HelperWidgets", 2, 0, "QmlMaterialNodeProxy"); +} + +void QmlMaterialNodeProxy::updatePossibleTypeIndex() +{ + int newIndex = -1; + if (!m_currentType.isEmpty()) + newIndex = m_possibleTypes.indexOf(m_currentType); + + // Emit valid possible type index change even if the index doesn't change, as currentIndex on + // QML side will change to default internally if model is updated + if (m_possibleTypeIndex != -1 || m_possibleTypeIndex != newIndex) { + m_possibleTypeIndex = newIndex; + emit possibleTypeIndexChanged(); + } +} + +void QmlMaterialNodeProxy::updatePreviewModel() +{ + if (!hasQuick3DImport()) + return; + + AbstractView *view = m_materialNode.modelNode().view(); + ModelNode rootModelNode = view->rootModelNode(); + + // Read auxiliary preview Data + QString env = rootModelNode.auxiliaryDataWithDefault(materialPreviewEnvDocProperty).toString(); + QString envValue = rootModelNode.auxiliaryDataWithDefault(materialPreviewEnvValueDocProperty) + .toString(); + QString modelStr = rootModelNode.auxiliaryDataWithDefault(materialPreviewModelDocProperty).toString(); + + if (!envValue.isEmpty() && env != "Basic") { + env += '='; + env += envValue; + } + if (env.isEmpty()) + env = "SkyBox=preview_studio"; + + if (modelStr.isEmpty()) + modelStr = "#Sphere"; + + // Set node proxy properties + if (m_previewModel != modelStr) { + m_previewModel = modelStr; + emit previewModelChanged(); + } + + if (m_previewEnv != env) { + m_previewEnv = env; + emit previewEnvChanged(); + } +} + +void QmlMaterialNodeProxy::setMaterialNode(const QmlObjectNode &material) +{ + if (material == m_materialNode) + return; + + m_materialNode = material; + emit materialNodeChanged(); +} + +bool QmlMaterialNodeProxy::hasQuick3DImport() const +{ + return materialNode().isValid() && materialNode().model()->hasImport("QtQuick3D"_L1); +} + +AbstractView *QmlMaterialNodeProxy::materialView() const +{ + return materialNode().view(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmlmaterialnodeproxy.h b/src/plugins/qmldesigner/components/propertyeditor/qmlmaterialnodeproxy.h new file mode 100644 index 00000000000..58bf8935970 --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/qmlmaterialnodeproxy.h @@ -0,0 +1,89 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +#include +#include + +namespace QmlDesigner { + +class ModelNode; + +class QmlMaterialNodeProxy : public QObject +{ + Q_OBJECT + + Q_PROPERTY(ModelNode materialNode READ materialNode NOTIFY materialNodeChanged) + Q_PROPERTY(QStringList possibleTypes READ possibleTypes NOTIFY possibleTypesChanged) + Q_PROPERTY(int possibleTypeIndex READ possibleTypeIndex NOTIFY possibleTypeIndexChanged) + Q_PROPERTY(QString previewEnv MEMBER m_previewEnv WRITE setPreviewEnv NOTIFY previewEnvChanged) + Q_PROPERTY(QString previewModel MEMBER m_previewModel WRITE setPreviewModel NOTIFY previewModelChanged) + +public: + enum class ToolBarAction { + ApplyToSelected = 0, + ApplyToSelectedAdd, + AddNewMaterial, + DeleteCurrentMaterial, + OpenMaterialBrowser + }; + Q_ENUM(ToolBarAction) + + explicit QmlMaterialNodeProxy(); + ~QmlMaterialNodeProxy() override; + + void setup(const QmlObjectNode &objectNode); + + QStringList possibleTypes() const { return m_possibleTypes; } + + ModelNode materialNode() const; + + int possibleTypeIndex() const { return m_possibleTypeIndex; } + + void setCurrentType(const QString &type); + + Q_INVOKABLE void toolBarAction(int action); + + void setPreviewEnv(const QString &envAndValue); + void setPreviewModel(const QString &modelStr); + + void handleAuxiliaryPropertyChanges(); + + static void registerDeclarativeType(); + +signals: + void possibleTypesChanged(); + void possibleTypeIndexChanged(); + void materialNodeChanged(); + void previewEnvChanged(); + void previewModelChanged(); + +private: // Methods + void setPossibleTypes(const QStringList &types); + void updatePossibleTypes(); + void updatePossibleTypeIndex(); + void updatePreviewModel(); + void setMaterialNode(const QmlObjectNode &material); + + bool hasQuick3DImport() const; + + AbstractView *materialView() const; + +private: + bool m_has3DModelSelection = false; + + QmlObjectNode m_materialNode; + + QStringList m_possibleTypes; + int m_possibleTypeIndex = -1; + QString m_currentType; + + QString m_previewEnv; + QString m_previewModel; + QTimer m_previewUpdateTimer; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp index c5b4039540f..21f9dd47433 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp @@ -26,6 +26,7 @@ #include "propertymodel.h" #include "propertynamevalidator.h" #include "qmlanchorbindingproxy.h" +#include "qmlmaterialnodeproxy.h" #include "richtexteditor/richtexteditorproxy.h" #include "selectiondynamicpropertiesproxymodel.h" #include "theme.h" @@ -65,6 +66,7 @@ void Quick2PropertyEditorView::registerQmlTypes() ListValidator::registerDeclarativeType(); ColorPaletteBackend::registerDeclarativeType(); QmlAnchorBindingProxy::registerDeclarativeType(); + QmlMaterialNodeProxy::registerDeclarativeType(); BindingEditor::registerDeclarativeType(); ActionEditor::registerDeclarativeType(); AnnotationEditor::registerDeclarativeType(); From 0e32bb4beaaac456b3e2d988d89971e7a53b6418 Mon Sep 17 00:00:00 2001 From: Ali Kianian Date: Fri, 21 Feb 2025 18:27:00 +0200 Subject: [PATCH 21/54] QmlDesigner: Move material editor qml side as a property editor pane Task-number: QDS-14624 Change-Id: Ibf277846bf99370cecb8ec3af28117872eaaef21 Reviewed-by: Miikka Heikkinen Reviewed-by: Mahmoud Badri --- .../QtQuick3D/Material/ColorEditorPopup.qml | 62 ++++ .../QtQuick3D/Material/Preview.qml | 266 ++++++++++++++++++ .../QtQuick3D/Material/Toolbar.qml | 50 ++++ .../QtQuick3D/Material/TopSection.qml | 93 ++++++ .../QtQuick3D/MaterialPane.qml | 133 +++++++++ 5 files changed, 604 insertions(+) create mode 100644 share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/ColorEditorPopup.qml create mode 100644 share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/Preview.qml create mode 100644 share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/Toolbar.qml create mode 100644 share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/TopSection.qml create mode 100644 share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/MaterialPane.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/ColorEditorPopup.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/ColorEditorPopup.qml new file mode 100644 index 00000000000..6ea3428a71b --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/ColorEditorPopup.qml @@ -0,0 +1,62 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import StudioControls as StudioControls + +StudioControls.PopupDialog { + id: colorPopup + + property QtObject loaderItem: loader.item + property color originalColor + required property color currentColor + + signal activateColor(color : color) + + width: 260 + + onOriginalColorChanged: loader.updateOriginalColor() + onClosing: loader.active = false + + function open(showItem) { + loader.ensureActive() + colorPopup.show(showItem) + + loader.updateOriginalColor() + } + + Loader { + id: loader + + function ensureActive() { + if (!loader.active) + loader.active = true + } + + function updateOriginalColor() { + if (loader.status === Loader.Ready) + loader.item.originalColor = colorPopup.originalColor + } + + sourceComponent: StudioControls.ColorEditorPopup { + width: colorPopup.contentWidth + visible: colorPopup.visible + + onActivateColor: (color) => { + colorPopup.activateColor(color) + } + } + + Binding { + target: loader.item + property: "color" + value: colorPopup.currentColor + when: loader.status === Loader.Ready + } + + onLoaded: { + loader.updateOriginalColor() + colorPopup.titleBar = loader.item.titleBarContent + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/Preview.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/Preview.qml new file mode 100644 index 00000000000..8dd04372b06 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/Preview.qml @@ -0,0 +1,266 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import HelperWidgets 2.0 as HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +Rectangle { + id: root + + property HelperWidgets.QmlMaterialNodeProxy backend: materialNodeBackend + property alias pinned: pinButton.checked + property alias showPinButton: pinButton.visible + + property StudioTheme.ControlStyle buttonStyle: StudioTheme.ViewBarButtonStyle { + // This is how you can override stuff from the control styles + baseIconFontSize: StudioTheme.Values.bigIconFontSize + } + + Connections { + target: HelperWidgets.Controller + + function onCloseContextMenu() { + root.closeContextMenu() + } + } + + implicitHeight: image.height + + clip: true + color: "#000000" + + // Called from C++ to close context menu on focus out + function closeContextMenu() + { + modelMenu.close() + envMenu.close() + } + + function refreshPreview() + { + image.source = "" + image.source = "image://nodeInstance/preview" + } + + + Connections { + target: root.backend + + function onPreviewEnvChanged() { + envMenu.updateEnvParams(backend.previewEnv) + root.refreshPreview() + } + + function onPreviewModelChanged() { + root.refreshPreview() + } + } + + Image { + id: image + + anchors.fill: parent + fillMode: Image.PreserveAspectFit + + source: "image://nodeInstance/preview" + cache: false + smooth: true + + sourceSize.width: image.width + sourceSize.height: image.height + + Rectangle { + id: toolbarRect + + radius: 10 + color: StudioTheme.Values.themeToolbarBackground + width: optionsToolbar.width + 2 * toolbarRect.radius + height: optionsToolbar.height + toolbarRect.radius + anchors.left: parent.left + anchors.leftMargin: -toolbarRect.radius + anchors.verticalCenter: parent.verticalCenter + + Column { + id: optionsToolbar + + spacing: 5 + anchors.centerIn: parent + anchors.horizontalCenterOffset: optionsToolbar.spacing + + HelperWidgets.AbstractButton { + id: pinButton + + style: root.buttonStyle + buttonIcon: pinButton.checked ? StudioTheme.Constants.pin : StudioTheme.Constants.unpin + checkable: true + } + + HelperWidgets.AbstractButton { + id: previewEnvMenuButton + + style: root.buttonStyle + buttonIcon: StudioTheme.Constants.textures_medium + tooltip: qsTr("Select preview environment.") + onClicked: envMenu.popup() + } + + HelperWidgets.AbstractButton { + id: previewModelMenuButton + + style: root.buttonStyle + buttonIcon: StudioTheme.Constants.cube_medium + tooltip: qsTr("Select preview model.") + onClicked: modelMenu.popup() + } + } + } + } + + StudioControls.Menu { + id: modelMenu + + closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside + + ListModel { + id: modelMenuModel + ListElement { + modelName: qsTr("Cone") + modelStr: "#Cone" + } + ListElement { + modelName: qsTr("Cube") + modelStr: "#Cube" + } + ListElement { + modelName: qsTr("Cylinder") + modelStr: "#Cylinder" + } + ListElement { + modelName: qsTr("Sphere") + modelStr: "#Sphere" + } + } + + Repeater { + model: modelMenuModel + StudioControls.MenuItemWithIcon { + text: modelName + onClicked: root.backend.previewModel = modelStr + checkable: true + checked: root.backend.previewModel === modelStr + } + } + } + + StudioControls.Menu { + id: envMenu + + property string previewEnvName + property string previewEnvValue + + signal envParametersChanged() + + closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside + + Component.onCompleted: envMenu.updateEnvParams(root.backend.previewEnv) + + function updateEnvParams(str: string) { + let eqFound = str.lastIndexOf("=") + let newEnvName = (eqFound > 0) ? str.substr(0, eqFound) : str + let newEnvValue = (eqFound > 0) ? str.substr(eqFound + 1, str.length - eqFound) : "" + + if (envMenu.previewEnvName !== newEnvName + || envMenu.previewEnvValue !== newEnvValue) { + envMenu.previewEnvName = newEnvName + envMenu.previewEnvValue = newEnvValue + envMenu.envParametersChanged() + } + } + + EnvMenuItem { + envName: qsTr("Basic") + envStr: "Basic" + } + + EnvMenuItem { + id: colorItem + + property color color + property bool colorIsValid: false + + envName: qsTr("Color") + envStr: "Color" + checked: false + + Component.onCompleted: update() + onColorIsValidChanged: updatePopupOriginalColor() + + onClicked: { + colorItem.updatePopupOriginalColor() + colorPopup.open(colorItem) + } + + onColorChanged: { + colorItem.envStr = colorItem.checked + ? "Color=" + color.toString() + : "Color" + colorItem.commit() + } + + function updatePopupOriginalColor() { + if (colorItem.colorIsValid) + colorPopup.originalColor = colorItem.color + } + + function update() { + colorItem.checked = envMenu.previewEnvName === "Color" + if (colorItem.checked && envMenu.previewEnvValue) { + colorItem.color = envMenu.previewEnvValue + colorItem.colorIsValid = true + } else { + colorItem.colorIsValid = false + } + } + + Connections { + target: envMenu + function onEnvParametersChanged() { + colorItem.update(); + } + } + } + + EnvMenuItem { + envName: qsTr("Studio") + envStr: "SkyBox=preview_studio" + } + + EnvMenuItem { + envName: qsTr("Landscape") + envStr: "SkyBox=preview_landscape" + } + } + + ColorEditorPopup { + id: colorPopup + + currentColor: colorItem.color + onActivateColor: (color) => colorItem.color = color + } + + component EnvMenuItem: StudioControls.MenuItemWithIcon { + required property string envName + property string envStr + + function commit() { + root.backend.previewEnv = envStr + } + + text: envName + onClicked: commit() + checkable: false + checked: root.backend.previewEnv === envStr + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/Toolbar.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/Toolbar.qml new file mode 100644 index 00000000000..b3e5a7e147c --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/Toolbar.qml @@ -0,0 +1,50 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import HelperWidgets 2.0 +import HelperWidgets 2.0 as HelperWidgets +import StudioTheme as StudioTheme +Rectangle { + id: root + + property HelperWidgets.QmlMaterialNodeProxy backend: materialNodeBackend + + color: StudioTheme.Values.themeToolbarBackground + height: StudioTheme.Values.toolbarHeight + + Row { + id: row + spacing: StudioTheme.Values.toolbarSpacing + anchors.verticalCenter: parent.verticalCenter + leftPadding: 6 + + HelperWidgets.AbstractButton { + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.apply_medium + tooltip: qsTr("Apply material to selected model.") + onClicked: root.backend.toolBarAction(QmlMaterialNodeProxy.ApplyToSelected) + } + + HelperWidgets.AbstractButton { + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.create_medium + tooltip: qsTr("Create new material.") + onClicked: backend.toolBarAction(QmlMaterialNodeProxy.AddNewMaterial) + } + + HelperWidgets.AbstractButton { + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.delete_medium + tooltip: qsTr("Delete current material.") + onClicked: backend.toolBarAction(QmlMaterialNodeProxy.DeleteCurrentMaterial) + } + + HelperWidgets.AbstractButton { + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.materialBrowser_medium + tooltip: qsTr("Open material browser.") + onClicked: backend.toolBarAction(QmlMaterialNodeProxy.OpenMaterialBrowser) + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/TopSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/TopSection.qml new file mode 100644 index 00000000000..b52befa0a0f --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Material/TopSection.qml @@ -0,0 +1,93 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import HelperWidgets 2.0 as HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +StudioControls.SplitView { + id: root + + property HelperWidgets.QmlMaterialNodeProxy backend: materialNodeBackend + property alias showImage: previewLoader.active + property Component previewComponent: null + + width: parent.width + implicitHeight: showImage ? previewLoader.implicitHeight + nameSection.implicitHeight : nameSection.implicitHeight + + orientation: Qt.Vertical + + Loader { + id: previewLoader + + SplitView.fillWidth: true + SplitView.minimumWidth: 152 + SplitView.preferredHeight: previewLoader.visible ? Math.min(root.width * 0.75, 400) : 0 + SplitView.minimumHeight: previewLoader.visible ? 150 : 0 + SplitView.maximumHeight: previewLoader.visible ? 600 : 0 + + visible: previewLoader.active && previewLoader.item + + sourceComponent: root.previewComponent + } + + HelperWidgets.Section { + id: nameSection + + // Section with hidden header is used so properties are aligned with the other sections' properties + hideHeader: true + SplitView.fillWidth: true + SplitView.preferredHeight: implicitHeight + SplitView.maximumHeight: implicitHeight + bottomPadding: StudioTheme.Values.sectionPadding * 2 + collapsible: false + + HelperWidgets.SectionLayout { + HelperWidgets.PropertyLabel { text: qsTr("Name") } + + HelperWidgets.SecondColumnLayout { + HelperWidgets.Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth } + + HelperWidgets.LineEdit { + id: matName + + implicitWidth: StudioTheme.Values.singleControlColumnWidth + width: StudioTheme.Values.singleControlColumnWidth + placeholderText: qsTr("Material name") + showTranslateCheckBox: false + showExtendedFunctionButton: false + + Timer { + running: true + interval: 0 + onTriggered: matName.backendValue = backendValues.objectName + // backendValues.objectName is not available yet without the Timer + } + + // allow only alphanumeric characters, underscores, no space at start, and 1 space between words + validator: RegularExpressionValidator { regularExpression: /^(\w+\s)*\w+$/ } + } + + HelperWidgets.ExpandingSpacer {} + } + + HelperWidgets.PropertyLabel { text: qsTr("Type") } + + HelperWidgets.SecondColumnLayout { + HelperWidgets.Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth } + + HelperWidgets.ComboBox { + currentIndex: backend.possibleTypeIndex + model: backend.possibleTypes + showExtendedFunctionButton: false + implicitWidth: StudioTheme.Values.singleControlColumnWidth + enabled: backend.possibleTypes.length > 1 + + onActivated: changeTypeName(currentValue) + } + } + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/MaterialPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/MaterialPane.qml new file mode 100644 index 00000000000..13e41c8506b --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/MaterialPane.qml @@ -0,0 +1,133 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtCore +import QtQuick +import QtQuick.Controls +import HelperWidgets 2.0 +import StudioControls 1.0 as StudioControls +import "Material" as Material + +Item { + id: root + + width: 420 + height: 420 + + // invoked from C++ to refresh material preview image + signal refreshPreview() + + // Called from C++ to close context menu on focus out + function closeContextMenu() + { + Controller.closeContextMenu() + } + + Material.Toolbar { + id: toolbar + + width: parent.width + } + + Settings { + id: settings + + property var topSection + property bool dockMode + } + + StudioControls.SplitView { + id: splitView + + readonly property bool isHorizontal: splitView.orientation == Qt.Horizontal + + anchors.top: toolbar.bottom + anchors.bottom: parent.bottom + width: parent.width + orientation: splitView.width > 1000 ? Qt.Horizontal : Qt.Vertical + clip: true + + Loader { + id: leftSideView + + SplitView.fillWidth: leftSideView.visible + SplitView.fillHeight: leftSideView.visible + SplitView.minimumWidth: leftSideView.visible ? 300 : 0 + SplitView.minimumHeight: leftSideView.visible ? 300 : 0 + + active: splitView.isHorizontal + visible: leftSideView.active && leftSideView.item + + sourceComponent: PreviewComponent {} + } + + PropertyEditorPane { + id: itemPane + + clip: true + SplitView.fillWidth: !leftSideView.visible + SplitView.fillHeight: true + SplitView.minimumWidth: leftSideView.visible ? 400 : 0 + SplitView.maximumWidth: leftSideView.visible ? 800 : -1 + + headerDocked: !leftSideView.visible && settings.dockMode + + headerComponent: Material.TopSection { + id: topSection + + Component.onCompleted: topSection.restoreState(settings.topSection) + Component.onDestruction: settings.topSection = topSection.saveState() + previewComponent: PreviewComponent {} + showImage: !splitView.isHorizontal + } + + DynamicPropertiesSection { + propertiesModel: SelectionDynamicPropertiesModel {} + visible: !hasMultiSelection + } + + Loader { + id: specificsTwo + + property string theSource: specificQmlData + + width: itemPane.width + visible: specificsTwo.theSource !== "" + sourceComponent: specificQmlComponent + + onTheSourceChanged: { + specificsTwo.active = false + specificsTwo.active = true + } + } + + Item { // spacer + width: 1 + height: 10 + visible: specificsTwo.visible + } + + Loader { + id: specificsOne + width: itemPane.width + source: specificsUrl + } + } + } + + component PreviewComponent : Material.Preview { + id: previewItem + + pinned: settings.dockMode + showPinButton: !leftSideView.visible + onPinnedChanged: settings.dockMode = previewItem.pinned + + Connections { + target: root + + function onRefreshPreview() { + previewItem.refreshPreview() + } + } + } +} From 18780dcbbdb468568895164573d98df1742e2061 Mon Sep 17 00:00:00 2001 From: Ali Kianian Date: Thu, 27 Feb 2025 17:42:51 +0200 Subject: [PATCH 22/54] QmlDesigner: Add backend for textures in PropertyEditor Task-number: QDS-14805 Change-Id: I71a5935af3c2cddbeb87f496d2e15464244639c2 Reviewed-by: Miikka Heikkinen Reviewed-by: Mahmoud Badri --- src/plugins/qmldesigner/CMakeLists.txt | 1 + .../propertyeditorqmlbackend.cpp | 6 + .../propertyeditor/propertyeditorqmlbackend.h | 2 + .../propertyeditor/qmltexturenodeproxy.cpp | 170 ++++++++++++++++++ .../propertyeditor/qmltexturenodeproxy.h | 64 +++++++ .../quick2propertyeditorview.cpp | 2 + 6 files changed, 245 insertions(+) create mode 100644 src/plugins/qmldesigner/components/propertyeditor/qmltexturenodeproxy.cpp create mode 100644 src/plugins/qmldesigner/components/propertyeditor/qmltexturenodeproxy.h diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index baa53432110..8c4150bc8db 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -424,6 +424,7 @@ extend_qtc_plugin(QmlDesigner qmlanchorbindingproxy.cpp qmlanchorbindingproxy.h qmlmaterialnodeproxy.cpp qmlmaterialnodeproxy.h qmlmodelnodeproxy.cpp qmlmodelnodeproxy.h + qmltexturenodeproxy.cpp qmltexturenodeproxy.h quick2propertyeditorview.cpp quick2propertyeditorview.h propertyeditorutils.cpp propertyeditorutils.h ) diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp index c9297694fd0..f97d9b6dfeb 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp @@ -339,6 +339,7 @@ void PropertyEditorQmlBackend::handleVariantPropertyChangedInModelNodeProxy(cons void PropertyEditorQmlBackend::handleBindingPropertyChangedInModelNodeProxy(const BindingProperty &property) { m_backendModelNode.handleBindingPropertyChanged(property); + m_backendTextureNode.handleBindingPropertyChanged(property); updateInstanceImage(); } @@ -350,11 +351,13 @@ void PropertyEditorQmlBackend::handleBindingPropertyInModelNodeProxyAboutToChang if (expressionNode.metaInfo().isQtQuick3DTexture()) updateInstanceImage(); } + m_backendTextureNode.handleBindingPropertyChanged(property); } void PropertyEditorQmlBackend::handlePropertiesRemovedInModelNodeProxy(const AbstractProperty &property) { m_backendModelNode.handlePropertiesRemoved(property); + m_backendTextureNode.handlePropertiesRemoved(property); updateInstanceImage(); } @@ -559,6 +562,7 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q context()->setContextProperty("modelNodeBackend", &m_backendModelNode); m_backendMaterialNode.setup(qmlObjectNode); + m_backendTextureNode.setup(qmlObjectNode); // className auto valueObject = qobject_cast(variantToQObject( @@ -995,6 +999,7 @@ void PropertyEditorQmlBackend::setupContextProperties() context()->setContextProperties({ {"modelNodeBackend", QVariant::fromValue(&m_backendModelNode)}, {"materialNodeBackend", QVariant::fromValue(&m_backendMaterialNode)}, + {"textureNodeBackend", QVariant::fromValue(&m_backendTextureNode)}, {"anchorBackend", QVariant::fromValue(&m_backendAnchorBinding)}, {"transaction", QVariant::fromValue(m_propertyEditorTransaction.get())}, {"dummyBackendValue", QVariant::fromValue(m_dummyPropertyEditorValue.get())}, @@ -1051,6 +1056,7 @@ bool PropertyEditorQmlBackend::checkIfUrlExists(const QUrl &url) void PropertyEditorQmlBackend::emitSelectionToBeChanged() { m_backendModelNode.emitSelectionToBeChanged(); + m_backendTextureNode.updateSelectionDetails(); } void PropertyEditorQmlBackend::emitSelectionChanged() diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h index 8e735232870..e58822b076e 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h @@ -9,6 +9,7 @@ #include "qmlanchorbindingproxy.h" #include "qmlmaterialnodeproxy.h" #include "qmlmodelnodeproxy.h" +#include "qmltexturenodeproxy.h" #include "quick2propertyeditorview.h" #include @@ -129,6 +130,7 @@ private: Utils::UniqueObjectPtr m_view = nullptr; QmlAnchorBindingProxy m_backendAnchorBinding; QmlMaterialNodeProxy m_backendMaterialNode; + QmlTextureNodeProxy m_backendTextureNode; QmlModelNodeProxy m_backendModelNode; std::unique_ptr m_propertyEditorTransaction; std::unique_ptr m_dummyPropertyEditorValue; diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmltexturenodeproxy.cpp b/src/plugins/qmldesigner/components/propertyeditor/qmltexturenodeproxy.cpp new file mode 100644 index 00000000000..b030d08d4df --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/qmltexturenodeproxy.cpp @@ -0,0 +1,170 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qmltexturenodeproxy.h" + +#include +#include +#include +#include +#include + +#include + +namespace QmlDesigner { + +using namespace Qt::StringLiterals; + +QmlTextureNodeProxy::QmlTextureNodeProxy() = default; + +QmlTextureNodeProxy::~QmlTextureNodeProxy() = default; + +void QmlTextureNodeProxy::setup(const QmlObjectNode &objectNode) +{ + const QmlObjectNode texture = objectNode.metaInfo().isQtQuick3DTexture() ? objectNode + : QmlObjectNode{}; + + setTextureNode(texture); + updateSelectionDetails(); +} + +void QmlTextureNodeProxy::updateSelectionDetails() +{ + QScopeGuard falseSetter{ + std::bind_front(&QmlTextureNodeProxy::setSelectedNodeAcceptsMaterial, this, false)}; + + if (!textureNode()) + return; + + QmlObjectNode selectedNode = textureNode().view()->singleSelectedModelNode(); + if (!selectedNode) + return; + + falseSetter.dismiss(); + setSelectedNodeAcceptsMaterial(selectedNode.hasBindingProperty("materials")); +} + +void QmlTextureNodeProxy::handlePropertyChanged(const AbstractProperty &property) +{ + if (!textureNode()) + return; + + QmlObjectNode node = property.parentModelNode(); + if (!node) + return; + + QmlObjectNode selectedNode = textureNode().view()->singleSelectedModelNode(); + if (!selectedNode) + return; + + if (property.name() == "materials"_L1 + && (selectedNode == node || selectedNode.propertyChangeForCurrentState() == node)) { + updateSelectionDetails(); + } +} + +void QmlTextureNodeProxy::handleBindingPropertyChanged(const BindingProperty &property) +{ + handlePropertyChanged(property); +} + +void QmlTextureNodeProxy::handlePropertiesRemoved(const AbstractProperty &property) +{ + handlePropertyChanged(property); +} + +QmlObjectNode QmlTextureNodeProxy::textureNode() const +{ + return m_textureNode; +} + +bool QmlTextureNodeProxy::hasTexture() const +{ + return textureNode().isValid(); +} + +bool QmlTextureNodeProxy::selectedNodeAcceptsMaterial() const +{ + return m_selectedNodeAcceptsMaterial; +} + +QString QmlTextureNodeProxy::resolveResourcePath(const QString &path) const +{ + if (Utils::FilePath::fromString(path).isAbsolutePath()) + return path; + + return QmlDesignerPlugin::instance() + ->documentManager() + .currentDesignDocument() + ->fileName() + .absolutePath() + .pathAppended(path) + .cleanPath() + .toUrlishString(); +} + +void QmlTextureNodeProxy::toolbarAction(int action) +{ + if (!hasQuick3DImport()) + return; + + switch (action) { + case ToolBarAction::ApplyToSelected: { + if (!textureNode()) + return; + AbstractView *view = textureNode().view(); + QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialBrowser"); + ModelNode targetNode = view->singleSelectedModelNode(); + view->emitCustomNotification("apply_texture_to_model3D", {targetNode, textureNode()}); + } break; + + case ToolBarAction::AddNewTexture: { + if (!textureNode()) + break; + ModelNode newTexture = CreateTexture(textureNode().view()).execute(); + QTimer::singleShot(0, this, [newTexture]() { + if (newTexture) + newTexture.model()->setSelectedModelNodes({newTexture}); + }); + } break; + + case ToolBarAction::DeleteCurrentTexture: { + if (textureNode()) { + AbstractView *view = textureNode().view(); + view->executeInTransaction(__FUNCTION__, [&] { textureNode().destroy(); }); + } + } break; + + case ToolBarAction::OpenMaterialBrowser: { + QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialBrowser", true); + } break; + } +} + +void QmlTextureNodeProxy::registerDeclarativeType() +{ + qmlRegisterType("HelperWidgets", 2, 0, "QmlTextureNodeProxy"); +} + +void QmlTextureNodeProxy::setTextureNode(const QmlObjectNode &node) +{ + if (m_textureNode == node) + return; + m_textureNode = node; + emit textureNodeChanged(); +} + +void QmlTextureNodeProxy::setSelectedNodeAcceptsMaterial(bool value) +{ + if (m_selectedNodeAcceptsMaterial == value) + return; + m_selectedNodeAcceptsMaterial = value; + emit selectedNodeAcceptsMaterialChanged(); +} + +bool QmlTextureNodeProxy::hasQuick3DImport() const +{ + return textureNode().isValid() && textureNode().model()->hasImport("QtQuick3D"_L1); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmltexturenodeproxy.h b/src/plugins/qmldesigner/components/propertyeditor/qmltexturenodeproxy.h new file mode 100644 index 00000000000..eee8f9b5f2f --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/qmltexturenodeproxy.h @@ -0,0 +1,64 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +#include + +namespace QmlDesigner { + +class QmlTextureNodeProxy : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QmlObjectNode textureNode READ textureNode NOTIFY textureNodeChanged) + Q_PROPERTY(bool hasTexture READ hasTexture NOTIFY textureNodeChanged) + Q_PROPERTY( + bool selectedNodeAcceptsMaterial + READ selectedNodeAcceptsMaterial + NOTIFY selectedNodeAcceptsMaterialChanged) + +public: + enum ToolBarAction { + ApplyToSelected, + AddNewTexture, + DeleteCurrentTexture, + OpenMaterialBrowser + }; + Q_ENUM(ToolBarAction) + + explicit QmlTextureNodeProxy(); + ~QmlTextureNodeProxy() override; + + void setup(const QmlObjectNode &objectNode); + + void updateSelectionDetails(); + void handlePropertyChanged(const AbstractProperty &property); + void handleBindingPropertyChanged(const BindingProperty &property); + void handlePropertiesRemoved(const AbstractProperty &property); + + QmlObjectNode textureNode() const; + bool hasTexture() const; + bool selectedNodeAcceptsMaterial() const; + + Q_INVOKABLE QString resolveResourcePath(const QString &path) const; + Q_INVOKABLE void toolbarAction(int action); + + static void registerDeclarativeType(); + +signals: + void textureNodeChanged(); + void selectedNodeAcceptsMaterialChanged(); + +private: + void setTextureNode(const QmlObjectNode &node); + void setSelectedNodeAcceptsMaterial(bool value); + bool hasQuick3DImport() const; + + QmlObjectNode m_textureNode; + bool m_selectedNodeAcceptsMaterial = false; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp index 21f9dd47433..c5cf236bdcc 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp @@ -27,6 +27,7 @@ #include "propertynamevalidator.h" #include "qmlanchorbindingproxy.h" #include "qmlmaterialnodeproxy.h" +#include "qmltexturenodeproxy.h" #include "richtexteditor/richtexteditorproxy.h" #include "selectiondynamicpropertiesproxymodel.h" #include "theme.h" @@ -67,6 +68,7 @@ void Quick2PropertyEditorView::registerQmlTypes() ColorPaletteBackend::registerDeclarativeType(); QmlAnchorBindingProxy::registerDeclarativeType(); QmlMaterialNodeProxy::registerDeclarativeType(); + QmlTextureNodeProxy::registerDeclarativeType(); BindingEditor::registerDeclarativeType(); ActionEditor::registerDeclarativeType(); AnnotationEditor::registerDeclarativeType(); From ea820c3fcaa3c3bb5f05801e36cb117ce7558345 Mon Sep 17 00:00:00 2001 From: Ali Kianian Date: Thu, 27 Feb 2025 17:44:41 +0200 Subject: [PATCH 23/54] QmlDesigner: Move texture editor qml side as a property editor pane Task-number: QDS-14805 Change-Id: I0e011bdbc218d2c1f0469e55137c49890ed864e5 Reviewed-by: Shrief Gabr Reviewed-by: Mahmoud Badri Reviewed-by: Miikka Heikkinen --- .../QtQuick3D/Texture/ToolBar.qml | 54 ++++++++++++ .../QtQuick3D/Texture/TopSection.qml | 88 +++++++++++++++++++ .../QtQuick3D/TexturePane.qml | 82 +++++++++++++++++ 3 files changed, 224 insertions(+) create mode 100644 share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Texture/ToolBar.qml create mode 100644 share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Texture/TopSection.qml create mode 100644 share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TexturePane.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Texture/ToolBar.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Texture/ToolBar.qml new file mode 100644 index 00000000000..36ed80d6ca8 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Texture/ToolBar.qml @@ -0,0 +1,54 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import HelperWidgets 2.0 +import StudioTheme as StudioTheme + +Rectangle { + id: root + + property QmlTextureNodeProxy backend: textureNodeBackend + + color: StudioTheme.Values.themeToolbarBackground + implicitHeight: StudioTheme.Values.toolbarHeight + + Row { + id: row + spacing: StudioTheme.Values.toolbarSpacing + anchors.verticalCenter: parent.verticalCenter + leftPadding: 6 + + AbstractButton { + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.apply_medium + enabled: backend.hasTexture && backend.selectedNodeAcceptsMaterial && hasQuick3DImport && hasMaterialLibrary + tooltip: qsTr("Apply texture to selected model's material.") + onClicked: backend.toolbarAction(QmlTextureNodeProxy.ApplyToSelected) + } + + AbstractButton { + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.create_medium + enabled: hasQuick3DImport && hasMaterialLibrary + tooltip: qsTr("Create new texture.") + onClicked: backend.toolbarAction(QmlTextureNodeProxy.AddNewTexture) + } + + AbstractButton { + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.delete_medium + enabled: backend.hasTexture && hasQuick3DImport && hasMaterialLibrary + tooltip: qsTr("Delete current texture.") + onClicked: backend.toolbarAction(QmlTextureNodeProxy.DeleteCurrentTexture) + } + + AbstractButton { + style: StudioTheme.Values.viewBarButtonStyle + buttonIcon: StudioTheme.Constants.materialBrowser_medium + enabled: backend.hasTexture && hasQuick3DImport && hasMaterialLibrary + tooltip: qsTr("Open material browser.") + onClicked: backend.toolbarAction(QmlTextureNodeProxy.OpenMaterialBrowser) + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Texture/TopSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Texture/TopSection.qml new file mode 100644 index 00000000000..0ac948ddedc --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Texture/TopSection.qml @@ -0,0 +1,88 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import HelperWidgets 2.0 as HelperWidgets +import StudioTheme as StudioTheme + +Rectangle { + id: root + + property HelperWidgets.QmlTextureNodeProxy backend: textureNodeBackend + + readonly property string sourcePath: backendValues.source ? backendValues.source.valueToString : "" + readonly property string previewPath: "image://qmldesigner_thumbnails/" + backend.resolveResourcePath(root.sourcePath) + + function refreshPreview() + { + texturePreview.source = "" + texturePreview.source = root.previewPath + } + + color: StudioTheme.Values.themePanelBackground + implicitHeight: column.height + + Column { + id: column + + Item { implicitWidth: 1; implicitHeight: 10 } // spacer + + Rectangle { + id: previewRect + anchors.horizontalCenter: parent.horizontalCenter + width: 152 + height: 152 + color: "#000000" + + Image { + id: texturePreview + asynchronous: true + width: 150 + height: 150 + fillMode: Image.PreserveAspectFit + anchors.centerIn: parent + source: root.previewPath + } + } + + HelperWidgets.Section { + id: nameSection + + // Section with hidden header is used so properties are aligned with the other sections' properties + hideHeader: true + implicitWidth: root.width + bottomPadding: StudioTheme.Values.sectionPadding * 2 + collapsible: false + + HelperWidgets.SectionLayout { + HelperWidgets.PropertyLabel { text: qsTr("Name") } + + HelperWidgets.SecondColumnLayout { + HelperWidgets.Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth } + + HelperWidgets.LineEdit { + id: texName + + implicitWidth: StudioTheme.Values.singleControlColumnWidth + width: StudioTheme.Values.singleControlColumnWidth + placeholderText: qsTr("Texture name") + showTranslateCheckBox: false + showExtendedFunctionButton: false + + Timer { + running: true + interval: 0 + onTriggered: texName.backendValue = backendValues.objectName + // backendValues.objectName is not available yet without the Timer + } + + // allow only alphanumeric characters, underscores, no space at start, and 1 space between words + validator: RegularExpressionValidator { regularExpression: /^(\w+\s)*\w+$/ } + } + + HelperWidgets.ExpandingSpacer {} + } + } + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TexturePane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TexturePane.qml new file mode 100644 index 00000000000..a78be386859 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TexturePane.qml @@ -0,0 +1,82 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Layouts +import HelperWidgets 2.0 +import StudioTheme as StudioTheme +import "Texture" as Texture + +Rectangle { + id: itemPane + + width: 420 + height: 420 + color: StudioTheme.Values.themePanelBackground + + // invoked from C++ to refresh material preview image + function refreshPreview() + { + topSection.refreshPreview() + } + + // Called from C++ to close context menu on focus out + function closeContextMenu() + { + Controller.closeContextMenu() + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + Texture.ToolBar { + Layout.fillWidth: true + } + + Texture.TopSection { + id: topSection + + Layout.fillWidth: true + } + + PropertyEditorPane { + Layout.fillWidth: true + Layout.fillHeight: true + + DynamicPropertiesSection { + propertiesModel: SelectionDynamicPropertiesModel {} + visible: !hasMultiSelection + } + + Loader { + id: specificsTwo + + property string theSource: specificQmlData + + anchors.left: parent.left + anchors.right: parent.right + visible: specificsTwo.theSource !== "" + sourceComponent: specificQmlComponent + + onTheSourceChanged: { + specificsTwo.active = false + specificsTwo.active = true + } + } + + Item { + width: 1 + height: 10 + visible: specificsTwo.visible + } + + Loader { + id: specificsOne + anchors.left: parent.left + anchors.right: parent.right + source: specificsUrl + } + } + } +} From f92524e5d99b29730a64ba93aea7c3f29ad54fca Mon Sep 17 00:00:00 2001 From: Aleksei German Date: Mon, 3 Mar 2025 13:52:38 +0100 Subject: [PATCH 24/54] QmlDesigner: Fix for missing QtQuick import Pick-to: qds/4.7 Fixes: QDS-14828 Change-Id: Id8ed1cd1daf16d0773a4ae0431745ab8956adb6b Reviewed-by: Aleksei German --- .../qmldesigner/components/itemlibrary/itemlibrarymodel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp index 8024ccf2816..3baacb2571f 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp @@ -401,6 +401,7 @@ void ItemLibraryModel::update(Model *model) // we need to exclude all items from unsupported imports but only if they are not user-defined modules if (!(entry.category() == ItemLibraryImport::userComponentsTitle()) + && !entry.requiredImport().isEmpty() && !mcuManager.allowedImports().contains(entry.requiredImport())) { blocked = true; } From 9726fffd3d3bc1473f80636da4f57ed1b0468700 Mon Sep 17 00:00:00 2001 From: Shrief Gabr Date: Thu, 30 Jan 2025 17:20:41 +0200 Subject: [PATCH 25/54] QmlDesigner: Show feedback popup only if telemetry is enabled - Also removed logic that tracks the status of the previous shutdown. - Cleaned up a deprecated invokeMethod call. Task-number: QDS-14649 Change-Id: I3d753610e8d35eedbb7cb6129a1f079cdada1785 Reviewed-by: Eike Ziller --- src/plugins/qmldesigner/qmldesignerplugin.cpp | 65 ++++++++----------- src/plugins/qmldesigner/qmldesignerplugin.h | 1 - 2 files changed, 27 insertions(+), 39 deletions(-) diff --git a/src/plugins/qmldesigner/qmldesignerplugin.cpp b/src/plugins/qmldesigner/qmldesignerplugin.cpp index 5026b91fa24..e0ab88fc453 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.cpp +++ b/src/plugins/qmldesigner/qmldesignerplugin.cpp @@ -16,6 +16,7 @@ #include "settingspage.h" #include "shortcutmanager.h" #include "toolbar.h" +#include "utils/checkablemessagebox.h" #include #include @@ -271,14 +272,17 @@ bool QmlDesignerPlugin::initialize(const QStringList & /*arguments*/, QString * Sqlite::LibraryInitializer::initialize(); QDir{}.mkpath(Core::ICore::cacheResourcePath().toUrlishString()); - QAction *action = new QAction(tr("Give Feedback..."), this); - Core::Command *cmd = Core::ActionManager::registerAction(action, "Help.GiveFeedback"); - Core::ActionManager::actionContainer(Core::Constants::M_HELP) - ->addAction(cmd, Core::Constants::G_HELP_SUPPORT); + if (Core::ICore::isQtDesignStudio()) { + QAction *action = new QAction(tr("Give Feedback..."), this); + action->setVisible(false); // keep hidden unless UsageStatistic plugin activates it + Core::Command *cmd = Core::ActionManager::registerAction(action, "Help.GiveFeedback"); + Core::ActionManager::actionContainer(Core::Constants::M_HELP) + ->addAction(cmd, Core::Constants::G_HELP_SUPPORT); - connect(action, &QAction::triggered, this, [this] { - launchFeedbackPopupInternal(QGuiApplication::applicationDisplayName()); - }); + connect(action, &QAction::triggered, this, [this] { + launchFeedbackPopupInternal(QGuiApplication::applicationDisplayName()); + }); + } d = new QmlDesignerPluginPrivate; d->timer.start(); @@ -309,18 +313,8 @@ bool QmlDesignerPlugin::initialize(const QStringList & /*arguments*/, QString * if (Core::ICore::isQtDesignStudio()) { d->toolBar = ToolBar::create(); d->statusBar = ToolBar::createStatusBar(); - - // uses simplified Telemetry settings page in case of Qt Design Studio - ExtensionSystem::PluginSpec *usageStatistic = Utils::findOrDefault(ExtensionSystem::PluginManager::plugins(), [](ExtensionSystem::PluginSpec *p) { - return p->id() == "usagestatistic"; - }); - - if (usageStatistic && usageStatistic->plugin()) - QMetaObject::invokeMethod(usageStatistic->plugin(), "useSimpleUi", true); } - initializeShutdownSettings(); - return true; } @@ -355,33 +349,26 @@ void QmlDesignerPlugin::extensionsInitialized() Core::IWizardFactory::registerFeatureProvider(new FullQDSFeatureProvider); } -void QmlDesignerPlugin::initializeShutdownSettings() -{ - auto settings = Core::ICore::settings(); - - if (!settings->contains("ShutdownCount")) - settings->setValue("ShutdownCount", 0); - - m_lastShutdownType = settings->value("LastShutdownType", "UserQuit").toString(); - settings->setValue("LastShutdownType", "Crash"); // value will persist unless changed in aboutToShutdown() -} - ExtensionSystem::IPlugin::ShutdownFlag QmlDesignerPlugin::aboutToShutdown() { Utils::QtcSettings *settings = Core::ICore::settings(); - int shutdownCount = settings->value("ShutdownCount", 0).toInt(); - if (m_lastShutdownType == "UserQuit") - settings->setValue("ShutdownCount", ++shutdownCount); - - settings->setValue("LastShutdownType", "UserQuit"); - if (shutdownCount != 5) // feedback popup should be displayed on the 5th shutdown + if (!Utils::CheckableDecider("FeedbackPopup").shouldAskAgain()) return SynchronousShutdown; - m_shutdownPending = true; - launchFeedbackPopupInternal(QGuiApplication::applicationDisplayName()); + int shutdownCount = settings->value("ShutdownCount", 0).toInt(); + settings->setValue("ShutdownCount", ++shutdownCount); - return AsynchronousShutdown; + if (!settings->value("UsageStatistic/TrackingEnabled").toBool()) + return SynchronousShutdown; + + if (shutdownCount >= 5) { + m_shutdownPending = true; + launchFeedbackPopupInternal(QGuiApplication::applicationDisplayName()); + return AsynchronousShutdown; + } + + return SynchronousShutdown; } static QStringList allUiQmlFilesforCurrentProject(const Utils::FilePath &fileName) @@ -886,8 +873,10 @@ void QmlDesignerPlugin::closeFeedbackPopup() m_feedbackWidget = nullptr; } - if (m_shutdownPending) + if (m_shutdownPending) { + Utils::CheckableDecider("FeedbackPopup").doNotAskAgain(); emit asynchronousShutdownFinished(); + } } void QmlDesignerPlugin::emitUsageStatisticsTime(const QString &identifier, int elapsed) diff --git a/src/plugins/qmldesigner/qmldesignerplugin.h b/src/plugins/qmldesigner/qmldesignerplugin.h index d9713fa4538..4952e96d76b 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.h +++ b/src/plugins/qmldesigner/qmldesignerplugin.h @@ -135,7 +135,6 @@ private: // variables QElapsedTimer m_usageTimer; bool m_delayedInitialized = false; bool m_shutdownPending = false; - QString m_lastShutdownType; }; } // namespace QmlDesigner From 1cba6bf5af4670f0c3d206b4685c1f2ac3a00d01 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 4 Mar 2025 15:16:15 +0200 Subject: [PATCH 26/54] QmlDesigner: Close bundle zip properly In some cases icons are added synchronously, in which case the final json addition would reopen the zip file without closing. Fixes: QDS-14856 Change-Id: Ie6401e274c50901c7e871e6d8bed53eaf3c3c356 Reviewed-by: Mahmoud Badri --- .../components/componentcore/bundlehelper.cpp | 21 ++++++++++++------- .../components/componentcore/bundlehelper.h | 5 +++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp b/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp index 671cbcc8f52..82c950a3e63 100644 --- a/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp +++ b/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp @@ -236,7 +236,7 @@ void BundleHelper::exportBundle(const QList &nodes, const QPixmap &ic nodesToExport.append(node); } - m_remainingIcons = nodesToExport.size(); + m_remainingFiles = nodesToExport.size() + 1; for (const ModelNode &node : std::as_const(nodesToExport)) { if (node.isComponent()) @@ -247,6 +247,7 @@ void BundleHelper::exportBundle(const QList &nodes, const QPixmap &ic jsonObj["items"] = itemsArr; m_zipWriter->addFile(Constants::BUNDLE_JSON_FILENAME, QJsonDocument(jsonObj).toJson()); + maybeCloseZip(); } QJsonObject BundleHelper::exportComponent(const ModelNode &node) @@ -281,7 +282,7 @@ QJsonObject BundleHelper::exportComponent(const ModelNode &node) // add icon QString filePath = compFilePath.path(); getImageFromCache(filePath, [this, iconPath](const QImage &image) { - addIconAndCloseZip(iconPath, image); + addIconToZip(iconPath, image); }); return { @@ -344,10 +345,10 @@ QJsonObject BundleHelper::exportNode(const ModelNode &node, const QPixmap &iconP if (iconPixmapToSave.isNull()) { getImageFromCache(qmlFilePath.toFSPathString(), [this, iconPath](const QImage &image) { - addIconAndCloseZip(iconPath, image); + addIconToZip(iconPath, image); }); } else { - addIconAndCloseZip(iconPath, iconPixmapToSave); + addIconToZip(iconPath, iconPixmapToSave); } return { @@ -358,6 +359,12 @@ QJsonObject BundleHelper::exportNode(const ModelNode &node, const QPixmap &iconP }; } +void BundleHelper::maybeCloseZip() +{ + if (--m_remainingFiles <= 0) + m_zipWriter->close(); +} + QPair> BundleHelper::modelNodeToQmlString(const ModelNode &node, int depth) { static QStringList depListIds; @@ -541,16 +548,14 @@ void BundleHelper::getImageFromCache(const QString &qmlPath, }); } -void BundleHelper::addIconAndCloseZip(const QString &iconPath, const auto &image) { // auto: QImage or QPixmap +void BundleHelper::addIconToZip(const QString &iconPath, const auto &image) { // auto: QImage or QPixmap QByteArray iconByteArray; QBuffer buffer(&iconByteArray); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "PNG"); m_zipWriter->addFile(iconPath, iconByteArray); - - if (--m_remainingIcons <= 0) - m_zipWriter->close(); + maybeCloseZip(); }; QString BundleHelper::getImportPath() const diff --git a/src/plugins/qmldesigner/components/componentcore/bundlehelper.h b/src/plugins/qmldesigner/components/componentcore/bundlehelper.h index ddd79f33158..edeb91f76e6 100644 --- a/src/plugins/qmldesigner/components/componentcore/bundlehelper.h +++ b/src/plugins/qmldesigner/components/componentcore/bundlehelper.h @@ -71,18 +71,19 @@ private: QString getExportPath(const ModelNode &node) const; bool isMaterialBundle(const QString &bundleId) const; bool isItemBundle(const QString &bundleId) const; - void addIconAndCloseZip(const QString &iconPath, const auto &image); + void addIconToZip(const QString &iconPath, const auto &image); Utils::FilePath componentPath(const ModelNode &node) const; QSet getBundleComponentDependencies(const ModelNode &node) const; QJsonObject exportComponent(const ModelNode &node); QJsonObject exportNode(const ModelNode &node, const QPixmap &iconPixmap = QPixmap()); + void maybeCloseZip(); QPointer m_view; QPointer m_widget; Utils::UniqueObjectPtr m_importer; std::unique_ptr m_zipWriter; std::unique_ptr m_tempDir; - int m_remainingIcons = 0; + int m_remainingFiles = 0; static constexpr char BUNDLE_VERSION[] = "1.0"; }; From f94679b61a10e24c298b27548f82ba28f9273951 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 4 Mar 2025 17:00:29 +0200 Subject: [PATCH 27/54] QmlDesigner: Export materials as user material bundles If materials are exported as user3D bundles, they will be imported under 3D scene rather than material library. Fixes: QDS-14859 Change-Id: Ib0c34113e2b497b48ab686a8e63787b88fcd6b7a Reviewed-by: Mahmoud Badri --- .../qmldesigner/components/componentcore/bundlehelper.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp b/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp index 82c950a3e63..7fef634f2f8 100644 --- a/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp +++ b/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp @@ -221,7 +221,6 @@ void BundleHelper::exportBundle(const QList &nodes, const QPixmap &ic auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); QJsonObject jsonObj; - jsonObj["id"] = compUtils.user3DBundleId(); jsonObj["version"] = BUNDLE_VERSION; QJsonArray itemsArr; @@ -235,6 +234,9 @@ void BundleHelper::exportBundle(const QList &nodes, const QPixmap &ic if (!isChild) nodesToExport.append(node); } + jsonObj["id"] = !nodesToExport.isEmpty() && nodesToExport[0].metaInfo().isQtQuick3DMaterial() + ? compUtils.userMaterialsBundleId() + : compUtils.user3DBundleId(); m_remainingFiles = nodesToExport.size() + 1; From 6ac1a9ba7f56ffac31b15211b6cba3cbf4fb7855 Mon Sep 17 00:00:00 2001 From: Andrii Semkiv Date: Wed, 19 Feb 2025 16:02:31 +0100 Subject: [PATCH 28/54] MCU Support: Show deployment issues in issues pane MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MCU deployment issues will be shown in the Issues output pane. Change-Id: I5b3751d65a8e46e24a68591f9a0c6f7f77492324 Reviewed-by: Sivert Krøvel Reviewed-by: Thomas Hartmann --- src/plugins/mcusupport/mcubuildstep.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/plugins/mcusupport/mcubuildstep.cpp b/src/plugins/mcusupport/mcubuildstep.cpp index 83756879c71..a24ff5add91 100644 --- a/src/plugins/mcusupport/mcubuildstep.cpp +++ b/src/plugins/mcusupport/mcubuildstep.cpp @@ -100,6 +100,11 @@ DeployMcuProcessStep::DeployMcuProcessStep(ProjectExplorer::BuildStepList *bc, I cmdLine.addArg(directory); return cmdLine; }); + + connect(this, &BuildStep::addOutput, this, [](const QString &str, OutputFormat fmt) { + if (fmt == OutputFormat::ErrorMessage) + showError(str); + }); } // Workaround for QDS-13763, when UL-10456 is completed this can be removed with the next LTS From 395a8018148e4d5bc3afb11fe0c44f0e2acf8efc Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Thu, 27 Feb 2025 12:09:48 +0100 Subject: [PATCH 29/54] QmlDesigner: Remove call to clearComponentCache() This is called also the first time and can have unwanted side effects. Change-Id: I373dd748499c9f924a3263796e4cd584959f55ac Reviewed-by: Tim Jenssen --- src/plugins/insight/insightwidget.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/insight/insightwidget.cpp b/src/plugins/insight/insightwidget.cpp index 37db4340ff8..21e75f04549 100644 --- a/src/plugins/insight/insightwidget.cpp +++ b/src/plugins/insight/insightwidget.cpp @@ -103,7 +103,6 @@ void InsightWidget::reloadQmlSource() { QString statesListQmlFilePath = qmlSourcesPath() + QStringLiteral("/Main.qml"); QTC_ASSERT(QFileInfo::exists(statesListQmlFilePath), return ); - engine()->clearComponentCache(); setSource(QUrl::fromLocalFile(statesListQmlFilePath)); if (!rootObject()) { From 3bf918e29359ff3c3aa10b477d52d577235ca40b Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Thu, 6 Mar 2025 09:45:27 +0100 Subject: [PATCH 30/54] Increase concept diagnostics depth only for C++ Before it was tested if the C++ compiler would be GCC. It was not testing if the compiler would be C++. Fixes: QTCREATORBUG-32583 Change-Id: I2ca3614dc4a429bc594fade81b86cedceb2ef98c Reviewed-by: Eike Ziller --- cmake/QtCreatorAPIInternal.cmake | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cmake/QtCreatorAPIInternal.cmake b/cmake/QtCreatorAPIInternal.cmake index 37e0f017b3d..ce7489b4c18 100644 --- a/cmake/QtCreatorAPIInternal.cmake +++ b/cmake/QtCreatorAPIInternal.cmake @@ -196,9 +196,7 @@ function(qtc_enable_sanitize _target _sanitize_flags) endfunction() function(qtc_deeper_concept_diagnostic_depth _target) - if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - target_compile_options("${_target}" PUBLIC -fconcepts-diagnostics-depth=8) - endif() + target_compile_options("${_target}" PRIVATE $<$:-fconcepts-diagnostics-depth=8>) endfunction() function(qtc_add_link_flags_no_undefined target) From 2bb235da40c62d7684dd50c7bde67f250ab86595 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Fri, 7 Feb 2025 10:38:34 +0100 Subject: [PATCH 31/54] QmlDesigner: There were issues with escaping the json Change-Id: Ibedc675a38a0323587836788727a29b0454717f6 Reviewed-by: Marco Bubke --- .../libs/designercore/projectstorage/projectstoragetypes.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstoragetypes.h b/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstoragetypes.h index 5c776313479..71279f57412 100644 --- a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstoragetypes.h +++ b/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstoragetypes.h @@ -1255,8 +1255,6 @@ public: using NanotraceHR::keyValue; auto dict = dictonary(keyValue("type name", typeAnnotation.typeName), keyValue("icon path", typeAnnotation.iconPath), - keyValue("item library json", typeAnnotation.itemLibraryJson), - keyValue("hints json", typeAnnotation.hintsJson), keyValue("type id", typeAnnotation.typeId), keyValue("source id", typeAnnotation.sourceId), keyValue("module id", typeAnnotation.moduleId), From 3ca60700ce69628e2cb69ca1fd0e32e5703b06b3 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Thu, 6 Mar 2025 16:51:42 +0100 Subject: [PATCH 32/54] QmlDesigner: Implement ProjectStorageErrorNotifier * This patch adds forwarding of the errors of the ProjectStorage to our UI. We create build issues that will be forwarded to our output pane. * This required breaking dependecies annd moving ProjectStorageErrorNotifier out of DesignerCore. * Moved QmlDesignerProjectManager and ProjectStorageErrorNotifier to project folder to avoid polution of main folder. * Adjusting includes and export macros. Task-number: QDS-14880 Change-Id: Id8628e274086a9cb63ac8fcd416e4b0d508b10eb Reviewed-by: Marco Bubke --- src/plugins/qmldesigner/CMakeLists.txt | 11 ++- .../libs/designercore/CMakeLists.txt | 1 - .../projectstorage/projectstorage.h | 2 +- .../projectstorageerrornotifier.cpp | 43 ----------- .../projectstorage/projectstorageupdater.h | 2 +- .../project/projectstorageerrornotifier.cpp | 74 +++++++++++++++++++ .../projectstorageerrornotifier.h | 8 +- .../qmldesignerprojectmanager.cpp | 2 +- .../{ => project}/qmldesignerprojectmanager.h | 0 .../tests/testdesignercore/CMakeLists.txt | 1 - 10 files changed, 91 insertions(+), 53 deletions(-) delete mode 100644 src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageerrornotifier.cpp create mode 100644 src/plugins/qmldesigner/project/projectstorageerrornotifier.cpp rename src/plugins/qmldesigner/{libs/designercore/projectstorage => project}/projectstorageerrornotifier.h (84%) rename src/plugins/qmldesigner/{ => project}/qmldesignerprojectmanager.cpp (99%) rename src/plugins/qmldesigner/{ => project}/qmldesignerprojectmanager.h (100%) diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 8c4150bc8db..7bf60b155ad 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -104,7 +104,6 @@ add_qtc_plugin(QmlDesigner qmldesignericons.h qmldesignerplugin.cpp qmldesignerplugin.h qmldesignerexternaldependencies.cpp qmldesignerexternaldependencies.h - qmldesignerprojectmanager.cpp qmldesignerprojectmanager.h settingspage.cpp settingspage.h shortcutmanager.cpp shortcutmanager.h designermcumanager.cpp designermcumanager.h @@ -133,6 +132,16 @@ if (QTC_STATIC_BUILD AND TARGET QmlDesigner) extend_qtc_target(QmlDesigner PUBLIC_DEPENDS TextEditor) endif() +extend_qtc_plugin(QmlDesigner + PUBLIC_INCLUDES project + SOURCES_PREFIX project + SOURCES + qmldesignerprojectmanager.cpp + qmldesignerprojectmanager.h + projectstorageerrornotifier.cpp + projectstorageerrornotifier.h +) + extend_qtc_plugin(QmlDesigner PUBLIC_INCLUDES instances SOURCES_PREFIX instances diff --git a/src/plugins/qmldesigner/libs/designercore/CMakeLists.txt b/src/plugins/qmldesigner/libs/designercore/CMakeLists.txt index e3d8adb3697..81ce99bb191 100644 --- a/src/plugins/qmldesigner/libs/designercore/CMakeLists.txt +++ b/src/plugins/qmldesigner/libs/designercore/CMakeLists.txt @@ -399,7 +399,6 @@ extend_qtc_library(QmlDesignerCore projectstorageupdater.cpp projectstorageupdater.h projectstorage.cpp projectstorage.h projectstorageerrornotifierinterface.h - projectstorageerrornotifier.cpp projectstorageerrornotifier.h typeannotationreader.cpp typeannotationreader.h qmldocumentparserinterface.h qmltypesparserinterface.h diff --git a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorage.h b/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorage.h index f7a0ad43af1..f11921330f3 100644 --- a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorage.h +++ b/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorage.h @@ -4,7 +4,7 @@ #pragma once #include "commontypecache.h" -#include "projectstorageerrornotifier.h" +#include "projectstorageerrornotifierinterface.h" #include "projectstorageexceptions.h" #include "projectstorageinterface.h" #include "projectstoragetypes.h" diff --git a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageerrornotifier.cpp b/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageerrornotifier.cpp deleted file mode 100644 index b722e90dd4f..00000000000 --- a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageerrornotifier.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "projectstorageerrornotifier.h" - -#include "sourcepathstorage/sourcepathcache.h" - -namespace QmlDesigner { - -void ProjectStorageErrorNotifier::typeNameCannotBeResolved(Utils::SmallStringView typeName, - SourceId sourceId) -{ - qDebug() << "Missing type name: " << typeName - << " in file: " << m_pathCache.sourcePath(sourceId).toStringView(); -} - -void ProjectStorageErrorNotifier::missingDefaultProperty(Utils::SmallStringView typeName, - Utils::SmallStringView propertyName, - SourceId sourceId) - -{ - qDebug() << "Missing default property: " << propertyName << " in type: " << typeName - << " in file: " << m_pathCache.sourcePath(sourceId).toStringView(); -} - -void ProjectStorageErrorNotifier::propertyNameDoesNotExists(Utils::SmallStringView propertyName, - SourceId sourceId) -{ - qDebug() << "Missing property: " << propertyName - << " in file: " << m_pathCache.sourcePath(sourceId).toStringView(); -} - -void ProjectStorageErrorNotifier::qmlDocumentDoesNotExistsForQmldirEntry(Utils::SmallStringView typeName, - Storage::Version, - SourceId qmlDocumentSourceId, - SourceId qmldirSourceId) -{ - qDebug() << "Not existing Qml Document " - << m_pathCache.sourcePath(qmlDocumentSourceId).toStringView() << " for type " - << typeName << " in file: " << m_pathCache.sourcePath(qmldirSourceId).toStringView(); -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageupdater.h b/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageupdater.h index 364f2c36f86..de042968930 100644 --- a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageupdater.h +++ b/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageupdater.h @@ -4,7 +4,7 @@ #pragma once #include "filestatus.h" -#include "projectstorageerrornotifier.h" +#include "projectstorageerrornotifierinterface.h" #include "projectstorageids.h" #include "projectstoragepathwatchernotifierinterface.h" #include "projectstoragepathwatchertypes.h" diff --git a/src/plugins/qmldesigner/project/projectstorageerrornotifier.cpp b/src/plugins/qmldesigner/project/projectstorageerrornotifier.cpp new file mode 100644 index 00000000000..f6fefe2b297 --- /dev/null +++ b/src/plugins/qmldesigner/project/projectstorageerrornotifier.cpp @@ -0,0 +1,74 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "projectstorageerrornotifier.h" + +#include + +#include +#include +#include + +#include + +namespace QmlDesigner { + +namespace { + +void logIssue(ProjectExplorer::Task::TaskType type, const QString &message, const SourcePath &sourcePath) +{ + const Utils::Id category = ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM; + + Utils::FilePath filePath = Utils::FilePath::fromUserInput(sourcePath.toQString()); + ProjectExplorer::Task task(type, message, filePath, -1, category); + ProjectExplorer::TaskHub::addTask(task); + ProjectExplorer::TaskHub::requestPopup(); +} +} // namespace + +void ProjectStorageErrorNotifier::typeNameCannotBeResolved(Utils::SmallStringView typeName, + SourceId sourceId) +{ + const QString typeNameString{typeName}; + + logIssue(ProjectExplorer::Task::Warning, + Tr::tr("Missing type %1 name.").arg(typeNameString), + m_pathCache.sourcePath(sourceId)); +} + +void ProjectStorageErrorNotifier::missingDefaultProperty(Utils::SmallStringView typeName, + Utils::SmallStringView propertyName, + SourceId sourceId) + +{ + const QString typeNameString{typeName}; + const QString propertyNameString{propertyName}; + + logIssue(ProjectExplorer::Task::Warning, + Tr::tr("Missing default property: %1 in type %2.").arg(propertyNameString).arg(typeNameString), + m_pathCache.sourcePath(sourceId)); +} + +void ProjectStorageErrorNotifier::propertyNameDoesNotExists(Utils::SmallStringView propertyName, + SourceId sourceId) +{ + const QString propertyNameString{propertyName}; + + logIssue(ProjectExplorer::Task::Warning, + Tr::tr("Missing property %1 in type %2.").arg(propertyNameString), + m_pathCache.sourcePath(sourceId)); +} + +void ProjectStorageErrorNotifier::qmlDocumentDoesNotExistsForQmldirEntry(Utils::SmallStringView typeName, + Storage::Version, + SourceId qmlDocumentSourceId, + SourceId qmldirSourceId) +{ + const QString typeNameString{typeName}; + const QString missingPath = m_pathCache.sourcePath(qmlDocumentSourceId).toQString(); + logIssue(ProjectExplorer::Task::Warning, + Tr::tr("Not existing Qml Document %1 for type %2.").arg(missingPath).arg(typeNameString), + m_pathCache.sourcePath(qmldirSourceId)); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageerrornotifier.h b/src/plugins/qmldesigner/project/projectstorageerrornotifier.h similarity index 84% rename from src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageerrornotifier.h rename to src/plugins/qmldesigner/project/projectstorageerrornotifier.h index bea20ce394c..ae720bac693 100644 --- a/src/plugins/qmldesigner/libs/designercore/projectstorage/projectstorageerrornotifier.h +++ b/src/plugins/qmldesigner/project/projectstorageerrornotifier.h @@ -3,15 +3,15 @@ #pragma once -#include "projectstorageerrornotifierinterface.h" +#include + +#include #include -#include namespace QmlDesigner { -class QMLDESIGNERCORE_EXPORT ProjectStorageErrorNotifier final - : public ProjectStorageErrorNotifierInterface +class QMLDESIGNER_EXPORT ProjectStorageErrorNotifier final : public ProjectStorageErrorNotifierInterface { public: ProjectStorageErrorNotifier(PathCacheType &pathCache) diff --git a/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp b/src/plugins/qmldesigner/project/qmldesignerprojectmanager.cpp similarity index 99% rename from src/plugins/qmldesigner/qmldesignerprojectmanager.cpp rename to src/plugins/qmldesigner/project/qmldesignerprojectmanager.cpp index 13fa82b4c72..b9fbf8d3438 100644 --- a/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp +++ b/src/plugins/qmldesigner/project/qmldesignerprojectmanager.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qmldesignerprojectmanager.h" +#include "projectstorageerrornotifier.h" #include #include @@ -12,7 +13,6 @@ #include #include #include -#include #include #include #include diff --git a/src/plugins/qmldesigner/qmldesignerprojectmanager.h b/src/plugins/qmldesigner/project/qmldesignerprojectmanager.h similarity index 100% rename from src/plugins/qmldesigner/qmldesignerprojectmanager.h rename to src/plugins/qmldesigner/project/qmldesignerprojectmanager.h diff --git a/tests/unit/tests/testdesignercore/CMakeLists.txt b/tests/unit/tests/testdesignercore/CMakeLists.txt index 5cdb2935e6e..240c6b36390 100644 --- a/tests/unit/tests/testdesignercore/CMakeLists.txt +++ b/tests/unit/tests/testdesignercore/CMakeLists.txt @@ -351,7 +351,6 @@ extend_qtc_library(TestDesignerCore projectstorageupdater.cpp projectstorageupdater.h projectstorage.cpp projectstorage.h projectstorageerrornotifierinterface.h - projectstorageerrornotifier.cpp projectstorageerrornotifier.h typeannotationreader.cpp typeannotationreader.h qmldocumentparserinterface.h qmltypesparserinterface.h From 77455efafad6630087ea22a59e198f629ccde9c9 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Thu, 6 Mar 2025 17:34:17 +0100 Subject: [PATCH 33/54] QmlDesigner: Fix DocumentManager::currentFilePath If no QML file was opened this was not working since we returned too early. This fixes the function in case no QML file was opened. Pick-to: qds/4.7 Change-Id: Ie6b8b4becfee4ed05760e31b766d8d0a9b88a666 Reviewed-by: Tim Jenssen --- src/plugins/qmldesigner/documentmanager.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/documentmanager.cpp b/src/plugins/qmldesigner/documentmanager.cpp index c650c42713e..3ab5b8d937d 100644 --- a/src/plugins/qmldesigner/documentmanager.cpp +++ b/src/plugins/qmldesigner/documentmanager.cpp @@ -351,12 +351,16 @@ Utils::FilePath DocumentManager::currentProjectDirPath() { QTC_ASSERT(QmlDesignerPlugin::instance(), return {}); - if (!QmlDesignerPlugin::instance()->currentDesignDocument()) + if (!QmlDesignerPlugin::instance()->currentDesignDocument()) { + if (ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject()) + return project->projectDirectory(); return {}; + } Utils::FilePath qmlFileName = QmlDesignerPlugin::instance()->currentDesignDocument()->fileName(); ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::projectForFile(qmlFileName); + if (project) return project->projectDirectory(); From 0c5d7ce4dccefcb44c782bd24fa1fddfb2f849e4 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Thu, 6 Mar 2025 16:40:45 +0200 Subject: [PATCH 34/54] QmlDesigner: Remove TextureSpecifics and fix new panes MaterialPane now includes MaterialSection and TexturePane includes TextureSection. Removed those sections from any subtype specifics. Added hack to ignore specifics for Texture types. Fixes: QDS-14878 Change-Id: Iffea1be81dc7e12372434cee9135942c7c117082 Reviewed-by: Marco Bubke --- .../QtQuick3D/CubeMapTextureSpecifics.qml | 15 --------------- .../QtQuick3D/CustomMaterialSpecifics.qml | 4 ---- .../QtQuick3D/DefaultMaterialSpecifics.qml | 4 ---- .../QtQuick3D/MaterialPane.qml | 4 ++++ .../QtQuick3D/PrincipledMaterialSpecifics.qml | 4 ---- .../QtQuick3D/SpecularGlossyMaterialSpecifics.qml | 4 ---- .../QtQuick3D/TexturePane.qml | 4 ++++ .../QtQuick3D/TextureSpecifics.qml | 14 -------------- .../propertyeditor/propertyeditorview.cpp | 4 ++++ 9 files changed, 12 insertions(+), 45 deletions(-) delete mode 100644 share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/CubeMapTextureSpecifics.qml delete mode 100644 share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TextureSpecifics.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/CubeMapTextureSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/CubeMapTextureSpecifics.qml deleted file mode 100644 index f6716f32bb3..00000000000 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/CubeMapTextureSpecifics.qml +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only - -import QtQuick 2.15 -import QtQuick.Layouts 1.15 -import HelperWidgets 2.0 - -Column { - width: parent.width - - // CubeMapTexture inherits Texture but doesn't provide any extra properties itself - TextureSection { - width: parent.width - } -} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/CustomMaterialSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/CustomMaterialSpecifics.qml index 6911a9eb914..06294ff7128 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/CustomMaterialSpecifics.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/CustomMaterialSpecifics.qml @@ -11,8 +11,4 @@ Column { CustomMaterialSection { width: parent.width } - - MaterialSection { - width: parent.width - } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/DefaultMaterialSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/DefaultMaterialSpecifics.qml index 0e0c3fc2557..c40bcba6f12 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/DefaultMaterialSpecifics.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/DefaultMaterialSpecifics.qml @@ -11,8 +11,4 @@ Column { DefaultMaterialSection { width: parent.width } - - MaterialSection { - width: parent.width - } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/MaterialPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/MaterialPane.qml index 13e41c8506b..099ed904bba 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/MaterialPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/MaterialPane.qml @@ -112,6 +112,10 @@ Item { width: itemPane.width source: specificsUrl } + + MaterialSection { + width: itemPane.width + } } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/PrincipledMaterialSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/PrincipledMaterialSpecifics.qml index ff10311f9bb..937c6b9996c 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/PrincipledMaterialSpecifics.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/PrincipledMaterialSpecifics.qml @@ -11,8 +11,4 @@ Column { PrincipledMaterialSection { width: parent.width } - - MaterialSection { - width: parent.width - } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/SpecularGlossyMaterialSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/SpecularGlossyMaterialSpecifics.qml index b84e824c543..a96dccecb6d 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/SpecularGlossyMaterialSpecifics.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/SpecularGlossyMaterialSpecifics.qml @@ -11,8 +11,4 @@ Column { SpecularGlossyMaterialSection { width: parent.width } - - MaterialSection { - width: parent.width - } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TexturePane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TexturePane.qml index a78be386859..a7a3613c663 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TexturePane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TexturePane.qml @@ -77,6 +77,10 @@ Rectangle { anchors.right: parent.right source: specificsUrl } + + TextureSection { + width: itemPane.width + } } } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TextureSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TextureSpecifics.qml deleted file mode 100644 index a52924a093f..00000000000 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TextureSpecifics.qml +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only - -import QtQuick 2.15 -import QtQuick.Layouts 1.15 -import HelperWidgets 2.0 - -Column { - width: parent.width - - TextureSection { - width: parent.width - } -} diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp index 3eb0c0c8d4e..f374e4c72f9 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp @@ -607,6 +607,10 @@ void PropertyEditorView::setupQmlBackend() auto [diffClassMetaInfo, qmlSpecificsFile] = diffType(commonAncestor, specificsClassMetaInfo); + // Hack to fix Textures in property views in case obsolete specifics are loaded from module + if (qmlFileUrl.toLocalFile().endsWith("TexturePane.qml")) + qmlSpecificsFile = QUrl{}; + QString specificQmlData = getSpecificQmlData(commonAncestor, m_selectedNode, diffClassMetaInfo); PropertyEditorQmlBackend *currentQmlBackend = getQmlBackend(m_qmlBackendHash, From 68573c80c911c9eefc175814ca19460f4e877a57 Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Mon, 3 Feb 2025 11:39:18 +0100 Subject: [PATCH 35/54] QmlDesigner: Replace Rectangle with Item Currently the root item of the ActionIndicator is a transparent Rectangle. Replace Rectangle with Item in the ActionIndicator component to potentially increase rendering performance. Change-Id: I09d2d53f8407eeef85ab5adb838b491609701507 Reviewed-by: Thomas Hartmann --- .../imports/StudioControls/ActionIndicator.qml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ActionIndicator.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ActionIndicator.qml index 96a28d6d5c8..8f5a85734a1 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ActionIndicator.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ActionIndicator.qml @@ -3,9 +3,9 @@ import QtQuick import QtQuick.Templates as T -import StudioTheme 1.0 as StudioTheme +import StudioTheme as StudioTheme -Rectangle { +Item { id: control property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle @@ -18,8 +18,6 @@ Rectangle { property bool pressed: false property bool forceVisible: false - color: "transparent" - implicitWidth: control.style.actionIndicatorSize.width implicitHeight: control.style.actionIndicatorSize.height From e300842d4b6b05af250492f49a1f189cb7d6c648 Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Fri, 7 Mar 2025 14:58:37 +0100 Subject: [PATCH 36/54] QmlDesigner: Fix Type error in rename dialog * Remove import versions * Add correct type to Dialog enums in order for them to be resolved correctly, otherwise this error comes up: "TypeError: Value is null and could not be converted to an object" * Improve if statement assignment Change-Id: I2c593a6a07e6aa5aa7af88413249d991074710fe Reviewed-by: Thomas Hartmann --- .../qtcreator/qmldesigner/stateseditor/Main.qml | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/share/qtcreator/qmldesigner/stateseditor/Main.qml b/share/qtcreator/qmldesigner/stateseditor/Main.qml index ac3c11d9fe1..f9058238a04 100644 --- a/share/qtcreator/qmldesigner/stateseditor/Main.qml +++ b/share/qtcreator/qmldesigner/stateseditor/Main.qml @@ -26,8 +26,8 @@ import QtQuick import QtQuick.Controls.Basic as Basic import StatesEditor -import HelperWidgets 2.0 as HelperWidgets -import StudioControls 1.0 as StudioControls +import HelperWidgets as HelperWidgets +import StudioControls as StudioControls import StudioTheme as StudioTheme import StatesEditorBackend @@ -313,7 +313,7 @@ Rectangle { StudioControls.Dialog { id: editDialog title: qsTr("Rename state group") - standardButtons: Dialog.Apply | Dialog.Cancel + standardButtons: Basic.Dialog.Apply | Basic.Dialog.Cancel x: editButton.x - Math.max(0, editButton.x + editDialog.width - root.width) y: toolBar.height width: Math.min(300, root.width) @@ -328,15 +328,11 @@ Rectangle { anchors.fill: parent onTextChanged: { - let btn = editDialog.standardButton(Dialog.Apply) + let btn = editDialog.standardButton(Basic.Dialog.Apply) if (!btn) return - if (editDialog.previousString !== editTextField.text) { - btn.enabled = true - } else { - btn.enabled = false - } + btn.enabled = (editDialog.previousString !== editTextField.text) } onAccepted: editDialog.accept() @@ -355,7 +351,7 @@ Rectangle { editTextField.text = StatesEditorBackend.statesEditorModel.activeStateGroup editDialog.previousString = StatesEditorBackend.statesEditorModel.activeStateGroup - let btn = editDialog.standardButton(Dialog.Apply) + let btn = editDialog.standardButton(Basic.Dialog.Apply) btn.enabled = false } } From 56f02830cd1045dd99462623a0238eefb33fbc2e Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Thu, 6 Mar 2025 16:58:02 +0100 Subject: [PATCH 37/54] QmlDesigner: Remove unused AssetExporterPlugin Task-number: QDS-14877 Change-Id: I7e8a128f4f498067c3fbb4d6632d43e869393f76 Reviewed-by: Thomas Hartmann --- .../qtdesignstudio/QtCreatorIDEBranding.cmake | 1 - src/plugins/qmldesigner/CMakeLists.txt | 33 -- .../assetexporterplugin/assetexportdialog.cpp | 228 -------- .../assetexporterplugin/assetexportdialog.h | 67 --- .../assetexporterplugin/assetexporter.cpp | 550 ------------------ .../assetexporterplugin/assetexporter.h | 99 ---- .../assetexporterplugin.cpp | 115 ---- .../assetexporterplugin/assetexporterplugin.h | 35 -- .../assetexporterplugin.json | 6 - .../assetexporterplugin.metainfo | 2 - .../assetexporterplugin.qrc | 5 - .../assetexporterplugin/assetexporterview.cpp | 142 ----- .../assetexporterplugin/assetexporterview.h | 62 -- .../assetexportpluginconstants.h | 66 --- .../assetexporterplugin/componentexporter.cpp | 158 ----- .../assetexporterplugin/componentexporter.h | 78 --- .../dumpers/assetnodedumper.cpp | 47 -- .../dumpers/assetnodedumper.h | 20 - .../dumpers/itemnodedumper.cpp | 76 --- .../dumpers/itemnodedumper.h | 22 - .../dumpers/nodedumper.cpp | 28 - .../assetexporterplugin/dumpers/nodedumper.h | 40 -- .../dumpers/textnodedumper.cpp | 90 --- .../dumpers/textnodedumper.h | 21 - .../exportnotification.cpp | 41 -- .../assetexporterplugin/exportnotification.h | 16 - .../assetexporterplugin/filepathmodel.cpp | 143 ----- .../assetexporterplugin/filepathmodel.h | 43 -- 28 files changed, 2234 deletions(-) delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.h delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/assetexporter.h delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.h delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.json delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.metainfo delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.qrc delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/assetexporterview.cpp delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/assetexporterview.h delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/componentexporter.h delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/dumpers/assetnodedumper.cpp delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/dumpers/assetnodedumper.h delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/dumpers/itemnodedumper.cpp delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/dumpers/itemnodedumper.h delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/dumpers/nodedumper.cpp delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/dumpers/nodedumper.h delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/dumpers/textnodedumper.cpp delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/dumpers/textnodedumper.h delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/exportnotification.cpp delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/exportnotification.h delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/filepathmodel.cpp delete mode 100644 src/plugins/qmldesigner/assetexporterplugin/filepathmodel.h diff --git a/dist/branding/qtdesignstudio/QtCreatorIDEBranding.cmake b/dist/branding/qtdesignstudio/QtCreatorIDEBranding.cmake index 8356743d8ac..92c2b049857 100644 --- a/dist/branding/qtdesignstudio/QtCreatorIDEBranding.cmake +++ b/dist/branding/qtdesignstudio/QtCreatorIDEBranding.cmake @@ -55,7 +55,6 @@ set(DESIGNSTUDIO_PLUGINS Texteditor UpdateInfo VcsBase - assetexporterplugin componentsplugin qmlpreviewplugin qtquickplugin) diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 7bf60b155ad..919ef925df3 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -798,39 +798,6 @@ extend_qtc_plugin(QmlDesigner dvconnector.cpp dvconnector.h ) -add_qtc_plugin(assetexporterplugin - PLUGIN_CLASS AssetExporterPlugin - CONDITION TARGET QmlDesigner - PLUGIN_DEPENDS - Core ProjectExplorer QmlDesigner - DEPENDS Utils Qt::Qml Qt::QuickPrivate - PUBLIC_INCLUDES assetexporterplugin - PLUGIN_PATH ${QmlDesignerPluginInstallPrefix} -) - -extend_qtc_plugin(assetexporterplugin - CONDITION ENABLE_COMPILE_WARNING_AS_ERROR - PROPERTIES COMPILE_WARNING_AS_ERROR ON -) - -extend_qtc_plugin(assetexporterplugin - SOURCES_PREFIX assetexporterplugin - SOURCES - assetexportdialog.h assetexportdialog.cpp - assetexporter.h assetexporter.cpp - assetexporterplugin.h assetexporterplugin.cpp - assetexporterview.h assetexporterview.cpp - assetexportpluginconstants.h - componentexporter.h componentexporter.cpp - exportnotification.h exportnotification.cpp - filepathmodel.h filepathmodel.cpp - dumpers/assetnodedumper.h dumpers/assetnodedumper.cpp - dumpers/itemnodedumper.h dumpers/itemnodedumper.cpp - dumpers/nodedumper.h dumpers/nodedumper.cpp - dumpers/textnodedumper.h dumpers/textnodedumper.cpp - assetexporterplugin.qrc -) - add_qtc_plugin(componentsplugin PLUGIN_CLASS ComponentsPlugin CONDITION TARGET QmlDesigner diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp deleted file mode 100644 index 658bbc88437..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "assetexportdialog.h" - -#include "../qmldesignertr.h" -#include "assetexportpluginconstants.h" -#include "filepathmodel.h" - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace ProjectExplorer; -using namespace Utils; - -namespace QmlDesigner { - -static void addFormattedMessage(OutputFormatter *formatter, const QString &str, OutputFormat format) -{ - if (!formatter) - return; - - QPlainTextEdit *edit = formatter->plainTextEdit(); - QScrollBar *scroll = edit->verticalScrollBar(); - bool isAtBottom = scroll && scroll->value() == scroll->maximum(); - - QString msg = str + "\n"; - formatter->appendMessage(msg, format); - - if (isAtBottom) - scroll->setValue(scroll->maximum()); -} - -AssetExportDialog::AssetExportDialog(const FilePath &exportPath, - AssetExporter &assetExporter, FilePathModel &model, - QWidget *parent) : - QDialog(parent), - m_assetExporter(assetExporter), - m_filePathModel(model), - m_filesView(new QListView), - m_exportLogs(new QPlainTextEdit), - m_outputFormatter(new Utils::OutputFormatter()) -{ - resize(768, 480); - setWindowTitle(Tr::tr("Export Components")); - - m_stackedWidget = new QStackedWidget; - - m_exportProgress = new QProgressBar; - m_exportProgress->setRange(0,0); - - auto optionsWidget = new QWidget; - - auto advancedOptions = new DetailsWidget; - advancedOptions->setSummaryText(tr("Advanced Options")); - advancedOptions->setWidget(optionsWidget); - - m_buttonBox = new QDialogButtonBox; - m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Close); - - m_exportPath = new PathChooser; - m_exportPath->setExpectedKind(PathChooser::Kind::SaveFile); - m_exportPath->setFilePath( - exportPath.pathAppended( - ProjectExplorer::ProjectManager::startupProject()->displayName() + ".metadata" - )); - m_exportPath->setPromptDialogTitle(tr("Choose Export File")); - m_exportPath->setPromptDialogFilter(tr("Metadata file (*.metadata)")); - m_exportPath->lineEdit()->setReadOnly(true); - m_exportPath->addButton(tr("Open"), this, [this] { - Core::FileUtils::showInGraphicalShell(m_exportPath->filePath()); - }); - - m_exportAssetsCheck = new QCheckBox(tr("Export assets"), this); - m_exportAssetsCheck->setChecked(true); - - m_perComponentExportCheck = new QCheckBox(tr("Export components separately"), this); - m_perComponentExportCheck->setChecked(false); - - m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false); - - m_stackedWidget->addWidget(m_filesView); - m_filesView->setModel(&m_filePathModel); - - m_exportLogs->setReadOnly(true); - m_outputFormatter->setPlainTextEdit(m_exportLogs); - m_stackedWidget->addWidget(m_exportLogs); - switchView(false); - - connect(m_buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, [this] { - m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false); - m_assetExporter.cancel(); - }); - - m_exportBtn = m_buttonBox->addButton(tr("Export"), QDialogButtonBox::AcceptRole); - m_exportBtn->setEnabled(false); - connect(m_exportBtn, &QPushButton::clicked, this, &AssetExportDialog::onExport); - connect(&m_filePathModel, &FilePathModel::modelReset, this, [this] { - m_exportProgress->setRange(0, 1000); - m_exportProgress->setValue(0); - m_exportBtn->setEnabled(true); - }); - - connect(m_buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, [this] { - close(); - }); - m_buttonBox->button(QDialogButtonBox::Close)->setVisible(false); - - connect(&m_assetExporter, &AssetExporter::stateChanged, - this, &AssetExportDialog::onExportStateChanged); - connect(&m_assetExporter, &AssetExporter::exportProgressChanged, - this, &AssetExportDialog::updateExportProgress); - - connect(&taskHub(), &TaskHub::taskAdded, this, &AssetExportDialog::onTaskAdded); - - using namespace Layouting; - - Column { - m_exportAssetsCheck, - m_perComponentExportCheck, - st, - noMargin, - }.attachTo(optionsWidget); - - Column { - Form { Tr::tr("Export path:"), m_exportPath }, - advancedOptions, - m_stackedWidget, - m_exportProgress, - m_buttonBox, - }.attachTo(this); -} - -AssetExportDialog::~AssetExportDialog() -{ - m_assetExporter.cancel(); -} - -void AssetExportDialog::onExport() -{ - switchView(true); - - updateExportProgress(0.0); - TaskHub::clearTasks(Constants::TASK_CATEGORY_ASSET_EXPORT); - m_exportLogs->clear(); - - Utils::FilePath selectedPath = m_exportPath->filePath(); - Utils::FilePath exportPath = m_perComponentExportCheck->isChecked() ? - (selectedPath.isDir() ? selectedPath : selectedPath.parentDir()) : - selectedPath; - - m_assetExporter.exportQml(m_filePathModel.files(), exportPath, - m_exportAssetsCheck->isChecked(), - m_perComponentExportCheck->isChecked()); -} - -void AssetExportDialog::onExportStateChanged(AssetExporter::ParsingState newState) -{ - switch (newState) { - case AssetExporter::ParsingState::ExportingDone: - m_exportBtn->setVisible(false); - m_buttonBox->button(QDialogButtonBox::Close)->setVisible(true); - break; - default: - break; - } - - m_exportBtn->setEnabled(newState == AssetExporter::ParsingState::ExportingDone); - m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(m_assetExporter.isBusy()); -} - -void AssetExportDialog::updateExportProgress(double value) -{ - value = std::max(0.0, std::min(1.0, value)); - m_exportProgress->setValue(std::round(value * 1000)); -} - -void AssetExportDialog::switchView(bool showExportView) -{ - if (showExportView) - m_stackedWidget->setCurrentWidget(m_exportLogs); - else - m_stackedWidget->setCurrentWidget(m_filesView); -} - -void AssetExportDialog::onTaskAdded(const ProjectExplorer::Task &task) -{ - Utils::OutputFormat format = Utils::NormalMessageFormat; - if (task.category == Constants::TASK_CATEGORY_ASSET_EXPORT) { - switch (task.type) { - case ProjectExplorer::Task::Error: - format = Utils::StdErrFormat; - break; - case ProjectExplorer::Task::Warning: - format = Utils::StdOutFormat; - break; - default: - format = Utils::NormalMessageFormat; - } - addFormattedMessage(m_outputFormatter, task.description(), format); - } -} - -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.h b/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.h deleted file mode 100644 index bf277f82bbe..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.h +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "assetexporter.h" - -#include - -#include -#include - -QT_BEGIN_NAMESPACE -class QPushButton; -class QCheckBox; -class QDialogButtonBox; -class QListView; -class QPlainTextEdit; -class QProgressBar; -class QStackedWidget; -QT_END_NAMESPACE - -namespace Utils { -class OutputFormatter; -class PathChooser; -} - -namespace ProjectExplorer { -class Task; -} - -namespace QmlDesigner { - -class FilePathModel; - -class AssetExportDialog : public QDialog -{ - Q_OBJECT - -public: - explicit AssetExportDialog(const Utils::FilePath &exportPath, AssetExporter &assetExporter, - FilePathModel& model, QWidget *parent = nullptr); - ~AssetExportDialog(); - -private: - void onExport(); - void onExportStateChanged(AssetExporter::ParsingState newState); - void updateExportProgress(double value); - void switchView(bool showExportView); - void onTaskAdded(const ProjectExplorer::Task &task); - -private: - AssetExporter &m_assetExporter; - FilePathModel &m_filePathModel; - QPushButton *m_exportBtn = nullptr; - QCheckBox *m_exportAssetsCheck = nullptr; - QCheckBox *m_perComponentExportCheck = nullptr; - QListView *m_filesView = nullptr; - QPlainTextEdit *m_exportLogs = nullptr; - Utils::OutputFormatter *m_outputFormatter = nullptr; - Utils::PathChooser *m_exportPath = nullptr; - QDialogButtonBox *m_buttonBox = nullptr; - QStackedWidget *m_stackedWidget = nullptr; - QProgressBar *m_exportProgress = nullptr; -}; - -} // QmlDesigner diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp deleted file mode 100644 index 874618ccd31..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp +++ /dev/null @@ -1,550 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "assetexporter.h" -#include "componentexporter.h" -#include "exportnotification.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace ProjectExplorer; -using namespace std; -namespace { -bool makeParentPath(const Utils::FilePath &path) -{ - QDir d; - return d.mkpath(path.toFileInfo().absolutePath()); -} - -QByteArray generateHash(const QString &token) { - static uint counter = 0; - std::mt19937 gen(std::random_device().operator()()); - std::uniform_int_distribution<> distribution(1, 99999); - QByteArray data = QString("%1%2%3").arg(token).arg(++counter).arg(distribution(gen)).toLatin1(); - return QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex(); -} - -Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.assetExporter", QtInfoMsg) -Q_LOGGING_CATEGORY(loggerWarn, "qtc.designer.assetExportPlugin.assetExporter", QtWarningMsg) -Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.assetExporter", QtCriticalMsg) -} - -namespace QmlDesigner { - -class AssetDumper -{ -public: - AssetDumper(); - ~AssetDumper(); - - void dumpAsset(const QPixmap &p, const Utils::FilePath &path); - - /* Keeps on dumping until all assets are dumped, then quits */ - void quitDumper(); - - /* Aborts dumping */ - void abortDumper(); - -private: - void addAsset(const QPixmap &p, const Utils::FilePath &path); - void doDumping(QPromise &promise); - void savePixmap(const QPixmap &p, Utils::FilePath &path) const; - - QFuture m_dumpFuture; - QMutex m_queueMutex; - QWaitCondition m_queueCondition; - std::queue> m_assets; - std::atomic m_quitDumper; -}; - -AssetExporter::AssetExporter(AssetExporterView *view, - ProjectExplorer::Project *project, - ProjectStorageDependencies projectStorageDependencies) - : m_currentState(*this) - , m_project(project) - , m_view(view) - , m_projectStorageDependencies{projectStorageDependencies} -{ - connect(m_view, &AssetExporterView::loadingFinished, this, &AssetExporter::onQmlFileLoaded); - connect(m_view, &AssetExporterView::loadingError, this, &AssetExporter::notifyLoadError); -} - -AssetExporter::~AssetExporter() -{ - cancel(); -} - -void AssetExporter::exportQml(const Utils::FilePaths &qmlFiles, const Utils::FilePath &exportPath, - bool exportAssets, bool perComponentExport) -{ - m_perComponentExport = perComponentExport; - ExportNotification::addInfo(tr("Export root directory: %1.\nExporting assets: %2") - .arg(exportPath.isDir() - ? exportPath.toUserOutput() - : exportPath.parentDir().toUserOutput()) - .arg(exportAssets? tr("Yes") : tr("No"))); - - if (m_perComponentExport) - ExportNotification::addInfo(tr("Each component is exported separately.")); - - notifyProgress(0.0); - m_exportFiles = qmlFiles; - m_totalFileCount = m_exportFiles.count(); - m_components.clear(); - m_componentUuidCache.clear(); - m_exportPath = exportPath.isDir() ? exportPath : exportPath.parentDir(); - m_exportFile = exportPath.fileName(); - m_currentState.change(ParsingState::Parsing); - if (exportAssets) - m_assetDumper = make_unique(); - else - m_assetDumper.reset(); - - QTimer::singleShot(0, this, &AssetExporter::beginExport); -} - -void AssetExporter::beginExport() -{ - for (const Utils::FilePath &p : std::as_const(m_exportFiles)) { - if (m_cancelled) - break; - preprocessQmlFile(p); - } - - if (!m_cancelled) - triggerLoadNextFile(); -} - -void AssetExporter::cancel() -{ - if (!m_cancelled) { - ExportNotification::addInfo(tr("Canceling export.")); - m_assetDumper.reset(); - m_cancelled = true; - } -} - -bool AssetExporter::isBusy() const -{ - return m_currentState == AssetExporter::ParsingState::Parsing || - m_currentState == AssetExporter::ParsingState::ExportingAssets || - m_currentState == AssetExporter::ParsingState::WritingJson; -} - -const QPixmap &AssetExporter::generateAsset(const ModelNode &node) -{ - static QPixmap nullPixmap; - if (m_cancelled) - return nullPixmap; - - const QString uuid = node.auxiliaryDataWithDefault(uuidProperty).toString(); - QTC_ASSERT(!uuid.isEmpty(), return nullPixmap); - - if (!m_assets.contains(uuid)) { - // Generate asset. - QmlObjectNode objectNode(node); - QPixmap asset = objectNode.toQmlItemNode().instanceRenderPixmap(); - m_assets[uuid] = asset; - } - return m_assets[uuid]; -} - -Utils::FilePath AssetExporter::assetPath(const ModelNode &node, const Component *component, - const QString &suffix) const -{ - const QString uuid = node.auxiliaryDataWithDefault(uuidProperty).toString(); - if (!component || uuid.isEmpty()) - return {}; - - const Utils::FilePath assetExportDir = - m_perComponentExport ? componentExportDir(component) : m_exportPath; - const Utils::FilePath assetPath = assetExportDir.pathAppended("assets") - .pathAppended(uuid + suffix + ".png"); - - return assetPath; -} - -void AssetExporter::exportAsset(const QPixmap &asset, const Utils::FilePath &path) -{ - if (m_cancelled || !m_assetDumper) - return; - - m_assetDumper->dumpAsset(asset, path); -} - -void AssetExporter::exportComponent(const ModelNode &rootNode) -{ - qCDebug(loggerInfo) << "Exporting component" << rootNode.id(); - m_components.push_back(make_unique(*this, rootNode)); - m_components.back()->exportComponent(); -} - -void AssetExporter::notifyLoadError(AssetExporterView::LoadState state) -{ - QString errorStr = tr("Unknown error."); - switch (state) { - case AssetExporterView::LoadState::Exausted: - errorStr = tr("Loading file is taking too long."); - break; - case AssetExporterView::LoadState::QmlErrorState: - errorStr = tr("Cannot parse. The file contains coding errors."); - break; - default: - return; - } - qCDebug(loggerError) << "QML load error:" << errorStr; - ExportNotification::addError(tr("Loading components failed. %1").arg(errorStr)); -} - -void AssetExporter::notifyProgress(double value) const -{ - emit exportProgressChanged(value); -} - -void AssetExporter::onQmlFileLoaded() -{ - QTC_ASSERT(m_view && m_view->model(), qCDebug(loggerError) << "Null model"; return); - qCDebug(loggerInfo) << "Qml file load done" << m_view->model()->fileUrl(); - - QmlDesigner::DesignDocument *designDocument = QmlDesigner::QmlDesignerPlugin::instance() - ->documentManager() - .currentDesignDocument(); - if (designDocument->hasQmlParseErrors()) { - ExportNotification::addError(tr("Cannot export component. Document \"%1\" has parsing errors.") - .arg(designDocument->displayName())); - } else { - exportComponent(m_view->rootModelNode()); - if (Utils::Result res = m_view->saveQmlFile(); !res) { - ExportNotification::addError(tr("Error saving component file. %1") - .arg(res.error().isEmpty()? tr("Unknown") : res.error())); - } - } - notifyProgress((m_totalFileCount - m_exportFiles.count()) * 0.8 / m_totalFileCount); - triggerLoadNextFile(); -} - -Utils::FilePath AssetExporter::componentExportDir(const Component *component) const -{ - return m_exportPath.pathAppended(component->name()); -} - -void AssetExporter::preprocessQmlFile(const Utils::FilePath &path) -{ - // Load the QML file and assign UUIDs to items having none. - // Meanwhile cache the Component UUIDs as well -#ifdef QDS_USE_PROJECTSTORAGE - ModelPointer model = Model::create(m_projectStorageDependencies, - "Item", - {Import::createLibraryImport("QtQuick")}, - path.path()); -#else - ModelPointer model = Model::create("Item", 2, 7); -#endif - Utils::FileReader reader; - if (!reader.fetch(path)) { - ExportNotification::addError(tr("Cannot preprocess file: %1. Error %2") - .arg(path.toUserOutput()).arg(reader.errorString())); - return; - } - - QPlainTextEdit textEdit; - textEdit.setPlainText(QString::fromUtf8(reader.data())); - NotIndentingTextEditModifier *modifier = new NotIndentingTextEditModifier(textEdit.document()); - modifier->setParent(model.get()); - auto rewriterView = std::make_unique(m_view->externalDependencies(), - QmlDesigner::RewriterView::Validate); - rewriterView->setCheckSemanticErrors(false); - rewriterView->setTextModifier(modifier); - model->attachView(rewriterView.get()); - rewriterView->restoreAuxiliaryData(); - ModelNode rootNode = rewriterView->rootModelNode(); - if (!rootNode.isValid()) { - ExportNotification::addError(tr("Cannot preprocess file: %1").arg(path.toUrlishString())); - return; - } - - if (assignUuids(rootNode)) { - // Some UUIDs were assigned. Rewrite the file. - rewriterView->writeAuxiliaryData(); - const QByteArray data = textEdit.toPlainText().toUtf8(); - Utils::FileSaver saver(path, QIODevice::Text); - saver.write(data); - if (!saver.finalize()) { - ExportNotification::addError(tr("Cannot update %1.\n%2") - .arg(path.toUserOutput()).arg(saver.errorString())); - return; - } - - // Close the document if already open. - // UUIDS are changed and editor must reopen the document, otherwise stale state of the - // document is loaded. - for (Core::IDocument *doc : Core::DocumentModel::openedDocuments()) { - if (doc->filePath() == path) { - Core::EditorManager::closeDocuments({doc}, false); - break; - } - } - } - - // Cache component UUID - const QString uuid = rootNode.auxiliaryDataWithDefault(uuidProperty).toString(); - m_componentUuidCache[path.toUrlishString()] = uuid; -} - -bool AssetExporter::assignUuids(const ModelNode &root) -{ - // Assign an UUID to the node without one. - // Return true if an assignment takes place. - bool changed = false; - for (const ModelNode &node : root.allSubModelNodesAndThisNode()) { - const QString uuid = node.auxiliaryDataWithDefault(uuidProperty).toString(); - if (uuid.isEmpty()) { - // Assign an unique identifier to the node. - QByteArray uuid = generateUuid(node); - node.setAuxiliaryData(uuidProperty, QString::fromLatin1(uuid)); - changed = true; - } - } - return changed; -} - -QByteArray AssetExporter::generateUuid(const ModelNode &node) -{ - QByteArray uuid; - do { - uuid = generateHash(node.id()); - } while (m_usedHashes.contains(uuid)); - m_usedHashes.insert(uuid); - return uuid; -} - -QString AssetExporter::componentUuid(const ModelNode &instance) const -{ - // Returns the UUID of the component's root node - // Empty string is returned if the node is not an instance of a component within - // the project. - if (instance) { - const QString path = ModelUtils::componentFilePath(instance); - return m_componentUuidCache.value(path); - } - - return {}; -} - -void AssetExporter::triggerLoadNextFile() -{ - QTimer::singleShot(0, this, &AssetExporter::loadNextFile); -} - -void AssetExporter::loadNextFile() -{ - if (m_cancelled || m_exportFiles.isEmpty()) { - notifyProgress(0.8); - m_currentState.change(ParsingState::ParsingFinished); - writeMetadata(); - return; - } - - // Load the next pending file. - const Utils::FilePath file = m_exportFiles.takeFirst(); - ExportNotification::addInfo(tr("Exporting file %1.").arg(file.toUserOutput())); - qCDebug(loggerInfo) << "Loading next file" << file; - m_view->loadQmlFile(file); -} - -void AssetExporter::writeMetadata() const -{ - if (m_cancelled) { - notifyProgress(1.0); - ExportNotification::addInfo(tr("Export canceled.")); - m_currentState.change(ParsingState::ExportingDone); - return; - } - - - auto writeFile = [](const Utils::FilePath &path, const QJsonArray &artboards) { - if (!makeParentPath(path)) { - ExportNotification::addError(tr("Writing metadata failed. Cannot create file %1"). - arg(path.toUrlishString())); - return; - } - - ExportNotification::addInfo(tr("Writing metadata to file %1.").arg(path.toUserOutput())); - - QJsonObject jsonRoot; // TODO: Write plugin info to root - jsonRoot.insert("artboards", artboards); - QJsonDocument doc(jsonRoot); - if (doc.isNull() || doc.isEmpty()) { - ExportNotification::addError(tr("Empty JSON document.")); - return; - } - - Utils::FileSaver saver(path, QIODevice::Text); - saver.write(doc.toJson(QJsonDocument::Indented)); - if (!saver.finalize()) { - ExportNotification::addError(tr("Writing metadata failed. %1"). - arg(saver.errorString())); - } - }; - - m_currentState.change(ParsingState::WritingJson); - - auto const startupProject = ProjectExplorer::ProjectManager::startupProject(); - QTC_ASSERT(startupProject, return); - const QString projectName = startupProject->displayName(); - - if (m_perComponentExport) { - for (auto &component : m_components) { - const Utils::FilePath path = componentExportDir(component.get()); - writeFile(path.pathAppended(component->name() + ".metadata"), {component->json()}); - } - } else { - QJsonArray artboards; - std::transform(m_components.cbegin(), m_components.cend(), back_inserter(artboards), - [](const unique_ptr &c) {return c->json(); }); - writeFile(m_exportPath.pathAppended(m_exportFile), artboards); - } - notifyProgress(1.0); - ExportNotification::addInfo(tr("Export finished.")); - if (m_assetDumper) - m_assetDumper->quitDumper(); - m_currentState.change(ParsingState::ExportingDone); -} - -AssetExporter::State::State(AssetExporter &exporter) : - m_assetExporter(exporter) -{ - -} - -void AssetExporter::State::change(const ParsingState &state) -{ - qCDebug(loggerInfo()) << "Assetimporter State change: Old: " << m_state << "New: " << state; - if (m_state != state) { - m_state = state; - emit m_assetExporter.stateChanged(m_state); - } -} - -QDebug operator<<(QDebug os, const AssetExporter::ParsingState &s) -{ - os << static_cast::type>(s); - return os; -} - -AssetDumper::AssetDumper(): - m_quitDumper(false) -{ - m_dumpFuture = Utils::asyncRun(&AssetDumper::doDumping, this); -} - -AssetDumper::~AssetDumper() -{ - abortDumper(); -} - -void AssetDumper::dumpAsset(const QPixmap &p, const Utils::FilePath &path) -{ - addAsset(p, path); -} - -void AssetDumper::quitDumper() -{ - m_quitDumper = true; - m_queueCondition.wakeAll(); - if (!m_dumpFuture.isFinished()) - m_dumpFuture.waitForFinished(); -} - -void AssetDumper::abortDumper() -{ - if (!m_dumpFuture.isFinished()) { - m_dumpFuture.cancel(); - m_queueCondition.wakeAll(); - m_dumpFuture.waitForFinished(); - } -} - -void AssetDumper::addAsset(const QPixmap &p, const Utils::FilePath &path) -{ - QMutexLocker locker(&m_queueMutex); - qDebug() << "Save Asset:" << path; - m_assets.push({p, path}); -} - -void AssetDumper::doDumping(QPromise &promise) -{ - auto haveAsset = [this] (std::pair *asset) { - QMutexLocker locker(&m_queueMutex); - if (m_assets.empty()) - return false; - *asset = m_assets.front(); - m_assets.pop(); - return true; - }; - - forever { - std::pair asset; - if (haveAsset(&asset)) { - if (promise.isCanceled()) - break; - savePixmap(asset.first, asset.second); - } else { - if (m_quitDumper) - break; - QMutexLocker locker(&m_queueMutex); - m_queueCondition.wait(&m_queueMutex); - } - - if (promise.isCanceled()) - break; - } -} - -void AssetDumper::savePixmap(const QPixmap &p, Utils::FilePath &path) const -{ - if (p.isNull()) { - qCDebug(loggerWarn) << "Dumping null pixmap" << path; - return; - } - - if (!makeParentPath(path)) { - ExportNotification::addError(Tr::tr("Error creating asset directory. %1").arg(path.fileName())); - return; - } - - if (!p.save(path.toUrlishString())) { - ExportNotification::addError(Tr::tr("Error saving asset. %1").arg(path.fileName())); - } -} - -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.h b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.h deleted file mode 100644 index 36e7d29136b..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.h +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "assetexporterview.h" - -#include -#include - -#include - -namespace ProjectExplorer { class Project; } - -namespace QmlDesigner { -class AssetDumper; -class Component; - -class AssetExporter : public QObject -{ - Q_OBJECT - -public: - - enum class ParsingState { - Idle = 0, - Parsing, - ParsingFinished, - ExportingAssets, - ExportingAssetsFinished, - WritingJson, - ExportingDone - }; - - AssetExporter(AssetExporterView *view, - ProjectExplorer::Project *project, - ProjectStorageDependencies projectStorageDependencies); - ~AssetExporter(); - - void exportQml(const Utils::FilePaths &qmlFiles, const Utils::FilePath &exportPath, - bool exportAssets, bool perComponentExport); - - void cancel(); - bool isBusy() const; - - const QPixmap &generateAsset(const ModelNode &node); - Utils::FilePath assetPath(const ModelNode &node, const Component *component, - const QString &suffix = {}) const; - void exportAsset(const QPixmap &asset, const Utils::FilePath &path); - QByteArray generateUuid(const ModelNode &node); - QString componentUuid(const ModelNode &instance) const; - -signals: - void stateChanged(ParsingState); - void exportProgressChanged(double) const; - -private: - ParsingState currentState() const { return m_currentState.m_state; } - void exportComponent(const ModelNode &rootNode); - void writeMetadata() const; - void notifyLoadError(AssetExporterView::LoadState state); - void notifyProgress(double value) const; - void triggerLoadNextFile(); - void loadNextFile(); - - void onQmlFileLoaded(); - Utils::FilePath componentExportDir(const Component *component) const; - - void beginExport(); - void preprocessQmlFile(const Utils::FilePath &path); - bool assignUuids(const ModelNode &root); - -private: - mutable class State { - public: - State(AssetExporter&); - void change(const ParsingState &state); - operator ParsingState() const { return m_state; } - AssetExporter &m_assetExporter; - ParsingState m_state = ParsingState::Idle; - } m_currentState; - ProjectExplorer::Project *m_project = nullptr; - AssetExporterView *m_view = nullptr; - Utils::FilePaths m_exportFiles; - unsigned int m_totalFileCount = 0; - Utils::FilePath m_exportPath; - QString m_exportFile; - bool m_perComponentExport = false; - std::vector> m_components; - QHash m_componentUuidCache; - QSet m_usedHashes; - QHash m_assets; - ProjectStorageDependencies m_projectStorageDependencies; - std::unique_ptr m_assetDumper; - bool m_cancelled = false; -}; -QDebug operator<< (QDebug os, const QmlDesigner::AssetExporter::ParsingState& s); - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp deleted file mode 100644 index 6246e0ed36f..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "assetexporterplugin.h" - -#include "assetexportdialog.h" -#include "assetexporter.h" -#include "assetexporterview.h" -#include "assetexportpluginconstants.h" -#include "componentexporter.h" -#include "filepathmodel.h" -#include - -#include "dumpers/itemnodedumper.h" -#include "dumpers/textnodedumper.h" -#include "dumpers/assetnodedumper.h" - -#include "coreplugin/actionmanager/actionmanager.h" -#include "coreplugin/actionmanager/actioncontainer.h" -#include "coreplugin/documentmanager.h" -#include "qmldesigner/qmldesignerplugin.h" -#include "projectexplorer/projectexplorerconstants.h" -#include "projectexplorer/projectmanager.h" -#include "projectexplorer/project.h" -#include "projectexplorer/projectmanager.h" -#include "projectexplorer/taskhub.h" - -#include "extensionsystem/pluginmanager.h" -#include "extensionsystem/pluginspec.h" - -#include "utils/algorithm.h" - -#include -#include - -#include - -namespace QmlDesigner { - -AssetExporterPlugin::AssetExporterPlugin() - : m_projectManager{QmlDesigner::QmlDesignerPlugin::projectManagerForPluginInitializationOnly()} -{ - ProjectExplorer::TaskHub::addCategory({Constants::TASK_CATEGORY_ASSET_EXPORT, - tr("Asset Export"), - tr("Issues with exporting assets."), - false}); - - auto *designerPlugin = QmlDesigner::QmlDesignerPlugin::instance(); - - auto &viewManager = designerPlugin->viewManager(); - m_view = viewManager.registerView(std::make_unique( - designerPlugin->externalDependenciesForPluginInitializationOnly())); - - // Add dumper templates for factory instantiation. - Component::addNodeDumper(); - Component::addNodeDumper(); - Component::addNodeDumper(); - - // Instantiate actions created by the plugin. - addActions(); - - connect(ProjectExplorer::ProjectManager::instance(), - &ProjectExplorer::ProjectManager::startupProjectChanged, - this, &AssetExporterPlugin::updateActions); - - updateActions(); -} - -QString AssetExporterPlugin::pluginName() const -{ - return QLatin1String("AssetExporterPlugin"); -} - -void AssetExporterPlugin::onExport() -{ - auto startupProject = ProjectExplorer::ProjectManager::startupProject(); - if (!startupProject) - return; - - FilePathModel model(startupProject); - auto exportDir = startupProject->projectFilePath().parentDir(); - if (!exportDir.parentDir().isEmpty()) - exportDir = exportDir.parentDir(); - exportDir = exportDir.pathAppended(startupProject->displayName() + "_export"); - AssetExporter assetExporter(m_view, startupProject, m_projectManager.projectStorageDependencies()); - AssetExportDialog assetExporterDialog(exportDir, assetExporter, model); - assetExporterDialog.exec(); -} - -void AssetExporterPlugin::addActions() -{ - auto exportAction = new QAction(tr("Export Components"), this); - exportAction->setToolTip(tr("Export components in the current project.")); - connect(exportAction, &QAction::triggered, this, &AssetExporterPlugin::onExport); - Core::Command *cmd = Core::ActionManager::registerAction(exportAction, Constants::EXPORT_QML); - - // Add action to build menu - Core::ActionContainer *buildMenu = - Core::ActionManager::actionContainer(ProjectExplorer::Constants::M_BUILDPROJECT); - buildMenu->addAction(cmd, ProjectExplorer::Constants::G_BUILD_RUN); -} - -void AssetExporterPlugin::updateActions() -{ - auto project = ProjectExplorer::ProjectManager::startupProject(); - QAction* const exportAction = Core::ActionManager::command(Constants::EXPORT_QML)->action(); - exportAction->setEnabled(project && !project->needsConfiguration()); -} - -QString AssetExporterPlugin::metaInfo() const -{ - return QLatin1String(":/assetexporterplugin/assetexporterplugin.metainfo"); -} - -} //QmlDesigner diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.h b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.h deleted file mode 100644 index 1c2b779138e..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#pragma once - -#include - - -namespace QmlDesigner { -class AssetExporter; -class AssetExporterView; -class AssetExporterPlugin : public QObject, QmlDesigner::IWidgetPlugin -{ - Q_OBJECT - - Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QmlDesignerPlugin" FILE "assetexporterplugin.json") - - Q_DISABLE_COPY(AssetExporterPlugin) - Q_INTERFACES(QmlDesigner::IWidgetPlugin) - -public: - AssetExporterPlugin(); - - QString metaInfo() const final; - QString pluginName() const final; - -private: - void onExport(); - void addActions(); - void updateActions(); - - AssetExporterView *m_view = nullptr; - class QmlDesignerProjectManager &m_projectManager; -}; - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.json b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.json deleted file mode 100644 index a925eaca8e1..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "Vendor" : "The Qt Company Ltd", - "Category" : "Qt Quick", - "Description" : "Plugin for exporting assets and QML from QmlDesigner", - "Url" : "http://www.qt.io" -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.metainfo b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.metainfo deleted file mode 100644 index 5bfe70cffdf..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.metainfo +++ /dev/null @@ -1,2 +0,0 @@ -MetaInfo { -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.qrc b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.qrc deleted file mode 100644 index 8db1e0adafb..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.qrc +++ /dev/null @@ -1,5 +0,0 @@ - - - assetexporterplugin.metainfo - - diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.cpp deleted file mode 100644 index ef41f435adb..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.cpp +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "assetexporterview.h" - -#include "qmlitemnode.h" -#include "rewriterview.h" - -#include "coreplugin/editormanager/editormanager.h" -#include "coreplugin/editormanager/ieditor.h" -#include "coreplugin/modemanager.h" -#include "coreplugin/coreconstants.h" - -#include - -namespace { -Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.view", QtInfoMsg) -Q_LOGGING_CATEGORY(loggerWarn, "qtc.designer.assetExportPlugin.view", QtWarningMsg) -//Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.view", QtCriticalMsg) - -const int RetryIntervalMs = 500; -const int MinRetry = 2; -} - -namespace QmlDesigner { - -AssetExporterView::AssetExporterView(ExternalDependenciesInterface &externalDependencies) - : AbstractView(externalDependencies) - , m_timer(this) -{ - m_timer.setInterval(RetryIntervalMs); - // We periodically check if file is loaded. - connect(&m_timer, &QTimer::timeout, this, &AssetExporterView::handleTimerTimeout); -} - - -bool AssetExporterView::loadQmlFile(const Utils::FilePath &path, uint timeoutSecs) -{ - qCDebug(loggerInfo) << "Load file" << path; - if (loadingState() == LoadState::Busy) - return false; - - setState(LoadState::Busy); - m_retryCount = std::max(MinRetry, static_cast((timeoutSecs * 1000) / RetryIntervalMs)); - m_currentEditor = Core::EditorManager::openEditor(path, Utils::Id(), - Core::EditorManager::DoNotMakeVisible); - Core::ModeManager::activateMode(Core::Constants::MODE_DESIGN); - Core::ModeManager::setFocusToCurrentMode(); - m_timer.start(); - return true; -} - -Utils::Result AssetExporterView::saveQmlFile() const -{ - if (!m_currentEditor) { - qCDebug(loggerWarn) << "Saving QML file failed. No editor."; - return Utils::Result::Error("Saving QML file failed. No editor."); - } - - return m_currentEditor->document()->save(); -} - -void AssetExporterView::modelAttached(Model *model) -{ - if (model->rewriterView() && !model->rewriterView()->errors().isEmpty()) - setState(LoadState::QmlErrorState); - - AbstractView::modelAttached(model); -} - -void AssetExporterView:: -instanceInformationsChanged(const QMultiHash &informationChangeHash) -{ - if (inErrorState() || loadingState() == LoadState::Loaded) - return; // Already reached error or connected state. - - // We expect correct dimensions are available if the rootnode's - // information change message is received. - const auto nodes = informationChangeHash.keys(); - bool hasRootNode = std::any_of(nodes.begin(), nodes.end(), [](const ModelNode &n) { - return n.isRootNode(); - }); - if (hasRootNode) - handleMaybeDone(); -} - -void AssetExporterView::instancesPreviewImageChanged([[maybe_unused]] const QVector &nodeList) -{ - emit previewChanged(); -} - -bool AssetExporterView::inErrorState() const -{ - return m_state == LoadState::Exausted || m_state == LoadState::QmlErrorState; -} - -bool AssetExporterView::isLoaded() const -{ - return isAttached() && QmlItemNode(rootModelNode()).isValid(); -} - -void AssetExporterView::setState(AssetExporterView::LoadState state) -{ - if (state != m_state) { - m_state = state; - qCDebug(loggerInfo) << "Loading state changed" << m_state; - if (inErrorState() || m_state == LoadState::Loaded) { - m_timer.stop(); - // TODO: Send the loaded signal with a delay. The assumption that model attached and a - // valid root object is enough to declare a QML file is ready is incorrect. A ideal - // solution would be that the QML Puppet notifies file ready signal. - if (m_state == LoadState::Loaded) - QTimer::singleShot(2000, this, &AssetExporterView::loadingFinished); - else - emit loadingError(m_state); - } - } -} - -void AssetExporterView::handleMaybeDone() -{ - if (isLoaded()) - setState(LoadState::Loaded); -} - -void AssetExporterView::handleTimerTimeout() -{ - if (!inErrorState() && loadingState() != LoadState::Loaded) - handleMaybeDone(); - - if (--m_retryCount < 0) - setState(LoadState::Exausted); -} - -} - -QT_BEGIN_NAMESPACE -QDebug operator<<(QDebug os, const QmlDesigner::AssetExporterView::LoadState &s) -{ - os << static_cast::type>(s); - return os; -} -QT_END_NAMESPACE diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.h b/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.h deleted file mode 100644 index 7ed01126e21..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.h +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#pragma once - -#include "abstractview.h" - -#include - -#include -#include - -namespace Core { class IEditor; } - -namespace QmlDesigner { - -class AssetExporterView : public AbstractView -{ - Q_OBJECT -public: - enum class LoadState { - Idle = 1, - Busy, - Exausted, - QmlErrorState, - Loaded - }; - - AssetExporterView(ExternalDependenciesInterface &externalDependencies); - - bool loadQmlFile(const Utils::FilePath &path, uint timeoutSecs = 10); - Utils::Result saveQmlFile() const; - - void modelAttached(Model *model) override; - void instanceInformationsChanged(const QMultiHash &informationChangeHash) override; - void instancesPreviewImageChanged(const QVector &nodeList) override; - - LoadState loadingState() const { return m_state; } - bool inErrorState() const; - -signals: - void loadingFinished(); - void loadingError(LoadState); - void previewChanged(); - -private: - bool isLoaded() const; - void setState(LoadState state); - void handleMaybeDone(); - void handleTimerTimeout(); - - Core::IEditor *m_currentEditor = nullptr; - QTimer m_timer; - int m_retryCount = 0; - LoadState m_state = LoadState::Idle; - bool m_waitForPuppet = false; -}; - -} - -QT_BEGIN_NAMESPACE -QDebug operator<<(QDebug os, const QmlDesigner::AssetExporterView::LoadState &s); -QT_END_NAMESPACE diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h b/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h deleted file mode 100644 index 02d56b4eab6..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#pragma once - -#include - -namespace QmlDesigner { -namespace Constants { - -const char EXPORT_QML[] = "Designer.ExportPlugin.ExportQml"; - -const char TASK_CATEGORY_ASSET_EXPORT[] = "AssetExporter.Export"; - -//*************************************************************************** -// Metadata tags -//*************************************************************************** -// Plugin info tags -const char PluginInfoTag[] = "pluginInfo"; -const char MetadataVersionTag[] = "metadataVersion"; - -const char DocumentInfoTag[] = "documentInfo"; -const char DocumentNameTag[] = "name"; - -// Layer data tags -const char ArtboardListTag[] = "artboards"; - -const char NameTag[] = "name"; - -const char XPosTag[] = "x"; -const char YPosTag[] = "y"; -const char WidthTag[] = "width"; -const char HeightTag[] = "height"; - -const char MetadataTag[] = "metadata"; -const char ChildrenTag[] = "children"; -const char CustomIdTag[] = "customId"; -const char QmlIdTag[] = "qmlId"; -const char ExportTypeTag[] = "exportType"; -const char ExportTypeComponent[] = "component"; -const char ExportTypeChild[] = "child"; -const char QmlPropertiesTag[] = "qmlProperties"; -const char ImportsTag[] = "extraImports"; -const char UuidTag[] = "uuid"; -const char ClipTag[] = "clip"; -const char AssetDataTag[] = "assetData"; -const char ReferenceAssetTag[] = "referenceAsset"; -const char AssetPathTag[] = "assetPath"; -const char AssetBoundsTag[] = "assetBounds"; -const char OpacityTag[] = "opacity"; -const char TypeNameTag[] = "typeName"; -const char TypeIdTag[] = "typeId"; - -const char TextDetailsTag[] = "textDetails"; -const char FontFamilyTag[] = "fontFamily"; -const char FontSizeTag[] = "fontSize"; -const char FontStyleTag[] = "fontStyle"; -const char LetterSpacingTag[] = "kerning"; -const char TextColorTag[] = "textColor"; -const char TextContentTag[] = "contents"; -const char IsMultilineTag[] = "multiline"; -const char LineHeightTag[] = "lineHeight"; -const char HAlignTag[] = "horizontalAlignment"; -const char VAlignTag[] = "verticalAlignment"; - -} -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp b/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp deleted file mode 100644 index 7530bd9a7ba..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "componentexporter.h" -#include "assetexporter.h" -#include "assetexportpluginconstants.h" -#include "exportnotification.h" -#include "dumpers/nodedumper.h" - -#include "model.h" -#include "nodeabstractproperty.h" -#include "nodemetainfo.h" -#include "qmlitemnode.h" -#include "rewriterview.h" - -#include "utils/qtcassert.h" - -#include -#include -#include -#include - -namespace { -Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.modelExporter", QtInfoMsg) -} - -namespace QmlDesigner { -using namespace Constants; - -std::vector> Component::m_readers; -Component::Component(AssetExporter &exporter, const ModelNode &rootNode): - m_exporter(exporter), - m_rootNode(rootNode) -{ - m_name = m_rootNode.id(); - if (m_name.isEmpty()) - m_name = QString::fromUtf8(m_rootNode.type()); -} - -QJsonObject Component::json() const -{ - return m_json; -} - -AssetExporter &Component::exporter() -{ - return m_exporter; -} - -void Component::exportComponent() -{ - QTC_ASSERT(m_rootNode.isValid(), return); - m_json = nodeToJson(m_rootNode); - // Change the export type to component - QJsonObject metadata = m_json.value(MetadataTag).toObject(); - metadata.insert(ExportTypeTag, ExportTypeComponent); - addReferenceAsset(metadata); - m_json.insert(MetadataTag, metadata); - addImports(); -} - -const QString &Component::name() const -{ - return m_name; -} - -NodeDumper *Component::createNodeDumper(const ModelNode &node) const -{ - std::unique_ptr reader; - for (auto &dumperCreator: m_readers) { - std::unique_ptr r(dumperCreator->instance(node)); - if (r->isExportable()) { - if (reader) { - if (reader->priority() < r->priority()) - reader = std::move(r); - } else { - reader = std::move(r); - } - } - } - - if (!reader) - qCDebug(loggerInfo()) << "No dumper for node" << node; - - return reader.release(); -} - -QJsonObject Component::nodeToJson(const ModelNode &node) -{ - QJsonObject jsonObject; - - // Don't export States, Connection, Timeline etc nodes. - if (!node.metaInfo().isQtQuickItem()) - return {}; - - std::unique_ptr dumper(createNodeDumper(node)); - if (dumper) { - jsonObject = dumper->json(*this); - } else { - ExportNotification::addError(tr("Error exporting node %1. Cannot parse type %2.") - .arg(node.id()).arg(QString::fromUtf8(node.type()))); - } - - QJsonArray children; - for (const ModelNode &childnode : node.directSubModelNodes()) { - const QJsonObject childJson = nodeToJson(childnode); - if (!childJson.isEmpty()) - children.append(childJson); - } - - if (!children.isEmpty()) - jsonObject.insert(ChildrenTag, children); - - return jsonObject; -} - -void Component::addReferenceAsset(QJsonObject &metadataObject) const -{ - QPixmap refAsset = m_exporter.generateAsset(m_rootNode); - stichChildrendAssets(m_rootNode, refAsset); - Utils::FilePath refAssetPath = m_exporter.assetPath(m_rootNode, this, "_ref"); - m_exporter.exportAsset(refAsset, refAssetPath); - QJsonObject assetData; - if (metadataObject.contains(AssetDataTag)) - assetData = metadataObject[AssetDataTag].toObject(); - assetData.insert(ReferenceAssetTag, refAssetPath.toUrlishString()); - metadataObject.insert(AssetDataTag, assetData); -} - -void Component::stichChildrendAssets(const ModelNode &node, QPixmap &parentPixmap) const -{ - if (!node.hasAnySubModelNodes()) - return; - - QPainter painter(&parentPixmap); - for (const ModelNode &child : node.directSubModelNodes()) { - QPixmap childPixmap = m_exporter.generateAsset(child); - if (childPixmap.isNull()) - continue; - stichChildrendAssets(child, childPixmap); - QTransform cTransform = QmlObjectNode(child).toQmlItemNode().instanceTransform(); - painter.setTransform(cTransform); - painter.drawPixmap(QPoint(0, 0), childPixmap); - } - painter.end(); -} - -void Component::addImports() -{ - QJsonArray importsArray; - for (const Import &import : m_rootNode.model()->imports()) - importsArray.append(import.toString()); - - if (!importsArray.empty()) - m_json.insert(Constants::ImportsTag, importsArray); -} - - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.h b/src/plugins/qmldesigner/assetexporterplugin/componentexporter.h deleted file mode 100644 index 59ee8a2e677..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.h +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#pragma once - -#include -#include -#include - -#include -#include - -#include "utils/qtcassert.h" - -QT_BEGIN_NAMESPACE -class QJsonArray; -QT_END_NAMESPACE - -namespace QmlDesigner { -class AssetExporter; -class ModelNode; -class Component; -class NodeDumper; - -namespace Internal { -class NodeDumperCreatorBase -{ -public: - virtual ~NodeDumperCreatorBase() {} -protected: - virtual NodeDumper *instance(const ModelNode &) const = 0; - friend Component; -}; - -template -class NodeDumperCreator : public NodeDumperCreatorBase -{ -public: - NodeDumperCreator() = default; - ~NodeDumperCreator() = default; - -protected: - NodeDumper *instance(const ModelNode &node) const { return new T(node); } -}; -} //Internal - -class Component -{ - Q_DECLARE_TR_FUNCTIONS(Component); - -public: - Component(AssetExporter& exporter, const ModelNode &rootNode); - - void exportComponent(); - const QString &name() const; - QJsonObject json() const; - - AssetExporter &exporter(); - - template static void addNodeDumper() - { - QTC_ASSERT((std::is_base_of::value), return); - m_readers.push_back(std::make_unique>()); - } -private: - NodeDumper* createNodeDumper(const ModelNode &node) const; - QJsonObject nodeToJson(const ModelNode &node); - void addReferenceAsset(QJsonObject &metadataObject) const; - void stichChildrendAssets(const ModelNode &node, QPixmap &parentPixmap) const; - void addImports(); - -private: - AssetExporter& m_exporter; - const ModelNode &m_rootNode; - QString m_name; - QJsonObject m_json; - static std::vector> m_readers; -}; -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/dumpers/assetnodedumper.cpp b/src/plugins/qmldesigner/assetexporterplugin/dumpers/assetnodedumper.cpp deleted file mode 100644 index 9561044c3ac..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/dumpers/assetnodedumper.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "assetnodedumper.h" -#include "assetexportpluginconstants.h" -#include "assetexporter.h" - -#include "qmlitemnode.h" -#include "componentexporter.h" - -#include "utils/fileutils.h" - -#include - -namespace QmlDesigner { -using namespace Constants; - -AssetNodeDumper::AssetNodeDumper(const ModelNode &node) - : ItemNodeDumper(node) -{ - -} - -bool AssetNodeDumper::isExportable() const -{ - auto qtQuickImageMetaInfo = model()->qtQuickImageMetaInfo(); - auto qtQuickRectangleMetaInfo = model()->qtQuickRectangleMetaInfo(); - return metaInfo().isBasedOn(qtQuickImageMetaInfo, qtQuickRectangleMetaInfo); -} - -QJsonObject AssetNodeDumper::json(Component &component) const -{ - QJsonObject jsonObject = ItemNodeDumper::json(component); - - AssetExporter &exporter = component.exporter(); - const Utils::FilePath assetPath = exporter.assetPath(m_node, &component); - exporter.exportAsset(exporter.generateAsset(m_node), assetPath); - - QJsonObject assetData; - assetData.insert(AssetPathTag, assetPath.toUrlishString()); - QJsonObject metadata = jsonObject.value(MetadataTag).toObject(); - metadata.insert(AssetDataTag, assetData); - jsonObject.insert(MetadataTag, metadata); - return jsonObject; -} -} - diff --git a/src/plugins/qmldesigner/assetexporterplugin/dumpers/assetnodedumper.h b/src/plugins/qmldesigner/assetexporterplugin/dumpers/assetnodedumper.h deleted file mode 100644 index 1a5449efafc..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/dumpers/assetnodedumper.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#pragma once - -#include "itemnodedumper.h" - -namespace QmlDesigner { -class Component; - -class AssetNodeDumper : public ItemNodeDumper -{ -public: - AssetNodeDumper(const ModelNode &node); - ~AssetNodeDumper() override = default; - - bool isExportable() const override; - int priority() const override { return 200; } - QJsonObject json(Component &component) const override; -}; -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/dumpers/itemnodedumper.cpp b/src/plugins/qmldesigner/assetexporterplugin/dumpers/itemnodedumper.cpp deleted file mode 100644 index e7159d76626..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/dumpers/itemnodedumper.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "itemnodedumper.h" -#include "assetexportpluginconstants.h" -#include "assetexporter.h" -#include "componentexporter.h" - -#include "qmlitemnode.h" -#include "annotation.h" - -namespace { -static QString capitalize(const QString &str) -{ - if (str.isEmpty()) - return {}; - QString tmp = str; - tmp[0] = QChar(str[0]).toUpper().toLatin1(); - return tmp; -} -} - -namespace QmlDesigner { -using namespace Constants; - -ItemNodeDumper::ItemNodeDumper(const ModelNode &node) - : NodeDumper(node) -{ - -} - -bool QmlDesigner::ItemNodeDumper::isExportable() const -{ - return metaInfo().isQtQuickItem(); -} - -QJsonObject QmlDesigner::ItemNodeDumper::json([[maybe_unused]] QmlDesigner::Component &component) const -{ - const QmlObjectNode &qmlObjectNode = objectNode(); - QJsonObject jsonObject; - - const QString qmlId = qmlObjectNode.id(); - QString name = m_node.simplifiedTypeName(); - if (!qmlId.isEmpty()) - name.append("_" + capitalize(qmlId)); - - jsonObject.insert(NameTag, name); - - // Position relative to parent - QmlItemNode itemNode = qmlObjectNode.toQmlItemNode(); - QPointF pos = itemNode.instancePosition(); - jsonObject.insert(XPosTag, pos.x()); - jsonObject.insert(YPosTag, pos.y()); - - // size - QSizeF size = itemNode.instanceSize(); - jsonObject.insert(WidthTag, size.width()); - jsonObject.insert(HeightTag, size.height()); - - QJsonObject metadata; - metadata.insert(QmlIdTag, qmlId); - metadata.insert(UuidTag, uuid()); - metadata.insert(ExportTypeTag, ExportTypeChild); - metadata.insert(TypeNameTag, QString::fromLatin1(m_node.type())); - - if (m_node.hasCustomId()) - metadata.insert(CustomIdTag, m_node.customId()); - - QString typeId = component.exporter().componentUuid(m_node); - if (!typeId.isEmpty()) - metadata.insert(TypeIdTag, typeId); - - jsonObject.insert(MetadataTag, metadata); - return jsonObject; -} -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/dumpers/itemnodedumper.h b/src/plugins/qmldesigner/assetexporterplugin/dumpers/itemnodedumper.h deleted file mode 100644 index 9752d37caef..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/dumpers/itemnodedumper.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#pragma once - -#include "nodedumper.h" - -namespace QmlDesigner { -class ModelNode; -class Component; - -class ItemNodeDumper : public NodeDumper -{ -public: - ItemNodeDumper(const ModelNode &node); - - ~ItemNodeDumper() override = default; - - int priority() const override { return 100; } - bool isExportable() const override; - QJsonObject json(Component &component) const override; -}; -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/dumpers/nodedumper.cpp b/src/plugins/qmldesigner/assetexporterplugin/dumpers/nodedumper.cpp deleted file mode 100644 index 521180f2f28..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/dumpers/nodedumper.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "nodedumper.h" -#include "assetexportpluginconstants.h" - -#include - -namespace QmlDesigner { -NodeDumper::NodeDumper(const ModelNode &node) - : m_node(node) - , m_objectNode(node) - , m_metaInfo(node.metaInfo()) - , m_model{node.model()} -{ - -} - -QVariant NodeDumper::propertyValue(const PropertyName &name) const -{ - return m_objectNode.instanceValue(name); -} - -QString NodeDumper::uuid() const -{ - return m_node.auxiliaryDataWithDefault(uuidProperty).toString(); -} - -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/dumpers/nodedumper.h b/src/plugins/qmldesigner/assetexporterplugin/dumpers/nodedumper.h deleted file mode 100644 index 17bace9ad61..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/dumpers/nodedumper.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#pragma once - -#include "qmlobjectnode.h" - -#include -#include - -namespace QmlDesigner { -class Component; -class ModelNode; - -class NodeDumper -{ -public: - NodeDumper(const ModelNode &node); - - virtual ~NodeDumper() = default; - - virtual int priority() const = 0; - virtual bool isExportable() const = 0; - virtual QJsonObject json(Component& component) const = 0; - - const NodeMetaInfo &metaInfo() const { return m_metaInfo; } - const QmlObjectNode& objectNode() const { return m_objectNode; } - QVariant propertyValue(const PropertyName &name) const; - QString uuid() const; - - Model *model() const { return m_model; } - -protected: - const ModelNode &m_node; - -private: - QmlObjectNode m_objectNode; - NodeMetaInfo m_metaInfo; - Model *m_model = nullptr; -}; -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/dumpers/textnodedumper.cpp b/src/plugins/qmldesigner/assetexporterplugin/dumpers/textnodedumper.cpp deleted file mode 100644 index 5f96d3e2175..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/dumpers/textnodedumper.cpp +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "textnodedumper.h" -#include "assetexportpluginconstants.h" - -#include - -#include -#include -#include -#include -#include - -#include - -#include - -namespace { -const QHash AlignMapping{ - {"AlignRight", "RIGHT"}, - {"AlignHCenter", "CENTER"}, - {"AlignJustify", "JUSTIFIED"}, - {"AlignLeft", "LEFT"}, - {"AlignTop", "TOP"}, - {"AlignVCenter", "CENTER"}, - {"AlignBottom", "BOTTOM"} -}; - -QString toJsonAlignEnum(QString value) { - if (value.isEmpty() || !AlignMapping.contains(value)) - return {}; - return AlignMapping[value]; -} -} - - -namespace QmlDesigner { -using namespace Constants; - -TextNodeDumper::TextNodeDumper(const ModelNode &node) - : ItemNodeDumper(node) -{ - -} - -bool TextNodeDumper::isExportable() const -{ - auto qtQuickTextMetaInfo = model()->qtQuickTextMetaInfo(); - auto qtQuickControlsLabelMetaInfo = model()->qtQuickControlsLabelMetaInfo(); - return metaInfo().isBasedOn(qtQuickTextMetaInfo, qtQuickControlsLabelMetaInfo); -} - -QJsonObject TextNodeDumper::json([[maybe_unused]] Component &component) const -{ - QJsonObject jsonObject = ItemNodeDumper::json(component); - - QJsonObject textDetails; - textDetails.insert(TextContentTag, propertyValue("text").toString()); - - QFont font = propertyValue("font").value(); - QFontInfo fontInfo(font); - textDetails.insert(FontFamilyTag, fontInfo.family()); - textDetails.insert(FontStyleTag, fontInfo.styleName()); - textDetails.insert(FontSizeTag, fontInfo.pixelSize()); - textDetails.insert(LetterSpacingTag, font.letterSpacing()); - - - QColor fontColor(propertyValue("font.color").toString()); - textDetails.insert(TextColorTag, fontColor.name(QColor::HexArgb)); - - textDetails.insert(HAlignTag, toJsonAlignEnum(propertyValue("horizontalAlignment").toString())); - textDetails.insert(VAlignTag, toJsonAlignEnum(propertyValue("verticalAlignment").toString())); - - textDetails.insert(IsMultilineTag, propertyValue("wrapMode").toString().compare("NoWrap") != 0); - - // Calculate line height in pixels - QFontMetricsF fm(font); - auto lineHeightMode = propertyValue("lineHeightMode").value(); - double lineHeight = propertyValue("lineHeight").toDouble(); - qreal lineHeightPx = (lineHeightMode == QQuickText::FixedHeight) ? - lineHeight : qCeil(fm.height()) * lineHeight; - textDetails.insert(LineHeightTag, lineHeightPx); - - QJsonObject metadata = jsonObject.value(MetadataTag).toObject(); - metadata.insert(TextDetailsTag, textDetails); - jsonObject.insert(MetadataTag, metadata); - return jsonObject; -} -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/dumpers/textnodedumper.h b/src/plugins/qmldesigner/assetexporterplugin/dumpers/textnodedumper.h deleted file mode 100644 index b084bdae48c..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/dumpers/textnodedumper.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#pragma once - -#include "itemnodedumper.h" - -namespace QmlDesigner { -class Component; - -class TextNodeDumper : public ItemNodeDumper -{ -public: - TextNodeDumper(const ModelNode &node); - ~TextNodeDumper() override = default; - - bool isExportable() const override; - int priority() const override { return 200; } - QJsonObject json(Component &component) const override; -}; - -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/exportnotification.cpp b/src/plugins/qmldesigner/assetexporterplugin/exportnotification.cpp deleted file mode 100644 index e0dc335f3a5..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/exportnotification.cpp +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "exportnotification.h" -#include "assetexportpluginconstants.h" - -#include "projectexplorer/taskhub.h" - -#include - -namespace { -Q_LOGGING_CATEGORY(loggerDebug, "qtc.designer.assetExportPlugin.exportNotification", QtDebugMsg) -} - -using namespace ProjectExplorer; -namespace { -static void addTask(Task::TaskType type, const QString &desc) -{ - qCDebug(loggerDebug) << desc; - Task task(type, desc, {}, -1, QmlDesigner::Constants::TASK_CATEGORY_ASSET_EXPORT); - TaskHub::addTask(task); -} -} - -namespace QmlDesigner { - -void ExportNotification::addError(const QString &errMsg) -{ - addTask(Task::Error, errMsg); -} - -void ExportNotification::addWarning(const QString &warningMsg) -{ - addTask(Task::Warning, warningMsg); -} - -void ExportNotification::addInfo(const QString &infoMsg) -{ - addTask(Task::Unknown, infoMsg); -} -} // QmlDesigner diff --git a/src/plugins/qmldesigner/assetexporterplugin/exportnotification.h b/src/plugins/qmldesigner/assetexporterplugin/exportnotification.h deleted file mode 100644 index e27124ce530..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/exportnotification.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -namespace QmlDesigner { -class ExportNotification -{ -public: - static void addError(const QString &errMsg); - static void addWarning(const QString &warningMsg); - static void addInfo(const QString &infoMsg); -}; -} // QmlDesigner diff --git a/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.cpp b/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.cpp deleted file mode 100644 index f5df42df396..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.cpp +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "filepathmodel.h" - -#include "exportnotification.h" - -#include -#include - -#include - -#include -#include - -using namespace ProjectExplorer; - -namespace { -Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.filePathModel", QtCriticalMsg) -Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.filePathModel", QtInfoMsg) - -void findQmlFiles(QPromise &promise, const Project *project) -{ - if (!project || promise.isCanceled()) - return; - - int index = 0; - project->files([&promise, &index](const Node* node) ->bool { - if (promise.isCanceled()) - return false; - Utils::FilePath path = node->filePath(); - bool isComponent = !path.fileName().isEmpty() && path.fileName().front().isUpper(); - if (isComponent && node->filePath().endsWith(".ui.qml")) - promise.addResult(path, index++); - return true; - }); -} -} - -namespace QmlDesigner { - -FilePathModel::FilePathModel(ProjectExplorer::Project *project, QObject *parent) - : QAbstractListModel(parent), - m_project(project) -{ - QTimer::singleShot(0, this, &FilePathModel::processProject); -} - -FilePathModel::~FilePathModel() -{ - if (m_preprocessWatcher && !m_preprocessWatcher->isCanceled() && - !m_preprocessWatcher->isFinished()) { - ExportNotification::addInfo(tr("Canceling file preparation.")); - m_preprocessWatcher->cancel(); - m_preprocessWatcher->waitForFinished(); - qCDebug(loggerInfo) << "Canceled file preparation."; - } -} - -Qt::ItemFlags FilePathModel::flags(const QModelIndex &index) const -{ - Qt::ItemFlags itemFlags = QAbstractListModel::flags(index); - if (index.isValid()) - itemFlags |= Qt::ItemIsUserCheckable; - return itemFlags; -} - -int FilePathModel::rowCount(const QModelIndex &parent) const -{ - if (!parent.isValid()) - return m_files.size(); - return 0; -} - -QVariant FilePathModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return {}; - - switch (role) { - case Qt::DisplayRole: - return m_files[index.row()].toUserOutput(); - case Qt::CheckStateRole: - return m_skipped.count(m_files[index.row()]) ? Qt::Unchecked : Qt::Checked; - default: - break; - } - - return {}; -} - -bool FilePathModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (!index.isValid() || role != Qt::CheckStateRole) - return false; - - const Utils::FilePath path = m_files[index.row()]; - if (value == Qt::Checked) - m_skipped.erase(path); - else - m_skipped.insert(path); - - emit dataChanged(index, index); - return true; -} - -Utils::FilePaths FilePathModel::files() const -{ - Utils::FilePaths selectedPaths; - std::copy_if(m_files.begin(), m_files.end(), std::back_inserter(selectedPaths), - [this](const Utils::FilePath &path) { - return !m_skipped.count(path); - }); - return selectedPaths; -} - -void FilePathModel::processProject() -{ - if (m_preprocessWatcher && !m_preprocessWatcher->isCanceled() && - !m_preprocessWatcher->isFinished()) { - qCDebug(loggerError) << "Previous model load not finished."; - return; - } - - beginResetModel(); - m_preprocessWatcher.reset(new QFutureWatcher(this)); - connect(m_preprocessWatcher.get(), - &QFutureWatcher::resultReadyAt, - this, - [this](int resultIndex) { - beginInsertRows(index(0, 0), m_files.size(), m_files.size()); - m_files.append(m_preprocessWatcher->resultAt(resultIndex)); - endInsertRows(); - }); - - connect(m_preprocessWatcher.get(), &QFutureWatcher::finished, - this, &FilePathModel::endResetModel); - - QFuture f = Utils::asyncRun(&findQmlFiles, m_project); - m_preprocessWatcher->setFuture(f); -} - - -} diff --git a/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.h b/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.h deleted file mode 100644 index 8158c578182..00000000000 --- a/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -#include -#include -#include - -#include -#include - -namespace ProjectExplorer { -class Project; -} - -namespace QmlDesigner { -class FilePathModel : public QAbstractListModel -{ - Q_DECLARE_TR_FUNCTIONS(QmlDesigner::FilePathModel) - -public: - FilePathModel(ProjectExplorer::Project *project, QObject *parent = nullptr); - ~FilePathModel() override; - - Qt::ItemFlags flags(const QModelIndex &index) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - bool setData(const QModelIndex &index, const QVariant &value, int role) override; - - Utils::FilePaths files() const; -private: - void processProject(); - - ProjectExplorer::Project *m_project = nullptr; - std::unique_ptr> m_preprocessWatcher; - std::unordered_set m_skipped; - Utils::FilePaths m_files; -}; - -} From 3e1049e5369994af2a451a0cdcea518b359ca57d Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Thu, 27 Feb 2025 15:27:28 +0100 Subject: [PATCH 38/54] Utils: Use [[likely]] and [[unlikely]] Change-Id: I82cd1873c57c01cf39796e624e97dc9f0fdfbf0f Reviewed-by: Thomas Hartmann --- src/libs/utils/smallstring.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libs/utils/smallstring.h b/src/libs/utils/smallstring.h index f502c7e7489..8f3f4eed310 100644 --- a/src/libs/utils/smallstring.h +++ b/src/libs/utils/smallstring.h @@ -112,13 +112,13 @@ public: ~BasicSmallString() noexcept { - if (Q_UNLIKELY(hasAllocatedMemory())) + if (hasAllocatedMemory()) [[unlikely]] Memory::deallocate(m_data.data()); } BasicSmallString(const BasicSmallString &other) noexcept { - if (Q_LIKELY(other.isShortString() || other.isReadOnlyReference())) + if (other.isShortString() || other.isReadOnlyReference()) [[likely]] m_data = other.m_data; else new (this) BasicSmallString{other.data(), other.size()}; @@ -126,10 +126,10 @@ public: BasicSmallString &operator=(const BasicSmallString &other) noexcept { - if (Q_LIKELY(this != &other)) { + if (this != &other) [[likely]] { this->~BasicSmallString(); - if (Q_LIKELY(other.isShortString() || other.isReadOnlyReference())) + if (other.isShortString() || other.isReadOnlyReference()) [[likely]] m_data = other.m_data; else new (this) BasicSmallString{other.data(), other.size()}; @@ -146,7 +146,7 @@ public: BasicSmallString &operator=(BasicSmallString &&other) noexcept { - if (Q_LIKELY(this != &other)) { + if (this != &other) [[likely]] { this->~BasicSmallString(); m_data = std::move(other.m_data); @@ -202,7 +202,7 @@ public: void reserve(size_type newCapacity) noexcept { if (fitsNotInCapacity(newCapacity)) { - if (Q_UNLIKELY(hasAllocatedMemory())) { + if (hasAllocatedMemory()) { m_data.setPointer(Memory::reallocate(m_data.data(), newCapacity)); m_data.setAllocatedCapacity(newCapacity); } else { From 993e88e088e6ceed36a122aa2720bd1bad7195f2 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Thu, 27 Feb 2025 17:16:39 +0100 Subject: [PATCH 39/54] Utils: Remove more SmallStringView member With C++ 23 we can hopefully remove the completely. Change-Id: Ia656bf23bf8e80a3737d37c8b7321aa839c04b69 Reviewed-by: Thomas Hartmann --- src/libs/sqlite/sqlstatementbuilder.cpp | 2 +- src/libs/utils/smallstringview.h | 54 +-------- .../sourcepathstorage/sourcepathcache.h | 2 +- .../sourcepathstorage/sourcepathview.h | 6 +- .../project/qmldesignerprojectmanager.cpp | 4 +- .../unittests/utils/smallstring-test.cpp | 111 ------------------ 6 files changed, 8 insertions(+), 171 deletions(-) diff --git a/src/libs/sqlite/sqlstatementbuilder.cpp b/src/libs/sqlite/sqlstatementbuilder.cpp index 243fb6460af..6602c3903ac 100644 --- a/src/libs/sqlite/sqlstatementbuilder.cpp +++ b/src/libs/sqlite/sqlstatementbuilder.cpp @@ -191,7 +191,7 @@ void SqlStatementBuilder::clearSqlStatement() void SqlStatementBuilder::checkIfPlaceHolderExists(Utils::SmallStringView name) const { - if (name.size() < 2 || !name.startsWith('$') || !m_sqlTemplate.contains(name)) + if (name.size() < 2 || !name.starts_with('$') || !m_sqlTemplate.contains(name)) throwException("SqlStatementBuilder::bind: placeholder name does not exist!", name.data()); } diff --git a/src/libs/utils/smallstringview.h b/src/libs/utils/smallstringview.h index 44940707696..9c8ab7cc125 100644 --- a/src/libs/utils/smallstringview.h +++ b/src/libs/utils/smallstringview.h @@ -13,12 +13,6 @@ #include #include -#if __cpp_lib_constexpr_string >= 201907L -#define constexpr_string constexpr -#else -#define constexpr_string -#endif - namespace Utils { template @@ -36,41 +30,14 @@ class SmallStringView : public std::string_view public: using std::string_view::string_view; - constexpr SmallStringView(const_iterator begin, const_iterator end) noexcept - : std::string_view{std::addressof(*begin), static_cast(std::distance(begin, end))} - {} - -#ifdef Q_CC_MSVC - constexpr SmallStringView(const char *const begin, const char *const end) noexcept - : std::string_view{begin, static_cast(std::distance(begin, end))} - {} -#endif - template = 0> constexpr SmallStringView(const String &string) noexcept : std::string_view{string.data(), static_cast(string.size())} {} - static constexpr SmallStringView fromUtf8(const char *const characterPointer) - { - return SmallStringView(characterPointer); - } - constexpr size_type isEmpty() const noexcept { return empty(); } - constexpr - SmallStringView mid(size_type position) const noexcept - { - return SmallStringView(data() + position, size() - position); - } - - constexpr - SmallStringView mid(size_type position, size_type length) const noexcept - { - return SmallStringView(data() + position, length); - } - - constexpr_string operator std::string() const { return std::string(data(), size()); } + operator std::string() const { return std::string(data(), size()); } explicit operator QString() const { return QString::fromUtf8(data(), int(size())); } @@ -89,25 +56,6 @@ public: { return QUtf8StringView(data(), Utils::ssize(*this)); } - constexpr bool startsWith(SmallStringView subStringToSearch) const noexcept - { - if (size() >= subStringToSearch.size()) - return !std::char_traits::compare(data(), - subStringToSearch.data(), - subStringToSearch.size()); - - return false; - } - - constexpr bool startsWith(char characterToSearch) const noexcept - { - return *begin() == characterToSearch; - } - - constexpr bool endsWith(SmallStringView ending) const noexcept - { - return size() >= ending.size() && std::equal(ending.rbegin(), ending.rend(), rbegin()); - } }; inline constexpr auto operator<=>(const SmallStringView &first, const SmallStringView &second) diff --git a/src/plugins/qmldesigner/libs/designercore/sourcepathstorage/sourcepathcache.h b/src/plugins/qmldesigner/libs/designercore/sourcepathstorage/sourcepathcache.h index 47877803e3b..c7d3184f78a 100644 --- a/src/plugins/qmldesigner/libs/designercore/sourcepathstorage/sourcepathcache.h +++ b/src/plugins/qmldesigner/libs/designercore/sourcepathstorage/sourcepathcache.h @@ -79,7 +79,7 @@ public: SourceContextId sourceContextId(Utils::SmallStringView sourceContextPath) const override { Utils::SmallStringView path = sourceContextPath.back() == '/' - ? sourceContextPath.mid(0, sourceContextPath.size() - 1) + ? sourceContextPath.substr(0, sourceContextPath.size() - 1) : sourceContextPath; return m_sourceContextPathCache.id(path); diff --git a/src/plugins/qmldesigner/libs/designercore/sourcepathstorage/sourcepathview.h b/src/plugins/qmldesigner/libs/designercore/sourcepathstorage/sourcepathview.h index 8523c70a928..9100154d7e2 100644 --- a/src/plugins/qmldesigner/libs/designercore/sourcepathstorage/sourcepathview.h +++ b/src/plugins/qmldesigner/libs/designercore/sourcepathstorage/sourcepathview.h @@ -51,13 +51,13 @@ public: Utils::SmallStringView directory() const noexcept { - return mid(0, std::size_t(std::max(std::ptrdiff_t(0), m_slashIndex))); + return substr(0, std::size_t(std::max(std::ptrdiff_t(0), m_slashIndex))); } Utils::SmallStringView name() const noexcept { - return mid(std::size_t(m_slashIndex + 1), - std::size_t(std::ptrdiff_t(size()) - m_slashIndex - std::ptrdiff_t(1))); + return substr(std::size_t(m_slashIndex + 1), + std::size_t(std::ptrdiff_t(size()) - m_slashIndex - std::ptrdiff_t(1))); } static diff --git a/src/plugins/qmldesigner/project/qmldesignerprojectmanager.cpp b/src/plugins/qmldesigner/project/qmldesignerprojectmanager.cpp index b9fbf8d3438..e450576b9a9 100644 --- a/src/plugins/qmldesigner/project/qmldesignerprojectmanager.cpp +++ b/src/plugins/qmldesigner/project/qmldesignerprojectmanager.cpp @@ -100,13 +100,13 @@ auto makeCollectorDispatcherChain(ImageCacheCollector &nodeInstanceCollector, std::make_pair([](Utils::SmallStringView filePath, [[maybe_unused]] Utils::SmallStringView state, [[maybe_unused]] const QmlDesigner::ImageCache::AuxiliaryData - &auxiliaryData) { return filePath.endsWith(".qml"); }, + &auxiliaryData) { return filePath.ends_with(".qml"); }, &nodeInstanceCollector), std::make_pair( [](Utils::SmallStringView filePath, [[maybe_unused]] Utils::SmallStringView state, [[maybe_unused]] const QmlDesigner::ImageCache::AuxiliaryData &auxiliaryData) { - return filePath.endsWith(".mesh") || filePath.startsWith("#"); + return filePath.ends_with(".mesh") || filePath.starts_with("#"); }, &meshImageCollector), std::make_pair( diff --git a/tests/unit/tests/unittests/utils/smallstring-test.cpp b/tests/unit/tests/unittests/utils/smallstring-test.cpp index 6b5af085ba7..99775cbedb1 100644 --- a/tests/unit/tests/unittests/utils/smallstring-test.cpp +++ b/tests/unit/tests/unittests/utils/smallstring-test.cpp @@ -726,44 +726,6 @@ TYPED_TEST(SmallString, from_q_byte_array) ASSERT_THAT(text, SmallString("short string")); } -TYPED_TEST(SmallString, mid_one_parameter) -{ - using SmallString = typename TestFixture::String; - SmallString text("some text"); - - auto midString = text.mid(5); - - ASSERT_THAT(midString, Eq(SmallString("text"))); -} - -TYPED_TEST(SmallString, mid_two_parameter) -{ - using SmallString = typename TestFixture::String; - SmallString text("some text and more"); - - auto midString = text.mid(5, 4); - - ASSERT_THAT(midString, Eq(SmallString("text"))); -} - -TYPED_TEST(SmallString, small_string_view_mid_one_parameter) -{ - SmallStringView text("some text"); - - auto midString = text.mid(5); - - ASSERT_THAT(midString, Eq(SmallStringView("text"))); -} - -TYPED_TEST(SmallString, small_string_view_mid_two_parameter) -{ - SmallStringView text("some text and more"); - - auto midString = text.mid(5, 4); - - ASSERT_THAT(midString, Eq(SmallStringView("text"))); -} - TYPED_TEST(SmallString, size_of_empty_stringl) { using SmallString = typename TestFixture::String; @@ -1442,79 +1404,6 @@ TYPED_TEST(SmallString, dont_reserve_if_nothing_is_replaced_for_shorter_replacem ASSERT_TRUE(text.isReadOnlyReference()); } -TYPED_TEST(SmallString, starts_with) -{ - using SmallString = typename TestFixture::String; - - SmallString text("$column"); - - ASSERT_FALSE(text.startsWith("$columnxxx")); - ASSERT_TRUE(text.startsWith("$column")); - ASSERT_TRUE(text.startsWith("$col")); - ASSERT_FALSE(text.startsWith("col")); - ASSERT_TRUE(text.startsWith('$')); - ASSERT_FALSE(text.startsWith('@')); -} - -TYPED_TEST(SmallString, starts_with_string_view) -{ - SmallStringView text("$column"); - - ASSERT_FALSE(text.startsWith("$columnxxx")); - ASSERT_TRUE(text.startsWith("$column")); - ASSERT_TRUE(text.startsWith("$col")); - ASSERT_FALSE(text.startsWith("col")); - ASSERT_TRUE(text.startsWith('$')); - ASSERT_FALSE(text.startsWith('@')); -} - -TYPED_TEST(SmallString, starts_with_qstringview) -{ - using SmallString = typename TestFixture::String; - using namespace Qt::StringLiterals; - - SmallString text("$column"); - - ASSERT_FALSE(text.startsWith(u"$columnxxx"_s)); - ASSERT_TRUE(text.startsWith(u"$column"_s)); - ASSERT_TRUE(text.startsWith(u"$col"_s)); - ASSERT_FALSE(text.startsWith(u"col"_s)); - ASSERT_TRUE(text.startsWith(u"$"_s)); - ASSERT_FALSE(text.startsWith(u"@"_s)); -} - -TYPED_TEST(SmallString, ends_with) -{ - using SmallString = typename TestFixture::String; - - SmallString text("/my/path"); - - ASSERT_TRUE(text.endsWith("/my/path")); - ASSERT_TRUE(text.endsWith("path")); - ASSERT_FALSE(text.endsWith("paths")); - ASSERT_TRUE(text.endsWith('h')); - ASSERT_FALSE(text.endsWith('x')); -} - -TYPED_TEST(SmallString, ends_with_string_view) -{ - SmallStringView text("/my/path"); - - ASSERT_TRUE(text.endsWith("/my/path")); - ASSERT_TRUE(text.endsWith("path")); - ASSERT_FALSE(text.endsWith("paths")); -} - -TYPED_TEST(SmallString, ends_with_small_string) -{ - using SmallString = typename TestFixture::String; - - SmallString text("/my/path"); - - ASSERT_TRUE(text.endsWith(SmallString("path"))); - ASSERT_TRUE(text.endsWith('h')); -} - TYPED_TEST(SmallString, reserve_smaller_than_short_string_capacity) { using SmallString = typename TestFixture::String; From ef711343afe77d9aba4a64f319d550904df5dfd1 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Fri, 7 Mar 2025 19:03:03 +0100 Subject: [PATCH 40/54] Sqlite: Simplify insertUpdateDelete That hopefully is clearing up the intention. Change-Id: I89ef34dddaf8453b28361be8875c73d0dd4e9d5b Reviewed-by: Thomas Hartmann --- src/libs/sqlite/sqlitealgorithms.h | 74 +++++++++++++++--------------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/src/libs/sqlite/sqlitealgorithms.h b/src/libs/sqlite/sqlitealgorithms.h index a410e59aa90..a48ebb0a958 100644 --- a/src/libs/sqlite/sqlitealgorithms.h +++ b/src/libs/sqlite/sqlitealgorithms.h @@ -36,6 +36,36 @@ void insertUpdateDelete(SqliteRange &&sqliteRange, auto endValueIterator = values.end(); auto lastValueIterator = endValueIterator; + auto doUpdate = [&](const auto &sqliteValue, const auto &value) { + UpdateChange updateChange = updateCallback(sqliteValue, value); + switch (updateChange) { + case UpdateChange::Update: + lastValueIterator = currentValueIterator; + break; + case UpdateChange::No: + lastValueIterator = endValueIterator; + break; + } + ++currentSqliteIterator; + ++currentValueIterator; + }; + + auto doInsert = [&](const auto &value) { + insertCallback(value); + ++currentValueIterator; + }; + + auto doDelete = [&](const auto &sqliteValue) { + if (lastValueIterator != endValueIterator) { + if (compareKey(sqliteValue, *lastValueIterator) != 0) + deleteCallback(sqliteValue); + lastValueIterator = endValueIterator; + } else { + deleteCallback(sqliteValue); + } + ++currentSqliteIterator; + }; + while (true) { bool hasMoreValues = currentValueIterator != endValueIterator; bool hasMoreSqliteValues = currentSqliteIterator != endSqliteIterator; @@ -43,44 +73,16 @@ void insertUpdateDelete(SqliteRange &&sqliteRange, auto &&sqliteValue = *currentSqliteIterator; auto &&value = *currentValueIterator; auto compare = compareKey(sqliteValue, value); - if (compare == 0) { - UpdateChange updateChange = updateCallback(sqliteValue, value); - switch (updateChange) { - case UpdateChange::Update: - lastValueIterator = currentValueIterator; - break; - case UpdateChange::No: - lastValueIterator = endValueIterator; - break; - } - ++currentSqliteIterator; - ++currentValueIterator; - } else if (compare > 0) { - insertCallback(value); - ++currentValueIterator; - } else if (compare < 0) { - if (lastValueIterator != endValueIterator) { - if (compareKey(sqliteValue, *lastValueIterator) != 0) - deleteCallback(sqliteValue); - lastValueIterator = endValueIterator; - } else { - deleteCallback(sqliteValue); - } - ++currentSqliteIterator; - } + if (compare == 0) + doUpdate(sqliteValue, value); + else if (compare > 0) + doInsert(value); + else if (compare < 0) + doDelete(sqliteValue); } else if (hasMoreValues) { - insertCallback(*currentValueIterator); - ++currentValueIterator; + doInsert(*currentValueIterator); } else if (hasMoreSqliteValues) { - auto &&sqliteValue = *currentSqliteIterator; - if (lastValueIterator != endValueIterator) { - if (compareKey(sqliteValue, *lastValueIterator) != 0) - deleteCallback(sqliteValue); - lastValueIterator = endValueIterator; - } else { - deleteCallback(sqliteValue); - } - ++currentSqliteIterator; + doDelete(*currentSqliteIterator); } else { break; } From c495374b13eba0cc6f371625bb8446567a06eea9 Mon Sep 17 00:00:00 2001 From: Shrief Gabr Date: Mon, 10 Mar 2025 15:14:26 +0200 Subject: [PATCH 41/54] QmlDesigner: Fix assets thumbnails not visible on macOS Fixes: QDS-14851 Change-Id: I209c5f8c379149aae95cb745083e1c7e15136107 Reviewed-by: Miikka Heikkinen --- .../qmldesigner/imagecachecollectors/imagecachecollector.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/plugins/qmldesigner/imagecachecollectors/imagecachecollector.cpp b/src/plugins/qmldesigner/imagecachecollectors/imagecachecollector.cpp index aff2b792f2f..56d7a3e400f 100644 --- a/src/plugins/qmldesigner/imagecachecollectors/imagecachecollector.cpp +++ b/src/plugins/qmldesigner/imagecachecollectors/imagecachecollector.cpp @@ -157,6 +157,10 @@ bool ImageCacheCollector::runProcess(const QStringList &arguments) const QProcessUniquePointer puppetProcess{new QProcess}; + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.remove("QSG_RHI_BACKEND"); + puppetProcess->setProcessEnvironment(env); + QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, puppetProcess.get(), From 30c62e3a32a668b1f8add2ad1ddf34e935d4d155 Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Fri, 31 Jan 2025 16:57:10 +0100 Subject: [PATCH 42/54] QmlDesigner: Improve design system view * Add custom SpinBox with input and indicator * Set value on SpinBox destruction when value was changed * Add binding indicator * Implement binding menu and connect it to the backend * Add readonly state to custom Switch * Update StudioControls.DialogButton style * Add dialogs for creating, deleting and renaming collections * Fix crash in CollectionModel::setData * Add themes/mode ComboBox * Fix color editor connection * Add overlay for header view editing Task-number: QDS-11856 Change-Id: Ibc5fdd915a298162ed4970fb2845d7484a8f042a Reviewed-by: Thomas Hartmann --- .../qmldesigner/designsystem/Main.qml | 932 +++++++++++++++--- .../DesignSystemControls/BindingIndicator.qml | 62 ++ .../imports/DesignSystemControls/SpinBox.qml | 365 +++++++ .../DesignSystemControls/SpinBoxIndicator.qml | 247 +++++ .../DesignSystemControls/SpinBoxInput.qml | 100 ++ .../imports/DesignSystemControls/Switch.qml | 21 +- .../imports/DesignSystemControls/qmldir | 4 + .../imports/StudioControls/DialogButton.qml | 32 +- .../designsystemview/collectionmodel.cpp | 9 +- .../designsystemview/collectionmodel.h | 1 + .../designsysteminterface.cpp | 1 + 11 files changed, 1604 insertions(+), 170 deletions(-) create mode 100644 share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/BindingIndicator.qml create mode 100644 share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBox.qml create mode 100644 share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBoxIndicator.qml create mode 100644 share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBoxInput.qml diff --git a/share/qtcreator/qmldesigner/designsystem/Main.qml b/share/qtcreator/qmldesigner/designsystem/Main.qml index 7c444c44e4f..9695f95270a 100644 --- a/share/qtcreator/qmldesigner/designsystem/Main.qml +++ b/share/qtcreator/qmldesigner/designsystem/Main.qml @@ -13,6 +13,7 @@ import DesignSystemControls as DSC import StudioControls as StudioControls import StudioTheme as StudioTheme +import StudioQuickUtils Rectangle { id: root @@ -33,6 +34,11 @@ Rectangle { readonly property int iconSize: 16 readonly property int leftPadding: 14 + readonly property int rightPadding: 14 + + property var customStyle: StudioTheme.ControlStyle { + border.idle: root.borderColor + } width: 400 height: 400 @@ -41,6 +47,10 @@ Rectangle { function loadModel(name) { root.currentCollectionName = name tableView.model = DesignSystemBackend.dsInterface.model(name) + + topLeftCell.visible = tableView.model.columnCount() + createModeButton.enabled = tableView.model.rowCount() + modelConnections.target = tableView.model } function groupString(gt) { @@ -56,6 +66,178 @@ Rectangle { return "unknow_group" } + function setValue(value: var, row: int, column: int, isBinding: bool): bool { + console.log("setValue(", value, row, column, isBinding, ")") + return tableView.model.setData(tableView.index(row, column), + DesignSystemBackend.dsInterface.createThemeProperty("", value, isBinding), + Qt.EditRole) + } + + Connections { + id: modelConnections + ignoreUnknownSignals: true // model might initially be null + + function onModelReset() { + topLeftCell.visible = tableView.model.columnCount() + createModeButton.enabled = tableView.model.rowCount() + } + } + + StudioControls.Dialog { + id: renameCollectionDialog + title: qsTr("Rename collection") + width: Math.min(300, root.width) + closePolicy: Popup.CloseOnEscape + anchors.centerIn: parent + modal: true + + onOpened: renameCollectionTextField.forceActiveFocus() + + contentItem: Column { + spacing: 20 + width: parent.width + + StudioControls.TextField { + id: renameCollectionTextField + actionIndicatorVisible: false + translationIndicatorVisible: false + width: parent.width + + onAccepted: renameCollectionDialog.accept() + onRejected: renameCollectionDialog.reject() + } + + Row { + spacing: 10 + anchors.right: parent.right + + StudioControls.DialogButton { + text: qsTr("Rename") + enabled: (renameCollectionDialog.previousString !== renameCollectionTextField.text) + onClicked: renameCollectionDialog.accept() + } + + StudioControls.DialogButton { + text: qsTr("Cancel") + onClicked: renameCollectionDialog.reject() + } + } + } + + onAccepted: { + DesignSystemBackend.dsInterface.renameCollection(collectionsComboBox.currentText, + renameCollectionTextField.text) + renameCollectionDialog.close() + } + + property string previousString + + onAboutToShow: { + renameCollectionTextField.text = collectionsComboBox.currentText + renameCollectionDialog.previousString = collectionsComboBox.currentText + } + } + + StudioControls.Dialog { + id: createCollectionDialog + title: qsTr("Create collection") + width: Math.min(300, root.width) + closePolicy: Popup.CloseOnEscape + anchors.centerIn: parent + modal: true + + onOpened: createCollectionTextField.forceActiveFocus() + + contentItem: Column { + spacing: 20 + width: parent.width + + StudioControls.TextField { + id: createCollectionTextField + actionIndicatorVisible: false + translationIndicatorVisible: false + width: parent.width + + text: qsTr("NewCollection") + + onAccepted: createCollectionDialog.accept() + onRejected: createCollectionDialog.reject() + } + + Row { + spacing: 10 + anchors.right: parent.right + + StudioControls.DialogButton { + text: qsTr("Create") + onClicked: createCollectionDialog.accept() + } + + StudioControls.DialogButton { + text: qsTr("Cancel") + onClicked: createCollectionDialog.reject() + } + } + } + + onAccepted: { + DesignSystemBackend.dsInterface.addCollection(createCollectionTextField.text) + root.loadModel(createCollectionTextField.text) + collectionsComboBox.currentIndex = collectionsComboBox.indexOfValue(createCollectionTextField.text) + createCollectionDialog.close() + } + } + + StudioControls.Dialog { + id: removeCollectionDialog + title: qsTr("Remove collection") + width: Math.min(300, root.width) + closePolicy: Popup.CloseOnEscape + anchors.centerIn: parent + modal: true + + onOpened: removeCollectionDialog.forceActiveFocus() + + contentItem: Column { + spacing: 20 + width: parent.width + + Text { + id: warningText + + text: qsTr("Are you sure? The action cannot be undone.") + color: StudioTheme.Values.themeTextColor + wrapMode: Text.WordWrap + width: parent.width + } + + Row { + spacing: 10 + anchors.right: parent.right + + StudioControls.DialogButton { + text: qsTr("Remove") + onClicked: removeCollectionDialog.accept() + } + + StudioControls.DialogButton { + text: qsTr("Cancel") + onClicked: removeCollectionDialog.reject() + } + } + } + + onAccepted: { + let currentCollectionName = collectionsComboBox.currentText + let currentCollectionIndex = collectionsComboBox.currentIndex + let previousCollectionIndex = (currentCollectionIndex === 0) ? 1 : currentCollectionIndex - 1 + + root.loadModel(collectionsComboBox.textAt(previousCollectionIndex)) + DesignSystemBackend.dsInterface.removeCollection(currentCollectionName) + removeCollectionDialog.close() + } + } + Rectangle { id: toolBar @@ -77,6 +259,15 @@ Rectangle { onActivated: root.loadModel(collectionsComboBox.currentText) } + StudioControls.ComboBox { + id: themesComboBox + style: StudioTheme.Values.viewBarControlStyle + anchors.verticalCenter: parent.verticalCenter + actionIndicatorVisible: false + model: tableView.model.themeNames + onActivated: tableView.model.setActiveTheme(themesComboBox.currentText) + } + StudioControls.IconTextButton { id: moreButton anchors.verticalCenter: parent.verticalCenter @@ -84,26 +275,33 @@ Rectangle { checkable: true checked: moreMenu.visible - onClicked: moreMenu.popup(0, moreButton.height) + onToggled: { + if (moreMenu.visible) + moreMenu.close() + else + moreMenu.popup(0, moreButton.height) + } StudioControls.Menu { id: moreMenu + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent + StudioControls.MenuItem { text: qsTr("Rename") - onTriggered: console.log(">>> Rename collection") + onTriggered: renameCollectionDialog.open() } StudioControls.MenuItem { text: qsTr("Delete") - onTriggered: console.log(">>> Delete collection") + onTriggered: removeCollectionDialog.open() } StudioControls.MenuSeparator {} StudioControls.MenuItem { text: qsTr("Create collection") - onTriggered: console.log(">>> Create collection") + onTriggered: createCollectionDialog.open() } } } @@ -127,9 +325,15 @@ Rectangle { required property bool editing + required property var resolvedValue + required property bool isActive required property bool isBinding required property var propertyValue + property bool creatingBinding: false + + readonly property bool bindingEditor: isBinding || creatingBinding + color: root.backgroundColor implicitWidth: root.cellWidth implicitHeight: root.cellHeight @@ -140,18 +344,24 @@ Rectangle { } component DataCell: Cell { + id: dataCell + HoverHandler { id: cellHoverHandler } - StudioControls.IconIndicator { - icon: isBinding ? StudioTheme.Constants.actionIconBinding - : StudioTheme.Constants.actionIcon + DSC.BindingIndicator { + icon.text: dataCell.isBinding ? StudioTheme.Constants.actionIconBinding + : StudioTheme.Constants.actionIcon + icon.color: dataCell.isBinding ? StudioTheme.Values.themeInteraction + : StudioTheme.Values.themeTextColor + anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right anchors.rightMargin: 10 - visible: cellHoverHandler.hovered + visible: !dataCell.editing && (cellHoverHandler.hovered || dataCell.isBinding) onClicked: { tableView.closeEditor() + menu.show(dataCell.row, dataCell.column) } } } @@ -171,37 +381,39 @@ Rectangle { leftPadding: root.leftPadding horizontalAlignment: TextInput.AlignLeft verticalAlignment: TextInput.AlignVCenter - color: StudioTheme.Values.themeTextColor - text: stringDelegate.display + color: stringDelegate.isBinding ? StudioTheme.Values.themeInteraction + : StudioTheme.Values.themeTextColor + text: stringDelegate.resolvedValue visible: !stringDelegate.editing } + // This edit delegate combines the binding editor and string value editor TableView.editDelegate: DSC.TextField { id: stringEditDelegate + style: root.customStyle + anchors.fill: parent leftPadding: root.leftPadding - // Only apply more to right padding when hovered - //rightPadding - horizontalAlignment: TextInput.AlignLeft verticalAlignment: TextInput.AlignVCenter - text: stringDelegate.display + text: stringDelegate.bindingEditor ? stringDelegate.propertyValue + : stringDelegate.resolvedValue + Component.onCompleted: stringEditDelegate.selectAll() + Component.onDestruction: stringDelegate.creatingBinding = false TableView.onCommit: { - console.log("onCommit", stringEditDelegate.text) - let index = TableView.view.index(stringDelegate.row, stringDelegate.column) - var prop = DesignSystemBackend.dsInterface.createThemeProperty("", - stringEditDelegate.text, - stringDelegate.isBinding) - TableView.view.model.setData(index, prop, Qt.EditRole) + root.setValue(stringEditDelegate.text, + stringDelegate.row, + stringDelegate.column, + stringDelegate.bindingEditor) } } - Component.onCompleted: console.log("DelegateChoice - string", stringDelegate.display) + //Component.onCompleted: console.log("DelegateChoice - string", stringDelegate.resolvedValue) } } @@ -214,38 +426,107 @@ Rectangle { Text { anchors.fill: parent leftPadding: root.leftPadding + rightPadding: root.rightPadding + horizontalAlignment: TextInput.AlignLeft verticalAlignment: TextInput.AlignVCenter - color: StudioTheme.Values.themeTextColor - text: numberDelegate.display - visible: !numberDelegate.editing + color: numberDelegate.isBinding ? StudioTheme.Values.themeInteraction + : StudioTheme.Values.themeTextColor + // -128 is the value of QLocale::FloatingPointShortest + text: Number(numberDelegate.resolvedValue).toLocaleString(Utils.locale, 'f', -128) } - TableView.editDelegate: SpinBox { - id: numberEditDelegate - + // This edit delegate has two different controls, one for number editing and one for + // binding editing. Depending on the mode one is hidden and the other is shown. + TableView.editDelegate: FocusScope { + id: numberEditDelegateFocusScope anchors.fill: parent - leftPadding: root.leftPadding - value: numberDelegate.display - from: -1000 // TODO define min/max - to: 1000 - editable: true + property bool alreadyCommited: false + + DSC.SpinBox { + id: numberEditDelegate + + property real previousValue: 0 + + style: root.customStyle + + anchors.fill: parent + + realValue: numberDelegate.resolvedValue + realFrom: -1000 // TODO define min/max + realTo: 1000 + editable: true + decimals: 2 + + focus: !numberDelegate.bindingEditor + visible: !numberDelegate.bindingEditor + + Component.onCompleted: { + numberEditDelegate.previousValue = numberDelegate.resolvedValue + numberEditDelegate.contentItem.selectAll() + } + Component.onDestruction: { + if (numberEditDelegateFocusScope.alreadyCommited || numberDelegate.bindingEditor) + return + + let val = numberEditDelegate.realValue + + if (numberEditDelegate.previousValue === val) + return + + root.setValue(val, + numberDelegate.row, + numberDelegate.column, + numberDelegate.bindingEditor) + } + } + + DSC.TextField { + id: numberBindingEditDelegate + + style: root.customStyle + + anchors.fill: parent + leftPadding: root.leftPadding + rightPadding: root.rightPadding + + horizontalAlignment: TextInput.AlignLeft + verticalAlignment: TextInput.AlignVCenter + + text: numberDelegate.propertyValue + + focus: numberDelegate.bindingEditor + visible: numberDelegate.bindingEditor + + Component.onCompleted: numberBindingEditDelegate.selectAll() + Component.onDestruction: numberDelegate.creatingBinding = false + } TableView.onCommit: { - let val = numberEditDelegate.valueFromText(numberEditDelegate.contentItem.text, - numberEditDelegate.locale) - console.log("onCommit", val) - let index = TableView.view.index(numberDelegate.row, numberDelegate.column) - var prop = DesignSystemBackend.dsInterface.createThemeProperty("", - val, - numberDelegate.isBinding) - TableView.view.model.setData(index, prop, Qt.EditRole) + // By default assume binding edit delegate is used + let val = numberBindingEditDelegate.text + + // If binding editor isn't used then the SpinBox value needs to be written + if (!numberDelegate.bindingEditor) { + numberEditDelegate.valueFromText(numberEditDelegate.contentItem.text, + numberEditDelegate.locale) + // Don't use return value of valueFromText as it is of type int. + // Internally the float value is set on realValue property. + val = numberEditDelegate.realValue + } + + root.setValue(val, + numberDelegate.row, + numberDelegate.column, + numberDelegate.bindingEditor) + + numberEditDelegateFocusScope.alreadyCommited = true } } - Component.onCompleted: console.log("DelegateChoice - number", display) + //Component.onCompleted: console.log("DelegateChoice - number", numberDelegate.resolvedValue) } } @@ -262,20 +543,62 @@ Rectangle { anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: root.leftPadding - checked: flagDelegate.display - text: flagDelegate.display + checked: flagDelegate.resolvedValue + text: flagDelegate.resolvedValue + + labelColor: flagDelegate.isBinding ? StudioTheme.Values.themeInteraction + : StudioTheme.Values.themeTextColor + + readonly: flagDelegate.isBinding onToggled: { - console.log("onCommit", flagEditDelegate.checked) - let index = flagDelegate.TableView.view.index(flagDelegate.row, flagDelegate.column) - var prop = DesignSystemBackend.dsInterface.createThemeProperty("", - flagEditDelegate.checked, - flagEditDelegate.isBinding) - flagDelegate.TableView.view.model.setData(index, prop, Qt.EditRole) + root.setValue(flagEditDelegate.checked, + flagDelegate.row, + flagDelegate.column, + false) } } - Component.onCompleted: console.log("DelegateChoice - bool", flagDelegate.display) + // Dummy item to show forbidden cursor when hovering over bound switch control + Item { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: root.leftPadding + + width: flagEditDelegate.indicator.width + height: flagEditDelegate.indicator.height + + visible: !flagEditDelegate.enabled + + HoverHandler { + enabled: !flagEditDelegate.enabled + cursorShape: Qt.ForbiddenCursor + } + } + + TableView.editDelegate: DSC.TextField { + id: flagBindingEditDelegate + + anchors.fill: parent + leftPadding: root.leftPadding + + horizontalAlignment: TextInput.AlignLeft + verticalAlignment: TextInput.AlignVCenter + + text: flagDelegate.bindingEditor ? flagDelegate.propertyValue + : flagDelegate.resolvedValue + Component.onCompleted: flagBindingEditDelegate.selectAll() + Component.onDestruction: flagDelegate.creatingBinding = false + + TableView.onCommit: { + root.setValue(flagBindingEditDelegate.text, + flagDelegate.row, + flagDelegate.column, + true) + } + } + + //Component.onCompleted: console.log("DelegateChoice - bool", flagDelegate.resolvedValue) } } @@ -286,18 +609,20 @@ Rectangle { id: colorDelegate Row { + id: colorDelegateRow anchors.fill: parent leftPadding: root.leftPadding spacing: 8 Rectangle { + id: colorDelegatePreview anchors.verticalCenter: parent.verticalCenter width: 20 height: 20 - color: colorDelegate.display + color: colorDelegate.resolvedValue border.color: "black" - border.width: 1 + border.width: StudioTheme.Values.border Image { anchors.fill: parent @@ -309,38 +634,338 @@ Rectangle { MouseArea { id: colorMouseArea anchors.fill: parent + enabled: !colorDelegate.isBinding - onClicked: { - if (popupDialog.visibility) { - popupDialog.close() + cursorShape: colorDelegate.isBinding ? Qt.ForbiddenCursor + : Qt.PointingHandCursor + + function togglePopup() { + if (colorPopup.visibility) { + colorPopup.close() } else { - popupDialog.ensureLoader() - popupDialog.show(colorDelegate) + colorPopup.modelIndex = tableView.index(colorDelegate.row, colorDelegate.column) - if (loader.status === Loader.Ready) - loader.item.originalColor = root.color + colorPopup.ensureLoader() + colorPopup.show(colorDelegate) + + if (loader.status === Loader.Ready) { + loader.item.color = colorDelegate.resolvedValue + loader.item.originalColor = colorDelegate.resolvedValue + } } + tableView.closeEditor() colorMouseArea.forceActiveFocus() } + + onClicked: colorMouseArea.togglePopup() } } Text { height: parent.height verticalAlignment: Qt.AlignVCenter - color: StudioTheme.Values.themeTextColor - text: colorDelegate.propertyValue + color: colorDelegate.isBinding ? StudioTheme.Values.themeInteraction + : StudioTheme.Values.themeTextColor + text: colorDelegate.resolvedValue } } - Component.onCompleted: console.log("DelegateChoice - color", colorDelegate.display) + // This edit delegate combines the binding editor and hex value editor + TableView.editDelegate: DSC.TextField { + id: colorEditDelegate + + anchors.fill: parent + leftPadding: root.leftPadding + + (colorDelegate.bindingEditor ? 0 + : (colorDelegatePreview.width + + colorDelegateRow.spacing)) + + horizontalAlignment: TextInput.AlignLeft + verticalAlignment: TextInput.AlignVCenter + + text: colorDelegate.bindingEditor ? colorDelegate.propertyValue + : colorDelegate.resolvedValue + + RegularExpressionValidator { + id: hexValidator + regularExpression: /#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?/g + } + + validator: colorDelegate.bindingEditor ? null : hexValidator + + Component.onCompleted: colorEditDelegate.selectAll() + Component.onDestruction: colorDelegate.creatingBinding = false + + TableView.onCommit: { + root.setValue(colorEditDelegate.text, + colorDelegate.row, + colorDelegate.column, + colorDelegate.bindingEditor) + } + + // Extra color Rectangle to be shown on top of edit delegate + Rectangle { + anchors.left: parent.left + anchors.leftMargin: root.leftPadding + anchors.verticalCenter: parent.verticalCenter + + width: colorDelegatePreview.width + height: colorDelegatePreview.height + color: colorDelegate.resolvedValue + border.color: "black" + border.width: StudioTheme.Values.border + + visible: !colorDelegate.bindingEditor + + Image { + anchors.fill: parent + source: "qrc:/navigator/icon/checkers.png" + fillMode: Image.Tile + z: -1 + } + + MouseArea { + anchors.fill: parent + enabled: !colorDelegate.isBinding + + onClicked: colorMouseArea.togglePopup() + } + } + } + + //Component.onCompleted: console.log("DelegateChoice - color", colorDelegate.resolvedValue) } } } - StudioControls.PopupDialog { - id: popupDialog + StudioControls.Menu { + id: menu + property var modelIndex + + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + + function show(row, column) { + menu.modelIndex = tableView.index(row, column) + menu.popup() + } + + StudioControls.MenuItem { + enabled: { + if (menu.modelIndex) + return tableView.itemAtIndex(menu.modelIndex).isBinding + + return false + } + text: qsTr("Reset") + onTriggered: { + let data = tableView.model.data(menu.modelIndex, CollectionModel.ResolvedValueRole) + var prop = DesignSystemBackend.dsInterface.createThemeProperty("", data, false) + let result = tableView.model.setData(menu.modelIndex, prop, Qt.EditRole) + } + } + + StudioControls.MenuItem { + text: qsTr("Set Binding") + onTriggered: { + let cell = tableView.itemAtIndex(menu.modelIndex) + cell.creatingBinding = true + tableView.edit(menu.modelIndex) + } + } + } + + StudioControls.Menu { + id: modeMenu + + property int column + + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + + function show(column) { // Qt.Horizontal + tableView.closeEditor() // Close all currently visible edit delegates + modeMenu.column = column + modeMenu.popup() + } + + StudioControls.MenuItem { + enabled: false + text: qsTr("Duplicate mode") + onTriggered: { console.log("Duplicate mode") } + } + + StudioControls.MenuItem { + text: qsTr("Rename mode") + onTriggered: overlay.show(modeMenu.column, Qt.Horizontal) + } + + StudioControls.MenuItem { + text: qsTr("Delete mode") + onTriggered: tableView.model.removeColumns(modeMenu.column, 1) + } + } + + StudioControls.Menu { + id: variableMenu + + property int row + + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + + function show(row) { // Qt.Vertical + tableView.closeEditor() // Close all currently visible edit delegates + variableMenu.row = row + variableMenu.popup() + } + + StudioControls.MenuItem { + enabled: false + text: qsTr("Duplicate variable") + onTriggered: { console.log("Duplicate variable") } + } + + StudioControls.MenuItem { + text: qsTr("Rename variable") + onTriggered: overlay.show(variableMenu.row, Qt.Vertical) + } + + StudioControls.MenuItem { + text: qsTr("Delete variable") + onTriggered: tableView.model.removeRows(variableMenu.row, 1) + } + } + + Rectangle { + id: overlay + + property int section + property var orientation + property var group + + width: 200 + height: 200 + color: "red" + visible: false + + z: 111 + parent: tableView.contentItem + + DSC.TextField { + id: overlayTextField + anchors.fill: parent + leftPadding: root.leftPadding + (overlayIcon.visible ? overlayIcon.width + 8 : 0) + + horizontalAlignment: TextInput.AlignLeft + verticalAlignment: TextInput.AlignVCenter + + onActiveFocusChanged: { + if (!overlayTextField.activeFocus) + overlay.hide() + } + + onAccepted: { + let result = tableView.model.setHeaderData(overlay.section, + overlay.orientation, + overlayTextField.text, + Qt.EditRole) + + // Revoke active focus from text field by forcing active focus on another item + tableView.forceActiveFocus() + } + + Text { + id: overlayIcon + anchors.left: parent.left + anchors.leftMargin: root.leftPadding + + height: parent.height + verticalAlignment: Qt.AlignVCenter + font.family: StudioTheme.Constants.iconFont.family + font.pixelSize: root.iconSize + color: StudioTheme.Values.themeTextColor + + visible: overlay.group !== undefined + + text: { + if (overlay.group === GroupType.Strings) + return StudioTheme.Constants.string_medium + + if (overlay.group === GroupType.Numbers) + return StudioTheme.Constants.number_medium + + if (overlay.group === GroupType.Flags) + return StudioTheme.Constants.flag_medium + + if (overlay.group === GroupType.Colors) + return StudioTheme.Constants.colorSelection_medium + + return StudioTheme.Constants.error_medium + } + } + } + + function show(section, orientation) { + // Close all currently visible edit delegates + tableView.closeEditor() + + if (orientation === Qt.Horizontal) + overlay.parent = horizontalHeaderView.contentItem + else + overlay.parent = verticalHeaderView.contentItem + + overlay.visible = true + overlay.section = section + overlay.orientation = orientation + overlay.group = tableView.model.headerData(section, + orientation, + CollectionModel.GroupRole) + + overlay.layout() + + overlayTextField.text = tableView.model.headerData(section, + orientation, + CollectionModel.EditRole) + overlayTextField.forceActiveFocus() + overlayTextField.selectAll() + } + + function hide() { + overlay.visible = false + } + + function layout() { + if (!overlay.visible) + return + + let item = null + + if (overlay.orientation === Qt.Horizontal) + item = horizontalHeaderView.itemAtCell(Qt.point(overlay.section, 0)) + else + item = verticalHeaderView.itemAtCell(Qt.point(0, overlay.section)) + + let insideViewport = item !== null + + //overlay.visible = insideViewport + if (insideViewport) { + overlay.x = item.x + overlay.y = item.y + overlay.width = item.width + overlay.height = item.height + } + } + + Connections { + target: tableView + + function onLayoutChanged() { overlay.layout() } + } + } + + StudioControls.PopupDialog { + id: colorPopup + + property var modelIndex property QtObject loaderItem: loader.item keepOpen: loader.item?.eyeDropperActive ?? false @@ -357,27 +982,18 @@ Rectangle { sourceComponent: StudioControls.ColorEditorPopup { id: popup - width: popupDialog.contentWidth - visible: popupDialog.visible - parentWindow: popupDialog.window + width: colorPopup.contentWidth + visible: colorPopup.visible + parentWindow: colorPopup.window onActivateColor: function(color) { - console.log("set color", color) - //colorBackend.activateColor(color) + popup.color = color + var prop = DesignSystemBackend.dsInterface.createThemeProperty("", color, false) + let result = tableView.model.setData(colorPopup.modelIndex, prop, Qt.EditRole) } } - Binding { - target: loader.item - property: "color" - value: root.color - when: loader.status === Loader.Ready - } - - onLoaded: { - loader.item.originalColor = root.color - popupDialog.titleBar = loader.item.titleBarContent - } + onLoaded: colorPopup.titleBar = loader.item.titleBarContent } } @@ -403,21 +1019,22 @@ Rectangle { // top left cell // TODO Can't use Cell as it contains required properties Rectangle { - anchors.right: horizontalHeader.left - anchors.bottom: verticalHeader.top + id: topLeftCell + anchors.right: horizontalHeaderView.left + anchors.bottom: verticalHeaderView.top anchors.rightMargin: -root.borderWidth anchors.bottomMargin: -root.borderWidth color: root.backgroundColor - implicitWidth: verticalHeader.width - implicitHeight: horizontalHeader.height + implicitWidth: verticalHeaderView.width + implicitHeight: horizontalHeaderView.height border { width: root.borderWidth color: root.borderColor } - visible: tableView.model // TODO good enough? - z: 101 + visible: tableView.model + z: 99 Row { // TODO might not be necessary anchors.fill: parent @@ -428,13 +1045,13 @@ Rectangle { height: parent.height verticalAlignment: Qt.AlignVCenter color: StudioTheme.Values.themeTextColor - text: "Name" + text: qsTr("Name") } } } HorizontalHeaderView { - id: horizontalHeader + id: horizontalHeaderView anchors.left: scrollView.left anchors.top: parent.top @@ -442,62 +1059,52 @@ Rectangle { syncView: tableView clip: true - z: 100 + z: overlay.visible ? 102 : 100 delegate: Cell { id: horizontalHeaderDelegate - property bool customEditing: false - TapHandler { - onTapped: { - console.log("onTapped") - tableView.closeEditor() - horizontalHeaderDelegate.customEditing = true - horizontalHeaderTextField.forceActiveFocus() + acceptedButtons: Qt.LeftButton | Qt.RightButton + onTapped: function(eventPoint, button) { + if (button === Qt.LeftButton) + overlay.show(horizontalHeaderDelegate.column, Qt.Horizontal) + + if (button === Qt.RightButton) + modeMenu.show(horizontalHeaderDelegate.column) } } - Text { + Row { anchors.fill: parent - leftPadding: root.leftPadding - verticalAlignment: Qt.AlignVCenter - color: StudioTheme.Values.themeTextColor - text: horizontalHeaderDelegate.display - visible: !horizontalHeaderDelegate.customEditing - } + spacing: 8 - DSC.TextField { - id: horizontalHeaderTextField - - anchors.fill: parent - leftPadding: root.leftPadding - horizontalAlignment: TextInput.AlignLeft - verticalAlignment: TextInput.AlignVCenter - text: horizontalHeaderDelegate.display - - visible: horizontalHeaderDelegate.customEditing - - //Component.onCompleted: stringEditDelegate.selectAll() - - onActiveFocusChanged: { - if (!horizontalHeaderTextField.activeFocus) - horizontalHeaderDelegate.customEditing = false + Text { + id: activeIcon + height: parent.height + leftPadding: root.leftPadding + verticalAlignment: Qt.AlignVCenter + font.family: StudioTheme.Constants.iconFont.family + font.pixelSize: StudioTheme.Values.baseIconFontSize + color: StudioTheme.Values.themeTextColor + text: StudioTheme.Constants.apply_small + visible: horizontalHeaderDelegate.isActive } - onEditingFinished: { - console.log("onEditingFinished", horizontalHeaderTextField.text) - horizontalHeaderDelegate.TableView.view.model.setHeaderData(horizontalHeaderDelegate.column, - Qt.Horizontal, - horizontalHeaderTextField.text, - Qt.EditRole) + Text { + height: parent.height + + leftPadding: horizontalHeaderDelegate.isActive ? 0 : root.leftPadding + verticalAlignment: Qt.AlignVCenter + color: StudioTheme.Values.themeTextColor + text: horizontalHeaderDelegate.display } } } } VerticalHeaderView { - id: verticalHeader + id: verticalHeaderView anchors.top: scrollView.top anchors.left: parent.left @@ -505,13 +1112,24 @@ Rectangle { syncView: tableView clip: true - z: 100 + z: overlay.visible ? 102 : 100 delegate: Cell { id: verticalHeaderDelegate required property int group + TapHandler { + acceptedButtons: Qt.LeftButton | Qt.RightButton + onTapped: function(eventPoint, button) { + if (button === Qt.LeftButton) + overlay.show(verticalHeaderDelegate.row, Qt.Vertical) + + if (button === Qt.RightButton) + variableMenu.show(verticalHeaderDelegate.row) + } + } + Row { anchors.fill: parent leftPadding: root.leftPadding @@ -555,8 +1173,8 @@ Rectangle { ScrollView { id: scrollView - anchors.left: verticalHeader.right - anchors.top: horizontalHeader.bottom + anchors.left: verticalHeaderView.right + anchors.top: horizontalHeaderView.bottom anchors.right: parent.right anchors.bottom: parent.bottom @@ -564,6 +1182,8 @@ Rectangle { anchors.topMargin: -root.borderWidth anchors.rightMargin: -root.borderWidth + z: 101 + contentItem: TableView { id: tableView @@ -577,7 +1197,6 @@ Rectangle { } onModelChanged: { - console.log("onModelChanged") tableView.clearColumnWidths() tableView.contentX = 0 tableView.contentY = 0 @@ -588,8 +1207,14 @@ Rectangle { columnSpacing: -root.borderWidth rowSpacing: -root.borderWidth clip: true + boundsBehavior: Flickable.StopAtBounds delegate: chooser + + onActiveFocusChanged: { + if (!tableView.activeFocus) + tableView.closeEditor() + } } ScrollBar.horizontal: StudioControls.TransientScrollBar { @@ -634,14 +1259,14 @@ Rectangle { Layout.fillHeight: true StudioControls.IconTextButton { + id: createModeButton anchors.centerIn: parent buttonIcon: StudioTheme.Constants.add_medium text: qsTr("Create mode") rotation: -90 + enabled: false - onClicked: { - tableView.model.insertColumn(0) - } + onClicked: tableView.model.insertColumns(0, 1) } } @@ -671,15 +1296,22 @@ Rectangle { width: createVariableButton.width + function insertInitalMode() { + if (tableView.model.columnCount()) + return + + tableView.model.insertColumn(0) + } + DSC.MenuItem { text: qsTr("Color") buttonIcon: StudioTheme.Constants.colorSelection_medium onTriggered: { - console.log(">>> Add Color Property") + createVariableMenu.insertInitalMode() tableView.model.addProperty(GroupType.Colors, - "color_new", - "#800080", - false) + "color_new", + "#800080", + false) } } @@ -687,11 +1319,11 @@ Rectangle { text: qsTr("Number") buttonIcon: StudioTheme.Constants.number_medium onTriggered: { - console.log(">>> Add Number Property") + createVariableMenu.insertInitalMode() tableView.model.addProperty(GroupType.Numbers, - "number_new", - 0, - false) + "number_new", + 0, + false) } } @@ -699,11 +1331,11 @@ Rectangle { text: qsTr("String") buttonIcon: StudioTheme.Constants.string_medium onTriggered: { - console.log(">>> Add String Property") - tableView.model.addProperty(GroupType.Flags, - "string_new", - "String value", - false) + createVariableMenu.insertInitalMode() + tableView.model.addProperty(GroupType.Strings, + "string_new", + "String value", + false) } } @@ -711,11 +1343,11 @@ Rectangle { text: qsTr("Boolean") buttonIcon: StudioTheme.Constants.flag_medium onTriggered: { - console.log(">>> Add Boolean Property") + createVariableMenu.insertInitalMode() tableView.model.addProperty(GroupType.Flags, - "boolean_new", - true, - false) + "boolean_new", + true, + false) } } } diff --git a/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/BindingIndicator.qml b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/BindingIndicator.qml new file mode 100644 index 00000000000..096b639e246 --- /dev/null +++ b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/BindingIndicator.qml @@ -0,0 +1,62 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Templates as T +import StudioTheme as StudioTheme + +Item { + id: control + + property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle + property alias icon: icon + + property bool hover: mouseArea.containsMouse//false + property bool pressed: false + property bool forceVisible: false + + implicitWidth: control.style.actionIndicatorSize.width + implicitHeight: control.style.actionIndicatorSize.height + + signal clicked + z: 10 + + T.Label { + id: icon + anchors.fill: parent + text: StudioTheme.Constants.actionIcon + color: control.style.icon.idle + font.family: StudioTheme.Constants.iconFont.family + font.pixelSize: control.style.baseIconFontSize + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + + states: [ + State { + name: "hover" + when: control.hover && !control.pressed && control.enabled + PropertyChanges { + target: icon + scale: 1.2 + visible: true + } + }, + State { + name: "disable" + when: !control.enabled + PropertyChanges { + target: icon + color: control.style.icon.disabled + } + } + ] + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + //onContainsMouseChanged: control.hover = mouseArea.containsMouse + onClicked: control.clicked() + } +} diff --git a/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBox.qml b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBox.qml new file mode 100644 index 00000000000..165ca1f21de --- /dev/null +++ b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBox.qml @@ -0,0 +1,365 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Templates as T +import StudioTheme as StudioTheme +import StudioQuickUtils + +T.SpinBox { + id: control + + property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle + + property real realFrom: 0.0 + property real realTo: 99.0 + property real realValue: 1.0 + property real realStepSize: 1.0 + + property alias labelColor: spinBoxInput.color + + property int decimals: 0 + + property real minStepSize: { + var tmpMinStepSize = Number((control.realStepSize * 0.1).toFixed(control.decimals)) + return (tmpMinStepSize) ? tmpMinStepSize : control.realStepSize + } + property real maxStepSize: { + var tmpMaxStepSize = Number((control.realStepSize * 10.0).toFixed(control.decimals)) + return (tmpMaxStepSize < control.realTo) ? tmpMaxStepSize : control.realStepSize + } + + property bool edit: spinBoxInput.activeFocus + // This property is used to indicate the global hover state + property bool hover: (spinBoxInput.hover || spinBoxIndicatorUp.hover || spinBoxIndicatorDown.hover) + && control.enabled + + property bool dirty: false // user modification flag + + property bool spinBoxIndicatorVisible: true + property real __spinBoxIndicatorWidth: control.style.spinBoxIndicatorSize.width + property real __spinBoxIndicatorHeight: control.height / 2 - control.style.borderWidth + + property alias compressedValueTimer: myTimer + + property string preFocusText: "" + + signal realValueModified + signal compressedRealValueModified + signal indicatorPressed + + locale: Utils.locale + + // Use custom wheel handling due to bugs + property bool __wheelEnabled: false + wheelEnabled: false + hoverEnabled: true + + width: control.style.controlSize.width + height: control.style.controlSize.height + + leftPadding: spinBoxIndicatorDown.x + spinBoxIndicatorDown.width + rightPadding: control.style.borderWidth + + font.pixelSize: control.style.baseFontSize + editable: true + + // Leave this in for now + from: -99 + value: 0 + to: 99 + + function checkAndClearFocus() { + if (!spinBoxIndicatorUp.activeFocus && !spinBoxIndicatorDown.activeFocus && !spinBoxInput.activeFocus) + control.focus = false + } + + DoubleValidator { + id: doubleValidator + locale: control.locale + notation: DoubleValidator.StandardNotation + decimals: control.decimals + bottom: Math.min(control.realFrom, control.realTo) + top: Math.max(control.realFrom, control.realTo) + } + + IntValidator { + id: intValidator + locale: control.locale + bottom: Math.round(Math.min(control.realFrom, control.realTo)) + top: Math.round(Math.max(control.realFrom, control.realTo)) + } + + validator: control.decimals === 0 ? intValidator : doubleValidator + + up.indicator: SpinBoxIndicator { + id: spinBoxIndicatorUp + style: control.style + parentHover: control.hover + parentEdit: control.edit + iconFlip: -1 + visible: control.spinBoxIndicatorVisible + onRealPressed: control.indicatorPressed() + onRealReleased: control.realIncrease() + onRealPressAndHold: control.realIncrease() + x: control.style.borderWidth + y: control.style.borderWidth + width: control.spinBoxIndicatorVisible ? control.__spinBoxIndicatorWidth : 0 + height: control.spinBoxIndicatorVisible ? control.__spinBoxIndicatorHeight : 0 + + realEnabled: (control.realFrom < control.realTo) ? (control.realValue < control.realTo) + : (control.realValue > control.realTo) + } + + down.indicator: SpinBoxIndicator { + id: spinBoxIndicatorDown + style: control.style + parentHover: control.hover + parentEdit: control.edit + visible: control.spinBoxIndicatorVisible + onRealPressed: control.indicatorPressed() + onRealReleased: control.realDecrease() + onRealPressAndHold: control.realDecrease() + x: control.style.borderWidth + y: spinBoxIndicatorUp.y + spinBoxIndicatorUp.height + width: control.spinBoxIndicatorVisible ? control.__spinBoxIndicatorWidth : 0 + height: control.spinBoxIndicatorVisible ? control.__spinBoxIndicatorHeight : 0 + + realEnabled: (control.realFrom < control.realTo) ? (control.realValue > control.realFrom) + : (control.realValue < control.realFrom) + } + + contentItem: SpinBoxInput { + id: spinBoxInput + style: control.style + validator: control.validator + font: control.font + readOnly: !control.editable + inputMethodHints: control.inputMethodHints + + function handleEditingFinished() { + control.checkAndClearFocus() + + // Keep the dirty state before calling setValueFromInput(), + // it will be set to false (cleared) internally + var valueModified = control.dirty + + control.setValueFromInput() + myTimer.stop() + + // Only trigger the signal, if the value was modified + if (valueModified) + control.compressedRealValueModified() + } + + onEditingFinished: { + spinBoxInput.focus = false + spinBoxInput.handleEditingFinished() + } + + onTextEdited: control.dirty = true + } + + background: Rectangle { + id: spinBoxBackground + color: control.style.background.idle + border.color: control.style.border.idle + border.width: control.style.borderWidth + x: 0 + width: control.width + height: control.height + } + + textFromValue: function (value, locale) { + // -128 is the value of QLocale::FloatingPointShortest + return Number(control.realValue).toLocaleString(locale, 'f', -128) + } + + valueFromText: function (text, locale) { + control.setRealValue(Number.fromLocaleString(locale, spinBoxInput.text)) + + return 0 + } + + states: [ + State { + name: "default" + when: control.enabled && !control.hover && !control.hovered + && !control.edit + PropertyChanges { + target: control + __wheelEnabled: false + } + PropertyChanges { + target: spinBoxInput + selectByMouse: false + } + PropertyChanges { + target: spinBoxBackground + color: control.style.background.idle + border.color: control.style.border.idle + } + }, + State { + name: "hover" + when: control.enabled && control.hover && control.hovered + && !control.edit + PropertyChanges { + target: spinBoxBackground + border.color: control.style.border.hover + } + }, + State { + name: "edit" + when: control.edit + PropertyChanges { + target: control + __wheelEnabled: true + } + PropertyChanges { + target: spinBoxInput + selectByMouse: true + } + PropertyChanges { + target: spinBoxBackground + border.color: control.style.border.interaction + } + }, + State { + name: "disable" + when: !control.enabled + PropertyChanges { + target: spinBoxBackground + border.color: control.style.border.disabled + } + } + ] + + Timer { + id: myTimer + repeat: false + running: false + interval: 400 + onTriggered: control.compressedRealValueModified() + } + + onRealValueChanged: { + control.setRealValue(control.realValue) // sanitize and clamp realValue + spinBoxInput.text = control.textFromValue(control.realValue, control.locale) + control.value = 0 // Without setting value back to 0, it can happen that one of + // the indicator will be disabled due to range logic. + } + onRealValueModified: myTimer.restart() + onFocusChanged: { + if (control.focus) + control.dirty = false + } + onDisplayTextChanged: spinBoxInput.text = control.displayText + onActiveFocusChanged: { + if (control.activeFocus) { // QTBUG-75862 && mySpinBox.focusReason === Qt.TabFocusReason) + control.preFocusText = spinBoxInput.text + spinBoxInput.selectAll() + } else { + // Make sure displayed value is correct after focus loss, as onEditingFinished + // doesn't trigger when value is something validator doesn't accept. + if (spinBoxInput.text === "") + spinBoxInput.text = control.textFromValue(control.realValue, control.locale) + + if (control.dirty) + spinBoxInput.handleEditingFinished() + } + } + onDecimalsChanged: spinBoxInput.text = control.textFromValue(control.realValue, control.locale) + + Keys.onPressed: function(event) { + if (event.key === Qt.Key_Up || event.key === Qt.Key_Down) { + event.accepted = true + + // Store current step size + var currStepSize = control.realStepSize + + // Set realStepSize according to used modifier key + if (event.modifiers & Qt.ControlModifier) + control.realStepSize = control.minStepSize + + if (event.modifiers & Qt.ShiftModifier) + control.realStepSize = control.maxStepSize + + if (event.key === Qt.Key_Up) + control.realIncrease() + else + control.realDecrease() + + // Reset realStepSize + control.realStepSize = currStepSize + } + + if (event.key === Qt.Key_Escape) { + spinBoxInput.text = control.preFocusText + control.dirty = true + spinBoxInput.handleEditingFinished() + } + } + + function clamp(v, lo, hi) { + return (v < lo || v > hi) ? Math.min(Math.max(lo, v), hi) : v + } + + function setValueFromInput() { + if (!control.dirty) + return + + // FIX: This is a temporary fix for QTBUG-74239 + var currValue = control.realValue + + // Call the function but don't use return value. The realValue property + // will be implicitly set inside the function/procedure. + control.valueFromText(spinBoxInput.text, control.locale) + + if (control.realValue !== currValue) { + control.realValueModified() + } else { + // Check if input text differs in format from the current value + var tmpInputValue = control.textFromValue(control.realValue, control.locale) + + if (tmpInputValue !== spinBoxInput.text) + spinBoxInput.text = tmpInputValue + } + + control.dirty = false + } + + function setRealValue(value) { + if (Number.isNaN(value)) + value = 0 + + if (control.decimals === 0) + value = Math.round(value) + + control.realValue = control.clamp(value, + control.validator.bottom, + control.validator.top) + } + + function realDecrease() { + // Store the current value for comparison + var currValue = control.realValue + control.valueFromText(spinBoxInput.text, control.locale) + + control.setRealValue(control.realValue - control.realStepSize) + + if (control.realValue !== currValue) + control.realValueModified() + } + + function realIncrease() { + // Store the current value for comparison + var currValue = control.realValue + control.valueFromText(spinBoxInput.text, control.locale) + + control.setRealValue(control.realValue + control.realStepSize) + + if (control.realValue !== currValue) + control.realValueModified() + } +} diff --git a/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBoxIndicator.qml b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBoxIndicator.qml new file mode 100644 index 00000000000..6fd56d1a1b7 --- /dev/null +++ b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBoxIndicator.qml @@ -0,0 +1,247 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Templates as T +import StudioTheme as StudioTheme + +Rectangle { + id: control + + property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle + + property bool hover: spinBoxIndicatorMouseArea.containsMouse + property bool pressed: spinBoxIndicatorMouseArea.containsPress + property bool released: false + property bool realEnabled: true + + property bool parentHover: false + property bool parentEdit: false + + signal realPressed + signal realPressAndHold + signal realReleased + + property alias iconFlip: spinBoxIndicatorIconScale.yScale + + + color: control.style.background.idle + border.width: 0 + + onEnabledChanged: control.syncEnabled() + onRealEnabledChanged: { + control.syncEnabled() + if (control.realEnabled === false) { + pressAndHoldTimer.stop() + spinBoxIndicatorMouseArea.pressedAndHeld = false + } + } + + // This function is meant to synchronize enabled with realEnabled to avoid + // the internal logic messing with the actual state. + function syncEnabled() { + control.enabled = control.realEnabled + } + + Timer { + id: pressAndHoldTimer + repeat: true + running: false + interval: 100 + onTriggered: control.realPressAndHold() + } + + // This MouseArea is a workaround to avoid some hover state related bugs + // when using the actual signal 'up.hovered'. QTBUG-74688 + MouseArea { + id: spinBoxIndicatorMouseArea + + property bool pressedAndHeld: false + + anchors.fill: parent + hoverEnabled: true + pressAndHoldInterval: 500 + onPressed: function(mouse) { + //if (control.__parentControl.activeFocus) + // control.forceActiveFocus() + + control.realPressed() + mouse.accepted = true + } + onPressAndHold: { + pressAndHoldTimer.restart() + spinBoxIndicatorMouseArea.pressedAndHeld = true + } + onReleased: function(mouse) { + // Only trigger real released when pressAndHold isn't active + if (!pressAndHoldTimer.running && containsMouse) + control.realReleased() + pressAndHoldTimer.stop() + mouse.accepted = true + spinBoxIndicatorMouseArea.pressedAndHeld = false + } + onEntered: { + if (spinBoxIndicatorMouseArea.pressedAndHeld) + pressAndHoldTimer.restart() + } + onExited: { + if (pressAndHoldTimer.running) + pressAndHoldTimer.stop() + } + } + + T.Label { + id: spinBoxIndicatorIcon + text: StudioTheme.Constants.upDownSquare2 + color: control.style.icon.idle + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: control.style.smallIconFontSize + font.family: StudioTheme.Constants.iconFont.family + anchors.fill: parent + transform: Scale { + id: spinBoxIndicatorIconScale + origin.x: 0 + origin.y: spinBoxIndicatorIcon.height / 2 + yScale: 1 + } + + states: [ + State { + name: "default" + when: control.enabled && !control.hover && !control.parentEdit && !control.parentHover + PropertyChanges { + target: spinBoxIndicatorIcon + color: control.style.icon.idle + } + }, + State { + name: "globalHover" + when: control.enabled && !control.hover && !control.parentEdit && control.parentHover + PropertyChanges { + target: spinBoxIndicatorIcon + color: control.style.icon.idle + } + }, + State { + name: "hover" + when: control.enabled && control.hover && !control.pressed && control.parentHover + PropertyChanges { + target: spinBoxIndicatorIcon + color: control.style.icon.hover + } + }, + State { + name: "press" + when: control.enabled && control.pressed + PropertyChanges { + target: spinBoxIndicatorIcon + color: control.style.icon.idle + } + }, + State { + name: "parentEdit" + when: control.parentEdit && control.enabled + PropertyChanges { + target: spinBoxIndicatorIcon + color: control.style.icon.idle + } + }, + State { + name: "disable" + when: !control.enabled + PropertyChanges { + target: spinBoxIndicatorIcon + color: control.style.icon.disabled + } + } + ] + } + + states: [ + State { + name: "default" + when: !control.parentEdit && !control.hover && !control.parentHover + PropertyChanges { + target: spinBoxIndicatorIcon + visible: false + } + PropertyChanges { + target: control + color: control.style.background.idle + } + }, + State { + name: "globalHover" + when: control.enabled && !control.hover && !control.parentEdit && control.parentHover + PropertyChanges { + target: spinBoxIndicatorIcon + visible: true + } + PropertyChanges { + target: control + color: control.style.background.globalHover + } + }, + State { + name: "hover" + when: control.enabled && control.hover && !control.pressed && control.parentHover + PropertyChanges { + target: spinBoxIndicatorIcon + visible: true + } + PropertyChanges { + target: control + color: control.style.background.hover + } + }, + State { + name: "press" + when: control.enabled && control.pressed + PropertyChanges { + target: spinBoxIndicatorIcon + visible: true + } + PropertyChanges { + target: control + color: control.style.interaction + } + }, + State { + name: "parentEdit" + when: control.parentEdit && control.enabled + PropertyChanges { + target: spinBoxIndicatorIcon + visible: true + } + PropertyChanges { + target: control + color: control.style.background.idle + } + }, + State { + name: "disable" + when: !control.enabled + PropertyChanges { + target: spinBoxIndicatorIcon + visible: false + } + PropertyChanges { + target: control + color: control.style.background.disabled + } + }, + State { + name: "limit" + when: !control.enabled && !control.realEnabled && control.parentHover + PropertyChanges { + target: spinBoxIndicatorIcon + visible: true + } + PropertyChanges { + target: control + color: control.style.background.idle + } + } + ] +} diff --git a/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBoxInput.qml b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBoxInput.qml new file mode 100644 index 00000000000..a4870eb9491 --- /dev/null +++ b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/SpinBoxInput.qml @@ -0,0 +1,100 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Templates as T +import StudioTheme as StudioTheme + +TextInput { + id: control + + property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle + + property bool edit: control.activeFocus + property bool drag: false + property bool hover: hoverHandler.hovered && control.enabled + + z: 2 + color: control.style.text.idle + selectionColor: control.style.text.selection + selectedTextColor: control.style.text.selectedText + + horizontalAlignment: Qt.AlignLeft + verticalAlignment: Qt.AlignVCenter + leftPadding: control.style.inputHorizontalPadding + rightPadding: control.style.inputHorizontalPadding + + selectByMouse: false + activeFocusOnPress: false + clip: true + + // TextInput focus needs to be set to activeFocus whenever it changes, + // otherwise TextInput will get activeFocus whenever the parent SpinBox gets + // activeFocus. This will lead to weird side effects. + onActiveFocusChanged: control.focus = control.activeFocus + + HoverHandler { id: hoverHandler } + + Rectangle { + id: textInputBackground + x: 0 + y: control.style.borderWidth + z: -1 + width: control.width + height: control.height - (control.style.borderWidth * 2) + color: control.style.background.idle + border.width: 0 + } + + // Ensure that we get Up and Down key press events first + Keys.onShortcutOverride: function(event) { + event.accepted = (event.key === Qt.Key_Up || event.key === Qt.Key_Down) + } + + states: [ + State { + name: "default" + when: control.enabled && !control.edit && !control.hover + PropertyChanges { + target: textInputBackground + color: control.style.background.idle + } + }, + State { + name: "globalHover" + when: !control.hover && !control.edit + PropertyChanges { + target: textInputBackground + color: control.style.background.globalHover + } + }, + State { + name: "hover" + when: control.hover && !control.edit + PropertyChanges { + target: textInputBackground + color: control.style.background.hover + } + }, + State { + name: "edit" + when: control.edit + PropertyChanges { + target: textInputBackground + color: control.style.background.interaction + } + }, + State { + name: "disable" + when: !control.enabled + PropertyChanges { + target: textInputBackground + color: control.style.background.disabled + } + PropertyChanges { + target: control + color: control.style.text.disabled + } + } + ] +} diff --git a/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/Switch.qml b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/Switch.qml index 4ccdca4486c..6f98f90f72b 100644 --- a/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/Switch.qml +++ b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/Switch.qml @@ -4,7 +4,7 @@ import QtQuick import QtQuick.Templates as T -import StudioTheme 1.0 as StudioTheme +import StudioTheme as StudioTheme T.Switch { id: control @@ -14,6 +14,7 @@ T.Switch { // This property is used to indicate the global hover state property bool hover: control.hovered && control.enabled property bool edit: false + property bool readonly: false property alias labelVisible: label.visible property alias labelColor: label.color @@ -33,6 +34,8 @@ T.Switch { hoverEnabled: true activeFocusOnTab: false + enabled: !control.readonly + indicator: Rectangle { id: switchBackground x: 0 @@ -60,6 +63,7 @@ T.Switch { color: control.style.icon.idle border.width: 0 } + } contentItem: T.Label { @@ -133,7 +137,7 @@ T.Switch { }, State { name: "disable" - when: !control.enabled && !control.checked + when: !control.enabled && !control.checked && !control.readonly PropertyChanges { target: switchBackground color: control.style.background.disabled @@ -186,8 +190,19 @@ T.Switch { }, State { name: "disableChecked" - when: !control.enabled && control.checked + when: !control.enabled && control.checked && !control.readonly extend: "disable" + }, + + State { + name: "readonly" + when: !control.enabled && !control.checked && control.readonly + extend: "default" + }, + State { + name: "readonlyChecked" + when: !control.enabled && control.checked && control.readonly + extend: "defaultChecked" } ] } diff --git a/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/qmldir b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/qmldir index 0f9345ed7d2..4556e1a8f3d 100644 --- a/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/qmldir +++ b/share/qtcreator/qmldesigner/designsystem/imports/DesignSystemControls/qmldir @@ -1,3 +1,7 @@ +BindingIndicator 1.0 BindingIndicator.qml MenuItem 1.0 MenuItem.qml +SpinBox 1.0 SpinBox.qml +SpinBoxIndicator 1.0 SpinBoxIndicator.qml +SpinBoxInput 1.0 SpinBoxInput.qml Switch 1.0 Switch.qml TextField 1.0 TextField.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/DialogButton.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/DialogButton.qml index cce96797726..fff8db4ae84 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/DialogButton.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/DialogButton.qml @@ -3,27 +3,29 @@ import QtQuick import QtQuick.Templates as T -import StudioTheme 1.0 as StudioTheme +import StudioTheme as StudioTheme T.Button { id: control property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle - implicitWidth: Math.max(buttonBackground ? buttonBackground.implicitWidth : 0, - textItem.implicitWidth + leftPadding + rightPadding) - implicitHeight: Math.max(buttonBackground ? buttonBackground.implicitHeight : 0, - textItem.implicitHeight + topPadding + bottomPadding) + implicitWidth: Math.max(control.style.squareControlSize.width, + implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(control.style.squareControlSize.height, + implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + leftPadding: control.style.dialogPadding rightPadding: control.style.dialogPadding background: Rectangle { - id: buttonBackground - implicitWidth: 70 - implicitHeight: 20 + id: controlBackground color: control.style.background.idle border.color: control.style.border.idle - anchors.fill: parent + border.width: control.style.borderWidth + radius: control.style.radius } contentItem: Text { @@ -41,7 +43,7 @@ T.Button { when: control.enabled && !control.down && !control.hovered && !control.checked PropertyChanges { - target: buttonBackground + target: controlBackground color: control.highlighted ? control.style.interaction : control.style.background.idle border.color: control.style.border.idle @@ -56,7 +58,7 @@ T.Button { when: control.enabled && control.hovered && !control.checked && !control.down PropertyChanges { - target: buttonBackground + target: controlBackground color: control.style.background.hover border.color: control.style.border.hover } @@ -70,9 +72,9 @@ T.Button { when: control.enabled && (control.checked || control.down) PropertyChanges { - target: buttonBackground - color: control.style.background.interaction - border.color: control.style.border.interaction + target: controlBackground + color: control.style.interaction + border.color: control.style.interaction } PropertyChanges { target: textItem @@ -83,7 +85,7 @@ T.Button { name: "disable" when: !control.enabled PropertyChanges { - target: buttonBackground + target: controlBackground color: control.style.background.disabled border.color: control.style.border.disabled } diff --git a/src/plugins/qmldesigner/components/designsystemview/collectionmodel.cpp b/src/plugins/qmldesigner/components/designsystemview/collectionmodel.cpp index a9c4922caf4..08caa829935 100644 --- a/src/plugins/qmldesigner/components/designsystemview/collectionmodel.cpp +++ b/src/plugins/qmldesigner/components/designsystemview/collectionmodel.cpp @@ -116,6 +116,11 @@ QVariant CollectionModel::headerData(int section, Qt::Orientation orientation, i Qt::ItemFlags CollectionModel::flags(const QModelIndex &index) const { + // If group type is FLAGS and not binding block editable + if (data(index, Roles::GroupRole).value() == GroupType::Flags + && !data(index, Roles::BindingRole).toBool()) + return QAbstractItemModel::flags(index); + return Qt::ItemIsEditable | QAbstractItemModel::flags(index); } @@ -214,9 +219,9 @@ bool CollectionModel::setData(const QModelIndex &index, const QVariant &value, i p.name = propName; const ThemeId id = m_themeIdList[index.column()]; if (m_collection->updateProperty(id, groupType, p)) { - beginResetModel(); updateCache(); - endResetModel(); + + emit dataChanged(index, index); } } default: diff --git a/src/plugins/qmldesigner/components/designsystemview/collectionmodel.h b/src/plugins/qmldesigner/components/designsystemview/collectionmodel.h index c0b43500012..118f55ce152 100644 --- a/src/plugins/qmldesigner/components/designsystemview/collectionmodel.h +++ b/src/plugins/qmldesigner/components/designsystemview/collectionmodel.h @@ -25,6 +25,7 @@ public: ResolvedValueRole, PropertyValueRole }; + Q_ENUM(Roles) Q_PROPERTY(QStringList themeNames READ themeNameList NOTIFY themeNameChanged FINAL) diff --git a/src/plugins/qmldesigner/components/designsystemview/designsysteminterface.cpp b/src/plugins/qmldesigner/components/designsystemview/designsysteminterface.cpp index e57131e9b98..3066e09e46a 100644 --- a/src/plugins/qmldesigner/components/designsystemview/designsysteminterface.cpp +++ b/src/plugins/qmldesigner/components/designsystemview/designsysteminterface.cpp @@ -16,6 +16,7 @@ DesignSystemInterface::DesignSystemInterface(DSStore *store) { qmlRegisterUncreatableMetaObject( QmlDesigner::staticMetaObject, "QmlDesigner.DesignSystem", 1, 0, "GroupType", ""); + qmlRegisterUncreatableType("QmlDesigner.DesignSystem", 1, 0, "CollectionModel", ""); } DesignSystemInterface::~DesignSystemInterface() {} From fd258651bde204459ab4668f84e1da6864a7e9c7 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Mon, 10 Mar 2025 16:21:37 +0200 Subject: [PATCH 43/54] QmlDesigner: Separate property view dynamic properties handling Now property view and connections view have separate dynamic property handling. This allows property view to change dynamic properties when connections view is detached, and also will better support the upcoming selection locking feature in property view. Fixes: QDS-14891 Change-Id: Ic1a03a635ec595800f995ac5920b7e94f7dc0ab9 Reviewed-by: Ali Kianian Reviewed-by: Thomas Hartmann Reviewed-by: Mahmoud Badri --- .../QtQuick/ItemPane.qml | 2 +- .../QtQuick/QtObjectPane.qml | 2 +- .../QtQuick3D/MaterialPane.qml | 2 +- .../QtQuick3D/Object3DPane.qml | 2 +- .../QtQuick3D/TexturePane.qml | 2 +- src/plugins/qmldesigner/CMakeLists.txt | 1 + ...pertyeditordynamicpropertiesproxymodel.cpp | 47 +++++++++++++++++++ ...ropertyeditordynamicpropertiesproxymodel.h | 41 ++++++++++++++++ .../propertyeditor/propertyeditorview.cpp | 46 +++++++++++++++++- .../propertyeditor/propertyeditorview.h | 9 ++++ .../quick2propertyeditorview.cpp | 4 +- 11 files changed, 150 insertions(+), 8 deletions(-) create mode 100644 src/plugins/qmldesigner/components/propertyeditor/propertyeditordynamicpropertiesproxymodel.cpp create mode 100644 src/plugins/qmldesigner/components/propertyeditor/propertyeditordynamicpropertiesproxymodel.h diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml index aeef8a9598e..9e80c293192 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml @@ -20,7 +20,7 @@ PropertyEditorPane { } DynamicPropertiesSection { - propertiesModel: SelectionDynamicPropertiesModel {} + propertiesModel: PropertyEditorDynamicPropertiesModel {} visible: !hasMultiSelection } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/QtObjectPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/QtObjectPane.qml index 12be7b8f346..78e0c2fd480 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/QtObjectPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/QtObjectPane.qml @@ -12,7 +12,7 @@ PropertyEditorPane { ComponentSection {} DynamicPropertiesSection { - propertiesModel: SelectionDynamicPropertiesModel {} + propertiesModel: PropertyEditorDynamicPropertiesModel {} visible: !hasMultiSelection } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/MaterialPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/MaterialPane.qml index 099ed904bba..b8174ad4be6 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/MaterialPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/MaterialPane.qml @@ -82,7 +82,7 @@ Item { } DynamicPropertiesSection { - propertiesModel: SelectionDynamicPropertiesModel {} + propertiesModel: PropertyEditorDynamicPropertiesModel {} visible: !hasMultiSelection } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Object3DPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Object3DPane.qml index e4f7554442c..142bd08ef64 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Object3DPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Object3DPane.qml @@ -18,7 +18,7 @@ PropertyEditorPane { anchors.right: parent.right DynamicPropertiesSection { - propertiesModel: SelectionDynamicPropertiesModel {} + propertiesModel: PropertyEditorDynamicPropertiesModel {} visible: !hasMultiSelection } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TexturePane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TexturePane.qml index a7a3613c663..e7c25024108 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TexturePane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/TexturePane.qml @@ -45,7 +45,7 @@ Rectangle { Layout.fillHeight: true DynamicPropertiesSection { - propertiesModel: SelectionDynamicPropertiesModel {} + propertiesModel: PropertyEditorDynamicPropertiesModel {} visible: !hasMultiSelection } diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 919ef925df3..e2d3d8303d2 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -423,6 +423,7 @@ extend_qtc_plugin(QmlDesigner gradientpresetlistmodel.cpp gradientpresetlistmodel.h instanceimageprovider.cpp instanceimageprovider.h propertyeditorcontextobject.cpp propertyeditorcontextobject.h + propertyeditordynamicpropertiesproxymodel.cpp propertyeditordynamicpropertiesproxymodel.h propertyeditorqmlbackend.cpp propertyeditorqmlbackend.h propertyeditortransaction.cpp propertyeditortransaction.h propertyeditorvalue.cpp propertyeditorvalue.h diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditordynamicpropertiesproxymodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditordynamicpropertiesproxymodel.cpp new file mode 100644 index 00000000000..29fb649143e --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditordynamicpropertiesproxymodel.cpp @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2025 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 "propertyeditordynamicpropertiesproxymodel.h" + +#include + +#include + +namespace QmlDesigner { + +PropertyEditorDynamicPropertiesProxyModel::PropertyEditorDynamicPropertiesProxyModel(QObject *parent) + : DynamicPropertiesProxyModel(parent) +{ + if (PropertyEditorView::instance()) + initModel(PropertyEditorView::instance()->dynamicPropertiesModel()); +} + +void PropertyEditorDynamicPropertiesProxyModel::registerDeclarativeType() +{ + DynamicPropertiesProxyModel::registerDeclarativeType(); + qmlRegisterType("HelperWidgets", 2, 0, "PropertyEditorDynamicPropertiesModel"); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditordynamicpropertiesproxymodel.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditordynamicpropertiesproxymodel.h new file mode 100644 index 00000000000..18e93bda895 --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditordynamicpropertiesproxymodel.h @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** Copyright (C) 2025 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 "dynamicpropertiesproxymodel.h" + +namespace QmlDesigner { + +class PropertyEditorDynamicPropertiesProxyModel : public DynamicPropertiesProxyModel +{ + Q_OBJECT +public: + explicit PropertyEditorDynamicPropertiesProxyModel(QObject *parent = nullptr); + + static void registerDeclarativeType(); +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp index f374e4c72f9..4f1626203cd 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp @@ -10,8 +10,10 @@ #include #include +#include #include #include +#include "qmldesignerplugin.h" #include #include @@ -69,7 +71,7 @@ PropertyEditorView::PropertyEditorView(AsynchronousImageCache &imageCache, , m_qmlBackEndForCurrentType(nullptr) , m_propertyComponentGenerator{PropertyEditorQmlBackend::propertyEditorResourcesPath(), model()} , m_locked(false) - + , m_dynamicPropertiesModel(new DynamicPropertiesModel(true, this)) { m_qmlDir = PropertyEditorQmlBackend::propertyEditorResourcesPath(); @@ -290,6 +292,11 @@ void PropertyEditorView::refreshMetaInfos(const TypeIds &deletedTypeIds) m_propertyComponentGenerator.refreshMetaInfos(deletedTypeIds); } +DynamicPropertiesModel *PropertyEditorView::dynamicPropertiesModel() const +{ + return m_dynamicPropertiesModel; +} + void PropertyEditorView::setExpressionOnObjectNode(const QmlObjectNode &constObjectNode, PropertyNameView name, const QString &newExpression) @@ -392,6 +399,24 @@ void PropertyEditorView::removeAliasForProperty(const ModelNode &modelNode, cons } } +PropertyEditorView *PropertyEditorView::instance() +{ + static PropertyEditorView *s_instance = nullptr; + + if (s_instance) + return s_instance; + + const QList views = QmlDesignerPlugin::instance()->viewManager().views(); + for (AbstractView *view : views) { + PropertyEditorView *propView = qobject_cast(view); + if (propView) + s_instance = propView; + } + + QTC_ASSERT(s_instance, return nullptr); + return s_instance; +} + void PropertyEditorView::updateSize() { if (!m_qmlBackEndForCurrentType) @@ -632,6 +657,8 @@ void PropertyEditorView::setupQmlBackend() setupInsight(rootModelNode(), currentQmlBackend); #endif // QDS_USE_PROJECTSTORAGE + + m_dynamicPropertiesModel->setSelectedNode(m_selectedNode); } void PropertyEditorView::commitVariantValueToModel(PropertyNameView propertyName, const QVariant &value) @@ -750,6 +777,7 @@ void PropertyEditorView::modelAboutToBeDetached(Model *model) m_qmlBackEndForCurrentType->propertyEditorTransaction()->end(); resetView(); + m_dynamicPropertiesModel->reset(); } void PropertyEditorView::propertiesRemoved(const QList &propertyList) @@ -818,12 +846,20 @@ void PropertyEditorView::propertiesRemoved(const QList &proper if (propertyName.contains("anchor")) m_qmlBackEndForCurrentType->backendAnchorBinding().invalidate(m_selectedNode); + + dynamicPropertiesModel()->dispatchPropertyChanges(property); } } if (changed) m_qmlBackEndForCurrentType->updateInstanceImage(); } +void PropertyEditorView::propertiesAboutToBeRemoved(const QList &propertyList) +{ + for (const auto &property : propertyList) + m_dynamicPropertiesModel->removeItem(property); +} + void PropertyEditorView::variantPropertiesChanged(const QList& propertyList, PropertyChangeFlags /*propertyChange*/) { if (noValidSelection()) @@ -850,6 +886,8 @@ void PropertyEditorView::variantPropertiesChanged(const QList& property.name()); if (node == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == node) { + if (property.isDynamic()) + m_dynamicPropertiesModel->updateItem(property); if ( QmlObjectNode(m_selectedNode).modelNode().property(property.name()).isBindingProperty()) setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name())); else @@ -865,6 +903,7 @@ void PropertyEditorView::variantPropertiesChanged(const QList& changed = true; } } + m_dynamicPropertiesModel->dispatchPropertyChanges(property); } if (changed) @@ -895,6 +934,8 @@ void PropertyEditorView::bindingPropertiesChanged(const QList & m_qmlBackEndForCurrentType->contextObject()->setHasAliasExport(QmlObjectNode(m_selectedNode).isAliasExported()); if (node == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == node) { + if (property.isDynamic()) + m_dynamicPropertiesModel->updateItem(property); if (property.name().contains("anchor")) m_qmlBackEndForCurrentType->backendAnchorBinding().invalidate(m_selectedNode); @@ -904,6 +945,7 @@ void PropertyEditorView::bindingPropertiesChanged(const QList & m_locked = false; changed = true; } + m_dynamicPropertiesModel->dispatchPropertyChanges(property); } if (changed) @@ -961,6 +1003,8 @@ void PropertyEditorView::nodeIdChanged(const ModelNode &node, const QString &new if (!QmlObjectNode(m_selectedNode).isValid()) return; + m_dynamicPropertiesModel->reset(); + if (m_qmlBackEndForCurrentType) { if (newId == Constants::MATERIAL_LIB_ID) m_qmlBackEndForCurrentType->contextObject()->setHasMaterialLibrary(true); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h index a8a6d525342..547a6d2aa7c 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h @@ -21,6 +21,7 @@ QT_END_NAMESPACE namespace QmlDesigner { class CollapseButton; +class DynamicPropertiesModel; class ModelNode; class PropertyEditorQmlBackend; class PropertyEditorView; @@ -46,6 +47,7 @@ public: const NodeAbstractProperty &parentProperty, PropertyChangeFlags propertyChange) override; void propertiesRemoved(const QList& propertyList) override; + void propertiesAboutToBeRemoved(const QList &propertyList) override; void modelAttached(Model *model) override; @@ -93,6 +95,8 @@ public: void refreshMetaInfos(const TypeIds &deletedTypeIds) override; + DynamicPropertiesModel *dynamicPropertiesModel() const; + static void setExpressionOnObjectNode(const QmlObjectNode &objectNode, PropertyNameView name, const QString &expression); @@ -124,6 +128,8 @@ private: //functions bool noValidSelection() const; void highlightTextureProperties(bool highlight = true); + static PropertyEditorView *instance(); + private: //variables AsynchronousImageCache &m_imageCache; ModelNode m_selectedNode; @@ -137,6 +143,9 @@ private: //variables PropertyEditorComponentGenerator m_propertyEditorComponentGenerator{m_propertyComponentGenerator}; bool m_locked; bool m_textureAboutToBeRemoved = false; + DynamicPropertiesModel *m_dynamicPropertiesModel = nullptr; + + friend class PropertyEditorDynamicPropertiesProxyModel; }; } //QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp index c5cf236bdcc..9e3a397fa90 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp @@ -29,7 +29,7 @@ #include "qmlmaterialnodeproxy.h" #include "qmltexturenodeproxy.h" #include "richtexteditor/richtexteditorproxy.h" -#include "selectiondynamicpropertiesproxymodel.h" +#include "propertyeditordynamicpropertiesproxymodel.h" #include "theme.h" #include "tooltip.h" @@ -76,7 +76,7 @@ void Quick2PropertyEditorView::registerQmlTypes() Tooltip::registerDeclarativeType(); EasingCurveEditor::registerDeclarativeType(); RichTextEditorProxy::registerDeclarativeType(); - SelectionDynamicPropertiesProxyModel::registerDeclarativeType(); + PropertyEditorDynamicPropertiesProxyModel::registerDeclarativeType(); DynamicPropertyRow::registerDeclarativeType(); PropertyChangesModel::registerDeclarativeType(); PropertyModel::registerDeclarativeType(); From a1b4b3594041e59592fd3c6023008b53f03b8974 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Thu, 6 Mar 2025 13:35:33 +0200 Subject: [PATCH 44/54] QmlDesigner: Unify add QtQuick3D links Fixes: QDS-14876 Change-Id: I6b3eaba09807d67c02dc895eb596d5bdce87f70f Reviewed-by: Mahmoud Badri --- .../ContentLibraryEffectsView.qml | 26 ++++++++++++------- .../ContentLibraryMaterialsView.qml | 24 ++++++++++------- .../ContentLibraryUserView.qml | 25 +++++++++++------- .../MaterialBrowser.qml | 16 ++++++++---- .../components/edit3d/edit3dwidget.cpp | 15 +++-------- .../materialbrowser/materialbrowserwidget.cpp | 6 +++++ .../materialbrowser/materialbrowserwidget.h | 1 + 7 files changed, 68 insertions(+), 45 deletions(-) diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryEffectsView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryEffectsView.qml index ca8650d116a..87777c04463 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryEffectsView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryEffectsView.qml @@ -109,29 +109,35 @@ HelperWidgets.ScrollView { id: infoText text: { - if (!ContentLibraryBackend.rootView.isQt6Project) + if (!ContentLibraryBackend.rootView.isQt6Project) { qsTr("Content Library effects are not supported in Qt5 projects.") - else if (!ContentLibraryBackend.rootView.hasQuick3DImport) - qsTr('To use Content Library, first - add the QtQuick3D module in the Components view.') - .arg(StudioTheme.Values.themeInteraction) - else if (!ContentLibraryBackend.effectsModel.hasRequiredQuick3DImport) + } else if (!ContentLibraryBackend.rootView.hasQuick3DImport) { + qsTr('To use Content Library effects, add the QtQuick3D module and the View3D + component in the Components view, or click + + here.').arg(StudioTheme.Values.themeInteraction) + } else if (!ContentLibraryBackend.effectsModel.hasRequiredQuick3DImport) { qsTr("To use Content Library, version 6.4 or later of the QtQuick3D module is required.") - else if (!ContentLibraryBackend.rootView.hasMaterialLibrary) + } else if (!ContentLibraryBackend.rootView.hasMaterialLibrary) { qsTr("Content Library is disabled inside a non-visual component.") - else if (!ContentLibraryBackend.effectsModel.bundleExists) + } else if (!ContentLibraryBackend.effectsModel.bundleExists) { qsTr("No effects available.") - else if (!searchBox.isEmpty()) + } else if (!searchBox.isEmpty()) { qsTr("No match found.") - else + } else { "" + } } textFormat: Text.RichText color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.baseFontSize topPadding: 10 leftPadding: 10 + rightPadding: 10 visible: ContentLibraryBackend.effectsModel.isEmpty + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + width: root.width onLinkActivated: ContentLibraryBackend.rootView.addQtQuick3D() } diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml index 0ace2e7efd0..acdcdb21c9d 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml @@ -114,30 +114,34 @@ HelperWidgets.ScrollView { id: infoText text: { - if (!ContentLibraryBackend.rootView.isQt6Project) + if (!ContentLibraryBackend.rootView.isQt6Project) { qsTr("Content Library materials are not supported in Qt5 projects.") - else if (!ContentLibraryBackend.rootView.hasQuick3DImport) - qsTr('To use Content Library, first - add the QtQuick3D module in the Components view.') - .arg(StudioTheme.Values.themeInteraction) - else if (!root.materialsModel.hasRequiredQuick3DImport) + } else if (!ContentLibraryBackend.rootView.hasQuick3DImport) { + qsTr('To use Content Library materials, add the QtQuick3D module and the View3D + component in the Components view, or click + + here.').arg(StudioTheme.Values.themeInteraction) + } else if (!root.materialsModel.hasRequiredQuick3DImport) { qsTr("To use Content Library, version 6.3 or later of the QtQuick3D module is required.") - else if (!ContentLibraryBackend.rootView.hasMaterialLibrary) + } else if (!ContentLibraryBackend.rootView.hasMaterialLibrary) { qsTr("Content Library is disabled inside a non-visual component.") - else if (!root.materialsModel.bundleExists) + } else if (!root.materialsModel.bundleExists) { qsTr("No materials available. Make sure you have an internet connection.") - else if (!searchBox.isEmpty()) + } else if (!searchBox.isEmpty()) { qsTr("No match found.") - else + } else { "" + } } textFormat: Text.RichText color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.baseFontSize topPadding: 10 leftPadding: 10 + rightPadding: 10 wrapMode: Text.WordWrap width: root.width - x + horizontalAlignment: Text.AlignHCenter onLinkActivated: ContentLibraryBackend.rootView.addQtQuick3D() } diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryUserView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryUserView.qml index 29d662615c5..2c371385dd1 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryUserView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryUserView.qml @@ -213,26 +213,33 @@ Item { text: { let categoryName = (categoryTitle === "3D") ? categoryTitle + " assets" : categoryTitle.toLowerCase() - if (!ContentLibraryBackend.rootView.isQt6Project) + if (!ContentLibraryBackend.rootView.isQt6Project) { qsTr("Content Library is not supported in Qt5 projects.") - else if (!ContentLibraryBackend.rootView.hasQuick3DImport && categoryTitle !== "Textures") - qsTr(`To use %1, first - add the QtQuick3D module in the Components view.`) - .arg(categoryName) - .arg(StudioTheme.Values.themeInteraction) - else if (!ContentLibraryBackend.rootView.hasMaterialLibrary && categoryTitle !== "Textures") + } else if (!ContentLibraryBackend.rootView.hasQuick3DImport && categoryTitle !== "Textures") { + qsTr('To use %1, add the QtQuick3D module and the View3D + component in the Components view, or click + + here.') + .arg(categoryName) + .arg(StudioTheme.Values.themeInteraction) + } else if (!ContentLibraryBackend.rootView.hasMaterialLibrary && categoryTitle !== "Textures") { qsTr("Content Library is disabled inside a non-visual component.") - else if (categoryEmpty) + } else if (categoryEmpty) { qsTr("There are no "+ categoryName + " in the User Assets.") - else + } else { "" + } } textFormat: Text.RichText color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.baseFontSize topPadding: 10 leftPadding: 10 + rightPadding: 10 visible: infoText.text !== "" + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + width: root.width onLinkActivated: ContentLibraryBackend.rootView.addQtQuick3D() } diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml index 987934e295f..6d935dc39f3 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml @@ -596,14 +596,18 @@ Item { anchors.centerIn: parent text: { - if (!materialBrowserModel.isQt6Project) + if (!materialBrowserModel.isQt6Project) { qsTr("Material Browser is not supported in Qt5 projects.") - else if (!materialBrowserModel.hasQuick3DImport) - qsTr("To use Material Browser, first add the QtQuick3D module in the Components view.") - else if (!materialBrowserModel.hasMaterialLibrary) + } else if (!materialBrowserModel.hasQuick3DImport) { + qsTr('To use the Material Browser, add the QtQuick3D module and the View3D + component in the Components view, or click + + here.').arg(StudioTheme.Values.themeInteraction) + } else if (!materialBrowserModel.hasMaterialLibrary) { qsTr("Material Browser is disabled inside a non-visual component.") - else + } else { "" + } } textFormat: Text.RichText @@ -611,6 +615,8 @@ Item { font.pixelSize: StudioTheme.Values.mediumFontSize horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap + + onLinkActivated: rootView.addQtQuick3D() } } diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index e3553ff5a36..124a6ab252d 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -184,6 +184,7 @@ Edit3DWidget::Edit3DWidget(Edit3DView *view) // Onboarding label contains instructions for new users how to get 3D content into the project m_onboardingLabel = new QLabel(this); m_onboardingLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); + m_onboardingLabel->setWordWrap(true); connect(m_onboardingLabel, &QLabel::linkActivated, this, &Edit3DWidget::linkActivated); fillLayout->addWidget(m_onboardingLabel.data()); @@ -418,19 +419,11 @@ void Edit3DWidget::showOnboardingLabel() if (text.isEmpty()) { if (m_view->externalDependencies().isQt6Project()) { QString labelText = - tr("Your file does not import Qt Quick 3D.

" - "To create a 3D view, add the" - " QtQuick3D" - " module in the" - " Components" - " view or click" + tr("To use the 3D view, add the QtQuick3D module and the View3D" + " component in the Components view or click" " here" ".

" - "To import 3D assets, select" - " +" - " in the" - " Assets" - " view."); + "To import 3D assets, select + in the Assets view."); text = labelText.arg(Utils::creatorColor(Utils::Theme::TextColorLink).name()); } else { text = tr("3D view is not supported in Qt5 projects."); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index 3e93a0922b4..a8b29b52b41 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -389,6 +390,11 @@ void MaterialBrowserWidget::exportMaterial() m_bundleHelper->exportBundle({mat}, m_previewImageProvider->getPixmap(mat)); } +void MaterialBrowserWidget::addQtQuick3D() +{ + Utils3D::addQuick3DImportAndView3D(m_materialBrowserView.data()); +} + QString MaterialBrowserWidget::qmlSourcesPath() { #ifdef SHARE_QML_PATH diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h index 7ebec0b622c..647847cb276 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h @@ -66,6 +66,7 @@ public: Q_INVOKABLE void addMaterialToContentLibrary(); Q_INVOKABLE void importMaterial(); Q_INVOKABLE void exportMaterial(); + Q_INVOKABLE void addQtQuick3D(); StudioQuickWidget *quickWidget() const; From 16bbb5f30fe2f423e97efe60194889f3334341de Mon Sep 17 00:00:00 2001 From: Mats Honkamaa Date: Tue, 11 Mar 2025 09:20:58 +0200 Subject: [PATCH 45/54] Doc: Add link to 4.7 release blog Fixes: QDS-14892 Change-Id: Ic33c20667d78b07c7123e47edf77275860399c37 Reviewed-by: Johanna Vanhatapio --- .../src/external-resources/external-resources-qds.qdoc | 4 ++++ doc/qtdesignstudio/src/whats new/qds-releases.qdoc | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/qtcreator/src/external-resources/external-resources-qds.qdoc b/doc/qtcreator/src/external-resources/external-resources-qds.qdoc index f730b626677..91c2c8c9433 100644 --- a/doc/qtcreator/src/external-resources/external-resources-qds.qdoc +++ b/doc/qtcreator/src/external-resources/external-resources-qds.qdoc @@ -141,6 +141,10 @@ \externalpage https://doc.qt.io/qtdesignstudio/qtquick-positioning.html#using-layouts \title Using layouts */ +/*! + \externalpage https://www.qt.io/blog/qt-design-studio-4.7-released + \title Qt Design Studio 4.7 released +*/ /*! \externalpage https://www.qt.io/blog/qt-design-studio-4.6-released \title Qt Design Studio 4.6 released diff --git a/doc/qtdesignstudio/src/whats new/qds-releases.qdoc b/doc/qtdesignstudio/src/whats new/qds-releases.qdoc index 4923d1b916d..cd203501b56 100644 --- a/doc/qtdesignstudio/src/whats new/qds-releases.qdoc +++ b/doc/qtdesignstudio/src/whats new/qds-releases.qdoc @@ -9,11 +9,12 @@ Read the \QDS release blog posts to see what's new in every version. - \section1 \QDS Release blog posts + \section1 \QDS release blog posts \section2 \QDS 4 \list + \li \l{Qt Design Studio 4.7 released} \li \l{Qt Design Studio 4.6 released} \li \l{Qt Design Studio 4.5.1 released} \li \l{Qt Design Studio 4.5 released} From 1278473e6920513df1478dc6c9e9b4cdd142c9b6 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 11 Mar 2025 14:11:15 +0200 Subject: [PATCH 46/54] QmlDesigner: Remove textRole workaround from EdittableListView Workaround is no longer needed as proper specifics are already in currently supported Qt kits. Change-Id: I3f67e8716c2cf2c217319d3e61728fca246e073f Reviewed-by: Mahmoud Badri --- .../imports/HelperWidgets/EditableListView.qml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/EditableListView.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/EditableListView.qml index b2f0498dc06..21271ae1ef0 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/EditableListView.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/EditableListView.qml @@ -24,9 +24,7 @@ Item { property real __actionIndicatorWidth: StudioTheme.Values.squareComponentWidth property real __actionIndicatorHeight: StudioTheme.Values.height property string typeFilter: "QtQuick3D.Material" - // This binding is a workaround to overcome the rather long adaption to new Qt versions. This - // should actually be fixed in the ModelSection.qml by setting the textRole: "idAndName". - property string textRole: (root.typeFilter === "QtQuick3D.Material") ? "idAndName" : "id" + property string textRole: "id" property string valueRole: "id" property int activatedReason: ComboBox.ActivatedReason.Other From 4146d1bd011bc34cbae6ba821096394926a9d1ea Mon Sep 17 00:00:00 2001 From: Jaime Resano Date: Mon, 20 Jan 2025 11:16:11 +0100 Subject: [PATCH 47/54] Improve Python generator The Python generator generates the .qrc resources file, pyproject.toml, main.py and a settings.py files. This way, pyside6-project can run the code without doing any change in the QML code. Change-Id: Iddcc0c7f7a48c581f656f6d156b17460d4106691 Reviewed-by: Knud Dollereder --- .../qmlprojectexporter/boilerplate.qrc | 3 + .../qmlprojectexporter/exporter.cpp | 1 + .../qmlprojectexporter/pythongenerator.cpp | 101 +++++++----------- .../qmlprojectexporter/pythongenerator.h | 5 +- .../templates/python_generator_main.tpl | 26 +++++ .../templates/python_generator_settings.tpl | 36 +++++++ .../templates/python_pyproject_toml.tpl | 5 + 7 files changed, 114 insertions(+), 63 deletions(-) create mode 100644 src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_generator_main.tpl create mode 100644 src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_generator_settings.tpl create mode 100644 src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_pyproject_toml.tpl diff --git a/src/plugins/qmlprojectmanager/qmlprojectexporter/boilerplate.qrc b/src/plugins/qmlprojectmanager/qmlprojectexporter/boilerplate.qrc index 4960324ab74..50e9909a17c 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectexporter/boilerplate.qrc +++ b/src/plugins/qmlprojectmanager/qmlprojectexporter/boilerplate.qrc @@ -11,5 +11,8 @@ templates/import_qml_components_h.tpl templates/qtquickcontrols2_conf.tpl templates/cmakelists_txt_shared.tpl + templates/python_generator_main.tpl + templates/python_generator_settings.tpl + templates/python_pyproject_toml.tpl diff --git a/src/plugins/qmlprojectmanager/qmlprojectexporter/exporter.cpp b/src/plugins/qmlprojectmanager/qmlprojectexporter/exporter.cpp index e228b53703a..40de901c4fe 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectexporter/exporter.cpp +++ b/src/plugins/qmlprojectmanager/qmlprojectexporter/exporter.cpp @@ -30,6 +30,7 @@ void Exporter::updateProject(QmlProject *project) void Exporter::updateProjectItem(QmlProjectItem *item, bool updateEnabled) { connect(item, &QmlProjectItem::filesChanged, m_cmakeGen, &CMakeGenerator::update); + connect(item, &QmlProjectItem::filesChanged, m_pythonGen, &PythonGenerator::update); connect(item, &QmlProjectItem::fileModified, m_cmakeGen, &CMakeGenerator::updateModifiedFile); if (updateEnabled) { diff --git a/src/plugins/qmlprojectmanager/qmlprojectexporter/pythongenerator.cpp b/src/plugins/qmlprojectmanager/qmlprojectexporter/pythongenerator.cpp index 11ae25fd2ce..636f47fa7d9 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectexporter/pythongenerator.cpp +++ b/src/plugins/qmlprojectmanager/qmlprojectexporter/pythongenerator.cpp @@ -3,41 +3,14 @@ #include "pythongenerator.h" #include "cmakewriter.h" +#include "resourcegenerator.h" #include "projectexplorer/projectmanager.h" #include "qmlprojectmanager/qmlproject.h" #include -namespace QmlProjectManager { - -namespace QmlProjectExporter { - -const char *PYTHON_MAIN_FILE_TEMPLATE = R"( -import os -import sys -from pathlib import Path - -from PySide6.QtGui import QGuiApplication -from PySide6.QtQml import QQmlApplicationEngine - -from autogen.settings import url, import_paths - -if __name__ == '__main__': - app = QGuiApplication(sys.argv) - engine = QQmlApplicationEngine() - - app_dir = Path(__file__).parent.parent - - engine.addImportPath(os.fspath(app_dir)) - for path in import_paths: - engine.addImportPath(os.fspath(app_dir / path)) - - engine.load(os.fspath(app_dir/url)) - if not engine.rootObjects(): - sys.exit(-1) - sys.exit(app.exec()) -)"; +namespace QmlProjectManager::QmlProjectExporter { void PythonGenerator::createMenuAction(QObject *parent) { @@ -66,9 +39,8 @@ PythonGenerator::PythonGenerator(QmlBuildSystem *bs) void PythonGenerator::updateMenuAction() { - FileGenerator::updateMenuAction( - "QmlProject.EnablePythonGenerator", - [this]() { return buildSystem()->enablePythonGeneration(); }); + FileGenerator::updateMenuAction("QmlProject.EnablePythonGenerator", + [this]() { return buildSystem()->enablePythonGeneration(); }); } void PythonGenerator::updateProject(QmlProject *project) @@ -77,38 +49,47 @@ void PythonGenerator::updateProject(QmlProject *project) return; Utils::FilePath projectPath = project->rootProjectDirectory(); - Utils::FilePath pythonPath = projectPath.pathAppended("Python"); - if (!pythonPath.exists()) - pythonPath.createDir(); + Utils::FilePath pythonFolderPath = projectPath.pathAppended("Python"); + if (!pythonFolderPath.exists()) + pythonFolderPath.createDir(); - Utils::FilePath mainFile = pythonPath.pathAppended("main.py"); - if (!mainFile.exists()) { - const QString mainContent = QString::fromUtf8(PYTHON_MAIN_FILE_TEMPLATE); - CMakeWriter::writeFile(mainFile, mainContent); + Utils::FilePath mainFilePath = pythonFolderPath.pathAppended("main.py"); + if (!mainFilePath.exists()) { + const QString mainFileTemplate = CMakeWriter::readTemplate( + ":/templates/python_generator_main"); + CMakeWriter::writeFile(mainFilePath, mainFileTemplate); } - Utils::FilePath autogenPath = pythonPath.pathAppended("autogen"); - if (!autogenPath.exists()) - autogenPath.createDir(); + Utils::FilePath pyprojectFilePath = pythonFolderPath.pathAppended("pyproject.toml"); + if (!pyprojectFilePath.exists()) { + const QString pyprojectFileTemplate = CMakeWriter::readTemplate( + ":/templates/python_pyproject_toml"); + const QString pyprojectFileContent = pyprojectFileTemplate.arg(project->displayName()); + CMakeWriter::writeFile(pyprojectFilePath, pyprojectFileContent); + } - Utils::FilePath settingsPath = autogenPath.pathAppended("settings.py"); - CMakeWriter::writeFile(settingsPath, settingsFileContent()); + Utils::FilePath autogenFolderPath = pythonFolderPath.pathAppended("autogen"); + if (!autogenFolderPath.exists()) + autogenFolderPath.createDir(); + + Utils::FilePath settingsFilePath = autogenFolderPath.pathAppended("settings.py"); + const QString settingsFileTemplate = CMakeWriter::readTemplate( + ":/templates/python_generator_settings"); + const QString settingsFileContent = settingsFileTemplate.arg(buildSystem()->mainFile()); + CMakeWriter::writeFile(settingsFilePath, settingsFileContent); + + // Python code uses the Qt resources collection file (.qrc) + ResourceGenerator::createQrc(project); } -QString PythonGenerator::settingsFileContent() const -{ - QTC_ASSERT(buildSystem(), return {}); +/*! + Regenerates the .qrc resources file +*/ +void PythonGenerator::update(const QSet &added, const QSet &removed) { + Q_UNUSED(added); + Q_UNUSED(removed); + ResourceGenerator::createQrc(qmlProject()); + // Generated Python code does not need to be updated +}; - QString content("\n"); - content.append("url = \"" + buildSystem()->mainFile() + "\"\n"); - - content.append("import_paths = [\n"); - for (const QString &path : buildSystem()->importPaths()) - content.append("\t\"" + path + "\",\n"); - content.append("]\n"); - - return content; -} - -} // namespace QmlProjectExporter. -} // namespace QmlProjectManager. +} // namespace QmlProjectExporter::QmlProjectManager. diff --git a/src/plugins/qmlprojectmanager/qmlprojectexporter/pythongenerator.h b/src/plugins/qmlprojectmanager/qmlprojectexporter/pythongenerator.h index 7c0a5810df5..34f8c2be38d 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectexporter/pythongenerator.h +++ b/src/plugins/qmlprojectmanager/qmlprojectexporter/pythongenerator.h @@ -21,11 +21,10 @@ public: static void createMenuAction(QObject *parent); PythonGenerator(QmlBuildSystem *bs); + + void update(const QSet &added, const QSet &removed); void updateMenuAction() override; void updateProject(QmlProject *project) override; - -private: - QString settingsFileContent() const; }; } // namespace QmlProjectExporter. diff --git a/src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_generator_main.tpl b/src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_generator_main.tpl new file mode 100644 index 00000000000..c768aa799bf --- /dev/null +++ b/src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_generator_main.tpl @@ -0,0 +1,26 @@ +import sys + +from PySide6.QtGui import QGuiApplication +from PySide6.QtQml import QQmlApplicationEngine + +from autogen.settings import setup_qt_environment + +# Import here the Python files that define QML elements + + +def main(): + app = QGuiApplication(sys.argv) + engine = QQmlApplicationEngine() + + setup_qt_environment(engine) + + if not engine.rootObjects(): + sys.exit(-1) + + ex = app.exec() + del engine + return ex + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_generator_settings.tpl b/src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_generator_settings.tpl new file mode 100644 index 00000000000..435ee55aac4 --- /dev/null +++ b/src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_generator_settings.tpl @@ -0,0 +1,36 @@ +# This file is automatically generated by Qt Design Studio. +import os +import sys +from pathlib import Path + +from PySide6.QtQml import QQmlApplicationEngine + +project_root = Path(__file__).parent.parent.parent + + +def setup_qt_environment(qml_engine: QQmlApplicationEngine): + """ + Load the QML application. Import the compiled resources when the application is deployed. + """ + qml_app_url = "%1" + + if "__compiled__" in globals(): + # Application has been deployed using pyside6-deploy + try: + import autogen.resources # noqa: F401 + except ImportError: + resource_file = Path(__file__).parent / "resources.py" + print( + f"Error: No compiled resources found in {resource_file.absolute()}\n" + f"Please compile the resources using pyside6-rcc or pyside6-project build", + file=sys.stderr, + ) + sys.exit(1) + + qml_engine.addImportPath(":/") + qml_engine.load(f":/{qml_app_url}") + return + + qml_engine.addImportPath(str(project_root.absolute())) + os.environ["QT_QUICK_CONTROLS_CONF"] = str(project_root / "qtquickcontrols2.conf") + qml_engine.load(str(project_root / qml_app_url)) diff --git a/src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_pyproject_toml.tpl b/src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_pyproject_toml.tpl new file mode 100644 index 00000000000..21c00189295 --- /dev/null +++ b/src/plugins/qmlprojectmanager/qmlprojectexporter/templates/python_pyproject_toml.tpl @@ -0,0 +1,5 @@ +[project] +name = "%1" + +[tool.pyside6-project] +files = ["main.py", "autogen/settings.py"] From 8996c4a9d8ddf24471fe9c91d8a1798120a84a73 Mon Sep 17 00:00:00 2001 From: Andrii Semkiv Date: Tue, 11 Mar 2025 13:56:02 +0100 Subject: [PATCH 48/54] QML Designer: reload QML puppet when adding fonts Automatically reload QML when importing fonts, so that the newly added font can be viewed in 2D preview right away. Note, this causes a visible flicker in the 2D preview. If anyone has ideas how to make it smoother, I'm open to suggestions. Fixes: QDS-14900 Change-Id: Ib7a354d0e25a087aabe690ebdafbf131b579dd40 Reviewed-by: Marco Bubke --- .../components/componentcore/modelnodeoperations.cpp | 6 +++++- .../qmldesigner/components/componentcore/viewmanager.cpp | 5 +++++ .../qmldesigner/components/componentcore/viewmanager.h | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index afd2ae12b31..cd67e806dd8 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -1129,7 +1129,11 @@ static QString getAssetDefaultDirectory(const QString &assetDir, const QString & AddFilesResult addFontToProject(const QStringList &fileNames, const QString &defaultDir, bool showDialog) { - return addFilesToProject(fileNames, getAssetDefaultDirectory("fonts", defaultDir), showDialog); + const AddFilesResult result = addFilesToProject(fileNames, + getAssetDefaultDirectory("fonts", defaultDir), + showDialog); + QmlDesignerPlugin::viewManager().view()->resetPuppet(); + return result; } AddFilesResult addSoundToProject(const QStringList &fileNames, const QString &defaultDir, bool showDialog) diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp index 0788e0e1121..062aefde089 100644 --- a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp @@ -512,6 +512,11 @@ const AbstractView *ViewManager::view() const return &d->nodeInstanceView; } +AbstractView *ViewManager::view() +{ + return &d->nodeInstanceView; +} + TextEditorView *ViewManager::textEditorView() { return &d->textEditorView; diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.h b/src/plugins/qmldesigner/components/componentcore/viewmanager.h index 08bbb313053..1ca067f213d 100644 --- a/src/plugins/qmldesigner/components/componentcore/viewmanager.h +++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.h @@ -72,6 +72,7 @@ public: void nextFileIsCalledInternally(); const AbstractView *view() const; + AbstractView *view(); TextEditorView *textEditorView(); void emitCustomNotification(const QString &identifier, const QList &nodeList, From 9fb0dc74ffba81d9617cd4a32cb4dfaee212daee Mon Sep 17 00:00:00 2001 From: Rafal Andrusieczko Date: Thu, 13 Feb 2025 17:00:46 +0100 Subject: [PATCH 49/54] QmlDesigner: Add a property search feature in the property view - add the PropertySearchBar control responsible for all property search logic - add the PropertySearchBar control to the PropertyEditorPane - add properties that support searching for items in the hierarchy - add properties/states that support changing visibility - add resetView function call in property editor view Task-number: QDS-14709 Fixes: QDS-14806 Fixes: QDS-14807 Fixes: QDS-14808 Change-Id: I2e2477c95e592c7e49e13c25f0bb204de6bdb38d Reviewed-by: Miikka Heikkinen Reviewed-by: Mahmoud Badri --- .../QtQuick/EffectsSection.qml | 6 + .../QtQuick/ItemPane.qml | 8 ++ .../QtQuick/emptyPane.qml | 5 + .../DynamicPropertiesSection.qml | 2 + .../HelperWidgets/PropertyEditorPane.qml | 26 +++- .../imports/HelperWidgets/PropertyLabel.qml | 11 ++ .../HelperWidgets/PropertySearchBar.qml | 135 ++++++++++++++++++ .../HelperWidgets/SecondColumnLayout.qml | 15 ++ .../imports/HelperWidgets/Section.qml | 11 ++ .../propertyeditor/propertyeditorview.cpp | 4 +- 10 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertySearchBar.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/EffectsSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/EffectsSection.qml index 635c2fd539d..4da7d9fb5d3 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/EffectsSection.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/EffectsSection.qml @@ -186,6 +186,8 @@ Section { spacing: 1 Section { + readonly property bool __isInEffectsSection: true // used by property search logic + sectionHeight: 37 anchors.left: parent.left anchors.right: parent.right @@ -232,6 +234,8 @@ Section { } Section { + readonly property bool __isInEffectsSection: true // used by property search logic + sectionHeight: 37 anchors.left: parent.left anchors.right: parent.right @@ -305,6 +309,8 @@ Section { Section { id: delegate + readonly property bool __isInEffectsSection: true // used by property search logic + property QtObject wrapper: modelNodeBackend.registerSubSelectionWrapper(modelData) property bool wasExpanded: false diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml index 9e80c293192..536ba9dd90d 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml @@ -90,9 +90,17 @@ PropertyEditorPane { StudioControls.TabButton { text: backendValues.__classNamePrivateInternal.value + onClicked: () => { + if (itemPane.searchBar.hasDoneSearch) + itemPane.searchBar.search(); + } } StudioControls.TabButton { text: qsTr("Layout") + onClicked: () => { + if (itemPane.searchBar.hasDoneSearch) + itemPane.searchBar.search(); + } } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/emptyPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/emptyPane.qml index 3ec325d8f14..c797860c762 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/emptyPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/emptyPane.qml @@ -13,6 +13,11 @@ Rectangle { height: 400 color: StudioTheme.Values.themePanelBackground + // Called from C++ to clear the search when the selected node changes + function clearSearch() { + // The function is empty, because it is a placeholder to match other panes + } + ColumnLayout { id: mainColumn anchors.fill: parent diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml index dea81cab815..88237093c66 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml @@ -507,6 +507,8 @@ Section { } PropertyLabel { + readonly property bool __inDynamicPropertiesSection: true + text: propertyName tooltip: propertyType Layout.alignment: Qt.AlignTop diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyEditorPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyEditorPane.qml index fa0def82c7d..192c53cc5ce 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyEditorPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyEditorPane.qml @@ -18,6 +18,7 @@ Rectangle { default property alias content: mainColumn.children property alias scrollView: mainScrollView + property alias searchBar: propertySearchBar property bool headerDocked: false readonly property Item headerItem: headerDocked ? dockedHeaderLoader.item : undockedHeaderLoader.item @@ -29,10 +30,23 @@ Rectangle { Controller.closeContextMenu() } + // Called from C++ to clear the search when the selected node changes + function clearSearch() { + propertySearchBar.clear(); + } + + PropertySearchBar { + id: propertySearchBar + + contentItem: mainColumn + width: parent.width + z: parent.z + 1 + } + Loader { id: dockedHeaderLoader - anchors.top: itemPane.top + anchors.top: propertySearchBar.bottom z: parent.z + 1 height: item ? item.implicitHeight : 0 width: parent.width @@ -123,6 +137,16 @@ Rectangle { HeaderBackground{} } + Label { + Layout.fillWidth: true + Layout.leftMargin: 10 + + visible: propertySearchBar.hasDoneSearch && !propertySearchBar.hasMatchSearch + text: qsTr("No match found.") + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.baseFont + } + Column { id: mainColumn diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyLabel.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyLabel.qml index 85bded2924c..85874946b65 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyLabel.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyLabel.qml @@ -9,10 +9,13 @@ import StudioTheme 1.0 as StudioTheme T.Label { id: label + readonly property bool __isPropertyLabel: true // used by property search logic + property alias tooltip: toolTipArea.tooltip property bool blockedByContext: false property bool blockedByTemplate: false // MCU + property bool searchNoMatch: false width: StudioTheme.Values.propertyLabelWidth color: StudioTheme.Values.themeTextColor @@ -34,6 +37,14 @@ T.Label { } states: [ + State { + name: "searchNoMatch" + when: searchNoMatch + PropertyChanges { + target: label + visible: false + } + }, State { name: "disabled" when: !label.enabled && !(label.blockedByContext || label.blockedByTemplate) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertySearchBar.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertySearchBar.qml new file mode 100644 index 00000000000..0083eac6355 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertySearchBar.qml @@ -0,0 +1,135 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +Rectangle { + id: root + + readonly property alias hasMatchSearch: internal.matched + readonly property alias hasDoneSearch: internal.searched + + property Item contentItem: null + + color: StudioTheme.Values.themeToolbarBackground + height: StudioTheme.Values.toolbarHeight + + function clear() { + internal.clear(); + searchBox.text = ""; + internal.timer.stop(); + } + + function search() { + internal.search(); + } + + StudioControls.SearchBox { + id: searchBox + + anchors.fill: parent + anchors.bottomMargin: StudioTheme.Values.toolbarVerticalMargin + anchors.topMargin: StudioTheme.Values.toolbarVerticalMargin + anchors.leftMargin: StudioTheme.Values.toolbarHorizontalMargin + anchors.rightMargin: StudioTheme.Values.toolbarHorizontalMargin + style: StudioTheme.Values.searchControlStyle + onSearchChanged: internal.timer.restart() + } + + QtObject { + id: internal + + readonly property var reverts: [] + readonly property Timer timer: Timer { + interval: 300 + repeat: false + + onTriggered: internal.search() + } + property bool matched: false + property bool searched: false + + function clear() { + internal.reverts.forEach(revert => revert()); + internal.reverts.length = 0; + internal.matched = false; + internal.searched = false; + } + + function search() { + internal.clear(); + const searchText = searchBox.text.toLowerCase(); + if (searchText.length > 0) { + internal.traverse(root.contentItem, searchText); + internal.searched = true; + } + } + + function disableSearchNoMatchAction(item) { + item.searchNoMatch = true; + internal.reverts.push(() => { + item.searchNoMatch = false; + }); + } + + function disableVisibleAction(item) { + item.visible = false; + internal.reverts.push(() => { + item.visible = true; + }); + } + + function enableSearchHideAction(item) { + item.searchHide = true; + internal.reverts.push(() => { + item.searchHide = false; + }); + } + + function expandSectionAction(item) { + internal.matched = true; + const prevValue = item.expanded; + item.expanded = true; + internal.reverts.push(() => { + item.expanded = prevValue; + }); + } + + function traverse(item, searchText) { + let hideSection = true; + let hideParentSection = true; + item.children.forEach((child, index, arr) => { + if (!child.visible) + return; + + if (child.__isPropertyLabel) { + const propertyLabel = child; + const text = propertyLabel.text.toLowerCase(); + if (!text.includes(searchText)) { + internal.disableSearchNoMatchAction(propertyLabel); + const action = propertyLabel.__inDynamicPropertiesSection ? internal.disableVisibleAction + : internal.disableSearchNoMatchAction; + const nextItem = arr[index + 1]; + action(nextItem); + } else { + hideSection = false; + } + } + hideSection &= internal.traverse(child, searchText); + if (child.__isSection) { + const action = hideSection ? internal.enableSearchHideAction + : internal.expandSectionAction; + action(child); + + if (child.__isInEffectsSection && !hideSection) + hideParentSection = false; + + hideSection = true; + } + }); + return hideParentSection && hideSection; + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SecondColumnLayout.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SecondColumnLayout.qml index e35317d0147..fa8d9a7e5c2 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SecondColumnLayout.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SecondColumnLayout.qml @@ -5,6 +5,21 @@ import QtQuick 2.15 import QtQuick.Layouts 1.15 RowLayout { + id: root + + property bool searchNoMatch: false + Layout.fillWidth: true spacing: 0 + + states: [ + State { + name: "searchNoMatch" + when: searchNoMatch + PropertyChanges { + target: root + visible: false + } + } + ] } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml index a24edc4904d..5af99822c71 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml @@ -10,6 +10,8 @@ import StudioTheme as StudioTheme Item { id: section + readonly property bool __isSection: true // used by property search logic + property string caption: "Title" property color labelColor: StudioTheme.Values.themeTextColor property int labelCapitalization: Font.AllUppercase @@ -58,6 +60,7 @@ Item { property bool dropEnabled: false property bool highlight: false property bool eyeEnabled: true // eye button enabled (on) + property bool searchHide: false property bool useDefaulContextMenu: true @@ -343,6 +346,14 @@ Item { } states: [ + State { + name: "Hide" + when: section.searchHide + PropertyChanges { + target: section + visible: false + } + }, State { name: "Collapsed" when: !section.expanded diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp index 4f1626203cd..09ddc373e7c 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp @@ -446,8 +446,10 @@ void PropertyEditorView::resetView() setupQmlBackend(); - if (m_qmlBackEndForCurrentType) + if (m_qmlBackEndForCurrentType) { m_qmlBackEndForCurrentType->emitSelectionChanged(); + QMetaObject::invokeMethod(m_qmlBackEndForCurrentType->widget()->rootObject(), "clearSearch"); + } m_locked = false; From 500e1e29ba044651e7caf529be47fddd468fddb9 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Fri, 14 Feb 2025 17:42:35 +0100 Subject: [PATCH 50/54] QmlDesigner: remove lookups in the model Use the result from try_emplace. So we save two additional lookups. The code gets simpler, too. Change-Id: Id26024dd2f23f7e345b5b699b6a7b619669bfee1 Reviewed-by: Thomas Hartmann --- .../libs/designercore/model/internalnode_p.h | 37 +++++----- .../libs/designercore/model/model.cpp | 72 ++++--------------- .../libs/qmldesignerutils/memory.h | 29 +++----- 3 files changed, 42 insertions(+), 96 deletions(-) diff --git a/src/plugins/qmldesigner/libs/designercore/model/internalnode_p.h b/src/plugins/qmldesigner/libs/designercore/model/internalnode_p.h index 8160e149e60..fee8341118c 100644 --- a/src/plugins/qmldesigner/libs/designercore/model/internalnode_p.h +++ b/src/plugins/qmldesigner/libs/designercore/model/internalnode_p.h @@ -11,8 +11,10 @@ #include "internalsignalhandlerproperty.h" #include "internalvariantproperty.h" +#include #include #include +#include #include #include @@ -146,46 +148,47 @@ public: auto nodeProperty(PropertyNameView name) const { return property(name); } template - Type *addProperty(PropertyNameView name) + std::tuple getProperty(PropertyNameView name) { auto [iter, inserted] = m_nameProperties.try_emplace( - name, std::make_shared(name, shared_from_this())); + name, makeLazySharedPtr(name, shared_from_this())); + auto flags = AbstractView::NoAdditionalChanges; if (inserted) - return static_cast(iter->second.get()); + flags = AbstractView::PropertiesAdded; - return nullptr; + return std::make_tuple(static_cast(iter->second.get()), flags); } - auto addBindingProperty(PropertyNameView name) + auto getBindingProperty(PropertyNameView name) { - return addProperty(name); + return getProperty(name); } - auto addSignalHandlerProperty(PropertyNameView name) + auto getSignalHandlerProperty(PropertyNameView name) { - return addProperty(name); + return getProperty(name); } - auto addSignalDeclarationProperty(PropertyNameView name) + auto getSignalDeclarationProperty(PropertyNameView name) { - return addProperty(name); + return getProperty(name); } - auto addNodeListProperty(PropertyNameView name) + auto getNodeListProperty(PropertyNameView name) { - return addProperty(name); + return getProperty(name); } - auto addVariantProperty(PropertyNameView name) + auto getVariantProperty(PropertyNameView name) { - return addProperty(name); + return getProperty(name); } - auto addNodeProperty(PropertyNameView name, const TypeName &dynamicTypeName) + auto getNodeProperty(PropertyNameView name, const TypeName &dynamicTypeName) { - auto property = addProperty(name); - property->setDynamicTypeName(dynamicTypeName); + auto property = getProperty(name); + std::get<0>(property)->setDynamicTypeName(dynamicTypeName); return property; } diff --git a/src/plugins/qmldesigner/libs/designercore/model/model.cpp b/src/plugins/qmldesigner/libs/designercore/model/model.cpp index c8956b45682..e3e98598364 100644 --- a/src/plugins/qmldesigner/libs/designercore/model/model.cpp +++ b/src/plugins/qmldesigner/libs/designercore/model/model.cpp @@ -341,7 +341,7 @@ InternalNodePointer ModelPrivate::createNode(TypeNameView typeName, using PropertyPair = QPair; for (const PropertyPair &propertyPair : propertyList) { - newNode->addVariantProperty(propertyPair.first); + newNode->getVariantProperty(propertyPair.first); newNode->variantProperty(propertyPair.first)->setValue(propertyPair.second); } @@ -1379,14 +1379,7 @@ void ModelPrivate::setBindingProperty(const InternalNodePointer &node, PropertyNameView name, const QString &expression) { - AbstractView::PropertyChangeFlags propertyChange = AbstractView::NoAdditionalChanges; - InternalBindingProperty *bindingProperty = nullptr; - if (auto property = node->property(name)) { - bindingProperty = property->to(); - } else { - bindingProperty = node->addBindingProperty(name); - propertyChange = AbstractView::PropertiesAdded; - } + auto [bindingProperty, propertyChange] = node->getBindingProperty(name); notifyBindingPropertiesAboutToBeChanged({bindingProperty}); bindingProperty->setExpression(expression); @@ -1415,14 +1408,7 @@ void ModelPrivate::setSignalHandlerProperty(const InternalNodePointer &node, PropertyNameView name, const QString &source) { - AbstractView::PropertyChangeFlags propertyChange = AbstractView::NoAdditionalChanges; - InternalSignalHandlerProperty *signalHandlerProperty = nullptr; - if (auto property = node->property(name)) { - signalHandlerProperty = property->to(); - } else { - signalHandlerProperty = node->addSignalHandlerProperty(name); - propertyChange = AbstractView::PropertiesAdded; - } + auto [signalHandlerProperty, propertyChange] = node->getSignalHandlerProperty(name); signalHandlerProperty->setSource(source); notifySignalHandlerPropertiesChanged({signalHandlerProperty}, propertyChange); @@ -1432,14 +1418,7 @@ void ModelPrivate::setSignalDeclarationProperty(const InternalNodePointer &node, PropertyNameView name, const QString &signature) { - AbstractView::PropertyChangeFlags propertyChange = AbstractView::NoAdditionalChanges; - InternalSignalDeclarationProperty *signalDeclarationProperty = nullptr; - if (auto property = node->property(name)) { - signalDeclarationProperty = property->to(); - } else { - signalDeclarationProperty = node->addSignalDeclarationProperty(name); - propertyChange = AbstractView::PropertiesAdded; - } + auto [signalDeclarationProperty, propertyChange] = node->getSignalDeclarationProperty(name); signalDeclarationProperty->setSignature(signature); notifySignalDeclarationPropertiesChanged({signalDeclarationProperty}, propertyChange); @@ -1449,14 +1428,7 @@ void ModelPrivate::setVariantProperty(const InternalNodePointer &node, PropertyNameView name, const QVariant &value) { - AbstractView::PropertyChangeFlags propertyChange = AbstractView::NoAdditionalChanges; - InternalVariantProperty *variantProperty = nullptr; - if (auto property = node->property(name)) { - variantProperty = property->to(); - } else { - variantProperty = node->addVariantProperty(name); - propertyChange = AbstractView::PropertiesAdded; - } + auto [variantProperty, propertyChange] = node->getVariantProperty(name); variantProperty->setValue(value); variantProperty->resetDynamicTypeName(); @@ -1468,14 +1440,7 @@ void ModelPrivate::setDynamicVariantProperty(const InternalNodePointer &node, const TypeName &dynamicPropertyType, const QVariant &value) { - AbstractView::PropertyChangeFlags propertyChange = AbstractView::NoAdditionalChanges; - InternalVariantProperty *variantProperty = nullptr; - if (auto property = node->property(name)) { - variantProperty = property->to(); - } else { - variantProperty = node->addVariantProperty(name); - propertyChange = AbstractView::PropertiesAdded; - } + auto [variantProperty, propertyChange] = node->getVariantProperty(name); variantProperty->setDynamicValue(dynamicPropertyType, value); notifyVariantPropertiesChanged(node, PropertyNameViews({name}), propertyChange); @@ -1486,14 +1451,7 @@ void ModelPrivate::setDynamicBindingProperty(const InternalNodePointer &node, const TypeName &dynamicPropertyType, const QString &expression) { - AbstractView::PropertyChangeFlags propertyChange = AbstractView::NoAdditionalChanges; - InternalBindingProperty *bindingProperty = nullptr; - if (auto property = node->property(name)) { - bindingProperty = property->to(); - } else { - bindingProperty = node->addBindingProperty(name); - propertyChange = AbstractView::PropertiesAdded; - } + auto [bindingProperty, propertyChange] = node->getBindingProperty(name); notifyBindingPropertiesAboutToBeChanged({bindingProperty}); bindingProperty->setDynamicExpression(dynamicPropertyType, expression); @@ -1524,16 +1482,14 @@ void ModelPrivate::reparentNode(const InternalNodePointer &parentNode, propertyChange); InternalNodeAbstractProperty *newParentProperty = nullptr; - if (auto property = parentNode->property(name)) { - newParentProperty = property->to(); - } else { - if (list) - newParentProperty = parentNode->addNodeListProperty(name); - else - newParentProperty = parentNode->addNodeProperty(name, dynamicTypeName); + AbstractView::PropertyChangeFlags newPropertyChange = AbstractView::NoAdditionalChanges; - propertyChange |= AbstractView::PropertiesAdded; - } + if (list) + std::tie(newParentProperty, newPropertyChange) = parentNode->getNodeListProperty(name); + else + std::tie(newParentProperty, newPropertyChange) = parentNode->getNodeProperty(name, + dynamicTypeName); + propertyChange |= newPropertyChange; Q_ASSERT(newParentProperty); diff --git a/src/plugins/qmldesigner/libs/qmldesignerutils/memory.h b/src/plugins/qmldesigner/libs/qmldesignerutils/memory.h index 17b8874d3bb..dd64c4a0a07 100644 --- a/src/plugins/qmldesigner/libs/qmldesignerutils/memory.h +++ b/src/plugins/qmldesigner/libs/qmldesignerutils/memory.h @@ -8,21 +8,20 @@ namespace QmlDesigner { -template -class LazyPtr +template +class LazySharedPtr { - using ResultType = Pointer::element_type; public: - LazyPtr(Arguments &&...arguments) + LazySharedPtr(Arguments &&...arguments) : m_arguments{std::forward_as_tuple(std::forward(arguments)...)} {} - operator Pointer() const + operator std::shared_ptr() const { return std::apply( [](auto &&...arguments) { - return Pointer(new ResultType(std::forward(arguments)...)); + return std::make_shared(std::forward(arguments)...); }, m_arguments); } @@ -31,22 +30,10 @@ private: std::tuple m_arguments; }; -template -using LazyUniquePtr = LazyPtr, Arguments...>; - -template -LazyUniquePtr makeLazyUniquePtr(Arguments &&...arguments) +template +LazySharedPtr makeLazySharedPtr(Arguments &&...arguments) { - return LazyUniquePtr(std::forward(arguments)...); -} - -template -using LazySharedPtr = LazyPtr, Arguments...>; - -template -LazySharedPtr makeLazySharedPtr(Arguments &&...arguments) -{ - return LazySharedPtr(std::forward(arguments)...); + return LazySharedPtr(std::forward(arguments)...); } } // namespace QmlDesigner From b0af280b3ff35b39f3234cb0d2f4da926b5ed85b Mon Sep 17 00:00:00 2001 From: Rafal Stawarski Date: Tue, 11 Mar 2025 10:44:45 +0100 Subject: [PATCH 51/54] PropertyEditorValue: improvement for isTranslated isTranslated needs to take into account dynamic properties as well so the highlight of 'tr' button works properly for translated dynamic properties in the property editor. Task-number: QDS-13513 Change-Id: I8694dc0b3b3d02351868fbe9a0777c6e4f1b6ee5 Reviewed-by: Thomas Hartmann --- .../propertyeditor/propertyeditorvalue.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp index 01b18a08709..1828e891727 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp @@ -222,9 +222,15 @@ void PropertyEditorValue::setIsValid(bool valid) bool PropertyEditorValue::isTranslated() const { if (modelNode().isValid()) { - if (auto metaInfo = modelNode().metaInfo(); - metaInfo.isValid() && metaInfo.hasProperty(name()) - && metaInfo.property(name()).propertyType().isString()) { + auto metaInfo = modelNode().metaInfo(); + auto isString = metaInfo.isValid() && metaInfo.hasProperty(name()) + && metaInfo.property(name()).propertyType().isString(); + + auto property = modelNode().property(name()); + auto isDynamicString = property.isValid() && property.isDynamic() + && property.dynamicTypeName() == TypeNameView("string"); + + if (isString || isDynamicString) { const QmlObjectNode objectNode(modelNode()); if (objectNode.hasBindingProperty(name())) { const QRegularExpression rx( From 59c9e79828eb90f9afe9f1886b48f51504bab1f7 Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Tue, 11 Mar 2025 14:59:15 +0100 Subject: [PATCH 52/54] QmlDesigner: Replace try/catch with transaction Change-Id: Ief1e09d206ec96a4169cf76414908cd8bd776208 Reviewed-by: Thomas Hartmann Reviewed-by: Vikas Pachdha --- src/plugins/qmldesigner/libs/designsystem/dsstore.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/plugins/qmldesigner/libs/designsystem/dsstore.cpp b/src/plugins/qmldesigner/libs/designsystem/dsstore.cpp index 42110e31dfc..b5de54cf2ab 100644 --- a/src/plugins/qmldesigner/libs/designsystem/dsstore.cpp +++ b/src/plugins/qmldesigner/libs/designsystem/dsstore.cpp @@ -72,11 +72,7 @@ std::optional modelSerializeHelper( view.setTextModifier(&modifier); model->attachView(&view); - try { - callback(model.get()); - } catch (const QmlDesigner::RewritingException &e) { - return e.description(); - } + view.executeInTransaction("DSStore::modelSerializeHelper", [&] { callback(model.get()); }); Utils::FileSaver saver(targetDir / (typeName + ".qml"), QIODevice::Text); saver.write(reformatQml(modifier.text())); From 9057a796e95925b367e21186551991c809d8130b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Jen=C3=9Fen?= Date: Wed, 12 Mar 2025 14:55:03 +0100 Subject: [PATCH 53/54] QmlDesigner: fix rare crash Change-Id: Ifbafc1d54e8839be115a0b4dda90da09d4ac4ff5 Reviewed-by: Thomas Hartmann --- src/plugins/studiowelcome/wizardhandler.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plugins/studiowelcome/wizardhandler.cpp b/src/plugins/studiowelcome/wizardhandler.cpp index bcba47c01ab..9417f66803c 100644 --- a/src/plugins/studiowelcome/wizardhandler.cpp +++ b/src/plugins/studiowelcome/wizardhandler.cpp @@ -39,8 +39,10 @@ void WizardHandler::destroyWizard() emit deletingWizard(); m_selectedPreset = -1; - m_wizard->deleteLater(); - m_wizard = nullptr; + if (m_wizard) { + m_wizard->deleteLater(); + m_wizard = nullptr; + } m_detailsPage = nullptr; } From 715ce2c36b2476bf68112bc43f1343c05d3a5791 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 11 Mar 2025 14:06:48 +0200 Subject: [PATCH 54/54] QmlDesigner: List type properties should never be split to subproperties Skip subproperty generation for list properties, as it doesn't make sense to have subproperties for them. Fixes: QDS-14882 Change-Id: I751a603ad375cc00b46652f46e80a9f65b132d8e Reviewed-by: Marco Bubke --- .../libs/designercore/metainfo/nodemetainfo.cpp | 16 ++++++++++------ .../unittests/metainfo/nodemetainfo-test.cpp | 16 ++++++++++++++-- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/plugins/qmldesigner/libs/designercore/metainfo/nodemetainfo.cpp b/src/plugins/qmldesigner/libs/designercore/metainfo/nodemetainfo.cpp index 4c98763660d..f863cc1addf 100644 --- a/src/plugins/qmldesigner/libs/designercore/metainfo/nodemetainfo.cpp +++ b/src/plugins/qmldesigner/libs/designercore/metainfo/nodemetainfo.cpp @@ -4733,6 +4733,13 @@ void addSubProperties(CompoundPropertyMetaInfos &inflatedProperties, inflatedProperties.emplace_back(std::move(propertyMetaInfo)); } +bool isValueOrNonListReadOnlyReference(const NodeMetaInfo &propertyType, + const PropertyMetaInfo &property) +{ + return propertyType.type() == MetaInfoType::Value + || (property.isReadOnly() && !property.isListProperty()); +} + } // namespace CompoundPropertyMetaInfos MetaInfoUtils::inflateValueProperties(PropertyMetaInfos properties) @@ -4756,12 +4763,10 @@ CompoundPropertyMetaInfos MetaInfoUtils::inflateValueAndReadOnlyProperties(Prope inflatedProperties.reserve(properties.size() * 2); for (auto &property : properties) { - if (auto propertyType = property.propertyType(); - propertyType.type() == MetaInfoType::Value || property.isReadOnly()) { + if (auto propertyType = property.propertyType(); isValueOrNonListReadOnlyReference(propertyType, property)) addSubProperties(inflatedProperties, property, propertyType); - } else { + else inflatedProperties.emplace_back(std::move(property)); - } } return inflatedProperties; @@ -4773,8 +4778,7 @@ CompoundPropertyMetaInfos MetaInfoUtils::addInflatedValueAndReadOnlyProperties(P inflatedProperties.reserve(properties.size() * 2); for (auto &property : properties) { - if (auto propertyType = property.propertyType(); - propertyType.type() == MetaInfoType::Value || property.isReadOnly()) { + if (auto propertyType = property.propertyType(); isValueOrNonListReadOnlyReference(propertyType, property)) { addSubProperties(inflatedProperties, property, propertyType); if (!property.isReadOnly()) inflatedProperties.emplace_back(std::move(property)); diff --git a/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp b/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp index 0ace7a2c578..5b957f15b8d 100644 --- a/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp +++ b/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp @@ -487,6 +487,11 @@ TEST_F(NodeMetaInfo, inflate_value_and_readonly_properties) PropertyDeclarationTraits::IsReadOnly, inputDeviceId); auto seatNamePropertyId = projectStorageMock.propertyDeclarationId(inputDeviceId, "seatName"); + auto listPropertyId = projectStorageMock.createProperty(metaInfo.id(), + "transform", + PropertyDeclarationTraits::IsList + | PropertyDeclarationTraits::IsReadOnly, + inputDeviceId); auto properties = QmlDesigner::MetaInfoUtils::inflateValueAndReadOnlyProperties( metaInfo.properties()); @@ -497,7 +502,8 @@ TEST_F(NodeMetaInfo, inflate_value_and_readonly_properties) Not(Contains(CompoundPropertyIds(fontPropertyId, IsFalse(), _))), Contains(CompoundPropertyIds(familyPropertyId, fontPropertyId, "font.family")), Contains(CompoundPropertyIds(pixelSizePropertyId, fontPropertyId, "font.pixelSize")), - Contains(CompoundPropertyIds(seatNamePropertyId, devicePropertyId, "device.seatName")))); + Contains(CompoundPropertyIds(seatNamePropertyId, devicePropertyId, "device.seatName")), + Not(Contains(CompoundPropertyIds(seatNamePropertyId, listPropertyId, _))))); } TEST_F(NodeMetaInfo, inflate_value_and_readonly_properties_handles_invalid) @@ -525,6 +531,11 @@ TEST_F(NodeMetaInfo, add_inflated_value_and_readonly_properties) PropertyDeclarationTraits::IsReadOnly, inputDeviceId); auto seatNamePropertyId = projectStorageMock.propertyDeclarationId(inputDeviceId, "seatName"); + auto listPropertyId = projectStorageMock.createProperty(metaInfo.id(), + "transform", + PropertyDeclarationTraits::IsList + | PropertyDeclarationTraits::IsReadOnly, + inputDeviceId); auto properties = QmlDesigner::MetaInfoUtils::addInflatedValueAndReadOnlyProperties( metaInfo.properties()); @@ -536,7 +547,8 @@ TEST_F(NodeMetaInfo, add_inflated_value_and_readonly_properties) Contains(CompoundPropertyIds(familyPropertyId, fontPropertyId, "font.family")), Contains(CompoundPropertyIds(pixelSizePropertyId, fontPropertyId, "font.pixelSize")), Not(Contains(CompoundPropertyIds(devicePropertyId, IsFalse(), _))), - Contains(CompoundPropertyIds(seatNamePropertyId, devicePropertyId, "device.seatName")))); + Contains(CompoundPropertyIds(seatNamePropertyId, devicePropertyId, "device.seatName")), + Not(Contains(CompoundPropertyIds(seatNamePropertyId, listPropertyId, _))))); } TEST_F(NodeMetaInfo, add_inflated_value_and_readonly_properties_handles_invalid)