QmlDesigner: Integrate Expression Builder

Task-number: QDS-10587
Change-Id: Ifc13a8364fccb74cb60d683f0e6c322d80baab50
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Henning Gruendl
2023-09-04 16:45:54 +02:00
committed by Henning Gründl
parent cbf4273bab
commit 5e8b5ec1f0
15 changed files with 1205 additions and 53 deletions

View File

@@ -107,19 +107,64 @@ Column {
onClicked: backend.removeCondition() onClicked: backend.removeCondition()
} }
ExpressionBuilder {
style: StudioTheme.Values.connectionPopupControlStyle
width: root.width
visible: backend.hasCondition
model: backend.conditionListModel
onRemove: function(index) {
console.log("remove", index)
backend.conditionListModel.removeToken(index)
}
onUpdate: function(index, value) {
console.log("update", index, value)
backend.conditionListModel.updateToken(index, value)
}
onAdd: function(value) {
console.log("add", value)
backend.conditionListModel.appendToken(value)
}
onInsert: function(index, value, type) {
console.log("insert", index, value, type)
if (type === ConditionListModel.Intermediate)
backend.conditionListModel.insertIntermediateToken(index, value)
else if (type === ConditionListModel.Shadow)
backend.conditionListModel.insertShadowToken(index, value)
else
backend.conditionListModel.insertToken(index, value)
}
onSetValue: function(index, value) {
console.log("setValue", index, value)
backend.conditionListModel.setShadowToken(index, value)
}
}
Flow { Flow {
spacing: root.horizontalSpacing spacing: root.horizontalSpacing
width: root.width width: root.width
Repeater {
Repeater {
model: backend.conditionListModel model: backend.conditionListModel
Text { Text {
text: value text: value
color: "white" color: "white"
Rectangle { Rectangle {
z: -1 z: -1
opacity: 0.2 opacity: 0.2
anchors.fill: parent
color: { color: {
if (type === ConditionListModel.Intermediate)
return "darkorange"
if (type === ConditionListModel.Invalid) if (type === ConditionListModel.Invalid)
return "red" return "red"
if (type === ConditionListModel.Operator) if (type === ConditionListModel.Operator)
@@ -128,8 +173,9 @@ Column {
return "green" return "green"
if (type === ConditionListModel.Variable) if (type === ConditionListModel.Variable)
return "yellow" return "yellow"
if (type === ConditionListModel.Shadow)
return "hotpink"
} }
anchors.fill: parent
} }
} }
} }
@@ -153,7 +199,8 @@ Column {
iconSize: StudioTheme.Values.baseFontSize iconSize: StudioTheme.Values.baseFontSize
iconFont: StudioTheme.Constants.font iconFont: StudioTheme.Constants.font
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
visible: action.currentValue !== ConnectionModelStatementDelegate.Custom && backend.hasCondition && !backend.hasElse visible: action.currentValue !== ConnectionModelStatementDelegate.Custom
&& backend.hasCondition && !backend.hasElse
onClicked: backend.addElse() onClicked: backend.addElse()
} }
@@ -165,7 +212,8 @@ Column {
iconSize: StudioTheme.Values.baseFontSize iconSize: StudioTheme.Values.baseFontSize
iconFont: StudioTheme.Constants.font iconFont: StudioTheme.Constants.font
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
visible: action.currentValue !== ConnectionModelStatementDelegate.Custom && backend.hasCondition && backend.hasElse visible: action.currentValue !== ConnectionModelStatementDelegate.Custom
&& backend.hasCondition && backend.hasElse
onClicked: backend.removeElse() onClicked: backend.removeElse()
} }
@@ -177,7 +225,8 @@ Column {
columnWidth: root.columnWidth columnWidth: root.columnWidth
statement: backend.koStatement statement: backend.koStatement
spacing: root.verticalSpacing spacing: root.verticalSpacing
visible: action.currentValue !== ConnectionModelStatementDelegate.Custom && backend.hasCondition && backend.hasElse visible: action.currentValue !== ConnectionModelStatementDelegate.Custom
&& backend.hasCondition && backend.hasElse
} }
// Editor // Editor

View File

@@ -0,0 +1,387 @@
// 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 StudioControls as StudioControls
import StudioTheme as StudioTheme
Rectangle {
id: root
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
property var conditionListModel: ConnectionsEditorEditorBackend.connectionModel.delegate.conditionListModel
property alias model: repeater.model
property int shadowPillIndex: -1
property bool shadowPillVisible: root.shadowPillIndex !== -1
property int heightBeforeShadowPill: Math.min(20, flow.childrenRect.height) // TODO Proper size value
property int expressionHeight: {
if (popup.visible)
return root.heightBeforeShadowPill + flow.spacing + 20
return root.heightBeforeShadowPill
}
signal remove(int index)
signal update(int index, var value)
signal add(var value)
signal insert(int index, var value, int type)
signal setValue(int index, var value)
signal setValueType(int index, var value, int type)
width: 400
height: root.expressionHeight + 2 * StudioTheme.Values.flowMargin
color: root.style.background.idle
border {
color: root.conditionListModel.valid ? root.style.border.idle
: StudioTheme.Values.themeError
width: root.style.borderWidth
}
onVisibleChanged: {
if (!root.visible)
popup.close()
}
// Is text input for creating new items currently used.
function textInputActive() { // TODO Make property
return newTextInput.activeFocus && newTextInput.visible
}
function getMappedItemRect(index: int) : rect {
let item = repeater.itemAt(index)
let itemRect = Qt.rect(item.x, item.y, item.width, item.height)
return flow.mapToItem(root, itemRect)
}
function placeCursor(index: int) : void {
var textInputPosition = Qt.point(0, 0)
if (!repeater.count) { // Empty repeater
let mappedItemRect = flow.mapToItem(root, 0, 0, 0, 0)
textInputPosition = Qt.point(mappedItemRect.x, mappedItemRect.y)
index = 0
} else { // Repeater is not empty
// Clamp index to 0 and num items in repeater
index = Math.min(Math.max(index, 0), repeater.count)
if (index === 0) {
// Needs to be placed in front of first repeater item
let mappedItemRect = root.getMappedItemRect(index)
textInputPosition = Qt.point(mappedItemRect.x - 4, // - 4 due to spacing of flow
mappedItemRect.y)
} else {
let mappedItemRect = root.getMappedItemRect(index - 1)
textInputPosition = Qt.point(mappedItemRect.x + mappedItemRect.width + 3,
mappedItemRect.y)
}
}
// Position text input, make it visible and set focus
newTextInput.x = textInputPosition.x
newTextInput.y = textInputPosition.y
newTextInput.index = index
newTextInput.visible = true
newTextInput.forceActiveFocus()
// Open suggestion popup
popup.open()
}
StudioControls.ToolTip {
id: toolTip
visible: mouseArea.containsMouse && toolTip.text !== ""
delay: 1000
text: root.conditionListModel.error
}
MouseArea {
id: mouseArea
anchors.fill: parent
cursorShape: Qt.IBeamCursor
hoverEnabled: true
onPressed: function (event) {
// Check if empty
if (!repeater.count) {
root.placeCursor(0)
return
}
// Map to flow item
let point = mouseArea.mapToItem(flow, Qt.point(event.x, event.y))
let horizontalDistance = Number.MAX_VALUE
let verticalDistance = Number.MAX_VALUE
let cursorPosition = 0
for (var i = 0; i < repeater.count; ++i) {
let item = repeater.itemAt(i)
let y = item.y + (item.height / 2)
// Vertical distance
let vDistance = Math.abs(point.y - y)
// Horizontal distance
let hLeftDistance = Math.abs(point.x - item.x)
let hRightDistance = Math.abs(point.x - (item.x + item.width))
// Early return if vertical distance increases
if (vDistance > verticalDistance)
break
if (vDistance <= verticalDistance) {
// Rest horizontal distance if vertical distance is smaller than before
if (vDistance !== verticalDistance)
horizontalDistance = Number.MAX_VALUE
if (hLeftDistance < horizontalDistance) {
horizontalDistance = hLeftDistance
cursorPosition = i
}
if (hRightDistance < horizontalDistance) {
horizontalDistance = hRightDistance
cursorPosition = i + 1
}
verticalDistance = vDistance
}
}
root.placeCursor(cursorPosition)
}
}
Flow {
id: flow
property int focusIndex: -1
anchors.fill: parent
anchors.margins: StudioTheme.Values.flowMargin
spacing: StudioTheme.Values.flowSpacing
onPositioningComplete: {
if (root.textInputActive())
root.placeCursor(newTextInput.index)
if (!root.shadowPillVisible)
root.heightBeforeShadowPill = flow.childrenRect.height
}
Repeater {
id: repeater
onItemRemoved: function(index, item) {
if (!root.textInputActive())
return
// Udpate the cursor position
if (index < newTextInput.index)
newTextInput.index = newTextInput.index - 1
}
onItemAdded: function(index, item) {
if (!root.textInputActive())
return
if (index >= newTextInput.index)
newTextInput.index = newTextInput.index + 1
if (!root.conditionListModel.valid && index === root.conditionListModel.errorIndex)
item.invalid = true
}
Pill {
id: pill
onRemove: function() {
// If pill has focus due to selection or keyboard navigation
if (pill.focus)
root.placeCursor(pill.index)
Qt.callLater(root.remove, pill.index)
}
onUpdate: function(value) {
if (value === "")
Qt.callLater(root.remove, pill.index) // Otherwise crash
else
Qt.callLater(root.update, pill.index, value)
}
onFocusChanged: function() {
if (pill.focus)
flow.focusIndex = pill.index
}
onSubmit: {
console.log("SUBMIT")
//newTextInput.index = pill.index + 1
newTextInput.visible = true
newTextInput.forceActiveFocus()
}
}
}
}
TextInput {
id: newTextInput
property int index
height: 20
topPadding: 1
font.pixelSize: root.style.baseFontSize
color: root.style.text.idle
visible: false
validator: RegularExpressionValidator { regularExpression: /^\S.+/ }
//onActiveFocusChanged: {
// if (!newTextInput.activeFocus && !root.shadowPillVisible) {
// console.log("CLOSE POPUP")
// popup.close()
// }
//}
onTextEdited: {
if (newTextInput.text === "")
return
newTextInput.visible = false
root.insert(newTextInput.index, newTextInput.text, ConditionListModel.Intermediate)
newTextInput.clear()
// Set focus on the newly created item
let newItem = repeater.itemAt(newTextInput.index)
newItem.forceActiveFocus()
}
Keys.onPressed: function (event) {
if (event.key === Qt.Key_Backspace) {
if (root.textInputActive()) {
let previousIndex = newTextInput.index - 1
if (previousIndex < 0)
return
let item = repeater.itemAt(previousIndex)
item.setCursorEnd()
item.forceActiveFocus()
popup.close()
}
}
}
}
SuggestionPopup {
id: popup
style: StudioTheme.Values.connectionPopupControlStyle
x: 0
y: root.height
width: root.width
//onOpened: console.log("POPUP opened")
//onClosed: console.log("POPUP closed")
onSelect: function(value) {
newTextInput.visible = true
newTextInput.forceActiveFocus()
if (root.shadowPillVisible) { // Active shadow pill
root.remove(root.shadowPillIndex)
root.shadowPillIndex = -1
}
root.insert(newTextInput.index, value, ConditionListModel.Variable)
// Clear search, reset stack view and tree model
popup.reset()
}
onSearchActiveChanged: {
if (popup.searchActive) {
root.heightBeforeShadowPill = flow.childrenRect.height
root.insert(newTextInput.index, "...", ConditionListModel.Shadow)
root.shadowPillIndex = newTextInput.index
} else {
if (!root.shadowPillVisible)
return
root.remove(root.shadowPillIndex)
root.shadowPillIndex = -1
}
}
onEntered: function(value) {
if (!popup.searchActive) {
if (!root.shadowPillVisible) {
root.heightBeforeShadowPill = flow.childrenRect.height
root.shadowPillIndex = newTextInput.index
root.insert(newTextInput.index, value, ConditionListModel.Shadow)
} else {
root.setValue(root.shadowPillIndex, value)
}
} else {
root.setValue(root.shadowPillIndex, value)
}
}
onExited: function(value) {
let shadowItem = repeater.itemAt(root.shadowPillIndex)
if (!popup.searchActive) {
if (root.shadowPillVisible && shadowItem?.value === value) {
root.remove(root.shadowPillIndex)
root.shadowPillIndex = -1
}
} else {
// Reset to 3 dots if still the same value as the exited item
if (shadowItem?.value === value)
root.setValue(root.shadowPillIndex, "...")
}
}
}
Keys.onPressed: function (event) {
if (event.key === Qt.Key_Left) {
if (root.textInputActive()) {
let previousIndex = newTextInput.index - 1
if (previousIndex < 0)
return
let item = repeater.itemAt(previousIndex)
item.setCursorEnd()
item.forceActiveFocus()
popup.close()
} else {
if (flow.focusIndex < 0)
return
root.placeCursor(flow.focusIndex)
}
} else if (event.key === Qt.Key_Right) {
if (root.textInputActive()) {
let nextIndex = newTextInput.index
if (nextIndex >= repeater.count)
return
let item = repeater.itemAt(nextIndex)
item.setCursorBegin()
item.forceActiveFocus()
popup.close()
} else {
root.placeCursor(flow.focusIndex + 1)
}
}
}
}

View File

@@ -0,0 +1,29 @@
// 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
ItemDelegate {
id: control
hoverEnabled: true
contentItem: Text {
leftPadding: 8
rightPadding: 8
text: control.text
font: control.font
opacity: enabled ? 1.0 : 0.3
color: control.hovered ? "#111111" : "white"
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
background: Rectangle {
implicitWidth: 200
implicitHeight: 30
opacity: enabled ? 1 : 0.3
color: control.hovered ? "#4DBFFF" : "transparent"
}
}

View File

@@ -0,0 +1,66 @@
// 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.Templates as T
import StudioTheme as StudioTheme
T.TreeViewDelegate {
id: control
hoverEnabled: true
implicitWidth: 200
implicitHeight: 30
//implicitWidth: leftMargin + __contentIndent + implicitContentWidth + rightPadding + rightMargin
//implicitHeight: Math.max(indicator ? indicator.height : 0, implicitContentHeight) * 1.25
indentation: 12
//leftMargin: 4
//rightMargin: 4
//spacing: 4
//topPadding: contentItem ? (height - contentItem.implicitHeight) / 2 : 0
leftPadding: control.leftMargin + control.__contentIndent
//required property int row
//required property var model
readonly property real __contentIndent: !control.isTreeNode ? 0
: (control.depth * control.indentation)
+ (control.indicator ? control.indicator.width + control.spacing : 0)
indicator: Item {
readonly property real __indicatorIndent: control.leftMargin + (control.depth * control.indentation)
x: __indicatorIndent
width: 30
height: 30
Text {
id: caret
font.family: StudioTheme.Constants.iconFont.family
font.pixelSize: StudioTheme.Values.smallIconFontSize
color: control.hovered ? "#111111" : "white" // TODO colors
text: StudioTheme.Constants.sectionToggle
rotation: control.expanded ? 0 : -90
anchors.centerIn: parent
}
}
background: Rectangle {
implicitWidth: 200
implicitHeight: 30
color: control.hovered ? "#4DBFFF" : "transparent"
}
contentItem: Text {
text: control.text
font: control.font
opacity: enabled ? 1.0 : 0.3
color: control.hovered ? "#111111" : "white"
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
}

View File

@@ -0,0 +1,167 @@
// 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 StudioControls as StudioControls
import StudioTheme as StudioTheme
FocusScope {
id: root
required property int index
required property string value
required property int type
function setCursorBegin() { textInput.cursorPosition = 0 }
function setCursorEnd() { textInput.cursorPosition = textInput.text.length }
function isEditable() { return root.type === ConditionListModel.Intermediate
|| root.type === ConditionListModel.Literal
|| root.type === ConditionListModel.Invalid }
function isIntermediate() { return root.type === ConditionListModel.Intermediate }
function isLiteral() { return root.type === ConditionListModel.Literal }
function isOperator() { return root.type === ConditionListModel.Operator }
function isProperty() { return root.type === ConditionListModel.Variable }
function isShadow() { return root.type === ConditionListModel.Shadow }
function isInvalid() { return root.type === ConditionListModel.Invalid || root.invalid }
signal remove()
signal update(var value)
signal submit()
readonly property int margin: StudioTheme.Values.flowPillMargin
property bool invalid: false
width: {
if (root.isEditable()) {
if (root.isInvalid())
return textInput.width + 1 + 2 * root.margin
else
return textInput.width + 1
}
return textItem.contentWidth + icon.width + root.margin
}
height: StudioTheme.Values.flowPillHeight
onActiveFocusChanged: {
if (root.activeFocus && root.isEditable())
textInput.forceActiveFocus()
}
Keys.onPressed: function (event) {
if (root.isEditable())
return
if (event.key === Qt.Key_Backspace || event.key === Qt.Key_Delete)
root.remove()
}
MouseArea {
id: rootMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: root.isEditable() ? Qt.IBeamCursor : Qt.ArrowCursor
onClicked: root.forceActiveFocus()
}
Rectangle {
id: pill
anchors.fill: parent
color: {
if (root.isShadow())
return StudioTheme.Values.themeInteraction
if (root.isEditable())
return "transparent"
return StudioTheme.Values.themePillBackground
}
border.color: root.isInvalid() ? StudioTheme.Values.themeWarning : "white" // TODO colors
border.width: {
if (root.isShadow())
return 0
if (root.isInvalid())
return 1
if (root.isEditable())
return 0
if (rootMouseArea.containsMouse || root.focus)
return 1
return 0
}
radius: 4
Row {
id: row
anchors.left: parent.left
anchors.leftMargin: root.margin
anchors.verticalCenter: parent.verticalCenter
visible: root.isOperator() || root.isProperty() || root.isShadow()
Text {
id: textItem
font.pixelSize: StudioTheme.Values.baseFontSize
color: root.isShadow() ? StudioTheme.Values.themeTextSelectedTextColor
: StudioTheme.Values.themeTextColor
text: root.value
anchors.verticalCenter: parent.verticalCenter
}
Item {
id: icon
width: root.isShadow() ? root.margin : StudioTheme.Values.flowPillHeight
height: StudioTheme.Values.flowPillHeight
visible: !root.isShadow()
Text {
font.family: StudioTheme.Constants.iconFont.family
font.pixelSize: StudioTheme.Values.smallIconFontSize
color: StudioTheme.Values.themeIconColor
text: StudioTheme.Constants.close_small
anchors.centerIn: parent
}
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: root.remove()
}
}
}
TextInput {
id: textInput
property bool dirty: false
x: root.isInvalid() ? root.margin : 0
height: StudioTheme.Values.flowPillHeight
topPadding: 1
font.pixelSize: StudioTheme.Values.baseFontSize
color: (rootMouseArea.containsMouse || textInput.activeFocus) ? StudioTheme.Values.themeIconColor
: StudioTheme.Values.themeTextColor
text: root.value
visible: root.isEditable()
enabled: root.isEditable()
validator: RegularExpressionValidator { regularExpression: /^\S+/ }
onEditingFinished: {
root.update(textInput.text) // emit
root.submit() // emit
}
onTextEdited: textInput.dirty = true
Keys.onPressed: function (event) {
if (event.key === Qt.Key_Backspace) {
if (textInput.text !== "")
return
root.remove() // emit
}
}
}
}
}

View File

@@ -0,0 +1,257 @@
// 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 as Controls
import StudioTheme as StudioTheme
import StudioControls as StudioControls
import ConnectionsEditorEditorBackend
Controls.Popup {
id: root
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
property var listModel: ConnectionsEditorEditorBackend.connectionModel.delegate.propertyListProxyModel
property var treeModel: ConnectionsEditorEditorBackend.connectionModel.delegate.propertyTreeModel
signal select(var value)
signal entered(var value)
signal exited(var value)
property alias searchActive: search.activeFocus
function reset() {
search.clear()
stack.pop(null, Controls.StackView.Immediate)
root.listModel.reset()
}
closePolicy: Controls.Popup.NoAutoClose
padding: 0
background: Rectangle {
implicitWidth: root.width
color: root.style.background.idle
border {
color: root.style.border.idle
width: root.style.borderWidth
}
}
contentItem: Column {
StudioControls.SearchBox {
id: search
width: parent.width
onSearchChanged: function(value) {
root.treeModel.setFilter(value)
}
}
Controls.StackView {
id: stack
width: parent.width
height: currentItem?.implicitHeight
clip: true
initialItem: mainView
}
Component {
id: mainView
Column {
Rectangle {
width: stack.width
height: 30
visible: root.listModel.parentName !== ""
color: backMouseArea.containsMouse ? "#4DBFFF" : "transparent"
MouseArea {
id: backMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
stack.pop(Controls.StackView.Immediate)
root.listModel.goUp() //treeModel.pop()
}
}
Row {
anchors.fill: parent
Item {
width: 30
height: 30
Text {
id: chevronLeft
font.family: StudioTheme.Constants.iconFont.family
font.pixelSize: root.style.baseIconFontSize
color: backMouseArea.containsMouse ? "#111111" : "white" // TODO colors
text: StudioTheme.Constants.back_medium
anchors.centerIn: parent
}
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: root.listModel.parentName
color: backMouseArea.containsMouse ? "#111111" : "white" // TODO colors
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
}
}
Rectangle {
width: stack.width - 8
height: 1
visible: root.listModel.parentName !== ""
color: "#3C3C3C"
anchors.horizontalCenter: parent.horizontalCenter
}
ListView {
id: listView
visible: search.empty
width: stack.width
implicitHeight: Math.min(380, childrenRect.height)
clip: true
model: root.listModel
delegate: MyListViewDelegate {
id: listViewDelegate
required property int index
required property string propertyName
required property int childCount
required property string expression
text: listViewDelegate.propertyName
implicitWidth: listView.width
onClicked: {
if (!listViewDelegate.childCount) {
root.select(listViewDelegate.expression)
return
}
stack.push(mainView, Controls.StackView.Immediate)
ListView.view.model.goInto(listViewDelegate.index)
}
onHoveredChanged: {
if (listViewDelegate.childCount)
return
if (listViewDelegate.hovered)
root.entered(listViewDelegate.expression)
else
root.exited(listViewDelegate.expression)
}
}
}
TreeView {
id: treeView
visible: !search.empty
width: stack.width
implicitHeight: Math.min(380, childrenRect.height)
clip: true
model: root.treeModel
// This is currently a workaround and should be cleaned up. Calling
// expandRecursively every time the filter changes is performance wise not good.
//Connections {
// target: proxyModel
// function onFilterChanged() { treeView.expandRecursively() }
//}
delegate: MyTreeViewDelegate {
id: treeViewDelegate
required property int index
required property string propertyName
required property int childCount
required property string expression
text: treeViewDelegate.propertyName
implicitWidth: treeView.width
onClicked: {
if (!treeViewDelegate.childCount)
root.select(treeViewDelegate.expression)
else
treeView.toggleExpanded(treeViewDelegate.index)
}
onHoveredChanged: {
if (treeViewDelegate.childCount)
return
if (treeViewDelegate.hovered)
root.entered(treeViewDelegate.expression)
else
root.exited(treeViewDelegate.expression)
}
}
}
}
}
Item {
visible: false
width: stack.width
height: flow.childrenRect.height + 2 * StudioTheme.Values.flowMargin
Flow {
id: flow
anchors.fill: parent
anchors.margins: StudioTheme.Values.flowMargin
spacing: StudioTheme.Values.flowSpacing
Repeater {
id: repeater
// TODO actual value + tooltip
model: ["AND", "OR", "equal", "not equal", "greater", "less", "greater then", "less then"]
Rectangle {
width: textItem.contentWidth + 14
height: 26
color: "#161616"
radius: 4
border {
color: "white"
width: mouseArea.containsMouse ? 1 : 0
}
MouseArea {
id: mouseArea
hoverEnabled: true
anchors.fill: parent
}
Text {
id: textItem
font.pixelSize: 12
color: "white"
text: modelData
anchors.centerIn: parent
}
}
}
}
}
}
}

View File

@@ -131,6 +131,7 @@ T.TabButton {
State { State {
name: "check" name: "check"
when: control.enabled && !control.pressed && control.checked when: control.enabled && !control.pressed && control.checked
extend: "hoverCheck"
PropertyChanges { PropertyChanges {
target: controlBackground target: controlBackground
color: control.style.interaction color: control.style.interaction

View File

@@ -1,9 +1,9 @@
// Copyright (C) 2021 The Qt Company Ltd. // Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick 2.15 import QtQuick
import QtQuick.Layouts 1.15 import QtQuick.Layouts
import HelperWidgets 2.0 import HelperWidgets
MouseArea { MouseArea {
id: mouseArea id: mouseArea

View File

@@ -13,6 +13,8 @@ T.TextField {
signal searchChanged(string searchText) signal searchChanged(string searchText)
property bool empty: control.text === ""
function isEmpty() { function isEmpty() {
return control.text === "" return control.text === ""
} }
@@ -81,7 +83,7 @@ T.TextField {
*/ */
} }
onTextChanged: control.searchChanged(text) onTextChanged: control.searchChanged(control.text)
T.Label { T.Label {
id: searchIcon id: searchIcon

View File

@@ -238,6 +238,12 @@ QtObject {
property real dialogButtonSpacing: 10 property real dialogButtonSpacing: 10
property real dialogButtonPadding: 4 property real dialogButtonPadding: 4
// NEW NEW NEW
readonly property int flowMargin: 7
readonly property int flowSpacing: 7 // Odd so cursor has a center location
readonly property int flowPillMargin: 4
readonly property int flowPillHeight: 20
// Theme Colors // Theme Colors
property bool isLightTheme: values.themeControlBackground.hsvValue > values.themeTextColor.hsvValue property bool isLightTheme: values.themeControlBackground.hsvValue > values.themeTextColor.hsvValue
@@ -434,6 +440,10 @@ QtObject {
property color themeDialogBackground: values.themeThumbnailBackground property color themeDialogBackground: values.themeThumbnailBackground
property color themeDialogOutline: values.themeInteraction property color themeDialogOutline: values.themeInteraction
// Expression Builder
property color themePillBackground: Theme.color(Theme.DSdockWidgetSplitter)
// Control Style Mapping // Control Style Mapping
property ControlStyle controlStyle: DefaultStyle {} property ControlStyle controlStyle: DefaultStyle {}
property ControlStyle connectionPopupControlStyle: ConnectionPopupControlStyle {} property ControlStyle connectionPopupControlStyle: ConnectionPopupControlStyle {}

View File

@@ -559,8 +559,13 @@ QHash<int, QByteArray> ConnectionModel::roleNames() const
} }
ConnectionModelBackendDelegate::ConnectionModelBackendDelegate(ConnectionModel *parent) ConnectionModelBackendDelegate::ConnectionModelBackendDelegate(ConnectionModel *parent)
: QObject(parent), m_signalDelegate(parent->connectionView()), m_okStatementDelegate(parent), : QObject(parent)
m_koStatementDelegate(parent), m_conditionListModel(parent) , m_signalDelegate(parent->connectionView())
, m_okStatementDelegate(parent)
, m_koStatementDelegate(parent)
, m_conditionListModel(parent)
, m_propertyTreeModel(parent->connectionView())
, m_propertyListProxyModel(&m_propertyTreeModel)
{ {
connect(&m_signalDelegate, &PropertyTreeModelDelegate::commitData, this, [this]() { connect(&m_signalDelegate, &PropertyTreeModelDelegate::commitData, this, [this]() {
handleTargetChanged(); handleTargetChanged();
@@ -753,6 +758,9 @@ void ConnectionModelBackendDelegate::setCurrentRow(int i)
m_currentRow = i; m_currentRow = i;
m_propertyTreeModel.resetModel();
m_propertyListProxyModel.setRowAndInternalId(0, -1);
//setup //setup
ConnectionModel *model = qobject_cast<ConnectionModel *>(parent()); ConnectionModel *model = qobject_cast<ConnectionModel *>(parent());
@@ -870,6 +878,16 @@ void ConnectionModelBackendDelegate::setSource(const QString &source)
emit sourceChanged(); emit sourceChanged();
} }
PropertyTreeModel *ConnectionModelBackendDelegate::propertyTreeModel()
{
return &m_propertyTreeModel;
}
PropertyListProxyModel *ConnectionModelBackendDelegate::propertyListProxyModel()
{
return &m_propertyListProxyModel;
}
void ConnectionModelBackendDelegate::setupCondition() void ConnectionModelBackendDelegate::setupCondition()
{ {
auto &condition = ConnectionEditorStatements::matchedCondition(m_handler); auto &condition = ConnectionEditorStatements::matchedCondition(m_handler);
@@ -1575,30 +1593,83 @@ ConditionListModel::ConditionToken ConditionListModel::tokenFromComparativeState
void ConditionListModel::insertToken(int index, const QString &value) void ConditionListModel::insertToken(int index, const QString &value)
{ {
beginInsertRows({}, index, index);
m_tokens.insert(index, valueToToken(value)); m_tokens.insert(index, valueToToken(value));
validateAndRebuildTokens(); validateAndRebuildTokens();
resetModel();
endInsertRows();
//resetModel();
} }
void ConditionListModel::updateToken(int index, const QString &value) void ConditionListModel::updateToken(int index, const QString &value)
{ {
m_tokens[index] = valueToToken(value); m_tokens[index] = valueToToken(value);
validateAndRebuildTokens(); validateAndRebuildTokens();
resetModel();
dataChanged(createIndex(index, 0), createIndex(index, 0));
//resetModel();
} }
void ConditionListModel::appendToken(const QString &value) void ConditionListModel::appendToken(const QString &value)
{ {
beginInsertRows({}, rowCount() - 1, rowCount() - 1);
insertToken(rowCount(), value); insertToken(rowCount(), value);
validateAndRebuildTokens(); validateAndRebuildTokens();
resetModel();
endInsertRows();
//resetModel();
} }
void ConditionListModel::removeToken(int index) void ConditionListModel::removeToken(int index)
{ {
beginRemoveRows({}, index, index);
m_tokens.remove(index, 1); m_tokens.remove(index, 1);
validateAndRebuildTokens(); validateAndRebuildTokens();
resetModel();
endRemoveRows();
//resetModel();
}
void ConditionListModel::insertIntermediateToken(int index, const QString &value)
{
beginInsertRows({}, index, index);
ConditionToken token;
token.type = Intermediate;
token.value = value;
m_tokens.insert(index, token);
endInsertRows();
//resetModel();
}
void ConditionListModel::insertShadowToken(int index, const QString &value)
{
beginInsertRows({}, index, index);
ConditionToken token;
token.type = Shadow;
token.value = value;
m_tokens.insert(index, token);
endInsertRows();
//resetModel();
}
void ConditionListModel::setShadowToken(int index, const QString &value)
{
m_tokens[index].type = Shadow;
m_tokens[index].value = value;
dataChanged(createIndex(index, 0), createIndex(index, 0));
//resetModel();
} }
bool ConditionListModel::valid() const bool ConditionListModel::valid() const
@@ -1655,20 +1726,29 @@ void ConditionListModel::command(const QString &string)
} }
} }
void ConditionListModel::setInvalid(const QString &errorMessage) void ConditionListModel::setInvalid(const QString &errorMessage, int index)
{ {
m_valid = false; m_valid = false;
m_errorMessage = errorMessage; m_errorMessage = errorMessage;
emit errorChanged(); emit errorChanged();
emit validChanged(); emit validChanged();
if (index != -1) {
m_errorIndex = index;
emit errorIndexChanged();
}
} }
void ConditionListModel::setValid() void ConditionListModel::setValid()
{ {
m_valid = true; m_valid = true;
m_errorMessage.clear(); m_errorMessage.clear();
m_errorIndex = -1;
emit errorChanged(); emit errorChanged();
emit validChanged(); emit validChanged();
emit errorIndexChanged();
} }
QString ConditionListModel::error() const QString ConditionListModel::error() const
@@ -1676,6 +1756,11 @@ QString ConditionListModel::error() const
return m_errorMessage; return m_errorMessage;
} }
int ConditionListModel::errorIndex() const
{
return m_errorIndex;
}
void ConditionListModel::internalSetup() void ConditionListModel::internalSetup()
{ {
setInvalid(tr("No Valid Condition")); setInvalid(tr("No Valid Condition"));
@@ -1766,11 +1851,26 @@ int ConditionListModel::checkOrder() const
it++; it++;
ret++; ret++;
} }
if (wasOperator)
return ret;
return -1; return -1;
} }
void ConditionListModel::validateAndRebuildTokens() void ConditionListModel::validateAndRebuildTokens()
{ {
/// NEW
auto it = m_tokens.begin();
while (it != m_tokens.end()) {
if (it->type == Intermediate)
*it = valueToToken(it->value);
it++;
}
// NEW
QString invalidValue; QString invalidValue;
const bool invalidToken = Utils::contains(m_tokens, const bool invalidToken = Utils::contains(m_tokens,
[&invalidValue](const ConditionToken &token) { [&invalidValue](const ConditionToken &token) {
@@ -1780,12 +1880,12 @@ void ConditionListModel::validateAndRebuildTokens()
}); });
if (invalidToken) { if (invalidToken) {
setInvalid(tr("Invalid token %").arg(invalidToken)); setInvalid(tr("Invalid token %1").arg(invalidValue));
return; return;
} }
if (int firstError = checkOrder() != -1) { if (int firstError = checkOrder() != -1) {
setInvalid(tr("Invalid order at %1").arg(firstError)); setInvalid(tr("Invalid order at %1").arg(firstError), firstError);
return; return;
} }
@@ -1831,9 +1931,6 @@ ConnectionEditorStatements::ConditionToken ConditionListModel::toOperatorStateme
if (token.value == "!==") if (token.value == "!==")
return ConnectionEditorStatements::ConditionToken::Not; return ConnectionEditorStatements::ConditionToken::Not;
if (token.value == "!==")
return ConnectionEditorStatements::ConditionToken::Not;
if (token.value == ">") if (token.value == ">")
return ConnectionEditorStatements::ConditionToken::LargerThan; return ConnectionEditorStatements::ConditionToken::LargerThan;

View File

@@ -96,9 +96,10 @@ class ConditionListModel : public QAbstractListModel
Q_PROPERTY(bool valid READ valid NOTIFY validChanged) Q_PROPERTY(bool valid READ valid NOTIFY validChanged)
Q_PROPERTY(bool empty READ empty NOTIFY emptyChanged) Q_PROPERTY(bool empty READ empty NOTIFY emptyChanged)
Q_PROPERTY(QString error READ error NOTIFY errorChanged) Q_PROPERTY(QString error READ error NOTIFY errorChanged)
Q_PROPERTY(int errorIndex READ errorIndex NOTIFY errorIndexChanged)
public: public:
enum ConditionType { Invalid, Operator, Literal, Variable }; enum ConditionType { Intermediate, Invalid, Operator, Literal, Variable, Shadow };
Q_ENUM(ConditionType) Q_ENUM(ConditionType)
struct ConditionToken struct ConditionToken
@@ -129,22 +130,28 @@ public:
Q_INVOKABLE void appendToken(const QString &value); Q_INVOKABLE void appendToken(const QString &value);
Q_INVOKABLE void removeToken(int index); Q_INVOKABLE void removeToken(int index);
Q_INVOKABLE void insertIntermediateToken(int index, const QString &value);
Q_INVOKABLE void insertShadowToken(int index, const QString &value);
Q_INVOKABLE void setShadowToken(int index, const QString &value);
bool valid() const; bool valid() const;
bool empty() const; bool empty() const;
//for debugging //for debugging
Q_INVOKABLE void command(const QString &string); Q_INVOKABLE void command(const QString &string);
void setInvalid(const QString &errorMessage); void setInvalid(const QString &errorMessage, int index = -1);
void setValid(); void setValid();
QString error() const; QString error() const;
int errorIndex() const;
signals: signals:
void validChanged(); void validChanged();
void emptyChanged(); void emptyChanged();
void conditionChanged(); void conditionChanged();
void errorChanged(); void errorChanged();
void errorIndexChanged();
private: private:
void internalSetup(); void internalSetup();
@@ -162,6 +169,7 @@ private:
QList<ConditionToken> m_tokens; QList<ConditionToken> m_tokens;
bool m_valid = false; bool m_valid = false;
QString m_errorMessage; QString m_errorMessage;
int m_errorIndex = -1;
}; };
class ConnectionModelStatementDelegate : public QObject class ConnectionModelStatementDelegate : public QObject
@@ -245,6 +253,9 @@ class ConnectionModelBackendDelegate : public QObject
Q_PROPERTY(bool hasElse READ hasElse NOTIFY hasElseChanged) Q_PROPERTY(bool hasElse READ hasElse NOTIFY hasElseChanged)
Q_PROPERTY(QString source READ source NOTIFY sourceChanged) Q_PROPERTY(QString source READ source NOTIFY sourceChanged)
Q_PROPERTY(PropertyTreeModel *propertyTreeModel READ propertyTreeModel CONSTANT)
Q_PROPERTY(PropertyListProxyModel *propertyListProxyModel READ propertyListProxyModel CONSTANT)
public: public:
explicit ConnectionModelBackendDelegate(ConnectionModel *parent = nullptr); explicit ConnectionModelBackendDelegate(ConnectionModel *parent = nullptr);
@@ -282,6 +293,10 @@ private:
ConditionListModel *conditionListModel(); ConditionListModel *conditionListModel();
QString source() const; QString source() const;
void setSource(const QString &source); void setSource(const QString &source);
PropertyTreeModel *propertyTreeModel();
PropertyListProxyModel *propertyListProxyModel();
void setupCondition(); void setupCondition();
void setupHandlerAndStatements(); void setupHandlerAndStatements();
@@ -303,6 +318,8 @@ private:
bool m_hasCondition = false; bool m_hasCondition = false;
bool m_hasElse = false; bool m_hasElse = false;
QString m_source; QString m_source;
PropertyTreeModel m_propertyTreeModel;
PropertyListProxyModel m_propertyListProxyModel;
}; };
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -88,10 +88,13 @@ public:
0, 0,
"ConnectionModelStatementDelegate"); "ConnectionModelStatementDelegate");
qmlRegisterType<ConditionListModel>("ConnectionsEditorEditorBackend", qmlRegisterType<ConditionListModel>("ConnectionsEditorEditorBackend", 1, 0, "ConditionListModel");
qmlRegisterType<PropertyTreeModel>("ConnectionsEditorEditorBackend", 1, 0, "PropertyTreeModel");
qmlRegisterType<PropertyListProxyModel>("ConnectionsEditorEditorBackend",
1, 1,
0, 0,
"ConditionListModel"); "PropertyListProxyModel");
Theme::setupTheme(engine()); Theme::setupTheme(engine());

View File

@@ -155,7 +155,8 @@ QVariant PropertyTreeModel::data(const QModelIndex &index, int role) const
if (role == RowRole) if (role == RowRole)
return index.row(); return index.row();
if (role == PropertyNameRole || role == PropertyPriorityRole || role == ExpressionRole) { if (role == PropertyNameRole || role == PropertyPriorityRole || role == ExpressionRole
|| role == ChildCountRole) {
if (!index.isValid()) if (!index.isValid())
return {}; return {};
@@ -166,18 +167,18 @@ QVariant PropertyTreeModel::data(const QModelIndex &index, int role) const
DataCacheItem item = m_indexHash[index.internalId()]; DataCacheItem item = m_indexHash[index.internalId()];
if (item.propertyName.isEmpty()) { //node if (role == ChildCountRole)
if (role == PropertyNameRole) return rowCount(index);
return item.modelNode.displayName();
return true; //nodes are always shown
}
if (role == ExpressionRole) if (role == ExpressionRole)
return QString(item.modelNode.id() + item.propertyName); return QString(item.modelNode.id() + "." + item.propertyName);
if (role == PropertyNameRole) if (role == PropertyNameRole) {
return item.propertyName; if (!item.propertyName.isEmpty())
return QString::fromUtf8(item.propertyName);
else
return item.modelNode.displayName();
}
static const auto priority = properityLists(); static const auto priority = properityLists();
if (std::find(priority.begin(), priority.end(), item.propertyName) != priority.end()) if (std::find(priority.begin(), priority.end(), item.propertyName) != priority.end())
@@ -187,6 +188,13 @@ QVariant PropertyTreeModel::data(const QModelIndex &index, int role) const
if (std::find(dynamic.begin(), dynamic.end(), item.propertyName) != dynamic.end()) if (std::find(dynamic.begin(), dynamic.end(), item.propertyName) != dynamic.end())
return true; // dynamic properties have priority return true; // dynamic properties have priority
if (item.propertyName.isEmpty()) { //node
//if (role == PropertyNameRole)
// return item.modelNode.displayName();
return true; // nodes are always shown
}
return false; return false;
} }
@@ -216,7 +224,7 @@ QVariant PropertyTreeModel::data(const QModelIndex &index, int role) const
} }
if (role == Qt::DisplayRole) if (role == Qt::DisplayRole)
return item.propertyName; return QString::fromUtf8(item.propertyName);
QFont f; QFont f;
auto priority = properityLists(); auto priority = properityLists();
@@ -241,7 +249,7 @@ QModelIndex PropertyTreeModel::index(int row, int column, const QModelIndex &par
return {}; return {};
if (!hasIndex(row, column, parent)) if (!hasIndex(row, column, parent))
return QModelIndex(); return {};
const int rootId = -1; const int rootId = -1;
@@ -312,7 +320,7 @@ QModelIndex PropertyTreeModel::parent(const QModelIndex &index) const
return ensureModelIndex(item.modelNode, row); return ensureModelIndex(item.modelNode, row);
} }
QPersistentModelIndex PropertyTreeModel::indexForInernalIdAndRow(int internalId, int row) QPersistentModelIndex PropertyTreeModel::indexForInternalIdAndRow(int internalId, int row)
{ {
return createIndex(row, 0, internalId); return createIndex(row, 0, internalId);
} }
@@ -323,7 +331,7 @@ int PropertyTreeModel::rowCount(const QModelIndex &parent) const
return 0; return 0;
if (!parent.isValid()) if (!parent.isValid())
return 1; return 1; //m_nodeList.size();
int internalId = parent.internalId(); int internalId = parent.internalId();
@@ -783,7 +791,8 @@ QHash<int, QByteArray> PropertyTreeModel::roleNames() const
{ {
static QHash<int, QByteArray> roleNames{{PropertyNameRole, "propertyName"}, static QHash<int, QByteArray> roleNames{{PropertyNameRole, "propertyName"},
{PropertyPriorityRole, "hasPriority"}, {PropertyPriorityRole, "hasPriority"},
{ExpressionRole, "expression"}}; {ExpressionRole, "expression"},
{ChildCountRole, "childCount"}};
return roleNames; return roleNames;
} }
@@ -792,17 +801,24 @@ PropertyListProxyModel::PropertyListProxyModel(PropertyTreeModel *parent)
: QAbstractListModel(), m_treeModel(parent) : QAbstractListModel(), m_treeModel(parent)
{} {}
void PropertyListProxyModel::setRowandInternalId(int row, int internalId) void PropertyListProxyModel::resetModel()
{ {
beginResetModel();
endResetModel();
}
void PropertyListProxyModel::setRowAndInternalId(int row, int internalId)
{
qDebug() << Q_FUNC_INFO << row << internalId;
QTC_ASSERT(m_treeModel, return ); QTC_ASSERT(m_treeModel, return );
if (internalId == -1) if (internalId == -1)
m_parentIndex = m_treeModel->index(0, 0); m_parentIndex = m_treeModel->index(0, 0);
else else
m_parentIndex = m_treeModel->indexForInernalIdAndRow(internalId, row); m_parentIndex = m_treeModel->index(row, 0, m_parentIndex);
//m_parentIndex = m_treeModel->indexForInternalIdAndRow(internalId, row);
beginResetModel(); resetModel();
endResetModel();
} }
int PropertyListProxyModel::rowCount(const QModelIndex &) const int PropertyListProxyModel::rowCount(const QModelIndex &) const
@@ -820,6 +836,40 @@ QVariant PropertyListProxyModel::data(const QModelIndex &index, int role) const
return m_treeModel->data(treeIndex, role); return m_treeModel->data(treeIndex, role);
} }
QHash<int, QByteArray> PropertyListProxyModel::roleNames() const
{
return m_treeModel->roleNames();
}
void PropertyListProxyModel::goInto(int row)
{
qDebug() << Q_FUNC_INFO << row << m_parentIndex.internalId();
setRowAndInternalId(row, 0); //m_parentIndex.internalId());
emit parentNameChanged();
}
void PropertyListProxyModel::goUp()
{
qDebug() << Q_FUNC_INFO;
m_parentIndex = m_treeModel->parent(m_parentIndex);
resetModel();
emit parentNameChanged();
}
void PropertyListProxyModel::reset()
{
setRowAndInternalId(0, -1); // TODO ???
emit parentNameChanged();
}
QString PropertyListProxyModel::parentName() const
{
return m_treeModel->data(m_parentIndex, PropertyTreeModel::UserRoles::PropertyNameRole).toString();
}
PropertyTreeModelDelegate::PropertyTreeModelDelegate(ConnectionView *parent) : m_model(parent) PropertyTreeModelDelegate::PropertyTreeModelDelegate(ConnectionView *parent) : m_model(parent)
{ {
connect(&m_nameCombboBox, &StudioQmlComboBoxBackend::activated, this, [this]() { connect(&m_nameCombboBox, &StudioQmlComboBoxBackend::activated, this, [this]() {

View File

@@ -31,6 +31,7 @@ public:
PropertyNameRole = Qt::UserRole + 1, PropertyNameRole = Qt::UserRole + 1,
PropertyPriorityRole, PropertyPriorityRole,
ExpressionRole, ExpressionRole,
ChildCountRole,
RowRole, RowRole,
InternalIdRole InternalIdRole
}; };
@@ -57,7 +58,7 @@ public:
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override; QModelIndex parent(const QModelIndex &index) const override;
QPersistentModelIndex indexForInernalIdAndRow(int internalId, int row); QPersistentModelIndex indexForInternalIdAndRow(int internalId, int row);
int rowCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override;
@@ -70,14 +71,13 @@ public:
}; };
void setPropertyType(PropertyTypes type); void setPropertyType(PropertyTypes type);
void setFilter(const QString &filter); Q_INVOKABLE void setFilter(const QString &filter);
QList<ModelNode> nodeList() const; QList<ModelNode> nodeList() const;
const std::vector<PropertyName> getProperties(const ModelNode &modelNode) const; const std::vector<PropertyName> getProperties(const ModelNode &modelNode) const;
ModelNode getModelNodeForId(const QString &id) const; ModelNode getModelNodeForId(const QString &id) const;
protected:
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
private: private:
@@ -127,12 +127,29 @@ private:
class PropertyListProxyModel : public QAbstractListModel class PropertyListProxyModel : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString parentName READ parentName NOTIFY parentNameChanged)
public: public:
PropertyListProxyModel(PropertyTreeModel *parent); PropertyListProxyModel(PropertyTreeModel *parent);
void setRowandInternalId(int row, int internalId);
void resetModel();
void setRowAndInternalId(int row, int internalId);
int rowCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override; QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void goInto(int row);
Q_INVOKABLE void goUp();
Q_INVOKABLE void reset();
QString parentName() const;
signals:
void parentNameChanged();
private: private:
ModelNode m_modelNode; ModelNode m_modelNode;
PropertyName m_propertyName; PropertyName m_propertyName;