From 4c91ed496ac535dfb074f8bcb69a7232f0023470 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Fri, 22 Jul 2022 16:46:48 +0200 Subject: [PATCH] QmlDesigner: Add dynamic properties to property editor Added dynamic properties section to property editor and material editor for all nodes. It shows all dynamic properties defined for the node with proper editors for the supported types. Dynamic properties can be added and removed via property editor as well. Material editor shows dynamic properties similarly. Fixes: QDS-7411 Change-Id: Id195f5ca23d55544cea29eb8996690a7eed1cc57 Reviewed-by: Thomas Hartmann Reviewed-by: Mahmoud Badri Reviewed-by: Reviewed-by: Qt CI Bot --- .../MaterialEditorPane.qml | 10 +- .../QtQuick/ItemPane.qml | 4 + .../QtQuick3D/Object3DPane.qml | 4 + .../imports/HelperWidgets/ColorEditor.qml | 4 + .../HelperWidgets/ColorEditorPopup.qml | 33 +- .../DynamicPropertiesSection.qml | 772 ++++++++++++++++++ .../HelperWidgets/ItemFilterComboBox.qml | 2 +- .../imports/HelperWidgets/SpinBox.qml | 4 + .../imports/HelperWidgets/qmldir | 1 + src/plugins/qmldesigner/CMakeLists.txt | 3 + .../connectioneditor/connectionview.cpp | 10 +- .../connectioneditor/connectionviewwidget.cpp | 8 +- .../components/connectioneditor/delegates.cpp | 8 +- .../dynamicpropertiesmodel.cpp | 156 ++-- .../connectioneditor/dynamicpropertiesmodel.h | 22 +- .../selectiondynamicpropertiesproxymodel.cpp | 43 + .../selectiondynamicpropertiesproxymodel.h | 37 + ...erialeditordynamicpropertiesproxymodel.cpp | 44 + ...aterialeditordynamicpropertiesproxymodel.h | 37 + .../materialeditor/materialeditorview.cpp | 50 +- .../materialeditor/materialeditorview.h | 10 + .../dynamicpropertiesproxymodel.cpp | 364 +++++++++ .../dynamicpropertiesproxymodel.h | 110 +++ .../quick2propertyeditorview.cpp | 3 + .../designercore/model/bindingproperty.cpp | 3 +- src/plugins/qmldesigner/qmldesignerplugin.qbs | 6 + 26 files changed, 1659 insertions(+), 89 deletions(-) create mode 100644 share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml create mode 100644 src/plugins/qmldesigner/components/connectioneditor/selectiondynamicpropertiesproxymodel.cpp create mode 100644 src/plugins/qmldesigner/components/connectioneditor/selectiondynamicpropertiesproxymodel.h create mode 100644 src/plugins/qmldesigner/components/materialeditor/materialeditordynamicpropertiesproxymodel.cpp create mode 100644 src/plugins/qmldesigner/components/materialeditor/materialeditordynamicpropertiesproxymodel.h create mode 100644 src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp create mode 100644 src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.h diff --git a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorPane.qml b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorPane.qml index 19d25e7fdc1..5fcc67bd26c 100644 --- a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorPane.qml +++ b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorPane.qml @@ -63,6 +63,10 @@ PropertyEditorPane { Item { width: 1; height: 10 } + DynamicPropertiesSection { + propertiesModel: MaterialEditorDynamicPropertiesModel {} + } + Loader { id: specificsTwo @@ -79,7 +83,11 @@ PropertyEditorPane { } } - Item { width: 1; height: 10 } + Item { + width: 1 + height: 10 + visible: specificsTwo.visible + } Loader { id: specificsOne diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml index da7baf018b1..140f05c1071 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml @@ -37,6 +37,10 @@ PropertyEditorPane { showState: true } + DynamicPropertiesSection { + propertiesModel: SelectionDynamicPropertiesModel {} + } + GeometrySection {} Section { diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Object3DPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Object3DPane.qml index 1a17b828081..f0261c7c753 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Object3DPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Object3DPane.qml @@ -40,6 +40,10 @@ PropertyEditorPane { anchors.left: parent.left anchors.right: parent.right + DynamicPropertiesSection { + propertiesModel: SelectionDynamicPropertiesModel {} + } + Loader { id: specificsTwo anchors.left: parent.left diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml index b3a7c1e9fb0..e16e2ec3126 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml @@ -66,6 +66,10 @@ SecondColumnLayout { colorEditor.backendValue.resetValue() } + function initEditor() { + cePopup.initEditor() + } + Connections { id: backendConnection target: colorEditor diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditorPopup.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditorPopup.qml index 6a015a7c08a..34574ec7285 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditorPopup.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditorPopup.qml @@ -44,6 +44,23 @@ T.Popup { property bool isInValidState: false + function initEditor() { + if (colorEditor.supportGradient && gradientModel.hasGradient) { + colorEditor.color = gradientLine.currentColor + gradientLine.currentColor = colorEditor.color + hexTextField.text = colorEditor.color + popupHexTextField.text = colorEditor.color + } + + cePopup.isInValidState = true + colorEditor.originalColor = colorEditor.color + colorPalette.selectedColor = colorEditor.color + colorPicker.color = colorEditor.color + + cePopup.createModel() + cePopup.determineActiveColorMode() + } + function commitGradientColor() { var hexColor = convertColorToString(colorEditor.color) cePopup.popupHexTextField.text = hexColor @@ -475,24 +492,10 @@ T.Popup { } } } - Connections { target: modelNodeBackend function onSelectionChanged() { - if (colorEditor.supportGradient && gradientModel.hasGradient) { - colorEditor.color = gradientLine.currentColor - gradientLine.currentColor = colorEditor.color - hexTextField.text = colorEditor.color - popupHexTextField.text = colorEditor.color - } - - cePopup.isInValidState = true - colorEditor.originalColor = colorEditor.color - colorPalette.selectedColor = colorEditor.color - colorPicker.color = colorEditor.color - - cePopup.createModel() - cePopup.determineActiveColorMode() + cePopup.initEditor() } } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml new file mode 100644 index 00000000000..df98ebe4107 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml @@ -0,0 +1,772 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import HelperWidgets 2.0 +import QtQuick.Templates 2.15 as T +import StudioControls 1.0 as StudioControls +import StudioTheme 1.0 as StudioTheme + +Section { + id: root + anchors.left: parent.left + anchors.right: parent.right + caption: qsTr("User Added Properties") + + property DynamicPropertiesModel propertiesModel: null + + property Component colorEditor: Component { + id: colorEditor + ColorEditor { + id: colorEditorControl + property string propertyType + + signal remove + + supportGradient: false + spacer.visible: false + + Spacer { implicitWidth: StudioTheme.Values.twoControlColumnGap } + + IconIndicator { + Layout.alignment: Qt.AlignLeft + + icon: StudioTheme.Constants.closeCross + onClicked: colorEditorControl.remove() + } + ExpandingSpacer {} + } + } + + property Component intEditor: Component { + id: intEditor + SecondColumnLayout { + id: layoutInt + property var backendValue + property string propertyType + + signal remove + + SpinBox { + maximumValue: 9999999 + minimumValue: -9999999 + backendValue: layoutInt.backendValue + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + + Item { + height: 10 + implicitWidth: { + return StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + + IconIndicator { + Layout.alignment: Qt.AlignLeft + icon: StudioTheme.Constants.closeCross + onClicked: layoutInt.remove() + } + + ExpandingSpacer {} + } + } + + property Component realEditor: Component { + id: realEditor + SecondColumnLayout { + id: layoutReal + property var backendValue + property string propertyType + + signal remove + + SpinBox { + backendValue: layoutReal.backendValue + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + stepSize: 0.1 + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + + Item { + height: 10 + implicitWidth: { + return StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + + IconIndicator { + Layout.alignment: Qt.AlignLeft + icon: StudioTheme.Constants.closeCross + onClicked: layoutReal.remove() + } + + ExpandingSpacer {} + } + } + + property Component stringEditor: Component { + id: stringEditor + SecondColumnLayout { + id: layoutString + property var backendValue + property string propertyType + + signal remove + + LineEdit { + backendValue: layoutString.backendValue + implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + + IconIndicator { + Layout.alignment: Qt.AlignLeft + icon: StudioTheme.Constants.closeCross + onClicked: layoutString.remove() + } + + ExpandingSpacer {} + } + } + + property Component boolEditor: Component { + id: boolEditor + SecondColumnLayout { + id: layoutBool + property var backendValue + property string propertyType + + signal remove + + CheckBox { + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + text: layoutBool.backendValue.value + backendValue: layoutBool.backendValue + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + + Item { + height: 10 + implicitWidth: { + return StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + + IconIndicator { + Layout.alignment: Qt.AlignLeft + icon: StudioTheme.Constants.closeCross + onClicked: layoutBool.remove() + } + + ExpandingSpacer {} + } + } + + property Component urlEditor: Component { + id: urlEditor + SecondColumnLayout { + id: layoutUrl + property var backendValue + property string propertyType + + signal remove + + UrlChooser { + backendValue: layoutUrl.backendValue + comboBox.implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + spacer.implicitWidth: StudioTheme.Values.controlLabelGap + } + + IconIndicator { + Layout.alignment: Qt.AlignLeft + icon: StudioTheme.Constants.closeCross + onClicked: layoutUrl.remove() + } + + ExpandingSpacer {} + } + } + + property Component aliasEditor: Component { + id: aliasEditor + SecondColumnLayout { + id: layoutAlias + property var backendValue + property string propertyType + property alias lineEdit: lineEdit + + signal remove + + function updateLineEditText() { + lineEdit.text = lineEdit.backendValue.expression + } + + LineEdit { + id: lineEdit + backendValue: layoutAlias.backendValue + implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + writeAsExpression: true + showTranslateCheckBox: false + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + + IconIndicator { + Layout.alignment: Qt.AlignLeft + icon: StudioTheme.Constants.closeCross + onClicked: layoutAlias.remove() + } + + ExpandingSpacer {} + } + } + + property Component textureInputEditor: Component { + id: textureInputEditor + SecondColumnLayout { + id: layoutTextureInput + property var backendValue + property string propertyType + + signal remove + + ItemFilterComboBox { + typeFilter: "QtQuick3D.TextureInput" + validator: RegExpValidator { regExp: /(^$|^[a-z_]\w*)/ } + backendValue: layoutTextureInput.backendValue + implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + + IconIndicator { + Layout.alignment: Qt.AlignLeft + icon: StudioTheme.Constants.closeCross + onClicked: layoutTextureInput.remove() + } + + ExpandingSpacer {} + } + } + + property Component vectorEditor: Component { + id: vectorEditor + ColumnLayout { + id: layoutVector + property var backendValue + property string propertyType + property int vecSize: 0 + property var proxyValues: [] + property var spinBoxes: [boxX, boxY, boxZ, boxW] + + signal remove + + onVecSizeChanged: updateProxyValues() + + spacing: StudioTheme.Values.sectionRowSpacing + + function isValidValue(v) { + return !(v === undefined || isNaN(v)) + } + + function updateExpression() { + for (let i = 0; i < vecSize; ++i) { + if (!isValidValue(proxyValues[i].value)) + return + } + + let expStr = "Qt.vector" + vecSize + "d("+proxyValues[0].value + for (let j=1; j < vecSize; ++j) + expStr += ", " + proxyValues[j].value + expStr += ")" + + layoutVector.backendValue.expression = expStr + } + + function updateProxyValues() { + if (!backendValue) + return; + + const startIndex = backendValue.expression.indexOf('(') + const endIndex = backendValue.expression.indexOf(')') + if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) + return + const numberStr = backendValue.expression.slice(startIndex + 1, endIndex) + const numbers = numberStr.split(",") + if (!Array.isArray(numbers) || numbers.length !== vecSize) + return + + let vals = [] + for (let i = 0; i < vecSize; ++i) { + vals[i] = parseFloat(numbers[i]) + if (!isValidValue(vals[i])) + return + } + + for (let j = 0; j < vecSize; ++j) + proxyValues[j].value = vals[j] + } + + SecondColumnLayout { + SpinBox { + id: boxX + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + text: "X" + tooltip: "X" + } + + Spacer { implicitWidth: StudioTheme.Values.controlGap } + + SpinBox { + id: boxY + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + text: "Y" + tooltip: "Y" + } + + Spacer { implicitWidth: StudioTheme.Values.controlGap } + + IconIndicator { + Layout.alignment: Qt.AlignLeft + icon: StudioTheme.Constants.closeCross + onClicked: layoutVector.remove() + } + + ExpandingSpacer {} + } + + SecondColumnLayout { + visible: vecSize > 2 + SpinBox { + id: boxZ + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + text: "Z" + tooltip: "Z" + visible: vecSize > 2 + } + + Spacer { implicitWidth: StudioTheme.Values.controlGap } + + SpinBox { + id: boxW + minimumValue: -9999999 + maximumValue: 9999999 + decimals: 2 + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + visible: vecSize > 3 + } + + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + ControlLabel { + text: "W" + tooltip: "W" + visible: vecSize > 3 + } + + Spacer { implicitWidth: StudioTheme.Values.controlGap } + + Item { + height: 10 + implicitWidth: { + return StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + visible: vecSize === 2 // Placeholder for last spinbox + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + + ExpandingSpacer {} + } + } + } + + property Component readonlyEditor: Component { + id: readonlyEditor + SecondColumnLayout { + id: layoutReadonly + property var backendValue + property string propertyType + + signal remove + + PropertyLabel { + tooltip: layoutReadonly.propertyType + horizontalAlignment: Text.AlignLeft + leftPadding: StudioTheme.Values.actionIndicatorWidth + text: qsTr("No editor for type: ") + layoutReadonly.propertyType + + width: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + Spacer { + implicitWidth: StudioTheme.Values.twoControlColumnGap + } + IconIndicator { + Layout.alignment: Qt.AlignLeft + icon: StudioTheme.Constants.closeCross + onClicked: layoutReadonly.remove() + } + + ExpandingSpacer {} + } + } + + Column { + width: parent.width + spacing: StudioTheme.Values.sectionRowSpacing + + Repeater { + id: repeater + model: root.propertiesModel + + property bool loadActive: true + onCountChanged: { + repeater.loadActive = false + repeater.loadActive = true + } + + SectionLayout { + DynamicPropertyRow { + id: propertyRow + model: root.propertiesModel + row: index + } + PropertyLabel { + text: propertyName + tooltip: propertyType + } + + Loader { + id: loader + asynchronous: true + active: repeater.loadActive + width: loader.item ? loader.item.width : 0 + height: loader.item ? loader.item.height : 0 + + sourceComponent: { + if (propertyType == "color") + return colorEditor + if (propertyType == "int") + return intEditor + if (propertyType == "real") + return realEditor + if (propertyType == "string") + return stringEditor + if (propertyType == "bool") + return boolEditor + if (propertyType == "url") + return urlEditor + if (propertyType == "alias") + return aliasEditor + if (propertyType == "variant") + return readonlyEditor + if (propertyType == "TextureInput") + return textureInputEditor + if (propertyType == "vector2d" || propertyType == "vector3d" || propertyType == "vector4d") + return vectorEditor + + return readonlyEditor + } + + onLoaded: { + loader.item.backendValue = propertyRow.backendValue + loader.item.propertyType = propertyType + if (sourceComponent == vectorEditor) { + let vecSize = 2 + if (propertyType == "vector3d") + vecSize = 3 + else if (propertyType == "vector4d") + vecSize = 4 + propertyRow.clearProxyBackendValues() + + for (let i = 0; i < vecSize; ++i) { + var newProxyValue = propertyRow.createProxyBackendValue() + loader.item.proxyValues.push(newProxyValue) + newProxyValue.valueChangedQml.connect(loader.item.updateExpression) + loader.item.spinBoxes[i].backendValue = newProxyValue + } + propertyRow.backendValue.expressionChanged.connect(loader.item.updateProxyValues) + loader.item.vecSize = vecSize + loader.item.updateProxyValues() + } else if (sourceComponent == aliasEditor) { + loader.item.lineEdit.text = propertyRow.backendValue.expression + loader.item.backendValue.expressionChanged.connect(loader.item.updateLineEditText) + } else if (sourceComponent == colorEditor) { + loader.item.initEditor() + } + } + + Connections { + target: loader.item + function onRemove() { + propertyRow.remove() + } + } + } + } + } + + SectionLayout { + PropertyLabel { + text: "" + tooltip: "" + } + + SecondColumnLayout { + Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth } + + StudioControls.AbstractButton { + + id: plusButton + buttonIcon: StudioTheme.Constants.plus + onClicked: { + cePopup.opened ? cePopup.close() : cePopup.open() + forceActiveFocus() + } + } + + ExpandingSpacer {} + } + } + } + + property T.Popup popup: T.Popup { + id: cePopup + + onOpened: { + cePopup.setPopupY() + cePopup.setMainScrollViewHeight() + } + + function setMainScrollViewHeight() { + if (Controller.mainScrollView == null) + return + + var mappedPos = plusButton.mapToItem(Controller.mainScrollView.contentItem, + cePopup.x, cePopup.y) + Controller.mainScrollView.temporaryHeight = mappedPos.y + cePopup.height + + StudioTheme.Values.colorEditorPopupMargin + } + + function setPopupY() { + if (Controller.mainScrollView == null) + return + + var mappedPos = plusButton.mapToItem(Controller.mainScrollView.contentItem, + plusButton.x, plusButton.y) + cePopup.y = Math.max(-mappedPos.y + StudioTheme.Values.colorEditorPopupMargin, + cePopup.__defaultY) + + textField.text = root.propertiesModel.newPropertyName() + } + + onClosed: Controller.mainScrollView.temporaryHeight = 0 + + property real __defaultX: (Controller.mainScrollView.contentItem.width + - StudioTheme.Values.colorEditorPopupWidth * 1.5) / 2 + + property real __defaultY: - StudioTheme.Values.colorEditorPopupPadding + - (StudioTheme.Values.colorEditorPopupSpacing * 2) + - StudioTheme.Values.defaultControlHeight + - StudioTheme.Values.colorEditorPopupLineHeight + + plusButton.width * 0.5 + + x: cePopup.__defaultX + y: cePopup.__defaultY + + width: 270 + height: 160 + + property int itemWidth: width / 2 + property int labelWidth: itemWidth - 32 + + padding: StudioTheme.Values.border + margins: 2 // If not defined margin will be -1 + + closePolicy: T.Popup.CloseOnPressOutside | T.Popup.CloseOnPressOutsideParent + + contentItem: Item { + id: content + Column { + spacing: StudioTheme.Values.sectionRowSpacing + RowLayout { + width: cePopup.width - 8 + PropertyLabel { + text: "Add New Property" + horizontalAlignment: Text.AlignLeft + leftPadding: 8 + width: cePopup.width - closeIndicator.width - 24 + } + IconIndicator { + id: closeIndicator + icon: StudioTheme.Constants.colorPopupClose + pixelSize: StudioTheme.Values.myIconFontSize * 1.4 + onClicked: cePopup.close() + Layout.alignment: Qt.AlignRight + } + } + RowLayout { + PropertyLabel { + id: textLabel + text: "Name" + width: cePopup.labelWidth + } + StudioControls.TextField { + id: textField + actionIndicator.visible: false + translationIndicatorVisible: false + width: cePopup.itemWidth + rightPadding: 8 + } + } + RowLayout { + PropertyLabel { + text: "Type" + width: cePopup.labelWidth + } + StudioControls.ComboBox { + id: comboBox + actionIndicator.visible: false + model: ["int", "real", "color", "string", "bool", "url", "alias", + "TextureInput", "vector2d", "vector3d", "vector4d"] + width: cePopup.itemWidth + } + } + Item { + width: 1 + height: StudioTheme.Values.sectionRowSpacing + } + + RowLayout { + width: cePopup.width + + StudioControls.AbstractButton { + id: acceptButton + + buttonIcon: qsTr("Add Property") + iconFont: StudioTheme.Constants.font + width: cePopup.width / 3 + + onClicked: { + root.propertiesModel.createProperty(textField.text, comboBox.currentText) + cePopup.close() + } + Layout.alignment: Qt.AlignHCenter + } + } + } + } + background: Rectangle { + color: StudioTheme.Values.themeControlBackground + border.color: StudioTheme.Values.themeInteraction + border.width: StudioTheme.Values.border + MouseArea { + // This area is to eat clicks so they do not go through the popup + anchors.fill: parent + acceptedButtons: Qt.AllButtons + } + } + + enter: Transition {} + exit: Transition {} + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ItemFilterComboBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ItemFilterComboBox.qml index a720950ea37..5df11405b39 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ItemFilterComboBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ItemFilterComboBox.qml @@ -83,7 +83,7 @@ HelperWidgets.ComboBox { comboBox.currentIndex = comboBox.find(text) - if (text === "") { + if (text === "" || text === "null") { comboBox.currentIndex = 0 comboBox.editText = comboBox.defaultItem } else { diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml index 853357200bf..efbbb189d3f 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml @@ -62,6 +62,10 @@ Item { } } + onBackendValueChanged: { + spinBox.enabled = backendValue === undefined ? false : !isBlocked(backendValue.name) + } + StudioControls.RealSpinBox { id: spinBox diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir index 9c1c873ceb8..5a18d24846b 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir @@ -20,6 +20,7 @@ ComponentSection 2.0 ComponentSection.qml ControlLabel 2.0 ControlLabel.qml singleton Controller 2.0 Controller.qml DoubleSpinBox 2.0 DoubleSpinBox.qml +DynamicPropertiesSection 2.0 DynamicPropertiesSection.qml EditableListView 2.0 EditableListView.qml ExpandingSpacer 2.0 ExpandingSpacer.qml ExpressionTextField 2.0 ExpressionTextField.qml diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 8b0081b1d89..ee8d411d776 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -301,6 +301,7 @@ extend_qtc_plugin(QmlDesigner fileresourcesmodel.cpp fileresourcesmodel.h itemfiltermodel.cpp itemfiltermodel.h gradientmodel.cpp gradientmodel.h + dynamicpropertiesproxymodel.cpp dynamicpropertiesproxymodel.h gradientpresetcustomlistmodel.cpp gradientpresetcustomlistmodel.h gradientpresetdefaultlistmodel.cpp gradientpresetdefaultlistmodel.h gradientpresetitem.cpp gradientpresetitem.h @@ -322,6 +323,7 @@ extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/materialeditor SOURCES materialeditorcontextobject.cpp materialeditorcontextobject.h + materialeditordynamicpropertiesproxymodel.cpp materialeditordynamicpropertiesproxymodel.h materialeditorqmlbackend.cpp materialeditorqmlbackend.h materialeditortransaction.cpp materialeditortransaction.h materialeditorview.cpp materialeditorview.h @@ -452,6 +454,7 @@ extend_qtc_plugin(QmlDesigner connectionviewwidget.cpp connectionviewwidget.h connectionviewwidget.ui delegates.cpp delegates.h dynamicpropertiesmodel.cpp dynamicpropertiesmodel.h + selectiondynamicpropertiesproxymodel.cpp selectiondynamicpropertiesproxymodel.h ) extend_qtc_plugin(QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp index b174cafe6db..eb2ca121db6 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp @@ -50,7 +50,7 @@ ConnectionView::ConnectionView(QObject *parent) : AbstractView(parent), m_connectionViewWidget(new ConnectionViewWidget()), m_connectionModel(new ConnectionModel(this)), m_bindingModel(new BindingModel(this)), - m_dynamicPropertiesModel(new DynamicPropertiesModel(this)), + m_dynamicPropertiesModel(new DynamicPropertiesModel(false, this)), m_backendModel(new BackendModel(this)) { connectionViewWidget()->setBindingModel(m_bindingModel); @@ -65,7 +65,7 @@ void ConnectionView::modelAttached(Model *model) { AbstractView::modelAttached(model); bindingModel()->selectionChanged(QList()); - dynamicPropertiesModel()->selectionChanged(QList()); + dynamicPropertiesModel()->reset(); connectionModel()->resetModel(); connectionViewWidget()->resetItemViews(); backendModel()->resetModel(); @@ -75,7 +75,7 @@ void ConnectionView::modelAboutToBeDetached(Model *model) { AbstractView::modelAboutToBeDetached(model); bindingModel()->selectionChanged(QList()); - dynamicPropertiesModel()->selectionChanged(QList()); + dynamicPropertiesModel()->reset(); connectionModel()->resetModel(); connectionViewWidget()->resetItemViews(); } @@ -121,7 +121,7 @@ void ConnectionView::propertiesAboutToBeRemoved(const QList &p bindingModel()->bindingRemoved(property.toBindingProperty()); dynamicPropertiesModel()->bindingRemoved(property.toBindingProperty()); } else if (property.isVariantProperty()) { - //### dynamicPropertiesModel->bindingRemoved(property.toVariantProperty()); + dynamicPropertiesModel()->variantRemoved(property.toVariantProperty()); } else if (property.isSignalHandlerProperty()) { connectionModel()->removeRowFromTable(property.toSignalHandlerProperty()); } @@ -167,7 +167,7 @@ void ConnectionView::selectedNodesChanged(const QList & selectedNodeL const QList & /*lastSelectedNodeList*/) { bindingModel()->selectionChanged(selectedNodeList); - dynamicPropertiesModel()->selectionChanged(selectedNodeList); + dynamicPropertiesModel()->reset(); connectionViewWidget()->bindingTableViewSelectionChanged(QModelIndex(), QModelIndex()); connectionViewWidget()->dynamicPropertiesTableViewSelectionChanged(QModelIndex(), QModelIndex()); diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp index dc230ef86c1..ae5eee75906 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp @@ -374,8 +374,8 @@ void ConnectionViewWidget::invalidateButtonStatus() } else if (currentTab() == DynamicPropertiesTab) { emit setEnabledRemoveButton(ui->dynamicPropertiesView->selectionModel()->hasSelection()); auto dynamicPropertiesModel = qobject_cast(ui->dynamicPropertiesView->model()); - emit setEnabledAddButton(dynamicPropertiesModel->connectionView()->model() && - dynamicPropertiesModel->connectionView()->selectedModelNodes().count() == 1); + emit setEnabledAddButton(dynamicPropertiesModel->view()->model() && + dynamicPropertiesModel->selectedNodes().count() == 1); } else if (currentTab() == BackendTab) { emit setEnabledAddButton(true); emit setEnabledRemoveButton(ui->backendView->selectionModel()->hasSelection()); @@ -545,9 +545,9 @@ void ConnectionViewWidget::editorForDynamic() QString newValue = m_dynamicEditor->bindingValue().trimmed(); if (m_dynamicIndex.isValid()) { - if (propertiesModel->connectionView()->isWidgetEnabled() + if (qobject_cast(propertiesModel->view())->isWidgetEnabled() && (propertiesModel->rowCount() > m_dynamicIndex.row())) { - propertiesModel->connectionView()->executeInTransaction( + propertiesModel->view()->executeInTransaction( "ConnectionView::setBinding", [this, propertiesModel, newValue]() { AbstractProperty abProp = propertiesModel->abstractPropertyForRow( m_dynamicIndex.row()); diff --git a/src/plugins/qmldesigner/components/connectioneditor/delegates.cpp b/src/plugins/qmldesigner/components/connectioneditor/delegates.cpp index 67a8473e5a4..08100be9c55 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/delegates.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/delegates.cpp @@ -209,11 +209,11 @@ QWidget *DynamicPropertiesDelegate::createEditor(QWidget *parent, const QStyleOp return widget; } - if (!model->connectionView()) { + if (!model->view()) { qWarning() << "BindingDelegate::createEditor no connection view"; return widget; } - model->connectionView()->allModelNodes(); + model->view()->allModelNodes(); switch (index.column()) { case DynamicPropertiesModel::TargetModelNodeRow: { @@ -239,6 +239,10 @@ QWidget *DynamicPropertiesDelegate::createEditor(QWidget *parent, const QStyleOp dynamicPropertiesComboBox->addItem(QLatin1String("url")); dynamicPropertiesComboBox->addItem(QLatin1String("color")); dynamicPropertiesComboBox->addItem(QLatin1String("variant")); + dynamicPropertiesComboBox->addItem(QLatin1String("TextureInput")); + dynamicPropertiesComboBox->addItem(QLatin1String("vector2d")); + dynamicPropertiesComboBox->addItem(QLatin1String("vector3d")); + dynamicPropertiesComboBox->addItem(QLatin1String("vector4d")); return dynamicPropertiesComboBox; }; case DynamicPropertiesModel::PropertyValueRow: { diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp index e7252a24994..880328e9067 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp @@ -37,6 +37,7 @@ #include #include +#include #include #include @@ -90,6 +91,16 @@ QVariant convertVariantForTypeName(const QVariant &variant, const QmlDesigner::T } else { returnValue = QColor(Qt::black); } + } else if (typeName == "vector2d") { + returnValue = "Qt.vector2d(0, 0)"; + } else if (typeName == "vector3d") { + returnValue = "Qt.vector3d(0, 0, 0)"; + } else if (typeName == "vector4d") { + returnValue = "Qt.vector4d(0, 0, 0 ,0)"; + } else if (typeName == "TextureInput") { + returnValue = "null"; + } else if (typeName == "alias") { + returnValue = "null"; } else if (typeName == "Item") { returnValue = 0; } @@ -119,9 +130,18 @@ QmlDesigner::PropertyName DynamicPropertiesModel::unusedProperty(const QmlDesign return propertyName; } -DynamicPropertiesModel::DynamicPropertiesModel(ConnectionView *parent) +bool DynamicPropertiesModel::isValueType(const TypeName &type) +{ + // "variant" is considered value type as it is initialized as one. + // This may need to change if we provide any kind of proper editor for it. + static const QSet valueTypes {"int", "real", "color", "string", "bool", "url", "variant"}; + return valueTypes.contains(type); +} + +DynamicPropertiesModel::DynamicPropertiesModel(bool explicitSelection, AbstractView *parent) : QStandardItemModel(parent) - , m_connectionView(parent) + , m_view(parent) + , m_explicitSelection(explicitSelection) { connect(this, &QStandardItemModel::dataChanged, this, &DynamicPropertiesModel::handleDataChanged); } @@ -133,8 +153,9 @@ void DynamicPropertiesModel::resetModel() setHorizontalHeaderLabels( QStringList({tr("Item"), tr("Property"), tr("Property Type"), tr("Property Value")})); - if (connectionView()->isAttached()) { - for (const ModelNode &modelNode : connectionView()->selectedModelNodes()) + if (m_view->isAttached()) { + const auto nodes = selectedNodes(); + for (const ModelNode &modelNode : nodes) addModelNode(modelNode); } @@ -146,8 +167,8 @@ void DynamicPropertiesModel::resetModel() //Value copying is optional BindingProperty DynamicPropertiesModel::replaceVariantWithBinding(const PropertyName &name, bool copyValue) { - if (connectionView()->selectedModelNodes().count() == 1) { - const ModelNode modelNode = connectionView()->selectedModelNodes().constFirst(); + if (selectedNodes().count() == 1) { + const ModelNode modelNode = selectedNodes().constFirst(); if (modelNode.isValid()) { if (modelNode.hasVariantProperty(name)) { try { @@ -181,8 +202,8 @@ BindingProperty DynamicPropertiesModel::replaceVariantWithBinding(const Property //If it's a BindingProperty, then replaces it with empty VariantProperty void DynamicPropertiesModel::resetProperty(const PropertyName &name) { - if (connectionView()->selectedModelNodes().count() == 1) { - const ModelNode modelNode = connectionView()->selectedModelNodes().constFirst(); + if (selectedNodes().count() == 1) { + const ModelNode modelNode = selectedNodes().constFirst(); if (modelNode.isValid()) { if (modelNode.hasProperty(name)) { try { @@ -190,11 +211,10 @@ void DynamicPropertiesModel::resetProperty(const PropertyName &name) if (abProp.isVariantProperty()) { VariantProperty property = abProp.toVariantProperty(); - QVariant newValue = convertVariantForTypeName(QVariant("none.none"), property.dynamicTypeName()); + QVariant newValue = convertVariantForTypeName({}, property.dynamicTypeName()); property.setDynamicTypeNameAndValue(property.dynamicTypeName(), newValue); - } - else if (abProp.isBindingProperty()) { + } else if (abProp.isBindingProperty()) { BindingProperty property = abProp.toBindingProperty(); TypeName oldType = property.dynamicTypeName(); @@ -202,9 +222,8 @@ void DynamicPropertiesModel::resetProperty(const PropertyName &name) modelNode.removeProperty(name); VariantProperty newProperty = modelNode.variantProperty(name); - QVariant newValue = convertVariantForTypeName(QVariant("none.none"), oldType); - newProperty.setDynamicTypeNameAndValue(oldType, - newValue); + QVariant newValue = convertVariantForTypeName({}, oldType); + newProperty.setDynamicTypeNameAndValue(oldType, newValue); } } catch (RewritingException &e) { @@ -226,8 +245,8 @@ void DynamicPropertiesModel::bindingPropertyChanged(const BindingProperty &bindi m_handleDataChanged = false; - QList selectedNodes = connectionView()->selectedModelNodes(); - if (!selectedNodes.contains(bindingProperty.parentModelNode())) + const QList nodes = selectedNodes(); + if (!nodes.contains(bindingProperty.parentModelNode())) return; if (!m_lock) { int rowNumber = findRowForBindingProperty(bindingProperty); @@ -249,17 +268,16 @@ void DynamicPropertiesModel::variantPropertyChanged(const VariantProperty &varia m_handleDataChanged = false; - QList selectedNodes = connectionView()->selectedModelNodes(); - if (!selectedNodes.contains(variantProperty.parentModelNode())) + const QList nodes = selectedNodes(); + if (!nodes.contains(variantProperty.parentModelNode())) return; if (!m_lock) { int rowNumber = findRowForVariantProperty(variantProperty); - if (rowNumber == -1) { + if (rowNumber == -1) addVariantProperty(variantProperty); - } else { + else updateVariantProperty(rowNumber); - } } m_handleDataChanged = true; @@ -269,8 +287,8 @@ void DynamicPropertiesModel::bindingRemoved(const BindingProperty &bindingProper { m_handleDataChanged = false; - QList selectedNodes = connectionView()->selectedModelNodes(); - if (!selectedNodes.contains(bindingProperty.parentModelNode())) + const QList nodes = selectedNodes(); + if (!nodes.contains(bindingProperty.parentModelNode())) return; if (!m_lock) { int rowNumber = findRowForBindingProperty(bindingProperty); @@ -280,17 +298,36 @@ void DynamicPropertiesModel::bindingRemoved(const BindingProperty &bindingProper m_handleDataChanged = true; } -void DynamicPropertiesModel::selectionChanged(const QList &selectedNodes) +void DynamicPropertiesModel::variantRemoved(const VariantProperty &variantProperty) +{ + m_handleDataChanged = false; + + const QList nodes = selectedNodes(); + if (!nodes.contains(variantProperty.parentModelNode())) + return; + if (!m_lock) { + int rowNumber = findRowForVariantProperty(variantProperty); + removeRow(rowNumber); + } + + m_handleDataChanged = true; +} + +void DynamicPropertiesModel::reset() { - Q_UNUSED(selectedNodes) m_handleDataChanged = false; resetModel(); m_handleDataChanged = true; } -ConnectionView *DynamicPropertiesModel::connectionView() const +void DynamicPropertiesModel::setSelectedNode(const ModelNode &node) { - return m_connectionView; + QTC_ASSERT(m_explicitSelection, return); + QTC_ASSERT(node.isValid(), return); + + m_selectedNodes.clear(); + m_selectedNodes.append(node); + reset(); } AbstractProperty DynamicPropertiesModel::abstractPropertyForRow(int rowNumber) const @@ -298,10 +335,10 @@ AbstractProperty DynamicPropertiesModel::abstractPropertyForRow(int rowNumber) c const int internalId = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 1).toInt(); const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 2).toString(); - if (!connectionView()->isAttached()) + if (!m_view->isAttached()) return AbstractProperty(); - ModelNode modelNode = connectionView()->modelNodeForInternalId(internalId); + ModelNode modelNode = m_view->modelNodeForInternalId(internalId); if (modelNode.isValid()) return modelNode.property(targetPropertyName.toUtf8()); @@ -314,7 +351,7 @@ BindingProperty DynamicPropertiesModel::bindingPropertyForRow(int rowNumber) con const int internalId = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 1).toInt(); const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 2).toString(); - ModelNode modelNode = connectionView()->modelNodeForInternalId(internalId); + ModelNode modelNode = m_view->modelNodeForInternalId(internalId); if (modelNode.isValid()) return modelNode.bindingProperty(targetPropertyName.toUtf8()); @@ -327,7 +364,7 @@ VariantProperty DynamicPropertiesModel::variantPropertyForRow(int rowNumber) con const int internalId = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 1).toInt(); const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 2).toString(); - ModelNode modelNode = connectionView()->modelNodeForInternalId(internalId); + ModelNode modelNode = m_view->modelNodeForInternalId(internalId); if (modelNode.isValid()) return modelNode.variantProperty(targetPropertyName.toUtf8()); @@ -364,8 +401,8 @@ void DynamicPropertiesModel::addDynamicPropertyForCurrentNode() { QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_PROPERTY_ADDED); - if (connectionView()->selectedModelNodes().count() == 1) { - const ModelNode modelNode = connectionView()->selectedModelNodes().constFirst(); + if (selectedNodes().count() == 1) { + const ModelNode modelNode = selectedNodes().constFirst(); if (modelNode.isValid()) { try { modelNode.variantProperty(unusedProperty(modelNode)).setDynamicTypeNameAndValue("string", QLatin1String("none.none")); @@ -420,16 +457,14 @@ QStringList DynamicPropertiesModel::possibleSourceProperties(const BindingProper void DynamicPropertiesModel::deleteDynamicPropertyByRow(int rowNumber) { - connectionView()->executeInTransaction("DynamicPropertiesModel::deleteDynamicPropertyByRow", [this, rowNumber]() { + m_view->executeInTransaction("DynamicPropertiesModel::deleteDynamicPropertyByRow", [this, rowNumber]() { BindingProperty bindingProperty = bindingPropertyForRow(rowNumber); if (bindingProperty.isValid()) { bindingProperty.parentModelNode().removeProperty(bindingProperty.name()); - } - - VariantProperty variantProperty = variantPropertyForRow(rowNumber); - - if (variantProperty.isValid()) { - variantProperty.parentModelNode().removeProperty(variantProperty.name()); + } else { + VariantProperty variantProperty = variantPropertyForRow(rowNumber); + if (variantProperty.isValid()) + variantProperty.parentModelNode().removeProperty(variantProperty.name()); } }); @@ -455,7 +490,6 @@ void DynamicPropertiesModel::addProperty(const QVariant &propertyValue, items.append(idItem); items.append(propertyNameItem); - propertyTypeItem = new QStandardItem(propertyType); items.append(propertyTypeItem); @@ -531,7 +565,7 @@ void DynamicPropertiesModel::updateValue(int row) if (bindingProperty.isBindingProperty()) { const QString expression = data(index(row, PropertyValueRow)).toString(); - RewriterTransaction transaction = connectionView()->beginRewriterTransaction(QByteArrayLiteral("DynamicPropertiesModel::updateValue")); + RewriterTransaction transaction = m_view->beginRewriterTransaction(QByteArrayLiteral("DynamicPropertiesModel::updateValue")); try { bindingProperty.setDynamicTypeNameAndExpression(bindingProperty.dynamicTypeName(), expression); transaction.commit(); //committing in the try block @@ -547,7 +581,7 @@ void DynamicPropertiesModel::updateValue(int row) if (variantProperty.isVariantProperty()) { const QVariant value = data(index(row, PropertyValueRow)); - RewriterTransaction transaction = connectionView()->beginRewriterTransaction(QByteArrayLiteral("DynamicPropertiesModel::updateValue")); + RewriterTransaction transaction = m_view->beginRewriterTransaction(QByteArrayLiteral("DynamicPropertiesModel::updateValue")); try { variantProperty.setDynamicTypeNameAndValue(variantProperty.dynamicTypeName(), value); transaction.commit(); //committing in the try block @@ -571,7 +605,7 @@ void DynamicPropertiesModel::updatePropertyName(int rowNumber) ModelNode targetNode = bindingProperty.parentModelNode(); if (bindingProperty.isBindingProperty()) { - connectionView()->executeInTransaction("DynamicPropertiesModel::updatePropertyName", [bindingProperty, newName, &targetNode](){ + m_view->executeInTransaction("DynamicPropertiesModel::updatePropertyName", [bindingProperty, newName, &targetNode](){ const QString expression = bindingProperty.expression(); const PropertyName dynamicPropertyType = bindingProperty.dynamicTypeName(); @@ -590,7 +624,7 @@ void DynamicPropertiesModel::updatePropertyName(int rowNumber) const PropertyName dynamicPropertyType = variantProperty.dynamicTypeName(); ModelNode targetNode = variantProperty.parentModelNode(); - connectionView()->executeInTransaction("DynamicPropertiesModel::updatePropertyName", [=](){ + m_view->executeInTransaction("DynamicPropertiesModel::updatePropertyName", [=](){ targetNode.variantProperty(newName).setDynamicTypeNameAndValue(dynamicPropertyType, value); targetNode.removeProperty(variantProperty.name()); }); @@ -616,7 +650,7 @@ void DynamicPropertiesModel::updatePropertyType(int rowNumber) const PropertyName propertyName = bindingProperty.name(); ModelNode targetNode = bindingProperty.parentModelNode(); - connectionView()->executeInTransaction("DynamicPropertiesModel::updatePropertyType", [=](){ + m_view->executeInTransaction("DynamicPropertiesModel::updatePropertyType", [=](){ targetNode.removeProperty(bindingProperty.name()); targetNode.bindingProperty(propertyName).setDynamicTypeNameAndExpression(newType, expression); }); @@ -632,12 +666,14 @@ void DynamicPropertiesModel::updatePropertyType(int rowNumber) ModelNode targetNode = variantProperty.parentModelNode(); const PropertyName propertyName = variantProperty.name(); - connectionView()->executeInTransaction("DynamicPropertiesModel::updatePropertyType", [=](){ + m_view->executeInTransaction("DynamicPropertiesModel::updatePropertyType", [=](){ targetNode.removeProperty(variantProperty.name()); - if (newType == "alias") { //alias properties have to be bindings - targetNode.bindingProperty(propertyName).setDynamicTypeNameAndExpression(newType, QLatin1String("none.none")); + if (!isValueType(newType)) { + targetNode.bindingProperty(propertyName).setDynamicTypeNameAndExpression( + newType, convertVariantForTypeName({}, newType).toString()); } else { - targetNode.variantProperty(propertyName).setDynamicTypeNameAndValue(newType, convertVariantForTypeName(value, newType)); + targetNode.variantProperty(propertyName).setDynamicTypeNameAndValue( + newType, convertVariantForTypeName(value, newType)); } }); @@ -656,7 +692,7 @@ ModelNode DynamicPropertiesModel::getNodeByIdOrParent(const QString &id, const M ModelNode modelNode; if (id != QLatin1String("parent")) { - modelNode = connectionView()->modelNodeForId(id); + modelNode = m_view->modelNodeForId(id); } else { if (targetNode.hasParentProperty()) { modelNode = targetNode.parentProperty().parentModelNode(); @@ -776,6 +812,24 @@ void DynamicPropertiesModel::handleException() resetModel(); } +const QList DynamicPropertiesModel::selectedNodes() const +{ + // If selected nodes are explicitly set, return those. + // Otherwise return actual selected nodes of the model. + if (m_explicitSelection) + return m_selectedNodes; + else + return m_view->selectedModelNodes(); +} + +const ModelNode DynamicPropertiesModel::singleSelectedNode() const +{ + if (m_explicitSelection) + return m_selectedNodes.first(); + else + return m_view->singleSelectedModelNode(); +} + } // namespace Internal } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h index a5b674c1f8a..6c4e4a65a33 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h @@ -33,9 +33,9 @@ namespace QmlDesigner { -namespace Internal { +class AbstractView; -class ConnectionView; +namespace Internal { class DynamicPropertiesModel : public QStandardItemModel { @@ -48,13 +48,17 @@ public: PropertyTypeRow = 2, PropertyValueRow = 3 }; - DynamicPropertiesModel(ConnectionView *parent = nullptr); + DynamicPropertiesModel(bool explicitSelection, AbstractView *parent); void bindingPropertyChanged(const BindingProperty &bindingProperty); void variantPropertyChanged(const VariantProperty &variantProperty); void bindingRemoved(const BindingProperty &bindingProperty); - void selectionChanged(const QList &selectedNodes); + void variantRemoved(const VariantProperty &variantProperty); + void reset(); + void setSelectedNode(const ModelNode &node); + const QList selectedNodes() const; + const ModelNode singleSelectedNode() const; - ConnectionView *connectionView() const; + AbstractView *view() const { return m_view; } AbstractProperty abstractPropertyForRow(int rowNumber) const; BindingProperty bindingPropertyForRow(int rowNumber) const; VariantProperty variantPropertyForRow(int rowNumber) const; @@ -71,6 +75,8 @@ public: QmlDesigner::PropertyName unusedProperty(const QmlDesigner::ModelNode &modelNode); + static bool isValueType(const TypeName &type); + protected: void addProperty(const QVariant &propertyValue, const QString &propertyType, @@ -97,12 +103,12 @@ private: void handleDataChanged(const QModelIndex &topLeft, const QModelIndex& bottomRight); void handleException(); -private: - ConnectionView *m_connectionView; + AbstractView *m_view = nullptr; bool m_lock = false; bool m_handleDataChanged = false; QString m_exceptionError; - + QList m_selectedNodes; + bool m_explicitSelection = false; }; } // namespace Internal diff --git a/src/plugins/qmldesigner/components/connectioneditor/selectiondynamicpropertiesproxymodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/selectiondynamicpropertiesproxymodel.cpp new file mode 100644 index 00000000000..00732479e81 --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/selectiondynamicpropertiesproxymodel.cpp @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "selectiondynamicpropertiesproxymodel.h" + +#include +#include + +using namespace QmlDesigner::Internal; + +SelectionDynamicPropertiesProxyModel::SelectionDynamicPropertiesProxyModel(QObject *parent) + : DynamicPropertiesProxyModel(parent) +{ + initModel(ConnectionView::instance()->dynamicPropertiesModel()); +} + +void SelectionDynamicPropertiesProxyModel::registerDeclarativeType() +{ + DynamicPropertiesProxyModel::registerDeclarativeType(); + qmlRegisterType("HelperWidgets", 2, 0, "SelectionDynamicPropertiesModel"); +} diff --git a/src/plugins/qmldesigner/components/connectioneditor/selectiondynamicpropertiesproxymodel.h b/src/plugins/qmldesigner/components/connectioneditor/selectiondynamicpropertiesproxymodel.h new file mode 100644 index 00000000000..124846dc860 --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/selectiondynamicpropertiesproxymodel.h @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +class SelectionDynamicPropertiesProxyModel : public DynamicPropertiesProxyModel +{ + Q_OBJECT +public: + explicit SelectionDynamicPropertiesProxyModel(QObject *parent = nullptr); + + static void registerDeclarativeType(); +}; diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditordynamicpropertiesproxymodel.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditordynamicpropertiesproxymodel.cpp new file mode 100644 index 00000000000..2a56561001f --- /dev/null +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditordynamicpropertiesproxymodel.cpp @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "materialeditordynamicpropertiesproxymodel.h" + +#include + +#include + +using namespace QmlDesigner; + +MaterialEditorDynamicPropertiesProxyModel::MaterialEditorDynamicPropertiesProxyModel(QObject *parent) + : DynamicPropertiesProxyModel(parent) +{ + initModel(MaterialEditorView::instance()->dynamicPropertiesModel()); +} + +void MaterialEditorDynamicPropertiesProxyModel::registerDeclarativeType() +{ + DynamicPropertiesProxyModel::registerDeclarativeType(); + qmlRegisterType("HelperWidgets", 2, 0, "MaterialEditorDynamicPropertiesModel"); +} diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditordynamicpropertiesproxymodel.h b/src/plugins/qmldesigner/components/materialeditor/materialeditordynamicpropertiesproxymodel.h new file mode 100644 index 00000000000..9d4e6aa9a47 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditordynamicpropertiesproxymodel.h @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "dynamicpropertiesproxymodel.h" + +class MaterialEditorDynamicPropertiesProxyModel : public DynamicPropertiesProxyModel +{ + Q_OBJECT +public: + explicit MaterialEditorDynamicPropertiesProxyModel(QObject *parent = nullptr); + + static void registerDeclarativeType(); +}; diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index e592548c682..d2c189f21d0 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -27,11 +27,13 @@ #include "materialeditorqmlbackend.h" #include "materialeditorcontextobject.h" +#include "materialeditordynamicpropertiesproxymodel.h" #include "propertyeditorvalue.h" #include "materialeditortransaction.h" #include "assetslibrarywidget.h" #include +#include #include #include #include @@ -70,6 +72,7 @@ namespace QmlDesigner { MaterialEditorView::MaterialEditorView(QWidget *parent) : AbstractView(parent) , m_stackedWidget(new QStackedWidget(parent)) + , m_dynamicPropertiesModel(new Internal::DynamicPropertiesModel(true, this)) { m_updateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F7), m_stackedWidget); connect(m_updateShortcut, &QShortcut::activated, this, &MaterialEditorView::reloadQml); @@ -92,6 +95,8 @@ MaterialEditorView::MaterialEditorView(QWidget *parent) QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css")))); m_stackedWidget->setMinimumWidth(250); QmlDesignerPlugin::trackWidgetFocusTime(m_stackedWidget, Constants::EVENT_MATERIALEDITOR_TIME); + + MaterialEditorDynamicPropertiesProxyModel::registerDeclarativeType(); } MaterialEditorView::~MaterialEditorView() @@ -314,6 +319,29 @@ void MaterialEditorView::currentTimelineChanged(const ModelNode &) m_qmlBackEnd->contextObject()->setHasActiveTimeline(QmlTimeline::hasActiveTimeline(this)); } +Internal::DynamicPropertiesModel *MaterialEditorView::dynamicPropertiesModel() const +{ + return m_dynamicPropertiesModel; +} + +MaterialEditorView *MaterialEditorView::instance() +{ + static MaterialEditorView *s_instance = nullptr; + + if (s_instance) + return s_instance; + + const auto views = QmlDesignerPlugin::instance()->viewManager().views(); + for (auto *view : views) { + MaterialEditorView *myView = qobject_cast(view); + if (myView) + s_instance = myView; + } + + QTC_ASSERT(s_instance, return nullptr); + return s_instance; +} + void MaterialEditorView::delayedResetView() { // TODO: it seems the delayed reset is not needed. Leaving it commented out for now just in case it @@ -592,6 +620,11 @@ void MaterialEditorView::setupQmlBackend() m_qmlBackEnd = currentQmlBackend; + if (m_hasMaterialRoot) + m_dynamicPropertiesModel->setSelectedNode(m_selectedMaterial); + else + m_dynamicPropertiesModel->reset(); + delayedTypeUpdate(); initPreviewData(); @@ -751,6 +784,7 @@ void MaterialEditorView::modelAttached(Model *model) void MaterialEditorView::modelAboutToBeDetached(Model *model) { AbstractView::modelAboutToBeDetached(model); + m_dynamicPropertiesModel->reset(); m_qmlBackEnd->materialEditorTransaction()->end(); } @@ -783,8 +817,9 @@ void MaterialEditorView::variantPropertiesChanged(const QList & bool changed = false; for (const VariantProperty &property : propertyList) { ModelNode node(property.parentModelNode()); - if (node == m_selectedMaterial || QmlObjectNode(m_selectedMaterial).propertyChangeForCurrentState() == node) { + if (property.isDynamic()) + m_dynamicPropertiesModel->variantPropertyChanged(property); if (m_selectedMaterial.property(property.name()).isBindingProperty()) setValue(m_selectedMaterial, property.name(), QmlObjectNode(m_selectedMaterial).instanceValue(property.name())); else @@ -810,6 +845,8 @@ void MaterialEditorView::bindingPropertiesChanged(const QList & m_qmlBackEnd->contextObject()->setHasAliasExport(QmlObjectNode(m_selectedMaterial).isAliasExported()); if (node == m_selectedMaterial || QmlObjectNode(m_selectedMaterial).propertyChangeForCurrentState() == node) { + if (property.isDynamic()) + m_dynamicPropertiesModel->bindingPropertyChanged(property); if (QmlObjectNode(m_selectedMaterial).modelNode().property(property.name()).isBindingProperty()) setValue(m_selectedMaterial, property.name(), QmlObjectNode(m_selectedMaterial).instanceValue(property.name())); else @@ -831,6 +868,16 @@ void MaterialEditorView::auxiliaryDataChanged(const ModelNode &node, const Prope m_qmlBackEnd->setValueforAuxiliaryProperties(m_selectedMaterial, name); } +void MaterialEditorView::propertiesAboutToBeRemoved(const QList &propertyList) +{ + for (const auto &property : propertyList) { + if (property.isBindingProperty()) + m_dynamicPropertiesModel->bindingRemoved(property.toBindingProperty()); + else if (property.isVariantProperty()) + m_dynamicPropertiesModel->variantRemoved(property.toVariantProperty()); + } +} + // request render image for the selected material node void MaterialEditorView::requestPreviewRender() { @@ -998,6 +1045,7 @@ void MaterialEditorView::customNotification(const AbstractView *view, const QStr if (identifier == "selected_material_changed") { if (!m_hasMaterialRoot) { m_selectedMaterial = nodeList.first(); + m_dynamicPropertiesModel->setSelectedNode(m_selectedMaterial); QTimer::singleShot(0, this, &MaterialEditorView::resetView); } } else if (identifier == "apply_to_selected_triggered") { diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h index e631ae9ca8a..563e52ae91b 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h @@ -44,6 +44,10 @@ namespace QmlDesigner { class ModelNode; class MaterialEditorQmlBackend; +namespace Internal { +class DynamicPropertiesModel; +} + class MaterialEditorView : public AbstractView { Q_OBJECT @@ -66,6 +70,7 @@ public: void variantPropertiesChanged(const QList &propertyList, PropertyChangeFlags propertyChange) override; void bindingPropertiesChanged(const QList &propertyList, PropertyChangeFlags propertyChange) override; void auxiliaryDataChanged(const ModelNode &node, const PropertyName &name, const QVariant &data) override; + void propertiesAboutToBeRemoved(const QList &propertyList) override; void resetView(); void currentStateChanged(const ModelNode &node) override; @@ -90,6 +95,10 @@ public: void currentTimelineChanged(const ModelNode &node) override; + Internal::DynamicPropertiesModel *dynamicPropertiesModel() const; + + static MaterialEditorView *instance(); + public slots: void handleToolBarAction(int action); void handlePreviewEnvChanged(const QString &envAndValue); @@ -142,6 +151,7 @@ private: QPointer m_colorDialog; QPointer m_itemLibraryInfo; + Internal::DynamicPropertiesModel *m_dynamicPropertiesModel = nullptr; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp new file mode 100644 index 00000000000..8acd5abca28 --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp @@ -0,0 +1,364 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "dynamicpropertiesproxymodel.h" + +#include "propertyeditorvalue.h" + +#include + +#include +#include +#include +#include +#include + +#include + +using namespace QmlDesigner; + +static const int propertyNameRole = Qt::UserRole + 1; +static const int propertyTypeRole = Qt::UserRole + 2; +static const int propertyValueRole = Qt::UserRole + 3; +static const int propertyBindingRole = Qt::UserRole + 4; + +DynamicPropertiesProxyModel::DynamicPropertiesProxyModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +void DynamicPropertiesProxyModel::initModel(QmlDesigner::Internal::DynamicPropertiesModel *model) +{ + m_model = model; + + connect(m_model, &QAbstractItemModel::modelAboutToBeReset, + this, &QAbstractItemModel::modelAboutToBeReset); + connect(m_model, &QAbstractItemModel::modelReset, + this, &QAbstractItemModel::modelReset); + + connect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved, + this, &QAbstractItemModel::rowsAboutToBeRemoved); + connect(m_model, &QAbstractItemModel::rowsRemoved, + this, &QAbstractItemModel::rowsRemoved); + connect(m_model, &QAbstractItemModel::rowsInserted, + this, &QAbstractItemModel::rowsInserted); + + connect(m_model, &QAbstractItemModel::dataChanged, + this, [this](const QModelIndex &topLeft, const QModelIndex &, const QList &) { + emit dataChanged(index(topLeft.row(), 0), + index(topLeft.row(), 0), + { propertyNameRole, propertyTypeRole, + propertyValueRole, propertyBindingRole }); + }); +} + +int DynamicPropertiesProxyModel::rowCount(const QModelIndex &) const +{ + return m_model->rowCount(); +} + +QHash DynamicPropertiesProxyModel::roleNames() const +{ + static QHash roleNames{{propertyNameRole, "propertyName"}, + {propertyTypeRole, "propertyType"}, + {propertyValueRole, "propertyValue"}, + {propertyBindingRole, "propertyBinding"}}; + + return roleNames; +} + +QVariant DynamicPropertiesProxyModel::data(const QModelIndex &index, int role) const +{ + if (index.isValid() && index.row() < rowCount()) { + AbstractProperty property = m_model->abstractPropertyForRow(index.row()); + + QTC_ASSERT(property.isValid(), return QVariant()); + + if (role == propertyNameRole) { + return property.name(); + } else if (propertyTypeRole) { + return property.dynamicTypeName(); + } else if (role == propertyValueRole) { + QmlObjectNode objectNode = property.parentQmlObjectNode(); + return objectNode.modelValue(property.name()); + } else if (role == propertyBindingRole) { + if (property.isBindingProperty()) + return property.toBindingProperty().expression(); + return QVariant(); + } + qWarning() << Q_FUNC_INFO << "invalid role"; + } else { + qWarning() << Q_FUNC_INFO << "invalid index"; + } + + return QVariant(); +} + +void DynamicPropertiesProxyModel::registerDeclarativeType() +{ + static bool registered = false; + if (!registered) + qmlRegisterType("HelperWidgets", 2, 0, "DynamicPropertiesModel"); +} + +QmlDesigner::Internal::DynamicPropertiesModel *DynamicPropertiesProxyModel::dynamicPropertiesModel() const +{ + return m_model; +} + +QString DynamicPropertiesProxyModel::newPropertyName() const +{ + auto propertiesModel = dynamicPropertiesModel(); + + return QString::fromUtf8(propertiesModel->unusedProperty( + propertiesModel->singleSelectedNode())); +} + +void DynamicPropertiesProxyModel::createProperty(const QString &name, const QString &type) +{ + QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_PROPERTY_ADDED); + + const auto selectedNodes = dynamicPropertiesModel()->selectedNodes(); + if (selectedNodes.count() == 1) { + const ModelNode modelNode = selectedNodes.constFirst(); + if (modelNode.isValid()) { + try { + if (Internal::DynamicPropertiesModel::isValueType(type.toUtf8())) { + QVariant value; + if (type == "int") + value = 0; + else if (type == "real") + value = 0.0; + else if (type == "color") + value = QColor(255, 255, 255); + else if (type == "string") + value = ""; + else if (type == "bool") + value = false; + else if (type == "url") + value = ""; + else if (type == "variant") + value = ""; + + modelNode.variantProperty(name.toUtf8()) + .setDynamicTypeNameAndValue(type.toUtf8(), value); + } else { + QString expression; + if (type == "alias") + expression = "null"; + else if (type == "TextureInput") + expression = "null"; + else if (type == "vector2d") + expression = "Qt.vector2d(0, 0)"; + else if (type == "vector3d") + expression = "Qt.vector3d(0, 0, 0)"; + else if (type == "vector4d") + expression = "Qt.vector4d(0, 0, 0 ,0)"; + + modelNode.bindingProperty(name.toUtf8()) + .setDynamicTypeNameAndExpression(type.toUtf8(), expression); + } + } catch (Exception &e) { + e.showException(); + } + } + } else { + qWarning() << " BindingModel::addBindingForCurrentNode not one node selected"; + } +} + +DynamicPropertyRow::DynamicPropertyRow(QObject *parent) +{ + m_backendValue = new PropertyEditorValue(this); + + QObject::connect(m_backendValue, + &PropertyEditorValue::valueChanged, + this, + [this](const QString &, const QVariant &value) { commitValue(value); }); + + QObject::connect(m_backendValue, + &PropertyEditorValue::expressionChanged, + this, + [this](const QString &) { commitExpression(m_backendValue->expression()); }); +} + +DynamicPropertyRow::~DynamicPropertyRow() +{ + clearProxyBackendValues(); +} + +void DynamicPropertyRow::registerDeclarativeType() +{ + qmlRegisterType("HelperWidgets", 2, 0, "DynamicPropertyRow"); +} + +void DynamicPropertyRow::setRow(int r) +{ + if (m_row == r) + return; + + m_row = r; + setupBackendValue(); + emit rowChanged(); +} + +int DynamicPropertyRow::row() const +{ + return m_row; +} + +void DynamicPropertyRow::setModel(DynamicPropertiesProxyModel *model) +{ + if (model == m_model) + return; + + if (m_model) { + disconnect(m_model, &QAbstractItemModel::dataChanged, + this, &DynamicPropertyRow::handleDataChanged); + } + + m_model = model; + + if (m_model) { + connect(m_model, &QAbstractItemModel::dataChanged, + this, &DynamicPropertyRow::handleDataChanged); + + if (m_row != -1) + setupBackendValue(); + } + + emit modelChanged(); +} + +DynamicPropertiesProxyModel *DynamicPropertyRow::model() const +{ + return m_model; +} + +PropertyEditorValue *DynamicPropertyRow::backendValue() const +{ + return m_backendValue; +} + +void DynamicPropertyRow::remove() +{ + m_model->dynamicPropertiesModel()->deleteDynamicPropertyByRow(m_row); +} + +PropertyEditorValue *DynamicPropertyRow::createProxyBackendValue() +{ + + PropertyEditorValue *newValue = new PropertyEditorValue(this); + m_proxyBackendValues.append(newValue); + + return newValue; +} + +void DynamicPropertyRow::clearProxyBackendValues() +{ + qDeleteAll(m_proxyBackendValues); + m_proxyBackendValues.clear(); +} + +void DynamicPropertyRow::setupBackendValue() +{ + if (!m_model) + return; + + QmlDesigner::AbstractProperty property = m_model->dynamicPropertiesModel()->abstractPropertyForRow(m_row); + if (!property.isValid()) + return; + + if (m_backendValue->name() != property.name()) + m_backendValue->setName(property.name()); + + ModelNode node = property.parentModelNode(); + if (node != m_backendValue->modelNode()) + m_backendValue->setModelNode(node); + + QVariant modelValue = property.parentQmlObjectNode().modelValue(property.name()); + if (modelValue != m_backendValue->value()) { + m_backendValue->setValue({}); + m_backendValue->setValue(modelValue); + } + + if (property.isBindingProperty()) { + QString expression = property.toBindingProperty().expression(); + if (m_backendValue->expression() != expression) + m_backendValue->setExpression(expression); + } + + emit m_backendValue->isBoundChanged(); +} + +void DynamicPropertyRow::commitValue(const QVariant &value) +{ + auto propertiesModel = m_model->dynamicPropertiesModel(); + VariantProperty variantProperty = propertiesModel->variantPropertyForRow(m_row); + + if (!Internal::DynamicPropertiesModel::isValueType(variantProperty.dynamicTypeName())) + return; + + auto view = propertiesModel->view(); + RewriterTransaction transaction = view->beginRewriterTransaction( + QByteArrayLiteral("DynamicPropertiesModel::commitValue")); + try { + if (variantProperty.value() != value) + variantProperty.setDynamicTypeNameAndValue(variantProperty.dynamicTypeName(), value); + transaction.commit(); //committing in the try block + } catch (Exception &e) { + e.showException(); + } +} + +void DynamicPropertyRow::commitExpression(const QString &expression) +{ + auto propertiesModel = m_model->dynamicPropertiesModel(); + BindingProperty bindingProperty = propertiesModel->bindingPropertyForRow(m_row); + + auto view = propertiesModel->view(); + RewriterTransaction transaction = view->beginRewriterTransaction( + QByteArrayLiteral("DynamicPropertiesModel::commitExpression")); + try { + QString theExpression = expression; + if (theExpression.isEmpty()) + theExpression = "null"; + + if (bindingProperty.expression() != theExpression) { + bindingProperty.setDynamicTypeNameAndExpression(bindingProperty.dynamicTypeName(), + theExpression); + } + transaction.commit(); //committing in the try block + } catch (Exception &e) { + e.showException(); + } + return; +} + +void DynamicPropertyRow::handleDataChanged(const QModelIndex &topLeft, const QModelIndex &, const QList &) +{ + if (topLeft.row() == m_row) + setupBackendValue(); +} diff --git a/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.h b/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.h new file mode 100644 index 00000000000..c347e310de4 --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "propertyeditorvalue.h" + +#include +#include + +#include +#include +#include +#include + +namespace QmlDesigner { +namespace Internal { +class DynamicPropertiesModel; +} +} // namespace QmlDesigner + +class DynamicPropertiesProxyModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit DynamicPropertiesProxyModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QHash roleNames() const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + static void registerDeclarativeType(); + + QmlDesigner::Internal::DynamicPropertiesModel *dynamicPropertiesModel() const; + + Q_INVOKABLE QString newPropertyName() const; + Q_INVOKABLE void createProperty(const QString &name, const QString &type); + +protected: + void initModel(QmlDesigner::Internal::DynamicPropertiesModel *model); + +private: + QmlDesigner::Internal::DynamicPropertiesModel *m_model = nullptr; +}; + +class DynamicPropertyRow : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int row READ row WRITE setRow NOTIFY rowChanged FINAL) + Q_PROPERTY(PropertyEditorValue *backendValue READ backendValue NOTIFY rowChanged FINAL) + Q_PROPERTY(DynamicPropertiesProxyModel *model READ model WRITE setModel NOTIFY modelChanged FINAL) + +public: + explicit DynamicPropertyRow(QObject *parent = nullptr); + ~DynamicPropertyRow(); + + static void registerDeclarativeType(); + + void setRow(int r); + int row() const; + void setModel(DynamicPropertiesProxyModel *model); + DynamicPropertiesProxyModel *model() const; + PropertyEditorValue *backendValue() const; + + Q_INVOKABLE void remove(); + Q_INVOKABLE PropertyEditorValue *createProxyBackendValue(); + Q_INVOKABLE void clearProxyBackendValues(); + +signals: + void rowChanged(); + void modelChanged(); + +private: + void setupBackendValue(); + void commitValue(const QVariant &value); + void commitExpression(const QString &expression); + void handleDataChanged(const QModelIndex &topLeft, const QModelIndex &, const QList &); + + int m_row = -1; + PropertyEditorValue *m_backendValue = nullptr; + DynamicPropertiesProxyModel *m_model = nullptr; + QList m_proxyBackendValues; +}; + +QML_DECLARE_TYPE(DynamicPropertyRow) diff --git a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp index 08358353c10..9523617637a 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp @@ -30,6 +30,7 @@ #include "bindingeditor/actioneditor.h" #include "bindingeditor/bindingeditor.h" #include "colorpalettebackend.h" +#include "selectiondynamicpropertiesproxymodel.h" #include "fileresourcesmodel.h" #include "gradientmodel.h" #include "gradientpresetcustomlistmodel.h" @@ -75,6 +76,8 @@ void Quick2PropertyEditorView::registerQmlTypes() Tooltip::registerDeclarativeType(); EasingCurveEditor::registerDeclarativeType(); RichTextEditorProxy::registerDeclarativeType(); + SelectionDynamicPropertiesProxyModel::registerDeclarativeType(); + DynamicPropertyRow::registerDeclarativeType(); const QString resourcePath = PropertyEditorQmlBackend::propertyEditorResourcesPath(); diff --git a/src/plugins/qmldesigner/designercore/model/bindingproperty.cpp b/src/plugins/qmldesigner/designercore/model/bindingproperty.cpp index d75147f9b3e..87f28267b3a 100644 --- a/src/plugins/qmldesigner/designercore/model/bindingproperty.cpp +++ b/src/plugins/qmldesigner/designercore/model/bindingproperty.cpp @@ -321,9 +321,10 @@ void BindingProperty::setDynamicTypeNameAndExpression(const TypeName &typeName, Internal::InternalProperty::Pointer internalProperty = internalNode()->property(name()); if (internalProperty->isBindingProperty() && internalProperty->toBindingProperty()->expression() == expression - && internalProperty->toBindingProperty()->dynamicTypeName() == typeName) + && internalProperty->toBindingProperty()->dynamicTypeName() == typeName) { return; + } } if (internalNode()->hasProperty(name()) && !internalNode()->property(name())->isBindingProperty()) diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index 9125ec82a3a..56ca1b19a20 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -691,6 +691,8 @@ Project { "materialbrowser/materialbrowserwidget.h", "materialeditor/materialeditorcontextobject.cpp", "materialeditor/materialeditorcontextobject.h", + "materialeditor/materialeditordynamicpropertiesproxymodel.cpp", + "materialeditor/materialeditordynamicpropertiesproxymodel.h", "materialeditor/materialeditorqmlbackend.cpp", "materialeditor/materialeditorqmlbackend.h", "materialeditor/materialeditortransaction.cpp", @@ -725,6 +727,8 @@ Project { "propertyeditor/colorpalettebackend.h", "propertyeditor/designerpropertymap.cpp", "propertyeditor/designerpropertymap.h", + "propertyeditor/dynamicpropertiesproxymodel.cpp", + "propertyeditor/dynamicpropertiesproxymodel.h", "propertyeditor/fileresourcesmodel.cpp", "propertyeditor/fileresourcesmodel.h", "propertyeditor/itemfiltermodel.cpp", @@ -843,6 +847,8 @@ Project { "connectioneditor/connectionviewwidget.ui", "connectioneditor/dynamicpropertiesmodel.cpp", "connectioneditor/dynamicpropertiesmodel.h", + "connectioneditor/selectiondynamicpropertiesproxymodel.cpp", + "connectioneditor/selectiondynamicpropertiesproxymodel.h", "connectioneditor/stylesheet.css", "curveeditor/curveeditorview.cpp", "curveeditor/curveeditorview.h",