forked from qt-creator/qt-creator
QmlDesigner: Integrate Expression Builder
Task-number: QDS-10587 Change-Id: Ifc13a8364fccb74cb60d683f0e6c322d80baab50 Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
committed by
Henning Gründl
parent
cbf4273bab
commit
5e8b5ec1f0
@@ -107,19 +107,64 @@ Column {
|
||||
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 {
|
||||
spacing: root.horizontalSpacing
|
||||
width: root.width
|
||||
Repeater {
|
||||
|
||||
Repeater {
|
||||
model: backend.conditionListModel
|
||||
|
||||
Text {
|
||||
text: value
|
||||
color: "white"
|
||||
|
||||
Rectangle {
|
||||
z: -1
|
||||
opacity: 0.2
|
||||
anchors.fill: parent
|
||||
color: {
|
||||
if (type === ConditionListModel.Intermediate)
|
||||
return "darkorange"
|
||||
if (type === ConditionListModel.Invalid)
|
||||
return "red"
|
||||
if (type === ConditionListModel.Operator)
|
||||
@@ -128,8 +173,9 @@ Column {
|
||||
return "green"
|
||||
if (type === ConditionListModel.Variable)
|
||||
return "yellow"
|
||||
if (type === ConditionListModel.Shadow)
|
||||
return "hotpink"
|
||||
}
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,7 +184,7 @@ Column {
|
||||
TextInput {
|
||||
id: commandInput
|
||||
width: root.width
|
||||
onAccepted: backend.conditionListModel.command(commandInput.text)
|
||||
onAccepted: backend.conditionListModel.command(commandInput.text)
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -153,7 +199,8 @@ Column {
|
||||
iconSize: StudioTheme.Values.baseFontSize
|
||||
iconFont: StudioTheme.Constants.font
|
||||
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()
|
||||
}
|
||||
@@ -165,7 +212,8 @@ Column {
|
||||
iconSize: StudioTheme.Values.baseFontSize
|
||||
iconFont: StudioTheme.Constants.font
|
||||
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()
|
||||
}
|
||||
@@ -177,7 +225,8 @@ Column {
|
||||
columnWidth: root.columnWidth
|
||||
statement: backend.koStatement
|
||||
spacing: root.verticalSpacing
|
||||
visible: action.currentValue !== ConnectionModelStatementDelegate.Custom && backend.hasCondition && backend.hasElse
|
||||
visible: action.currentValue !== ConnectionModelStatementDelegate.Custom
|
||||
&& backend.hasCondition && backend.hasElse
|
||||
}
|
||||
|
||||
// Editor
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
167
share/qtcreator/qmldesigner/connectionseditor/Pill.qml
Normal file
167
share/qtcreator/qmldesigner/connectionseditor/Pill.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,6 +131,7 @@ T.TabButton {
|
||||
State {
|
||||
name: "check"
|
||||
when: control.enabled && !control.pressed && control.checked
|
||||
extend: "hoverCheck"
|
||||
PropertyChanges {
|
||||
target: controlBackground
|
||||
color: control.style.interaction
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright (C) 2021 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.Layouts 1.15
|
||||
import HelperWidgets 2.0
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import HelperWidgets
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
@@ -13,6 +13,8 @@ T.TextField {
|
||||
|
||||
signal searchChanged(string searchText)
|
||||
|
||||
property bool empty: control.text === ""
|
||||
|
||||
function isEmpty() {
|
||||
return control.text === ""
|
||||
}
|
||||
@@ -81,7 +83,7 @@ T.TextField {
|
||||
*/
|
||||
}
|
||||
|
||||
onTextChanged: control.searchChanged(text)
|
||||
onTextChanged: control.searchChanged(control.text)
|
||||
|
||||
T.Label {
|
||||
id: searchIcon
|
||||
|
||||
@@ -238,6 +238,12 @@ QtObject {
|
||||
property real dialogButtonSpacing: 10
|
||||
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
|
||||
|
||||
property bool isLightTheme: values.themeControlBackground.hsvValue > values.themeTextColor.hsvValue
|
||||
@@ -434,6 +440,10 @@ QtObject {
|
||||
property color themeDialogBackground: values.themeThumbnailBackground
|
||||
property color themeDialogOutline: values.themeInteraction
|
||||
|
||||
// Expression Builder
|
||||
property color themePillBackground: Theme.color(Theme.DSdockWidgetSplitter)
|
||||
|
||||
|
||||
// Control Style Mapping
|
||||
property ControlStyle controlStyle: DefaultStyle {}
|
||||
property ControlStyle connectionPopupControlStyle: ConnectionPopupControlStyle {}
|
||||
|
||||
@@ -559,8 +559,13 @@ QHash<int, QByteArray> ConnectionModel::roleNames() const
|
||||
}
|
||||
|
||||
ConnectionModelBackendDelegate::ConnectionModelBackendDelegate(ConnectionModel *parent)
|
||||
: QObject(parent), m_signalDelegate(parent->connectionView()), m_okStatementDelegate(parent),
|
||||
m_koStatementDelegate(parent), m_conditionListModel(parent)
|
||||
: QObject(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]() {
|
||||
handleTargetChanged();
|
||||
@@ -753,6 +758,9 @@ void ConnectionModelBackendDelegate::setCurrentRow(int i)
|
||||
|
||||
m_currentRow = i;
|
||||
|
||||
m_propertyTreeModel.resetModel();
|
||||
m_propertyListProxyModel.setRowAndInternalId(0, -1);
|
||||
|
||||
//setup
|
||||
|
||||
ConnectionModel *model = qobject_cast<ConnectionModel *>(parent());
|
||||
@@ -870,6 +878,16 @@ void ConnectionModelBackendDelegate::setSource(const QString &source)
|
||||
emit sourceChanged();
|
||||
}
|
||||
|
||||
PropertyTreeModel *ConnectionModelBackendDelegate::propertyTreeModel()
|
||||
{
|
||||
return &m_propertyTreeModel;
|
||||
}
|
||||
|
||||
PropertyListProxyModel *ConnectionModelBackendDelegate::propertyListProxyModel()
|
||||
{
|
||||
return &m_propertyListProxyModel;
|
||||
}
|
||||
|
||||
void ConnectionModelBackendDelegate::setupCondition()
|
||||
{
|
||||
auto &condition = ConnectionEditorStatements::matchedCondition(m_handler);
|
||||
@@ -1575,30 +1593,83 @@ ConditionListModel::ConditionToken ConditionListModel::tokenFromComparativeState
|
||||
|
||||
void ConditionListModel::insertToken(int index, const QString &value)
|
||||
{
|
||||
beginInsertRows({}, index, index);
|
||||
|
||||
m_tokens.insert(index, valueToToken(value));
|
||||
validateAndRebuildTokens();
|
||||
resetModel();
|
||||
|
||||
endInsertRows();
|
||||
//resetModel();
|
||||
}
|
||||
|
||||
void ConditionListModel::updateToken(int index, const QString &value)
|
||||
{
|
||||
m_tokens[index] = valueToToken(value);
|
||||
validateAndRebuildTokens();
|
||||
resetModel();
|
||||
|
||||
dataChanged(createIndex(index, 0), createIndex(index, 0));
|
||||
//resetModel();
|
||||
}
|
||||
|
||||
void ConditionListModel::appendToken(const QString &value)
|
||||
{
|
||||
beginInsertRows({}, rowCount() - 1, rowCount() - 1);
|
||||
|
||||
insertToken(rowCount(), value);
|
||||
validateAndRebuildTokens();
|
||||
resetModel();
|
||||
|
||||
endInsertRows();
|
||||
//resetModel();
|
||||
}
|
||||
|
||||
void ConditionListModel::removeToken(int index)
|
||||
{
|
||||
beginRemoveRows({}, index, index);
|
||||
|
||||
m_tokens.remove(index, 1);
|
||||
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
|
||||
@@ -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_errorMessage = errorMessage;
|
||||
|
||||
emit errorChanged();
|
||||
emit validChanged();
|
||||
|
||||
if (index != -1) {
|
||||
m_errorIndex = index;
|
||||
emit errorIndexChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void ConditionListModel::setValid()
|
||||
{
|
||||
m_valid = true;
|
||||
m_errorMessage.clear();
|
||||
m_errorIndex = -1;
|
||||
|
||||
emit errorChanged();
|
||||
emit validChanged();
|
||||
emit errorIndexChanged();
|
||||
}
|
||||
|
||||
QString ConditionListModel::error() const
|
||||
@@ -1676,6 +1756,11 @@ QString ConditionListModel::error() const
|
||||
return m_errorMessage;
|
||||
}
|
||||
|
||||
int ConditionListModel::errorIndex() const
|
||||
{
|
||||
return m_errorIndex;
|
||||
}
|
||||
|
||||
void ConditionListModel::internalSetup()
|
||||
{
|
||||
setInvalid(tr("No Valid Condition"));
|
||||
@@ -1766,11 +1851,26 @@ int ConditionListModel::checkOrder() const
|
||||
it++;
|
||||
ret++;
|
||||
}
|
||||
|
||||
if (wasOperator)
|
||||
return ret;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
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;
|
||||
const bool invalidToken = Utils::contains(m_tokens,
|
||||
[&invalidValue](const ConditionToken &token) {
|
||||
@@ -1780,12 +1880,12 @@ void ConditionListModel::validateAndRebuildTokens()
|
||||
});
|
||||
|
||||
if (invalidToken) {
|
||||
setInvalid(tr("Invalid token %").arg(invalidToken));
|
||||
setInvalid(tr("Invalid token %1").arg(invalidValue));
|
||||
return;
|
||||
}
|
||||
|
||||
if (int firstError = checkOrder() != -1) {
|
||||
setInvalid(tr("Invalid order at %1").arg(firstError));
|
||||
setInvalid(tr("Invalid order at %1").arg(firstError), firstError);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1831,9 +1931,6 @@ ConnectionEditorStatements::ConditionToken ConditionListModel::toOperatorStateme
|
||||
if (token.value == "!==")
|
||||
return ConnectionEditorStatements::ConditionToken::Not;
|
||||
|
||||
if (token.value == "!==")
|
||||
return ConnectionEditorStatements::ConditionToken::Not;
|
||||
|
||||
if (token.value == ">")
|
||||
return ConnectionEditorStatements::ConditionToken::LargerThan;
|
||||
|
||||
|
||||
@@ -96,9 +96,10 @@ class ConditionListModel : public QAbstractListModel
|
||||
Q_PROPERTY(bool valid READ valid NOTIFY validChanged)
|
||||
Q_PROPERTY(bool empty READ empty NOTIFY emptyChanged)
|
||||
Q_PROPERTY(QString error READ error NOTIFY errorChanged)
|
||||
Q_PROPERTY(int errorIndex READ errorIndex NOTIFY errorIndexChanged)
|
||||
|
||||
public:
|
||||
enum ConditionType { Invalid, Operator, Literal, Variable };
|
||||
enum ConditionType { Intermediate, Invalid, Operator, Literal, Variable, Shadow };
|
||||
Q_ENUM(ConditionType)
|
||||
|
||||
struct ConditionToken
|
||||
@@ -129,22 +130,28 @@ public:
|
||||
Q_INVOKABLE void appendToken(const QString &value);
|
||||
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 empty() const;
|
||||
|
||||
//for debugging
|
||||
Q_INVOKABLE void command(const QString &string);
|
||||
|
||||
void setInvalid(const QString &errorMessage);
|
||||
void setInvalid(const QString &errorMessage, int index = -1);
|
||||
void setValid();
|
||||
|
||||
QString error() const;
|
||||
int errorIndex() const;
|
||||
|
||||
signals:
|
||||
void validChanged();
|
||||
void emptyChanged();
|
||||
void conditionChanged();
|
||||
void errorChanged();
|
||||
void errorIndexChanged();
|
||||
|
||||
private:
|
||||
void internalSetup();
|
||||
@@ -162,6 +169,7 @@ private:
|
||||
QList<ConditionToken> m_tokens;
|
||||
bool m_valid = false;
|
||||
QString m_errorMessage;
|
||||
int m_errorIndex = -1;
|
||||
};
|
||||
|
||||
class ConnectionModelStatementDelegate : public QObject
|
||||
@@ -245,6 +253,9 @@ class ConnectionModelBackendDelegate : public QObject
|
||||
Q_PROPERTY(bool hasElse READ hasElse NOTIFY hasElseChanged)
|
||||
Q_PROPERTY(QString source READ source NOTIFY sourceChanged)
|
||||
|
||||
Q_PROPERTY(PropertyTreeModel *propertyTreeModel READ propertyTreeModel CONSTANT)
|
||||
Q_PROPERTY(PropertyListProxyModel *propertyListProxyModel READ propertyListProxyModel CONSTANT)
|
||||
|
||||
public:
|
||||
explicit ConnectionModelBackendDelegate(ConnectionModel *parent = nullptr);
|
||||
|
||||
@@ -282,6 +293,10 @@ private:
|
||||
ConditionListModel *conditionListModel();
|
||||
QString source() const;
|
||||
void setSource(const QString &source);
|
||||
|
||||
PropertyTreeModel *propertyTreeModel();
|
||||
PropertyListProxyModel *propertyListProxyModel();
|
||||
|
||||
void setupCondition();
|
||||
void setupHandlerAndStatements();
|
||||
|
||||
@@ -303,6 +318,8 @@ private:
|
||||
bool m_hasCondition = false;
|
||||
bool m_hasElse = false;
|
||||
QString m_source;
|
||||
PropertyTreeModel m_propertyTreeModel;
|
||||
PropertyListProxyModel m_propertyListProxyModel;
|
||||
};
|
||||
|
||||
} // namespace QmlDesigner
|
||||
|
||||
@@ -88,10 +88,13 @@ public:
|
||||
0,
|
||||
"ConnectionModelStatementDelegate");
|
||||
|
||||
qmlRegisterType<ConditionListModel>("ConnectionsEditorEditorBackend",
|
||||
1,
|
||||
0,
|
||||
"ConditionListModel");
|
||||
qmlRegisterType<ConditionListModel>("ConnectionsEditorEditorBackend", 1, 0, "ConditionListModel");
|
||||
|
||||
qmlRegisterType<PropertyTreeModel>("ConnectionsEditorEditorBackend", 1, 0, "PropertyTreeModel");
|
||||
qmlRegisterType<PropertyListProxyModel>("ConnectionsEditorEditorBackend",
|
||||
1,
|
||||
0,
|
||||
"PropertyListProxyModel");
|
||||
|
||||
Theme::setupTheme(engine());
|
||||
|
||||
|
||||
@@ -155,7 +155,8 @@ QVariant PropertyTreeModel::data(const QModelIndex &index, int role) const
|
||||
if (role == RowRole)
|
||||
return index.row();
|
||||
|
||||
if (role == PropertyNameRole || role == PropertyPriorityRole || role == ExpressionRole) {
|
||||
if (role == PropertyNameRole || role == PropertyPriorityRole || role == ExpressionRole
|
||||
|| role == ChildCountRole) {
|
||||
if (!index.isValid())
|
||||
return {};
|
||||
|
||||
@@ -166,26 +167,33 @@ QVariant PropertyTreeModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
DataCacheItem item = m_indexHash[index.internalId()];
|
||||
|
||||
if (item.propertyName.isEmpty()) { //node
|
||||
if (role == PropertyNameRole)
|
||||
return item.modelNode.displayName();
|
||||
|
||||
return true; //nodes are always shown
|
||||
}
|
||||
if (role == ChildCountRole)
|
||||
return rowCount(index);
|
||||
|
||||
if (role == ExpressionRole)
|
||||
return QString(item.modelNode.id() + item.propertyName);
|
||||
return QString(item.modelNode.id() + "." + item.propertyName);
|
||||
|
||||
if (role == PropertyNameRole)
|
||||
return item.propertyName;
|
||||
if (role == PropertyNameRole) {
|
||||
if (!item.propertyName.isEmpty())
|
||||
return QString::fromUtf8(item.propertyName);
|
||||
else
|
||||
return item.modelNode.displayName();
|
||||
}
|
||||
|
||||
static const auto priority = properityLists();
|
||||
if (std::find(priority.begin(), priority.end(), item.propertyName) != priority.end())
|
||||
return true; //listed priority properties
|
||||
return true; // listed priority properties
|
||||
|
||||
auto dynamic = getDynamicProperties(item.modelNode);
|
||||
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;
|
||||
}
|
||||
@@ -216,7 +224,7 @@ QVariant PropertyTreeModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
|
||||
if (role == Qt::DisplayRole)
|
||||
return item.propertyName;
|
||||
return QString::fromUtf8(item.propertyName);
|
||||
|
||||
QFont f;
|
||||
auto priority = properityLists();
|
||||
@@ -241,7 +249,7 @@ QModelIndex PropertyTreeModel::index(int row, int column, const QModelIndex &par
|
||||
return {};
|
||||
|
||||
if (!hasIndex(row, column, parent))
|
||||
return QModelIndex();
|
||||
return {};
|
||||
|
||||
const int rootId = -1;
|
||||
|
||||
@@ -312,7 +320,7 @@ QModelIndex PropertyTreeModel::parent(const QModelIndex &index) const
|
||||
return ensureModelIndex(item.modelNode, row);
|
||||
}
|
||||
|
||||
QPersistentModelIndex PropertyTreeModel::indexForInernalIdAndRow(int internalId, int row)
|
||||
QPersistentModelIndex PropertyTreeModel::indexForInternalIdAndRow(int internalId, int row)
|
||||
{
|
||||
return createIndex(row, 0, internalId);
|
||||
}
|
||||
@@ -323,7 +331,7 @@ int PropertyTreeModel::rowCount(const QModelIndex &parent) const
|
||||
return 0;
|
||||
|
||||
if (!parent.isValid())
|
||||
return 1;
|
||||
return 1; //m_nodeList.size();
|
||||
|
||||
int internalId = parent.internalId();
|
||||
|
||||
@@ -783,7 +791,8 @@ QHash<int, QByteArray> PropertyTreeModel::roleNames() const
|
||||
{
|
||||
static QHash<int, QByteArray> roleNames{{PropertyNameRole, "propertyName"},
|
||||
{PropertyPriorityRole, "hasPriority"},
|
||||
{ExpressionRole, "expression"}};
|
||||
{ExpressionRole, "expression"},
|
||||
{ChildCountRole, "childCount"}};
|
||||
|
||||
return roleNames;
|
||||
}
|
||||
@@ -792,17 +801,24 @@ PropertyListProxyModel::PropertyListProxyModel(PropertyTreeModel *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 );
|
||||
|
||||
if (internalId == -1)
|
||||
m_parentIndex = m_treeModel->index(0, 0);
|
||||
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();
|
||||
endResetModel();
|
||||
resetModel();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
connect(&m_nameCombboBox, &StudioQmlComboBoxBackend::activated, this, [this]() {
|
||||
|
||||
@@ -31,6 +31,7 @@ public:
|
||||
PropertyNameRole = Qt::UserRole + 1,
|
||||
PropertyPriorityRole,
|
||||
ExpressionRole,
|
||||
ChildCountRole,
|
||||
RowRole,
|
||||
InternalIdRole
|
||||
};
|
||||
@@ -57,7 +58,7 @@ public:
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) 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 columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
@@ -70,14 +71,13 @@ public:
|
||||
};
|
||||
|
||||
void setPropertyType(PropertyTypes type);
|
||||
void setFilter(const QString &filter);
|
||||
Q_INVOKABLE void setFilter(const QString &filter);
|
||||
|
||||
QList<ModelNode> nodeList() const;
|
||||
|
||||
const std::vector<PropertyName> getProperties(const ModelNode &modelNode) const;
|
||||
ModelNode getModelNodeForId(const QString &id) const;
|
||||
|
||||
protected:
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
@@ -127,12 +127,29 @@ private:
|
||||
class PropertyListProxyModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString parentName READ parentName NOTIFY parentNameChanged)
|
||||
|
||||
public:
|
||||
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;
|
||||
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:
|
||||
ModelNode m_modelNode;
|
||||
PropertyName m_propertyName;
|
||||
|
||||
Reference in New Issue
Block a user