QmlDesigner: Add QML front-end for ConnectionView

This is still disabled by default until the UI gets polished.

Add roles to models. ConnectionModel, BindingModel and DynamicPropertiesModel.
In QML roles are used in the ListView to "mimic" columns. The columns are only
relevant for the "old" cpp QTableView and can be removed.

Making currentIndex part of the model. If rows are removed and added
again we have to "reset/keep" the currentIndex as part of the model, because
the index is temporarly invalid on the view.

Implementing DynamicPropertiesModelBackendDelegate as reference.
This is a pure "backend delegate" that exposes all relevant properties for
a current row to QML.

Adding StudioQuickWidget and exposing the backend.

Adding StudioQmlTextBackend and StudioQmlComboBoxBackend to
StudioQuickWidget (should be moved into their own files).
Those helper classes make is easy to expose a property to a combobox or
textfield. The backend has to know nothing about the actual frontend
and those classes act as a mini-model for a view in QML.
The API is similar to UI controls.

Change-Id: I7a2c6ad951306fbca1d586fb8f278acdd91a064b
Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io>
Reviewed-by: Knud Dollereder <knud.dollereder@qt.io>
This commit is contained in:
Thomas Hartmann
2023-07-04 19:57:59 +02:00
parent b85eb8aa04
commit 9a11fa3ef8
24 changed files with 2052 additions and 24 deletions

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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()
}
}
}
}
}

View File

@@ -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
}
}

View File

@@ -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)
}
}
}

View File

@@ -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
}
}

View File

@@ -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"
}
]
}

View File

@@ -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
}

View File

@@ -0,0 +1 @@
singleton Constants 1.0 Constants.qml

View File

@@ -13,14 +13,16 @@
#include <rewritertransaction.h>
#include <rewriterview.h>
#include <utils/qtcassert.h>
#include <QMessageBox>
#include <QTimer>
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<QStandardItem*> 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<int, QByteArray> BindingModel::roleNames() const
{
static QHash<int, QByteArray> 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<BindingModel *>(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<BindingModel *>(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<BindingModel *>(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

View File

@@ -7,16 +7,22 @@
#include <bindingproperty.h>
#include <variantproperty.h>
#include <studioquickwidget.h>
#include <QStandardItemModel>
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<int, QByteArray> 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

View File

@@ -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<int, QByteArray> ConnectionModel::roleNames() const
{
static QHash<int, QByteArray> roleNames{{TargetPropertyNameRole, "signal"},
{TargetNameRole, "target"},
{ActionTypeRole, "action"}};
return roleNames;
}
} // namespace QmlDesigner

View File

@@ -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<int, QByteArray> roleNames() const override;
private:
void handleDataChanged(const QModelIndex &topLeft, const QModelIndex& bottomRight);
void handleException();

View File

@@ -8,6 +8,7 @@
#include "bindingmodel.h"
#include "connectionmodel.h"
#include "dynamicpropertiesmodel.h"
#include "theme.h"
#include <bindingproperty.h>
#include <nodeabstractproperty.h>
@@ -16,19 +17,116 @@
#include <qmldesignerplugin.h>
#include <viewmanager.h>
#include <studioquickwidget.h>
#include <coreplugin/icore.h>
#include <coreplugin/messagebox.h>
#include <utils/qtcassert.h>
#include <QQmlEngine>
#include <QShortcut>
#include <QTableView>
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<DynamicPropertiesModel>("ConnectionsEditorEditorBackend", 1);
qmlRegisterAnonymousType<DynamicPropertiesModelBackendDelegate>(
"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<ConnectionView> 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()
{

View File

@@ -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<ConnectionViewWidget> m_connectionViewWidget;
ConnectionModel *m_connectionModel;
BindingModel *m_bindingModel;
DynamicPropertiesModel *m_dynamicPropertiesModel;
BackendModel *m_backendModel;
int m_currentIndex = 0;
QPointer<ConnectionViewQuickWidget> m_connectionViewQuickWidget;
};
} // namespace QmlDesigner

View File

@@ -90,6 +90,8 @@ ConnectionViewWidget::ConnectionViewWidget(QWidget *parent) :
this, &ConnectionViewWidget::handleTabChanged);
ui->stackedWidget->setCurrentIndex(0);
ui->stackedWidget->parentWidget()->hide();
}
ConnectionViewWidget::~ConnectionViewWidget()

View File

@@ -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<int, QByteArray> DynamicPropertiesModel::roleNames() const
{
static QHash<int, QByteArray> 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<DynamicPropertiesModel *>(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<DynamicPropertiesModel *>(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<DynamicPropertiesModel *>(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<DynamicPropertiesModel *>(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

View File

@@ -5,6 +5,8 @@
#include <nodeinstanceglobal.h>
#include <studioquickwidget.h>
#include <QStandardItemModel>
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<int, QByteArray> 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<ModelNode> 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

View File

@@ -9,6 +9,127 @@
#include <QQmlPropertyMap>
#include <QtQuickWidgets/QQuickWidget>
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: