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",