QmlDesigner: Fix ExpressionTextField popup

Fix the ListView/Popup which is shown on the ExpressionTextField when
requesting auto completion.

* Fix key behavior of the auto completion list
* Adapt to the look and feel of the property editor
* Fix size and position of the TextField and the overlayed Label

Task-number: QDS-2561
Change-Id: Ie8df6a2960b1c273600543532f0a136eb0c542b5
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Henning Gruendl
2020-07-16 13:01:41 +02:00
committed by Henning Gründl
parent d1aabbe262
commit 9f2bb4abaf
3 changed files with 197 additions and 83 deletions

View File

@@ -66,15 +66,12 @@ Rectangle {
RoundedPanel { RoundedPanel {
Layout.fillWidth: true Layout.fillWidth: true
height: 24 height: StudioTheme.Values.height
Label { Label {
x: 6
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: 16 anchors.leftMargin: StudioTheme.Values.inputHorizontalPadding
text: backendValues.className.value text: backendValues.className.value
verticalAlignment: Text.AlignVCenter
} }
ToolTipArea { ToolTipArea {
anchors.fill: parent anchors.fill: parent
@@ -88,10 +85,10 @@ Rectangle {
} }
ExpressionTextField { ExpressionTextField {
z: 2
id: typeLineEdit id: typeLineEdit
z: 2
completeOnlyTypes: true completeOnlyTypes: true
replaceCurrentTextByCompletion: true
anchors.fill: parent anchors.fill: parent
visible: false visible: false
@@ -107,11 +104,18 @@ Rectangle {
typeLineEdit.blockEditingFinished = true typeLineEdit.blockEditingFinished = true
if (visible) if (typeLineEdit.visible)
changeTypeName(typeLineEdit.text.trim()) changeTypeName(typeLineEdit.text.trim())
visible = false typeLineEdit.visible = false
typeLineEdit.blockEditingFinished = false typeLineEdit.blockEditingFinished = false
typeLineEdit.completionList.model = null
}
onRejected: {
typeLineEdit.visible = false
typeLineEdit.completionList.model = null
} }
} }

View File

@@ -63,15 +63,12 @@ Rectangle {
RoundedPanel { RoundedPanel {
Layout.fillWidth: true Layout.fillWidth: true
height: 24 height: StudioTheme.Values.height
Label { Label {
x: 6
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: 16 anchors.leftMargin: StudioTheme.Values.inputHorizontalPadding
text: backendValues.className.value text: backendValues.className.value
verticalAlignment: Text.AlignVCenter
} }
ToolTipArea { ToolTipArea {
anchors.fill: parent anchors.fill: parent
@@ -88,7 +85,7 @@ Rectangle {
z: 2 z: 2
id: typeLineEdit id: typeLineEdit
completeOnlyTypes: true completeOnlyTypes: true
replaceCurrentTextByCompletion: true
anchors.fill: parent anchors.fill: parent
visible: false visible: false
@@ -96,10 +93,26 @@ Rectangle {
showButtons: false showButtons: false
fixedSize: true fixedSize: true
property bool blockEditingFinished: false
onEditingFinished: { onEditingFinished: {
if (visible) if (typeLineEdit.blockEditingFinished)
return
typeLineEdit.blockEditingFinished = true
if (typeLineEdit.visible)
changeTypeName(typeLineEdit.text.trim()) changeTypeName(typeLineEdit.text.trim())
visible = false typeLineEdit.visible = false
typeLineEdit.blockEditingFinished = false
typeLineEdit.completionList.model = null
}
onRejected: {
typeLineEdit.visible = false
typeLineEdit.completionList.model = null
} }
} }

View File

@@ -23,13 +23,14 @@
** **
****************************************************************************/ ****************************************************************************/
import QtQuick 2.1 import QtQuick 2.15
import "Constants.js" as Constants import QtQuick.Window 2.15
import StudioControls 1.0 as StudioControls import QtQuick.Controls 2.15
import QtQuickDesignerTheme 1.0 import QtQuickDesignerTheme 1.0
import StudioControls 1.0 as StudioControls
import StudioTheme 1.0 as StudioTheme
StudioControls.TextField { StudioControls.TextField {
id: textField id: textField
signal rejected signal rejected
@@ -38,103 +39,199 @@ StudioControls.TextField {
actionIndicator.visible: false actionIndicator.visible: false
property bool completeOnlyTypes: false property bool completeOnlyTypes: false
property bool completionActive: listView.model !== null
property bool completionActive: listView.count > 0
property bool dotCompletion: false property bool dotCompletion: false
property int dotCursorPos: 0 property int dotCursorPos: 0
property string prefix property string prefix
property alias showButtons: buttonrow.visible property alias showButtons: buttonrow.visible
property bool fixedSize: false property bool fixedSize: false
property bool replaceCurrentTextByCompletion: false
property alias completionList: listView
function commitCompletion() { function commitCompletion() {
if (replaceCurrentTextByCompletion) {
textField.text = listView.currentItem.text
} else {
var cursorPos = textField.cursorPosition var cursorPos = textField.cursorPosition
var string = textField.text var string = textField.text
var before = string.slice(0, cursorPos - textField.prefix.length) var before = string.slice(0, cursorPos - textField.prefix.length)
var after = string.slice(cursorPos) var after = string.slice(cursorPos)
textField.text = before + listView.currentItem.text + after textField.text = before + listView.currentItem.text + after
textField.cursorPosition = cursorPos + listView.currentItem.text.length - prefix.length textField.cursorPosition = cursorPos + listView.currentItem.text.length - prefix.length
}
listView.model = null listView.model = null
} }
ListView { Popup {
id: listView id: textFieldPopup
x: textField.x
y: textField.height - StudioTheme.Values.border
width: textField.width
// TODO Setting the height on the popup solved the problem with the popup of height 0,
// but it has the problem that it sometimes extend over the border of the actual window
// and is then cut off.
height: Math.min(contentItem.implicitHeight + textFieldPopup.topPadding + textFieldPopup.bottomPadding,
textField.Window.height - topMargin - bottomMargin,
StudioTheme.Values.maxComboBoxPopupHeight)
padding: StudioTheme.Values.border
margins: 0 // If not defined margin will be -1
closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnPressOutsideParent
| Popup.CloseOnEscape | Popup.CloseOnReleaseOutside
| Popup.CloseOnReleaseOutsideParent
clip: true
cacheBuffer: 0
snapMode: ListView.SnapToItem
boundsBehavior: Flickable.StopAtBounds
visible: textField.completionActive visible: textField.completionActive
delegate: Text {
onClosed: listView.model = null
contentItem: ListView {
id: listView
clip: true
implicitHeight: contentHeight
boundsBehavior: Flickable.StopAtBounds
ScrollBar.vertical: StudioControls.ScrollBar {
id: popupScrollBar
}
model: null
delegate: ItemDelegate {
id: myItemDelegate
width: textFieldPopup.width - textFieldPopup.leftPadding - textFieldPopup.rightPadding
- (popupScrollBar.visible ? popupScrollBar.contentItem.implicitWidth
+ 2 : 0) // TODO Magic number
height: StudioTheme.Values.height - 2 * StudioTheme.Values.border
padding: 0
text: itemDelegateText.text
contentItem: Text {
id: itemDelegateText
leftPadding: 8
text: modelData text: modelData
color: Theme.color(Theme.PanelTextColorLight) color: StudioTheme.Values.themeTextColor
Rectangle { font: textField.font
visible: index === listView.currentIndex elide: Text.ElideRight
z: -1 verticalAlignment: Text.AlignVCenter
anchors.fill: parent }
color: Theme.qmlDesignerBackgroundColorDarkAlternate() background: Rectangle {
color: "transparent"
}
hoverEnabled: true
onHoveredChanged: {
if (hovered)
listView.currentIndex = index
}
onClicked: {
listView.currentIndex = index
if (textField.completionActive)
textField.commitCompletion()
} }
} }
anchors.top: parent.top highlight: Rectangle {
anchors.topMargin: 26 id: listViewHighlight
anchors.bottomMargin: textField.fixedSize ? -180 : 12 width: textFieldPopup.width - textFieldPopup.leftPadding - textFieldPopup.rightPadding
anchors.bottom: parent.bottom - (popupScrollBar.visible ? popupScrollBar.contentItem.implicitWidth
anchors.left: parent.left + 2 : 0)
width: 200 height: StudioTheme.Values.height - 2 * StudioTheme.Values.border
spacing: 2 color: StudioTheme.Values.themeInteraction
children: [ y: listView.currentItem.y
Rectangle { }
visible: textField.fixedSize highlightFollowsCurrentItem: false
anchors.fill: parent
color: Theme.qmlDesignerBackgroundColorDarker()
border.color: Theme.qmlDesignerBorderColor()
anchors.rightMargin: 12
z: -1
} }
] background: Rectangle {
color: StudioTheme.Values.themeControlBackground
border.color: StudioTheme.Values.themeInteraction
border.width: StudioTheme.Values.border
}
enter: Transition {
}
exit: Transition {
}
} }
verticalAlignment: Text.AlignTop verticalAlignment: Text.AlignTop
onPressed: listView.model = null
Keys.priority: Keys.BeforeItem Keys.priority: Keys.BeforeItem
Keys.onPressed: { Keys.onPressed: {
var text = textField.text
var pos = textField.cursorPosition
var explicitComplete = true
if (event.key === Qt.Key_Period) { switch (event.key) {
case Qt.Key_Period:
textField.dotCursorPos = textField.cursorPosition + 1 textField.dotCursorPos = textField.cursorPosition + 1
var list = autoComplete(textField.text+".", textField.dotCursorPos, false, textField.completeOnlyTypes) text = textField.text + "."
textField.prefix = list.pop() pos = textField.dotCursorPos
listView.model = list; explicitComplete = false
textField.dotCompletion = true textField.dotCompletion = true
} else { break
if (textField.completionActive) {
var list2 = autoComplete(textField.text + event.text, case Qt.Key_Right:
textField.cursorPosition + event.text.length, if (!textField.completionActive)
true, textField.completeOnlyTypes) return
textField.prefix = list2.pop()
listView.model = list2; pos = Math.min(textField.cursorPosition + 1, textField.text.length)
} break
case Qt.Key_Left:
if (!textField.completionActive)
return
pos = Math.max(0, textField.cursorPosition - 1)
break
case Qt.Key_Backspace:
if (!textField.completionActive)
return
pos = textField.cursorPosition - 1
if (pos < 0)
return
text = textField.text.substring(0, pos) + textField.text.substring(textField.cursorPosition)
break
case Qt.Key_Delete:
return
default:
if (!textField.completionActive)
return
var tmp = textField.text
text = tmp.substring(0, textField.cursorPosition) + event.text + tmp.substring(textField.cursorPosition)
pos = textField.cursorPosition + event.text.length
} }
var list = autoComplete(text.trim(), pos, explicitComplete, textField.completeOnlyTypes)
textField.prefix = text.substring(0, pos)
if (list.length && list[list.length - 1] === textField.prefix)
list.pop()
listView.model = list
} }
Keys.onSpacePressed: { Keys.onSpacePressed: {
if (event.modifiers & Qt.ControlModifier) { if (event.modifiers & Qt.ControlModifier) {
var list = autoComplete(textField.text, textField.cursorPosition, true, textField.completeOnlyTypes) var list = autoComplete(textField.text, textField.cursorPosition, true, textField.completeOnlyTypes)
textField.prefix = list.pop() textField.prefix = textField.text.substring(0, textField.cursorPosition)
listView.model = list; if (list.length && list[list.length - 1] === textField.prefix)
list.pop()
listView.model = list
textField.dotCompletion = false textField.dotCompletion = false
event.accepted = true; event.accepted = true;
if (list.length === 1)
textField.commitCompletion()
} else { } else {
event.accepted = false event.accepted = false
} }