diff --git a/share/qtcreator/qmldesigner/connectionseditor/BindingsDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialog.qml new file mode 100644 index 00000000000..d544393a33b --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialog.qml @@ -0,0 +1,13 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick + +PopupDialog { + property alias backend: form.backend + + BindingsDialogForm { + id: form + y: 32 + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml new file mode 100644 index 00000000000..932860f34cb --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml @@ -0,0 +1,101 @@ + +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +import QtQuick +import QtQuick.Controls +import StudioControls + +Rectangle { + width: 400 + height: 800 + color: "#1b1b1b" + + property var backend + + Text { + id: text1 + x: 10 + y: 25 + color: "#ffffff" + text: qsTr("Target") + font.pixelSize: 15 + } + + Text { + id: text111 + x: 80 + y: 25 + color: "red" + text: backend.targetNode + font.pixelSize: 15 + } + + TopLevelComboBox { + id: target + x: 101 + width: 210 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -335 + model: backend.property.model ?? [] + enabled: false + //I see no use case to actually change the property name + //onActivated: backend.targetNode.activateIndex(target.currentIndex) + property int currentTypeIndex: backend.property.currentIndex ?? 0 + onCurrentTypeIndexChanged: target.currentIndex = target.currentTypeIndex + } + + Text { + id: text2 + x: 13 + y: 111 + color: "#ffffff" + text: qsTr("Source Propety") + font.pixelSize: 15 + } + + TopLevelComboBox { + id: sourceNode + x: 135 + y: 98 + width: 156 + + model: backend.sourceNode.model ?? [] + + onModelChanged: sourceNode.currentIndex = sourceNode.currentTypeIndex + + onActivated: backend.sourceNode.activateIndex(sourceNode.currentIndex) + property int currentTypeIndex: backend.sourceNode.currentIndex ?? 0 + onCurrentTypeIndexChanged: sourceNode.currentIndex = sourceNode.currentTypeIndex + } + + Text { + x: 13 + y: 88 + color: "#ffffff" + text: qsTr("Source Node") + font.pixelSize: 15 + } + + TopLevelComboBox { + id: sourceProperty + x: 140 + y: 121 + width: 156 + + model: backend.sourceProperty.model ?? [] + onModelChanged: sourceProperty.currentIndex = sourceProperty.currentTypeIndex + onActivated: backend.sourceProperty.activateIndex( + sourceProperty.currentIndex) + property int currentTypeIndex: backend.sourceProperty.currentIndex ?? 0 + onCurrentTypeIndexChanged: sourceProperty.currentIndex = sourceProperty.currentTypeIndex + } + + Text { + id: text3 + x: 10 + y: 55 + color: "#ffffff" + text: qsTr("Property") + font.pixelSize: 15 + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/BindingsListView.qml b/share/qtcreator/qmldesigner/connectionseditor/BindingsListView.qml new file mode 100644 index 00000000000..1e8e123aa3a --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/BindingsListView.qml @@ -0,0 +1,123 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import ConnectionsEditor +import HelperWidgets 2.0 as HelperWidgets +import StudioControls 1.0 as StudioControls +import StudioTheme as StudioTheme +import ConnectionsEditorEditorBackend + +ListView { + id: listView + width: 606 + height: 160 + interactive: false + highlightMoveDuration: 0 + + onVisibleChanged: { + dialog.hide() + } + + property int modelCurrentIndex: listView.model.currentIndex ?? 0 + + /* Something weird with currentIndex happens when items are removed added. + listView.model.currentIndex contains the persistent index. + */ + onModelCurrentIndexChanged: { + listView.currentIndex = listView.model.currentIndex + } + + onCurrentIndexChanged: { + listView.currentIndex = listView.model.currentIndex + dialog.backend.currentRow = listView.currentIndex + } + + data: [ + BindingsDialog { + id: dialog + visible: false + backend: listView.model.delegate + } + ] + delegate: Item { + + width: 600 + height: 18 + + MouseArea { + id: mouseArea + anchors.fill: parent + onClicked: { + listView.model.currentIndex = index + listView.currentIndex = index + dialog.backend.currentRow = index + dialog.popup(mouseArea) + } + + property int currentIndex: listView.currentIndex + } + + Row { + id: row1 + x: 0 + y: 0 + width: 600 + height: 16 + spacing: 10 + + Text { + width: 120 + color: "#ffffff" + text: target ?? "" + anchors.verticalCenter: parent.verticalCenter + font.bold: false + } + + Text { + width: 120 + text: targetProperty ?? "" + color: "#ffffff" + anchors.verticalCenter: parent.verticalCenter + font.bold: false + } + + Text { + width: 120 + text: source ?? "" + anchors.verticalCenter: parent.verticalCenter + color: "#ffffff" + font.bold: false + } + + Text { + width: 120 + text: sourceProperty ?? "" + anchors.verticalCenter: parent.verticalCenter + color: "#ffffff" + font.bold: false + } + + Text { + width: 120 + + text: "-" + anchors.verticalCenter: parent.verticalCenter + horizontalAlignment: Text.AlignRight + font.pointSize: 14 + color: "#ffffff" + font.bold: true + MouseArea { + anchors.fill: parent + onClicked: listView.model.remove(index) + } + } + } + } + + highlight: Rectangle { + color: "#2a5593" + width: 600 + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml new file mode 100644 index 00000000000..a23ca40c9da --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml @@ -0,0 +1,10 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick + +PopupDialog { + ConnectionsDialogForm { + y: 32 + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml new file mode 100644 index 00000000000..d39aefdba3d --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml @@ -0,0 +1,242 @@ + +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +import QtQuick +import QtQuick.Controls +import StudioControls + +Rectangle { + width: 400 + height: 800 + color: "#1b1b1b" + + Text { + id: text1 + x: 10 + y: 25 + color: "#ffffff" + text: qsTr("Target:") + font.pixelSize: 15 + } + + TopLevelComboBox { + id: target + x: 95 + width: 210 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -367 + model: ["mySpinbox", "foo", "backendObject"] + } + + Text { + id: text2 + x: 10 + y: 131 + color: "#ffffff" + text: qsTr("Signal") + font.pixelSize: 15 + } + + TopLevelComboBox { + id: signal + x: 10 + y: 7 + width: 156 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -207 + model: ["onClicked", "onPressed", "onReleased"] + } + + TopLevelComboBox { + id: action + x: 207 + y: 7 + width: 156 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -207 + model: ["Call Function", "Assign", "ChnageState"] + } + + Text { + id: text3 + x: 207 + y: 131 + color: "#ffffff" + text: qsTr("Action") + font.pixelSize: 15 + } + + Item { + id: functionGroup + x: 0 + y: 276 + width: 400 + height: 176 + + Text { + id: text4 + x: 17 + y: -11 + color: "#ffffff" + text: qsTr("Target") + font.pixelSize: 15 + } + + TopLevelComboBox { + id: functionTarget + x: 10 + y: 7 + width: 156 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -48 + model: ["mySpinBox", "backendObject", "someButton"] + } + + TopLevelComboBox { + id: functionName + x: 203 + y: 7 + width: 156 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -48 + model: ["start", "trigger", "stop"] + } + + Text { + id: text5 + x: 203 + y: -11 + color: "#ffffff" + text: qsTr("Function") + font.pixelSize: 15 + } + } + + Item { + id: statesGroup + x: 0 + y: 383 + width: 400 + height: 106 + Text { + id: text6 + x: 17 + y: -11 + color: "#ffffff" + text: qsTr("State Group") + font.pixelSize: 15 + } + + TopLevelComboBox { + id: stateGroup + x: 10 + y: 7 + width: 156 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -12 + model: ["default", "State Group 01", "State Group 02"] + } + + TopLevelComboBox { + id: stateName + x: 209 + y: 7 + width: 156 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -12 + model: ["State 01", "State 02", "State 03"] + } + + Text { + id: text7 + x: 209 + y: -11 + color: "#ffffff" + text: qsTr("State") + font.pixelSize: 15 + } + } + + Item { + id: assignment + x: 10 + y: 505 + width: 400 + height: 106 + Text { + id: text8 + x: 17 + y: -11 + color: "#ffffff" + text: qsTr("target") + font.pixelSize: 15 + } + + TopLevelComboBox { + id: valueTarget + x: 10 + y: 7 + width: 156 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -12 + model: ["mySpinBox", "myButton", "backendObject"] + } + + TopLevelComboBox { + id: valueSource + x: 209 + y: 7 + width: 156 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -12 + model: ["mySpinBox", "myButton", "backendObject"] + } + + Text { + id: text9 + x: 209 + y: -11 + color: "#ffffff" + text: qsTr("source") + font.pixelSize: 15 + } + + Text { + id: text10 + x: 17 + y: 76 + color: "#ffffff" + text: qsTr("value") + font.pixelSize: 15 + } + + TopLevelComboBox { + id: valueOut + x: 10 + y: -2 + width: 156 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: 84 + model: ["width", "height", "opacity"] + } + + TopLevelComboBox { + id: valueIn + x: 209 + y: -2 + width: 156 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: 84 + model: ["width", "height", "x", "y"] + } + + Text { + id: text11 + x: 209 + y: 76 + color: "#ffffff" + text: qsTr("value") + font.pixelSize: 15 + } + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsListView.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsListView.qml new file mode 100644 index 00000000000..3238b6cd9b4 --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsListView.qml @@ -0,0 +1,115 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import ConnectionsEditor +import HelperWidgets 2.0 as HelperWidgets +import StudioControls 1.0 as StudioControls +import StudioTheme as StudioTheme +import ConnectionsEditorEditorBackend + +ListView { + id: listView + width: 606 + height: 160 + interactive: false + highlightMoveDuration: 0 + + onVisibleChanged: { + dialog.hide() + } + + property int modelCurrentIndex: listView.model.currentIndex ?? 0 + + /* + Something weird with currentIndex happens when items are removed added. + listView.model.currentIndex contains the persistent index. + */ + onModelCurrentIndexChanged: { + listView.currentIndex = listView.model.currentIndex + } + + onCurrentIndexChanged: { + listView.currentIndex = listView.model.currentIndex + } + + data: [ + ConnectionsDialog { + id: dialog + visible: false + } + ] + + delegate: Item { + + width: 600 + height: 18 + + MouseArea { + id: mouseArea + anchors.fill: parent + onClicked: { + listView.model.currentIndex = index + listView.currentIndex = index + dialog.popup(mouseArea) + } + + property int currentIndex: listView.currentIndex + } + + Row { + id: row1 + x: 0 + y: 0 + width: 600 + height: 16 + spacing: 10 + + Text { + width: 120 + color: "#ffffff" + text: target + anchors.verticalCenter: parent.verticalCenter + font.bold: false + } + + Text { + width: 120 + text: signal + color: "#ffffff" + anchors.verticalCenter: parent.verticalCenter + font.bold: false + } + + Text { + width: 120 + + text: action + anchors.verticalCenter: parent.verticalCenter + color: "#ffffff" + font.bold: false + } + + Text { + width: 120 + + text: "-" + anchors.verticalCenter: parent.verticalCenter + horizontalAlignment: Text.AlignRight + font.pointSize: 14 + color: "#ffffff" + font.bold: true + MouseArea { + anchors.fill: parent + onClicked: listView.model.remove(index) + } + } + } + } + + highlight: Rectangle { + color: "#2a5593" + width: 600 + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/Main.qml b/share/qtcreator/qmldesigner/connectionseditor/Main.qml new file mode 100644 index 00000000000..628a00a548a --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/Main.qml @@ -0,0 +1,117 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import ConnectionsEditor +import HelperWidgets 2.0 as HelperWidgets +import StudioControls 1.0 as StudioControls +import StudioTheme as StudioTheme +import ConnectionsEditorEditorBackend + +Rectangle { + width: 640 + height: 1080 + color: "#232222" + + Rectangle { + id: rectangle + x: 10 + y: 10 + width: 620 + height: 97 + color: "#333333" + + Rectangle { + id: rectangle1 + x: 10 + y: 10 + width: 600 + height: 34 + color: "#00ffffff" + border.width: 1 + + Text { + id: text1 + x: 10 + y: 10 + color: "#b5b2b2" + text: qsTr("Search") + font.pixelSize: 12 + } + } + + RowLayout { + x: 10 + y: 50 + TabCheckButton { + id: connections + text: "Connections" + checked: true + autoExclusive: true + checkable: true + } + + TabCheckButton { + id: bindings + text: "Bindings" + autoExclusive: true + checkable: true + } + + TabCheckButton { + id: properties + text: "Properties" + autoExclusive: true + checkable: true + } + } + + Text { + id: text2 + x: 577 + y: 58 + color: "#ffffff" + text: qsTr("+") + font.pixelSize: 18 + font.bold: true + MouseArea { + anchors.fill: parent + onClicked: { + print(ConnectionsEditorEditorBackend.dynamicPropertiesModel.delegate) + print(ConnectionsEditorEditorBackend.dynamicPropertiesModel.delegate.type) + print(ConnectionsEditorEditorBackend.dynamicPropertiesModel.delegate.type.model) + + if (connections.checked) + ConnectionsEditorEditorBackend.connectionModel.add() + else if (bindings.checked) + ConnectionsEditorEditorBackend.bindingModel.add() + else if (properties.checked) + ConnectionsEditorEditorBackend.dynamicPropertiesModel.add() + } + } + } + } + + ConnectionsListView { + visible: connections.checked + x: 17 + y: 124 + model: ConnectionsEditorEditorBackend.connectionModel + } + + BindingsListView { + visible: bindings.checked + x: 17 + y: 124 + model: ConnectionsEditorEditorBackend.bindingModel + } + + PropertiesListView { + visible: properties.checked + x: 17 + y: 124 + model: ConnectionsEditorEditorBackend.dynamicPropertiesModel + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/PopupDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/PopupDialog.qml new file mode 100644 index 00000000000..3aad2785ce2 --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/PopupDialog.qml @@ -0,0 +1,72 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls + +Window { + id: window + width: 400 + height: 800 + visible: true + flags: Qt.FramelessWindowHint || Qt.Dialog + + color: Qt.transparent + + property int bw: 5 + + function popup(item) { + print("popup " + item) + var padding = 12 + var p = item.mapToGlobal(0, 0) + dialog.x = p.x - dialog.width - padding + if (dialog.x < 0) + dialog.x = p.x + item.width + padding + dialog.y = p.y + dialog.show() + dialog.raise() + } + + Rectangle { + id: rectangle1 + color: "#d7d7d7" + border.color: "#232323" + anchors.fill: parent + + + + Rectangle { + id: rectangle + height: 32 + color: "#797979" + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.topMargin: 0 + anchors.leftMargin: 0 + anchors.rightMargin: 0 + DragHandler { + grabPermissions: TapHandler.CanTakeOverFromAnything + onActiveChanged: if (active) { window.startSystemMove(); } + } + + Rectangle { + id: rectangle2 + x: 329 + width: 16 + height: 16 + color: "#ffffff" + radius: 4 + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: 6 + + MouseArea { + id: mouseArea + anchors.fill: parent + onClicked: window.close() + } + } + } + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialog.qml new file mode 100644 index 00000000000..cd0ae16226a --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialog.qml @@ -0,0 +1,13 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick + +PopupDialog { + property alias backend: form.backend + + PropertiesDialogForm { + id: form + y: 32 + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml new file mode 100644 index 00000000000..c728027f30f --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialogForm.qml @@ -0,0 +1,76 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +import StudioControls + +Rectangle { + width: 400 + height: 800 + color: "#1b1b1b" + property var backend + + Text { + id: text1 + x: 10 + y: 25 + color: "#ffffff" + text: qsTr("Type:") + font.pixelSize: 15 + } + + TopLevelComboBox { + id: target + x: 95 + width: 210 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -367 + model: backend.type.model ?? [] + onActivated: backend.type.activateIndex(target.currentIndex) + property int currentTypeIndex: backend.type.currentIndex ?? 0 + onCurrentTypeIndexChanged: target.currentIndex = target.currentTypeIndex + } + + Text { + id: text2 + x: 10 + y: 131 + color: "#ffffff" + text: qsTr("Name") + font.pixelSize: 15 + } + + TextInput { + id: name + x: 70 + y: 131 + color: "white" + width: 156 + text: backend.name.text ?? "" + onEditingFinished: { + backend.name.activateText(name.text) + } + } + + Text { + x: 10 + y: 81 + color: "#ffffff" + text: qsTr("Value") + font.pixelSize: 15 + } + + TextInput { + id: value + color: "red" + x: 70 + y: 81 + width: 156 + text: backend.value.text ?? "" + onEditingFinished: { + backend.value.activateText(value.text) + } + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/PropertiesListView.qml b/share/qtcreator/qmldesigner/connectionseditor/PropertiesListView.qml new file mode 100644 index 00000000000..9e0822dc6c3 --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/PropertiesListView.qml @@ -0,0 +1,128 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import ConnectionsEditor +import HelperWidgets 2.0 as HelperWidgets +import StudioControls 1.0 as StudioControls +import StudioTheme as StudioTheme +import ConnectionsEditorEditorBackend + +ListView { + id: listView + width: 606 + height: 160 + interactive: false + highlightMoveDuration: 0 + + onVisibleChanged: { + dialog.hide() + } + + property int modelCurrentIndex: listView.model.currentIndex ?? 0 + + /* Something weird with currentIndex happens when items are removed added. + listView.model.currentIndex contains the persistent index. + */ + onModelCurrentIndexChanged: { + listView.currentIndex = listView.model.currentIndex + } + + onCurrentIndexChanged: { + listView.currentIndex = listView.model.currentIndex + dialog.backend.currentRow = listView.currentIndex + } + + data: [ + PropertiesDialog { + id: dialog + visible: false + backend: listView.model.delegate + } + ] + + delegate: Item { + + width: 600 + height: 18 + + MouseArea { + id: mouseArea + anchors.fill: parent + + property int currentIndex: listView.currentIndex + + Connections { + target: mouseArea + function onClicked() { + listView.model.currentIndex = index + listView.currentIndex = index + dialog.backend.currentRow = index + dialog.popup(mouseArea) + } + } + } + + Row { + id: row1 + x: 0 + y: 0 + width: 600 + height: 16 + spacing: 10 + + Text { + width: 120 + color: "#ffffff" + text: target ?? "" + anchors.verticalCenter: parent.verticalCenter + font.bold: false + } + + Text { + width: 120 + text: name ?? "" + color: "#ffffff" + anchors.verticalCenter: parent.verticalCenter + font.bold: false + } + + Text { + width: 120 + text: type ?? "" + anchors.verticalCenter: parent.verticalCenter + color: "#ffffff" + font.bold: false + } + + Text { + width: 120 + text: value ?? "" + anchors.verticalCenter: parent.verticalCenter + color: "#ffffff" + font.bold: false + } + + Text { + width: 120 + + text: "-" + anchors.verticalCenter: parent.verticalCenter + horizontalAlignment: Text.AlignRight + font.pointSize: 14 + color: "#ffffff" + font.bold: true + MouseArea { + anchors.fill: parent + onClicked: listView.model.remove(index) + } + } + } + } + + highlight: Rectangle { + color: "#2a5593" + width: 600 + } +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/TabCheckButton.qml b/share/qtcreator/qmldesigner/connectionseditor/TabCheckButton.qml new file mode 100644 index 00000000000..c49bc539157 --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/TabCheckButton.qml @@ -0,0 +1,80 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Templates + +Button { + id: control + + implicitWidth: Math.max( + buttonBackground ? buttonBackground.implicitWidth : 0, + textItem.implicitWidth + leftPadding + rightPadding) + implicitHeight: Math.max( + buttonBackground ? buttonBackground.implicitHeight : 0, + textItem.implicitHeight + topPadding + bottomPadding) + leftPadding: 4 + rightPadding: 4 + + text: "My Button" + + background: buttonBackground + Rectangle { + id: buttonBackground + color: "#047eff" + implicitWidth: 100 + implicitHeight: 40 + opacity: enabled ? 1 : 0.3 + radius: 12 + border.color: "#047eff" + } + + contentItem: textItem + Text { + id: textItem + text: control.text + + opacity: enabled ? 1.0 : 0.3 + color: "#ffffff" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + states: [ + State { + name: "normal" + when: !control.down && !control.checked + + PropertyChanges { + target: buttonBackground + visible: false + color: "#00000000" + border.color: "#047eff" + } + + PropertyChanges { + target: textItem + color: "#ffffff" + } + }, + State { + name: "down" + when: control.down && !control.checked + PropertyChanges { + target: textItem + color: "#ffffff" + } + + PropertyChanges { + target: buttonBackground + color: "#047eff" + border.color: "#00000000" + } + }, + State { + name: "down1" + when: control.checked + extend: "down" + } + ] +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/imports/ConnectionsEditor/Constants.qml b/share/qtcreator/qmldesigner/connectionseditor/imports/ConnectionsEditor/Constants.qml new file mode 100644 index 00000000000..c08adcf6360 --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/imports/ConnectionsEditor/Constants.qml @@ -0,0 +1,39 @@ +/**************************************************************************** +** +** 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 Singleton +import QtQuick + +QtObject { + readonly property int width: 1920 + readonly property int height: 1080 + + property int thumbnailSize: 250 + // Breakpoint to control when the state thumbnail view toggles from normal to tiny + readonly property int thumbnailBreak: 150 + + readonly property int minThumbSize: 100 + readonly property int maxThumbSize: 350 +} diff --git a/share/qtcreator/qmldesigner/connectionseditor/imports/ConnectionsEditor/qmldir b/share/qtcreator/qmldesigner/connectionseditor/imports/ConnectionsEditor/qmldir new file mode 100644 index 00000000000..616ac203530 --- /dev/null +++ b/share/qtcreator/qmldesigner/connectionseditor/imports/ConnectionsEditor/qmldir @@ -0,0 +1 @@ +singleton Constants 1.0 Constants.qml diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp index 191900d5e9d..2b2025ed83a 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp @@ -13,14 +13,16 @@ #include #include +#include + #include #include namespace QmlDesigner { BindingModel::BindingModel(ConnectionView *parent) - : QStandardItemModel(parent) - , m_connectionView(parent) + : QStandardItemModel(parent), m_connectionView(parent), + m_delegate(new BindingModelBackendDelegate(this)) { connect(this, &QStandardItemModel::dataChanged, this, &BindingModel::handleDataChanged); } @@ -40,6 +42,31 @@ void BindingModel::resetModel() endResetModel(); } +void BindingModel::add() +{ + addBindingForCurrentNode(); +} + +void BindingModel::remove(int row) +{ + deleteBindindByRow(row); +} + +int BindingModel::currentIndex() const +{ + return m_currentIndex; +} + +void BindingModel::setCurrentIndex(int i) +{ + if (m_currentIndex == i) + return; + + m_currentIndex = i; + + emit currentIndexChanged(); +} + void BindingModel::bindingChanged(const BindingProperty &bindingProperty) { m_handleDataChanged = false; @@ -232,6 +259,19 @@ void BindingModel::addBindingForCurrentNode() } } +static void updateDisplayRoles(QStandardItem *item, const BindingProperty &property) +{ + item->setData(property.parentModelNode().id(), BindingModel::TargetNameRole); + item->setData(property.name(), BindingModel::TargetPropertyNameRole); + + const AbstractProperty source = property.resolveToProperty(); + + if (source.isValid()) { + item->setData(source.parentModelNode().id(), BindingModel::SourceNameRole); + item->setData(source.name(), BindingModel::SourcePropertyNameRole); + } +} + void BindingModel::addBindingProperty(const BindingProperty &property) { QStandardItem *idItem; @@ -248,6 +288,7 @@ void BindingModel::addBindingProperty(const BindingProperty &property) QList items; items.append(idItem); + updateDisplayRoles(idItem, property); items.append(targetPropertyNameItem); QString sourceNodeName; @@ -267,6 +308,10 @@ void BindingModel::updateBindingProperty(int rowNumber) BindingProperty bindingProperty = bindingPropertyForRow(rowNumber); if (bindingProperty.isValid()) { + QStandardItem *idItem = item(rowNumber, 0); + if (idItem) + updateDisplayRoles(idItem, bindingProperty); + QString targetPropertyName = QString::fromUtf8(bindingProperty.name()); updateDisplayRole(rowNumber, TargetPropertyNameRow, targetPropertyName); QString sourceNodeName; @@ -355,6 +400,7 @@ void BindingModel::updateCustomData(QStandardItem *item, const BindingProperty & { item->setData(bindingProperty.parentModelNode().internalId(), Qt::UserRole + 1); item->setData(bindingProperty.name(), Qt::UserRole + 2); + updateDisplayRoles(item, bindingProperty); } int BindingModel::findRowForBinding(const BindingProperty &bindingProperty) @@ -369,6 +415,8 @@ int BindingModel::findRowForBinding(const BindingProperty &bindingProperty) bool BindingModel::getExpressionStrings(const BindingProperty &bindingProperty, QString *sourceNode, QString *sourceProperty) { + //TODO reimplement using existing helper functions + //### todo we assume no expressions yet const QString expression = bindingProperty.expression(); @@ -438,4 +486,159 @@ void BindingModel::handleException() resetModel(); } +QHash BindingModel::roleNames() const +{ + static QHash roleNames{{TargetNameRole, "target"}, + {TargetPropertyNameRole, "targetProperty"}, + {SourceNameRole, "source"}, + {SourcePropertyNameRole, "sourceProperty"}}; + + return roleNames; +} + +BindingModelBackendDelegate *BindingModel::delegate() const +{ + return m_delegate; +} + +BindingModelBackendDelegate::BindingModelBackendDelegate(BindingModel *parent) : QObject(parent) +{ + connect(&m_sourceNode, &StudioQmlComboBoxBackend::activated, this, [this]() { + handleSourceNodeChanged(); + }); + + connect(&m_sourceNodeProperty, &StudioQmlComboBoxBackend::activated, this, [this]() { + handleSourcePropertyChanged(); + }); +} + +int BindingModelBackendDelegate::currentRow() const +{ + return m_currentRow; +} + +void BindingModelBackendDelegate::setCurrentRow(int i) +{ + // See BindingDelegate::createEditor + + if (m_currentRow == i) + return; + + m_currentRow = i; + + //setup + + BindingModel *model = qobject_cast(parent()); + + QTC_ASSERT(model, return ); + + BindingProperty bindingProperty = model->bindingPropertyForRow(currentRow()); + + QString idLabel = bindingProperty.parentModelNode().id(); + if (idLabel.isEmpty()) + idLabel = bindingProperty.parentModelNode().simplifiedTypeName(); + + m_targetNode = idLabel; + + emit targetNodeChanged(); + + m_property.setModel(model->possibleTargetProperties(bindingProperty)); + m_property.setCurrentText(QString::fromUtf8(bindingProperty.name())); + + QStringList sourceNodes; + + for (const ModelNode &modelNode : model->connectionView()->allModelNodes()) { + if (!modelNode.id().isEmpty()) + sourceNodes.append(modelNode.id()); + } + + std::sort(sourceNodes.begin(), sourceNodes.end()); + m_sourceNode.setModel(sourceNodes); + + QString sourceNodeName; + QString sourcePropertyName; + model->getExpressionStrings(bindingProperty, &sourceNodeName, &sourcePropertyName); + + m_sourceNode.setCurrentText(sourceNodeName); + + m_sourceNodeProperty.setModel(model->possibleSourceProperties(bindingProperty)); + m_sourceNodeProperty.setCurrentText(sourcePropertyName); +} + +void BindingModelBackendDelegate::handleException() +{ + QMessageBox::warning(nullptr, tr("Error"), m_exceptionError); + //reset +} + +QString BindingModelBackendDelegate::targetNode() const +{ + return m_targetNode; +} + +StudioQmlComboBoxBackend *BindingModelBackendDelegate::property() +{ + return &m_property; +} + +StudioQmlComboBoxBackend *BindingModelBackendDelegate::sourceNode() +{ + return &m_sourceNode; +} + +StudioQmlComboBoxBackend *BindingModelBackendDelegate::sourceProperty() +{ + return &m_sourceNodeProperty; +} + +void BindingModelBackendDelegate::handleSourceNodeChanged() +{ + BindingModel *model = qobject_cast(parent()); + + QTC_ASSERT(model, return ); + QTC_ASSERT(model->connectionView(), return ); + + const QString sourceNode = m_sourceNode.currentText(); + const QString sourceProperty = m_sourceNodeProperty.currentText(); + + QString expression; + if (sourceProperty.isEmpty()) { + expression = sourceNode; + } else { + expression = sourceNode + QLatin1String(".") + sourceProperty; + } + + BindingProperty bindingProperty = model->bindingPropertyForRow(currentRow()); + model->connectionView()->executeInTransaction("BindingModel::updateExpression", + [&bindingProperty, expression]() { + bindingProperty.setExpression( + expression.trimmed()); + }); +} + +void BindingModelBackendDelegate::handleSourcePropertyChanged() +{ + BindingModel *model = qobject_cast(parent()); + + QTC_ASSERT(model, return ); + QTC_ASSERT(model->connectionView(), return ); + + const QString sourceNode = m_sourceNode.currentText(); + const QString sourceProperty = m_sourceNodeProperty.currentText(); + + QString expression; + if (sourceProperty.isEmpty()) { + expression = sourceNode; + } else { + expression = sourceNode + QLatin1String(".") + sourceProperty; + } + + BindingProperty bindingProperty = model->bindingPropertyForRow(currentRow()); + model->connectionView()->executeInTransaction("BindingModel::updateExpression", + [&bindingProperty, expression]() { + bindingProperty.setExpression( + expression.trimmed()); + }); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h index 12685679e94..1f469876852 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h @@ -7,16 +7,22 @@ #include #include +#include + #include namespace QmlDesigner { class ConnectionView; +class BindingModelBackendDelegate; class BindingModel : public QStandardItemModel { Q_OBJECT + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + Q_PROPERTY(BindingModelBackendDelegate *delegate READ delegate CONSTANT) + public: enum ColumnRoles { TargetModelNodeRow = 0, @@ -24,6 +30,15 @@ public: SourceModelNodeRow = 2, SourcePropertyNameRow = 3 }; + + enum UserRoles { + InternalIdRole = Qt::UserRole + 2, + TargetNameRole, + TargetPropertyNameRole, + SourceNameRole, + SourcePropertyNameRole + }; + BindingModel(ConnectionView *parent = nullptr); void bindingChanged(const BindingProperty &bindingProperty); void bindingRemoved(const BindingProperty &bindingProperty); @@ -37,6 +52,18 @@ public: void addBindingForCurrentNode(); void resetModel(); + Q_INVOKABLE void add(); + Q_INVOKABLE void remove(int row); + + int currentIndex() const; + void setCurrentIndex(int i); + bool getExpressionStrings(const BindingProperty &bindingProperty, + QString *sourceNode, + QString *sourceProperty); + +signals: + void currentIndexChanged(); + protected: void addBindingProperty(const BindingProperty &property); void updateBindingProperty(int rowNumber); @@ -46,11 +73,11 @@ protected: ModelNode getNodeByIdOrParent(const QString &id, const ModelNode &targetNode) const; void updateCustomData(QStandardItem *item, const BindingProperty &bindingProperty); int findRowForBinding(const BindingProperty &bindingProperty); - - bool getExpressionStrings(const BindingProperty &bindingProperty, QString *sourceNode, QString *sourceProperty); - void updateDisplayRole(int row, int columns, const QString &string); + QHash roleNames() const override; + BindingModelBackendDelegate *delegate() const; + private: void handleDataChanged(const QModelIndex &topLeft, const QModelIndex& bottomRight); void handleException(); @@ -60,7 +87,48 @@ private: bool m_lock = false; bool m_handleDataChanged = false; QString m_exceptionError; + int m_currentIndex = 0; + BindingModelBackendDelegate *m_delegate = nullptr; +}; +class BindingModelBackendDelegate : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int currentRow READ currentRow WRITE setCurrentRow NOTIFY currentRowChanged) + + Q_PROPERTY(QString targetNode READ targetNode NOTIFY targetNodeChanged) + Q_PROPERTY(StudioQmlComboBoxBackend *property READ property CONSTANT) + Q_PROPERTY(StudioQmlComboBoxBackend *sourceNode READ sourceNode CONSTANT) + Q_PROPERTY(StudioQmlComboBoxBackend *sourceProperty READ sourceProperty CONSTANT) + +public: + BindingModelBackendDelegate(BindingModel *parent = nullptr); + +signals: + void currentRowChanged(); + //void nameChanged(); + void targetNodeChanged(); + +private: + int currentRow() const; + void setCurrentRow(int i); + void handleException(); + QString targetNode() const; + + StudioQmlComboBoxBackend *property(); + StudioQmlComboBoxBackend *sourceNode(); + StudioQmlComboBoxBackend *sourceProperty(); + + void handleSourceNodeChanged(); + void handleSourcePropertyChanged(); + + StudioQmlComboBoxBackend m_property; + StudioQmlComboBoxBackend m_sourceNode; + StudioQmlComboBoxBackend m_sourceNodeProperty; + QString m_exceptionError; + int m_currentRow = -1; + QString m_targetNode; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp index 4abb7b21376..6fe8af59eb7 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp @@ -252,6 +252,19 @@ void ConnectionModel::updateCustomData(QStandardItem *item, const SignalHandlerP { item->setData(signalHandlerProperty.parentModelNode().internalId(), UserRoles::InternalIdRole); item->setData(signalHandlerProperty.name(), UserRoles::TargetPropertyNameRole); + item->setData(signalHandlerProperty.parentModelNode() + .bindingProperty("target") + .resolveToModelNode() + .id(), + UserRoles::TargetNameRole); + + // TODO signalHandlerProperty.source() contains a statement that defines the type. + // foo.bar() <- function call + // foo.state = "literal" //state change + //anything else is assignment + // e.g. foo.bal = foo2.bula ; foo.bal = "literal" ; goo.gal = true + + item->setData("Assignment", UserRoles::ActionTypeRole); } ModelNode ConnectionModel::getTargetNodeForConnection(const ModelNode &connection) const @@ -370,6 +383,16 @@ void ConnectionModel::removeRowFromTable(const SignalHandlerProperty &property) } } +void ConnectionModel::add() +{ + addConnection(); +} + +void ConnectionModel::remove(int row) +{ + deleteConnectionByRow(row); +} + void ConnectionModel::handleException() { QMessageBox::warning(nullptr, tr("Error"), m_exceptionError); @@ -522,4 +545,12 @@ QStringList ConnectionModel::getPossibleSignalsForConnection(const ModelNode &co return stringList; } +QHash ConnectionModel::roleNames() const +{ + static QHash roleNames{{TargetPropertyNameRole, "signal"}, + {TargetNameRole, "target"}, + {ActionTypeRole, "action"}}; + return roleNames; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h index 42cbe33fc77..fc1108d4035 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h @@ -26,7 +26,9 @@ public: }; enum UserRoles { InternalIdRole = Qt::UserRole + 1, - TargetPropertyNameRole + TargetPropertyNameRole, + TargetNameRole, + ActionTypeRole }; ConnectionModel(ConnectionView *parent = nullptr); @@ -49,6 +51,9 @@ public: void deleteConnectionByRow(int currentRow); void removeRowFromTable(const SignalHandlerProperty &property); + Q_INVOKABLE void add(); + Q_INVOKABLE void remove(int row); + protected: void addModelNode(const ModelNode &modelNode); void addConnection(const ModelNode &modelNode); @@ -61,6 +66,8 @@ protected: void updateCustomData(QStandardItem *item, const SignalHandlerProperty &signalHandlerProperty); QStringList getPossibleSignalsForConnection(const ModelNode &connection) const; + QHash roleNames() const override; + private: void handleDataChanged(const QModelIndex &topLeft, const QModelIndex& bottomRight); void handleException(); diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp index d8f84f3e0fc..9da3fcc7be4 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp @@ -8,6 +8,7 @@ #include "bindingmodel.h" #include "connectionmodel.h" #include "dynamicpropertiesmodel.h" +#include "theme.h" #include #include @@ -16,19 +17,116 @@ #include #include +#include + +#include +#include + #include +#include +#include #include namespace QmlDesigner { +static QString propertyEditorResourcesPath() +{ +#ifdef SHARE_QML_PATH + if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources"; +#endif + return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString(); +} + +class ConnectionViewQuickWidget : public StudioQuickWidget +{ + // Q_OBJECT carefull + +public: + ConnectionViewQuickWidget(ConnectionView *connectionEditorView) + : m_connectionEditorView(connectionEditorView) + + { + engine()->addImportPath(qmlSourcesPath()); + engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); + engine()->addImportPath(qmlSourcesPath() + "/imports"); + + m_qmlSourceUpdateShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_F12), this); + connect(m_qmlSourceUpdateShortcut, + &QShortcut::activated, + this, + &ConnectionViewQuickWidget::reloadQmlSource); + + //setObjectName(Constants::OBJECT_NAME_STATES_EDITOR); + setResizeMode(QQuickWidget::SizeRootObjectToView); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + auto map = registerPropertyMap("ConnectionsEditorEditorBackend"); + qmlRegisterAnonymousType("ConnectionsEditorEditorBackend", 1); + qmlRegisterAnonymousType( + "ConnectionsEditorEditorBackend", 1); + + map->setProperties( + {{"connectionModel", QVariant::fromValue(m_connectionEditorView->connectionModel())}}); + + map->setProperties( + {{"bindingModel", QVariant::fromValue(m_connectionEditorView->bindingModel())}}); + + map->setProperties( + {{"dynamicPropertiesModel", + QVariant::fromValue(m_connectionEditorView->dynamicPropertiesModel())}}); + + Theme::setupTheme(engine()); + + setMinimumWidth(195); + setMinimumHeight(195); + + // init the first load of the QML UI elements + reloadQmlSource(); + } + ~ConnectionViewQuickWidget() = default; + + static QString qmlSourcesPath() + { +#ifdef SHARE_QML_PATH + if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/connectionseditor"; +#endif + return Core::ICore::resourcePath("qmldesigner/connectionseditor").toString(); + } + +private: + void reloadQmlSource() + { + QString connectionEditorQmlFilePath = qmlSourcesPath() + QStringLiteral("/Main.qml"); + QTC_ASSERT(QFileInfo::exists(connectionEditorQmlFilePath), return ); + setSource(QUrl::fromLocalFile(connectionEditorQmlFilePath)); + + if (!rootObject()) { + QString errorString; + for (const QQmlError &error : errors()) + errorString += "\n" + error.toString(); + + Core::AsynchronousMessageBox::warning( + tr("Cannot Create QtQuick View"), + tr("ConnectionsEditorWidget: %1 cannot be created.%2") + .arg(qmlSourcesPath(), errorString)); + return; + } + } + +private: + QPointer m_connectionEditorView; + QShortcut *m_qmlSourceUpdateShortcut; +}; + ConnectionView::ConnectionView(ExternalDependenciesInterface &externalDependencies) - : AbstractView{externalDependencies} - , m_connectionViewWidget(new ConnectionViewWidget()) - , m_connectionModel(new ConnectionModel(this)) - , m_bindingModel(new BindingModel(this)) - , m_dynamicPropertiesModel(new DynamicPropertiesModel(false, this)) - , m_backendModel(new BackendModel(this)) + : AbstractView{externalDependencies}, m_connectionViewWidget(new ConnectionViewWidget()), + m_connectionModel(new ConnectionModel(this)), m_bindingModel(new BindingModel(this)), + m_dynamicPropertiesModel(new DynamicPropertiesModel(false, this)), + m_backendModel(new BackendModel(this)), + m_connectionViewQuickWidget(new ConnectionViewQuickWidget(this)) { connectionViewWidget()->setBindingModel(m_bindingModel); connectionViewWidget()->setConnectionModel(m_connectionModel); @@ -36,8 +134,11 @@ ConnectionView::ConnectionView(ExternalDependenciesInterface &externalDependenci connectionViewWidget()->setBackendModel(m_backendModel); } -ConnectionView::~ConnectionView() = default; - +ConnectionView::~ConnectionView() +{ + // Ensure that QML is deleted first to avoid calling back to C++. + delete m_connectionViewQuickWidget.data(); +} void ConnectionView::modelAttached(Model *model) { AbstractView::modelAttached(model); @@ -195,7 +296,14 @@ void ConnectionView::currentStateChanged(const ModelNode &) WidgetInfo ConnectionView::widgetInfo() { - return createWidgetInfo(m_connectionViewWidget.data(), + /* Enable new connection editor here */ + const bool newEditor = false; + + QWidget *widget = m_connectionViewWidget.data(); + if (newEditor) + widget = m_connectionViewQuickWidget.data(); + + return createWidgetInfo(widget, QLatin1String("ConnectionView"), WidgetInfo::LeftPane, 0, @@ -257,6 +365,20 @@ BackendModel *ConnectionView::backendModel() const return m_backendModel; } +int ConnectionView::currentIndex() const +{ + return m_currentIndex; +} + +void ConnectionView::setCurrentIndex(int i) +{ + if (m_currentIndex == i) + return; + + m_currentIndex = i; + emit currentIndexChanged(); +} + ConnectionView *ConnectionView::instance() { diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h index 89c6c489105..5997f230ad8 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h @@ -20,11 +20,14 @@ class BindingModel; class ConnectionModel; class DynamicPropertiesModel; class BackendModel; +class ConnectionViewQuickWidget; -class ConnectionView : public AbstractView +class ConnectionView : public AbstractView { Q_OBJECT + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + public: ConnectionView(ExternalDependenciesInterface &externalDependencies); ~ConnectionView() override; @@ -70,14 +73,24 @@ public: BindingModel *bindingModel() const; BackendModel *backendModel() const; + int currentIndex() const; + void setCurrentIndex(int i); + static ConnectionView *instance(); +signals: + void currentIndexChanged(); + private: //variables QPointer m_connectionViewWidget; + ConnectionModel *m_connectionModel; BindingModel *m_bindingModel; DynamicPropertiesModel *m_dynamicPropertiesModel; BackendModel *m_backendModel; + int m_currentIndex = 0; + + QPointer m_connectionViewQuickWidget; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp index edade6ae400..5a1fb2df819 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp @@ -90,6 +90,8 @@ ConnectionViewWidget::ConnectionViewWidget(QWidget *parent) : this, &ConnectionViewWidget::handleTabChanged); ui->stackedWidget->setCurrentIndex(0); + + ui->stackedWidget->parentWidget()->hide(); } ConnectionViewWidget::~ConnectionViewWidget() diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp index 4faf5d23f17..eb0976eaed9 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp @@ -152,10 +152,34 @@ QString DynamicPropertiesModel::defaultExpressionForType(const TypeName &type) return expression; } +void DynamicPropertiesModel::add() +{ + addDynamicPropertyForCurrentNode(); +} + +void DynamicPropertiesModel::remove(int row) +{ + deleteDynamicPropertyByRow(row); +} + +int DynamicPropertiesModel::currentIndex() const +{ + return m_currentIndex; +} + +void DynamicPropertiesModel::setCurrentIndex(int i) +{ + if (m_currentIndex == i) + return; + + m_currentIndex = i; + + emit currentIndexChanged(); +} + DynamicPropertiesModel::DynamicPropertiesModel(bool explicitSelection, AbstractView *parent) - : QStandardItemModel(parent) - , m_view(parent) - , m_explicitSelection(explicitSelection) + : QStandardItemModel(parent), m_view(parent), m_explicitSelection(explicitSelection), + m_delegate(new DynamicPropertiesModelBackendDelegate(this)) { connect(this, &QStandardItemModel::dataChanged, this, &DynamicPropertiesModel::handleDataChanged); } @@ -163,6 +187,7 @@ DynamicPropertiesModel::DynamicPropertiesModel(bool explicitSelection, AbstractV void DynamicPropertiesModel::resetModel() { beginResetModel(); + const int backIndex = m_currentIndex; clear(); setHorizontalHeaderLabels({tr("Item"), tr("Property"), tr("Property Type"), tr("Property Value")}); @@ -172,7 +197,9 @@ void DynamicPropertiesModel::resetModel() addModelNode(modelNode); } + emit currentIndexChanged(); endResetModel(); + m_currentIndex = backIndex; } @@ -344,6 +371,8 @@ void DynamicPropertiesModel::bindingRemoved(const BindingProperty &bindingProper removeRow(rowNumber); } + emit currentIndexChanged(); + m_handleDataChanged = true; } @@ -360,6 +389,8 @@ void DynamicPropertiesModel::variantRemoved(const VariantProperty &variantProper removeRow(rowNumber); } + emit currentIndexChanged(); + m_handleDataChanged = true; } @@ -368,6 +399,7 @@ void DynamicPropertiesModel::reset() m_handleDataChanged = false; resetModel(); m_handleDataChanged = true; + emit currentIndexChanged(); } void DynamicPropertiesModel::setSelectedNode(const ModelNode &node) @@ -597,6 +629,8 @@ void DynamicPropertiesModel::updateBindingProperty(int rowNumber) BindingProperty bindingProperty = bindingPropertyForRow(rowNumber); if (bindingProperty.isValid()) { + updateCustomData(rowNumber, bindingProperty); + QString propertyName = QString::fromUtf8(bindingProperty.name()); updateDisplayRole(rowNumber, PropertyNameRow, propertyName); QString value = bindingProperty.expression(); @@ -617,6 +651,7 @@ void DynamicPropertiesModel::updateVariantProperty(int rowNumber) VariantProperty variantProperty = variantPropertyForRow(rowNumber); if (variantProperty.isValid()) { + updateCustomData(rowNumber, variantProperty); QString propertyName = QString::fromUtf8(variantProperty.name()); updateDisplayRole(rowNumber, PropertyNameRow, propertyName); QVariant value = variantProperty.value(); @@ -787,6 +822,16 @@ void DynamicPropertiesModel::updateCustomData(QStandardItem *item, const Abstrac { item->setData(property.parentModelNode().internalId(), Qt::UserRole + 1); item->setData(property.name(), Qt::UserRole + 2); + + item->setData(property.parentModelNode().id(), TargetNameRole); + item->setData(property.name(), PropertyNameRole); + item->setData(property.parentModelNode().id(), TargetNameRole); + item->setData(property.dynamicTypeName(), PropertyTypeRole); + + if (property.isVariantProperty()) + item->setData(property.toVariantProperty().value(), PropertyValueRole); + if (property.isBindingProperty()) + item->setData(property.toBindingProperty().expression(), PropertyValueRole); } void DynamicPropertiesModel::updateCustomData(int row, const AbstractProperty &property) @@ -924,4 +969,217 @@ const ModelNode DynamicPropertiesModel::singleSelectedNode() const return m_view->singleSelectedModelNode(); } +QHash DynamicPropertiesModel::roleNames() const +{ + static QHash roleNames{{TargetNameRole, "target"}, + {PropertyNameRole, "name"}, + {PropertyTypeRole, "type"}, + {PropertyValueRole, "value"}}; + + return roleNames; +} + +DynamicPropertiesModelBackendDelegate *DynamicPropertiesModel::delegate() const +{ + return m_delegate; +} + +DynamicPropertiesModelBackendDelegate::DynamicPropertiesModelBackendDelegate( + DynamicPropertiesModel *parent) + : QObject(parent) +{ + m_type.setModel({"int", "bool", "var", "real", "string", "url", "color"}); + + connect(&m_type, &StudioQmlComboBoxBackend::activated, this, [this]() { handleTypeChanged(); }); + connect(&m_name, &StudioQmlTextBackend::activated, this, [this]() { handleNameChanged(); }); + connect(&m_value, &StudioQmlTextBackend::activated, this, [this]() { handleValueChanged(); }); +} + +int DynamicPropertiesModelBackendDelegate::currentRow() const +{ + return m_currentRow; +} + +void DynamicPropertiesModelBackendDelegate::setCurrentRow(int i) +{ + if (m_currentRow == i) + return; + + m_currentRow = i; + + //setup + + DynamicPropertiesModel *model = qobject_cast(parent()); + + QTC_ASSERT(model, return ); + + AbstractProperty property = model->abstractPropertyForRow(i); + + m_type.setCurrentText(QString::fromUtf8(property.dynamicTypeName())); + m_name.setText(QString::fromUtf8(property.name())); + + if (property.isVariantProperty()) + m_value.setText(property.toVariantProperty().value().toString()); + else if (property.isBindingProperty()) + m_value.setText(property.toBindingProperty().expression()); +} + +void DynamicPropertiesModelBackendDelegate::handleTypeChanged() +{ + //void DynamicPropertiesModel::updatePropertyType(int rowNumber) + const TypeName type = m_type.currentText().toUtf8(); + + DynamicPropertiesModel *model = qobject_cast(parent()); + + QTC_ASSERT(model, return ); + QTC_ASSERT(model->view(), return ); + + BindingProperty bindingProperty = model->bindingPropertyForRow(currentRow()); + + VariantProperty variantProperty = model->variantPropertyForRow(currentRow()); + + RewriterTransaction transaction = model->view()->beginRewriterTransaction(__FUNCTION__); + + try { + if (bindingProperty.isBindingProperty() || type == "var") { //var is always a binding + const QString expression = bindingProperty.expression(); + variantProperty.parentModelNode().removeProperty(variantProperty.name()); + bindingProperty.setDynamicTypeNameAndExpression(type, expression); + } else if (variantProperty.isVariantProperty()) { + variantProperty.parentModelNode().removeProperty(variantProperty.name()); + variantProperty.setDynamicTypeNameAndValue(type, variantValue()); + } + transaction.commit(); // committing in the try block + } catch (Exception &e) { + m_exceptionError = e.description(); + QTimer::singleShot(200, this, &DynamicPropertiesModelBackendDelegate::handleException); + } +} + +void DynamicPropertiesModelBackendDelegate::handleNameChanged() +{ + //see DynamicPropertiesModel::updatePropertyName + + const PropertyName newName = m_name.text().toUtf8(); + QTC_ASSERT(!newName.isEmpty(), return ); + + DynamicPropertiesModel *model = qobject_cast(parent()); + + QTC_ASSERT(model, return ); + QTC_ASSERT(model->view(), return ); + + BindingProperty bindingProperty = model->bindingPropertyForRow(currentRow()); + + ModelNode targetNode = bindingProperty.parentModelNode(); + + if (bindingProperty.isBindingProperty()) { + model->view()->executeInTransaction(__FUNCTION__, [bindingProperty, newName, &targetNode]() { + const QString expression = bindingProperty.expression(); + const PropertyName dynamicPropertyType = bindingProperty.dynamicTypeName(); + + targetNode.bindingProperty(newName).setDynamicTypeNameAndExpression(dynamicPropertyType, + expression); + targetNode.removeProperty(bindingProperty.name()); + }); + + return; + } + + VariantProperty variantProperty = model->variantPropertyForRow(currentRow()); + + if (variantProperty.isVariantProperty()) { + const QVariant value = variantProperty.value(); + const PropertyName dynamicPropertyType = variantProperty.dynamicTypeName(); + ModelNode targetNode = variantProperty.parentModelNode(); + + model->view()->executeInTransaction(__FUNCTION__, [=]() { + targetNode.variantProperty(newName).setDynamicTypeNameAndValue(dynamicPropertyType, + value); + targetNode.removeProperty(variantProperty.name()); + }); + } + + AbstractProperty property = targetNode.property(newName); + + //order might have changed because of name change we have to select the correct row + int newRow = model->findRowForProperty(property); + model->setCurrentIndex(newRow); + setCurrentRow(newRow); +} + +void DynamicPropertiesModelBackendDelegate::handleValueChanged() +{ + //see void DynamicPropertiesModel::updateValue(int row) + + DynamicPropertiesModel *model = qobject_cast(parent()); + + QTC_ASSERT(model, return ); + QTC_ASSERT(model->view(), return ); + + BindingProperty bindingProperty = model->bindingPropertyForRow(currentRow()); + + if (bindingProperty.isBindingProperty()) { + const QString expression = m_value.text(); + + RewriterTransaction transaction = model->view()->beginRewriterTransaction(__FUNCTION__); + try { + bindingProperty.setDynamicTypeNameAndExpression(bindingProperty.dynamicTypeName(), + expression); + transaction.commit(); // committing in the try block + } catch (Exception &e) { + m_exceptionError = e.description(); + QTimer::singleShot(200, this, &DynamicPropertiesModelBackendDelegate::handleException); + } + return; + } + + VariantProperty variantProperty = model->variantPropertyForRow(currentRow()); + + if (variantProperty.isVariantProperty()) { + RewriterTransaction transaction = model->view()->beginRewriterTransaction(__FUNCTION__); + try { + variantProperty.setDynamicTypeNameAndValue(variantProperty.dynamicTypeName(), + variantValue()); + transaction.commit(); // committing in the try block + } catch (Exception &e) { + m_exceptionError = e.description(); + QTimer::singleShot(200, this, &DynamicPropertiesModelBackendDelegate::handleException); + } + } +} + +void DynamicPropertiesModelBackendDelegate::handleException() +{ + QMessageBox::warning(nullptr, tr("Error"), m_exceptionError); + //reset +} + +QVariant DynamicPropertiesModelBackendDelegate::variantValue() const +{ + //improve + const QString type = m_type.currentText(); + if (type == "real" || type == "int") + return m_value.text().toFloat(); + + if (type == "bool") + return m_value.text() == "true"; + + return m_value.text(); +} + +StudioQmlComboBoxBackend *DynamicPropertiesModelBackendDelegate::type() +{ + return &m_type; +} + +StudioQmlTextBackend *DynamicPropertiesModelBackendDelegate::name() +{ + return &m_name; +} + +StudioQmlTextBackend *DynamicPropertiesModelBackendDelegate::value() +{ + return &m_value; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h index f094516e635..c41875dfc09 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h @@ -5,6 +5,8 @@ #include +#include + #include namespace QmlDesigner { @@ -15,6 +17,8 @@ class BindingProperty; class ModelNode; class VariantProperty; +class DynamicPropertiesModelBackendDelegate; + class DynamicPropertiesModel : public QStandardItemModel { Q_OBJECT @@ -22,11 +26,22 @@ class DynamicPropertiesModel : public QStandardItemModel public: enum ColumnRoles { TargetModelNodeRow = 0, - PropertyNameRow = 1, - PropertyTypeRow = 2, - PropertyValueRow = 3 + PropertyNameRow = 1, + PropertyTypeRow = 2, + PropertyValueRow = 3 }; + enum UserRoles { + InternalIdRole = Qt::UserRole + 2, + TargetNameRole, + PropertyNameRole, + PropertyTypeRole, + PropertyValueRole + }; + + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + Q_PROPERTY(DynamicPropertiesModelBackendDelegate *delegate READ delegate CONSTANT) + DynamicPropertiesModel(bool explicitSelection, AbstractView *parent); void bindingPropertyChanged(const BindingProperty &bindingProperty); @@ -62,6 +77,17 @@ public: static QVariant defaultValueForType(const TypeName &type); static QString defaultExpressionForType(const TypeName &type); + Q_INVOKABLE void add(); + Q_INVOKABLE void remove(int row); + + int currentIndex() const; + void setCurrentIndex(int i); + + int findRowForProperty(const AbstractProperty &abstractProperty) const; + +signals: + void currentIndexChanged(); + protected: void addProperty(const QVariant &propertyValue, const QString &propertyType, @@ -79,12 +105,17 @@ protected: void updateCustomData(int row, const AbstractProperty &property); int findRowForBindingProperty(const BindingProperty &bindingProperty) const; int findRowForVariantProperty(const VariantProperty &variantProperty) const; - int findRowForProperty(const AbstractProperty &abstractProperty) const; - bool getExpressionStrings(const BindingProperty &bindingProperty, QString *sourceNode, QString *sourceProperty); + bool getExpressionStrings(const BindingProperty &bindingProperty, + QString *sourceNode, + QString *sourceProperty); void updateDisplayRole(int row, int columns, const QString &string); + QHash roleNames() const override; + + DynamicPropertiesModelBackendDelegate *delegate() const; + private: void handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); void handleException(); @@ -95,6 +126,48 @@ private: QString m_exceptionError; QList m_selectedNodes; bool m_explicitSelection = false; + int m_currentIndex = 0; + + DynamicPropertiesModelBackendDelegate *m_delegate = nullptr; +}; + +class DynamicPropertiesModelBackendDelegate : public QObject +{ + Q_OBJECT + + Q_PROPERTY(StudioQmlComboBoxBackend *type READ type CONSTANT) + Q_PROPERTY(int currentRow READ currentRow WRITE setCurrentRow NOTIFY currentRowChanged) + Q_PROPERTY(StudioQmlTextBackend *name READ name CONSTANT) + Q_PROPERTY(StudioQmlTextBackend *value READ value CONSTANT) + //Q_PROPERTY(QString value READ value WRITE setValue NOTIFY valueChanged) + +public: + DynamicPropertiesModelBackendDelegate(DynamicPropertiesModel *parent = nullptr); + +signals: + void currentRowChanged(); + void nameChanged(); + void valueChanged(); + +private: + int currentRow() const; + void setCurrentRow(int i); + void handleTypeChanged(); + void handleNameChanged(); + void handleValueChanged(); + void handleException(); + QVariant variantValue() const; + + StudioQmlComboBoxBackend *type(); + + StudioQmlTextBackend *name(); + StudioQmlTextBackend *value(); + + StudioQmlComboBoxBackend m_type; + StudioQmlTextBackend m_name; + StudioQmlTextBackend m_value; + int m_currentRow = -1; + QString m_exceptionError; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesignerbase/studio/studioquickwidget.h b/src/plugins/qmldesignerbase/studio/studioquickwidget.h index ef64fb6a69d..61c2ff8922f 100644 --- a/src/plugins/qmldesignerbase/studio/studioquickwidget.h +++ b/src/plugins/qmldesignerbase/studio/studioquickwidget.h @@ -9,6 +9,127 @@ #include #include +class QMLDESIGNERBASE_EXPORT StudioQmlTextBackend : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) + +public: + explicit StudioQmlTextBackend(QObject *parent = nullptr) : QObject(parent) {} + + void setText(const QString &text) + { + if (m_text == text) + return; + + m_text = text; + emit textChanged(); + } + + QString text() const { return m_text; } + + Q_INVOKABLE void activateText(const QString &text) + { + if (m_text == text) + return; + + setText(text); + emit activated(text); + } + +signals: + void textChanged(); + void activated(const QString &text); + +private: + QString m_text; +}; + +class QMLDESIGNERBASE_EXPORT StudioQmlComboBoxBackend : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + Q_PROPERTY(QString currentText READ currentText WRITE setCurrentText NOTIFY currentTextChanged) + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(QStringList model READ model NOTIFY modelChanged) //TODO turn into model + +public: + explicit StudioQmlComboBoxBackend(QObject *parent = nullptr) : QObject(parent) {} + + void setModel(const QStringList &model) + { + if (m_model == model) + return; + m_model = model; + emit countChanged(); + emit modelChanged(); + emit currentTextChanged(); + emit currentIndexChanged(); + } + + QStringList model() const { return m_model; } + + int count() const { return m_model.count(); } + + QString currentText() const + { + if (m_currentIndex < 0) + return {}; + + if (m_model.isEmpty()) + return {}; + + if (m_currentIndex >= m_model.count()) + return {}; + + return m_model.at(m_currentIndex); + } + + int currentIndex() const { return m_currentIndex; } + + void setCurrentIndex(int i) + { + if (m_currentIndex == i) + return; + + m_currentIndex = i; + emit currentTextChanged(); + emit currentIndexChanged(); + } + + void setCurrentText(const QString &text) + { + if (currentText() == text) + return; + + if (!m_model.contains(text)) + return; + + setCurrentIndex(m_model.indexOf(text)); + } + + Q_INVOKABLE void activateIndex(int i) + { + if (m_currentIndex == i) + return; + setCurrentIndex(i); + emit activated(i); + } + +signals: + void currentIndexChanged(); + void currentTextChanged(); + void countChanged(); + void modelChanged(); + void activated(int i); + +private: + int m_currentIndex = -1; + QStringList m_model; +}; + class QMLDESIGNERBASE_EXPORT StudioPropertyMap : public QQmlPropertyMap { public: