QmlDesigner: Input lose focus after pressing enter

* Add losing focus after pressing Return/Enter for all TextInputs
* Add losing focus and reverting values after pressing Escape for all
  TextInputs
* FontComboBox fix initial value selection
* Code cleanup

Task-number: QDS-5972
Task-number: QDS-6028
Change-Id: Ice7449e89088f6e7da76eb7c2edefab647b109de
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Henning Gruendl
2022-05-16 08:31:22 +02:00
committed by Henning Gründl
parent e865b03448
commit 414fb08bc6
9 changed files with 182 additions and 142 deletions

View File

@@ -92,7 +92,6 @@ StudioControls.ComboBox {
onValueFromBackendChanged: colorLogic.invalidate()
function invalidate() {
if (comboBox.block)
return
@@ -140,6 +139,21 @@ StudioControls.ComboBox {
}
}
onAccepted: {
if (!comboBox.__isCompleted)
return
let inputValue = comboBox.editText
let index = comboBox.find(inputValue)
if (index !== -1)
inputValue = comboBox.textAt(index)
comboBox.backendValue.value = inputValue
comboBox.dirty = false
}
onCompressedActivated: {
if (!comboBox.__isCompleted)
return

View File

@@ -29,7 +29,7 @@ import HelperWidgets 2.0
import StudioControls 1.0 as StudioControls
StudioControls.ComboBox {
id: comboBox
id: root
property variant backendValue
property color textColor: colorLogic.textColor
@@ -39,17 +39,17 @@ StudioControls.ComboBox {
labelColor: colorLogic.textColor
editable: true
onTextColorChanged: setColor()
onTextColorChanged: root.setColor()
FileResourcesModel {
id: fileModel
modelNodeBackendProperty: modelNodeBackend
filter: comboBox.fontFilter
filter: root.fontFilter
}
function createFontLoader(fontUrl) {
return Qt.createQmlObject('import QtQuick 2.0; FontLoader { source: "' + fontUrl + '"; }',
comboBox, "dynamicFontLoader")
root, "dynamicFontLoader")
}
function setupModel() {
@@ -63,80 +63,83 @@ StudioControls.ComboBox {
// Remove duplicate family names
familyNames = [...new Set(familyNames)]
familyNames.sort()
comboBox.model = familyNames
root.model = familyNames
root.currentIndex = root.find(root.backendValue.value)
}
onModelChanged: editText = comboBox.backendValue.valueToString
function setColor() {
// Hack to style the text input
for (var i = 0; i < root.children.length; i++) {
if (root.children[i].text !== undefined) {
root.children[i].color = root.textColor
}
}
}
onModelChanged: root.editText = root.backendValue.valueToString
ExtendedFunctionLogic {
id: extFuncLogic
backendValue: comboBox.backendValue
backendValue: root.backendValue
}
actionIndicator.icon.color: extFuncLogic.color
actionIndicator.icon.text: extFuncLogic.glyph
actionIndicator.onClicked: extFuncLogic.show()
actionIndicator.forceVisible: extFuncLogic.menuVisible
actionIndicator.visible: comboBox.showExtendedFunctionButton
actionIndicator.visible: root.showExtendedFunctionButton
ColorLogic {
id: colorLogic
property string textValue: comboBox.backendValue.valueToString
backendValue: comboBox.backendValue
onTextValueChanged: comboBox.editText = colorLogic.textValue
property string textValue: root.backendValue.valueToString
backendValue: root.backendValue
onTextValueChanged: root.editText = colorLogic.textValue
}
onAccepted: {
if (backendValue === undefined)
if (root.backendValue === undefined)
return
if (editText === "")
if (root.editText === "")
return
if (backendValue.value !== editText)
backendValue.value = editText;
if (root.backendValue.value !== root.editText)
root.backendValue.value = root.editText
}
onActivated: {
if (backendValue === undefined)
onCompressedActivated: function(index, reason) { root.handleActivate(index) }
function handleActivate(index)
{
if (root.backendValue === undefined)
return
if (editText === "")
if (root.editText === "")
return
var indexText = comboBox.textAt(index)
var indexText = root.textAt(index)
if (backendValue.value !== indexText)
backendValue.value = indexText
if (root.backendValue.value !== indexText)
root.backendValue.value = indexText
}
Connections {
target: modelNodeBackend
function onSelectionChanged() {
comboBox.editText = backendValue.value
setupModel()
root.editText = root.backendValue.value
root.setupModel()
}
}
Component.onCompleted: {
setupModel()
root.setupModel()
// Hack to style the text input
for (var i = 0; i < comboBox.children.length; i++) {
if (comboBox.children[i].text !== undefined) {
comboBox.children[i].color = comboBox.textColor
comboBox.children[i].anchors.rightMargin = 34
comboBox.children[i].anchors.leftMargin = 18
for (var i = 0; i < root.children.length; i++) {
if (root.children[i].text !== undefined) {
root.children[i].color = root.textColor
root.children[i].anchors.rightMargin = 34
root.children[i].anchors.leftMargin = 18
}
}
}
function setColor() {
// Hack to style the text input
for (var i = 0; i < comboBox.children.length; i++) {
if (comboBox.children[i].text !== undefined) {
comboBox.children[i].color = comboBox.textColor
}
}
}
}

View File

@@ -32,36 +32,43 @@ StudioControls.TextField {
id: lineEdit
property variant backendValue
property color borderColor: "#222"
property color highlightColor: "orange"
color: lineEdit.edit ? StudioTheme.Values.themeTextColor : colorLogic.textColor
property bool showTranslateCheckBox: true
translationIndicatorVisible: showTranslateCheckBox
property bool writeValueManually: false
property bool writeAsExpression: false
property bool __dirty: false
property bool showTranslateCheckBox: true
property bool showExtendedFunctionButton: true
property string context
actionIndicator.visible: showExtendedFunctionButton
property bool __dirty: false
signal commitData
property string context
color: lineEdit.edit ? StudioTheme.Values.themeTextColor : colorLogic.textColor
actionIndicator.visible: lineEdit.showExtendedFunctionButton
translationIndicatorVisible: lineEdit.showTranslateCheckBox
function setTranslateExpression() {
if (translateFunction() === "qsTranslate") {
backendValue.expression = translateFunction()
+ "(\"" + backendValue.getTranslationContext()
+ "\", " + "\"" + escapeString(text) + "\")"
lineEdit.backendValue.expression = translateFunction()
+ "(\"" + lineEdit.backendValue.getTranslationContext()
+ "\", " + "\"" + lineEdit.escapeString(lineEdit.text) + "\")"
} else {
backendValue.expression = translateFunction() + "(\"" + escapeString(text) + "\")"
lineEdit.backendValue.expression = translateFunction()
+ "(\"" + lineEdit.escapeString(lineEdit.text) + "\")"
}
}
function escapeString(string) {
var str = string
str = str.replace(/\\/g, "\\\\")
str.replace(/\"/g, "\\\"")
str = str.replace(/\t/g, "\\t")
str = str.replace(/\r/g, "\\r")
str = str.replace(/\n/g, '\\n')
return str
}
ExtendedFunctionLogic {
id: extFuncLogic
backendValue: lineEdit.backendValue
@@ -79,60 +86,58 @@ StudioControls.TextField {
if (colorLogic.valueFromBackend === undefined) {
lineEdit.text = ""
} else {
if (writeValueManually)
if (lineEdit.writeValueManually)
lineEdit.text = convertColorToString(colorLogic.valueFromBackend)
else
lineEdit.text = colorLogic.valueFromBackend
}
__dirty = false
lineEdit.__dirty = false
}
}
onTextChanged: {
__dirty = true
}
onTextChanged: lineEdit.__dirty = true
Connections {
target: modelNodeBackend
function onSelectionToBeChanged() {
if (__dirty && !writeValueManually) {
if (writeAsExpression)
lineEdit.backendValue.expression = text
if (lineEdit.__dirty && !lineEdit.writeValueManually) {
if (lineEdit.writeAsExpression)
lineEdit.backendValue.expression = lineEdit.text
else
lineEdit.backendValue.value = text
} else if (__dirty) {
lineEdit.backendValue.value = lineEdit.text
} else if (lineEdit.__dirty) {
commitData()
}
__dirty = false
lineEdit.__dirty = false
}
}
onEditingFinished: {
if (writeValueManually)
if (lineEdit.writeValueManually)
return
if (!__dirty)
if (!lineEdit.__dirty)
return
if (backendValue.isTranslated) {
setTranslateExpression()
if (lineEdit.backendValue.isTranslated) {
lineEdit.setTranslateExpression()
} else {
if (writeAsExpression) {
if (lineEdit.backendValue.expression !== text)
lineEdit.backendValue.expression = text
} else if (lineEdit.backendValue.value !== text) {
lineEdit.backendValue.value = text
if (lineEdit.writeAsExpression) {
if (lineEdit.backendValue.expression !== lineEdit.text)
lineEdit.backendValue.expression = lineEdit.text
} else if (lineEdit.backendValue.value !== lineEdit.text) {
lineEdit.backendValue.value = lineEdit.text
}
}
__dirty = false
lineEdit.__dirty = false
}
property bool isTranslated: colorLogic.backendValue === undefined ? false
: colorLogic.backendValue.isTranslated
translationIndicator.onClicked: {
if (translationIndicator.checked) {
if (lineEdit.translationIndicator.checked) {
setTranslateExpression()
} else {
var textValue = lineEdit.text
@@ -141,7 +146,8 @@ StudioControls.TextField {
colorLogic.evaluate()
}
property variant backendValueValueInternal: backendValue === undefined ? 0 : backendValue.value
property variant backendValueValueInternal: lineEdit.backendValue === undefined ? 0
: lineEdit.backendValue.value
onBackendValueValueInternalChanged: {
if (lineEdit.backendValue === undefined)
lineEdit.translationIndicator.checked = false
@@ -155,15 +161,4 @@ StudioControls.TextField {
else
lineEdit.translationIndicator.checked = lineEdit.backendValue.isTranslated
}
function escapeString(string) {
var str = string
str = str.replace(/\\/g, "\\\\")
str.replace(/\"/g, "\\\"")
str = str.replace(/\t/g, "\\t")
str = str.replace(/\r/g, "\\r")
str = str.replace(/\n/g, '\\n')
return str
}
}

View File

@@ -51,15 +51,15 @@ Rectangle {
anchors.fill: parent
hoverEnabled: true
onClicked: {
if (myControl.activeFocus)
myControl.focus = false
if (myPopup.opened) {
myPopup.close()
} else {
myPopup.open()
myPopup.forceActiveFocus()
}
if (myControl.activeFocus)
myControl.focus = false
}
}

View File

@@ -65,6 +65,11 @@ T.ComboBox {
comboBoxPopup.close()
}
onActiveFocusChanged: {
if (myComboBox.activeFocus)
comboBoxInput.preFocusText = myComboBox.editText
}
ActionIndicator {
id: actionIndicator
myControl: myComboBox
@@ -76,19 +81,22 @@ T.ComboBox {
contentItem: ComboBoxInput {
id: comboBoxInput
property string preFocusText: ""
myControl: myComboBox
text: myComboBox.editText
onEditingFinished: {
comboBoxInput.deselect()
comboBoxInput.focus = false
myComboBox.focus = false
// Only trigger the signal, if the value was modified
if (myComboBox.dirty) {
myTimer.stop()
myComboBox.dirty = false
myComboBox.compressedActivated(myComboBox.find(myComboBox.editText),
ComboBox.ActivatedReason.EditingFinished)
myComboBox.accepted()
}
}
onTextEdited: myComboBox.dirty = true
@@ -312,7 +320,10 @@ T.ComboBox {
]
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape)
if (event.key === Qt.Key_Escape) {
myComboBox.editText = comboBoxInput.preFocusText
myComboBox.dirty = true
myComboBox.focus = false
}
}
}

View File

@@ -73,13 +73,13 @@ TextInput {
acceptedButtons: Qt.LeftButton
cursorShape: Qt.PointingHandCursor
onPressed: function(mouse) {
if (textInput.readOnly) {
if (!textInput.myControl.editable) {
if (myControl.popup.opened) {
myControl.popup.close()
myControl.focus = false
} else {
myControl.forceActiveFocus()
myControl.popup.open()
myControl.forceActiveFocus()
}
} else {
textInput.forceActiveFocus()

View File

@@ -113,8 +113,6 @@ Item {
myTimer.stop()
root.dirty = false
root.editText = root.editText.trim()
//root.compressedActivated(root.find(root.editText),
// ComboBox.ActivatedReason.EditingFinished)
}
root.finishEditing()

View File

@@ -79,6 +79,8 @@ T.SpinBox {
property alias compressedValueTimer: myTimer
property string preFocusText: ""
signal realValueModified
signal compressedRealValueModified
signal dragStarted
@@ -162,6 +164,8 @@ T.SpinBox {
validator: doubleValidator
function handleEditingFinished() {
mySpinBox.focus = false
// Keep the dirty state before calling setValueFromInput(),
// it will be set to false (cleared) internally
var valueModified = mySpinBox.dirty
@@ -174,7 +178,7 @@ T.SpinBox {
mySpinBox.compressedRealValueModified()
}
onEditingFinished: handleEditingFinished()
onEditingFinished: spinBoxInput.handleEditingFinished()
onTextEdited: mySpinBox.dirty = true
}
@@ -281,7 +285,7 @@ T.SpinBox {
id: myTimer
repeat: false
running: false
interval: 200
interval: 400
onTriggered: mySpinBox.compressedRealValueModified()
}
@@ -306,8 +310,10 @@ T.SpinBox {
}
onDisplayTextChanged: spinBoxInput.text = mySpinBox.displayText
onActiveFocusChanged: {
if (mySpinBox.activeFocus) // QTBUG-75862 && mySpinBox.focusReason === Qt.TabFocusReason)
if (mySpinBox.activeFocus) { // QTBUG-75862 && mySpinBox.focusReason === Qt.TabFocusReason)
mySpinBox.preFocusText = spinBoxInput.text
spinBoxInput.selectAll()
}
}
Keys.onPressed: function(event) {
@@ -333,8 +339,11 @@ T.SpinBox {
mySpinBox.realStepSize = currStepSize
}
if (event.key === Qt.Key_Escape)
mySpinBox.focus = false
if (event.key === Qt.Key_Escape) {
spinBoxInput.text = mySpinBox.preFocusText
mySpinBox.dirty = true
spinBoxInput.handleEditingFinished()
}
}
function clamp(v, lo, hi) {

View File

@@ -28,15 +28,15 @@ import QtQuick.Templates 2.15 as T
import StudioTheme 1.0 as StudioTheme
T.TextField {
id: myTextField
id: root
property alias actionIndicator: actionIndicator
property alias translationIndicator: translationIndicator
// This property is used to indicate the global hover state
property bool hover: (actionIndicator.hover || mouseArea.containsMouse
|| translationIndicator.hover) && myTextField.enabled
property bool edit: myTextField.activeFocus
|| translationIndicator.hover) && root.enabled
property bool edit: root.activeFocus
property alias actionIndicatorVisible: actionIndicator.visible
property real __actionIndicatorWidth: StudioTheme.Values.actionIndicatorWidth
@@ -46,6 +46,8 @@ T.TextField {
property real __translationIndicatorWidth: StudioTheme.Values.translationIndicatorWidth
property real __translationIndicatorHeight: StudioTheme.Values.translationIndicatorHeight
property string preFocusText: ""
horizontalAlignment: Qt.AlignLeft
verticalAlignment: Qt.AlignVCenter
@@ -78,7 +80,7 @@ T.TextField {
cursorShape: Qt.PointingHandCursor
onPressed: function(mouse) {
if (mouse.button === Qt.RightButton)
contextMenu.popup(myTextField)
contextMenu.popup(root)
mouse.accepted = false
}
@@ -86,43 +88,50 @@ T.TextField {
onPersistentSelectionChanged: {
if (!persistentSelection)
myTextField.deselect()
root.deselect()
}
ContextMenu {
id: contextMenu
myTextEdit: myTextField
myTextEdit: root
}
onActiveFocusChanged: {
if (root.activeFocus)
root.preFocusText = root.text
}
onEditChanged: {
if (myTextField.edit)
if (root.edit)
contextMenu.close()
}
onEditingFinished: root.focus = false
ActionIndicator {
id: actionIndicator
myControl: myTextField
myControl: root
x: 0
y: 0
width: actionIndicator.visible ? myTextField.__actionIndicatorWidth : 0
height: actionIndicator.visible ? myTextField.__actionIndicatorHeight : 0
width: actionIndicator.visible ? root.__actionIndicatorWidth : 0
height: actionIndicator.visible ? root.__actionIndicatorHeight : 0
}
Text {
id: placeholder
x: myTextField.leftPadding
y: myTextField.topPadding
width: myTextField.width - (myTextField.leftPadding + myTextField.rightPadding)
height: myTextField.height - (myTextField.topPadding + myTextField.bottomPadding)
x: root.leftPadding
y: root.topPadding
width: root.width - (root.leftPadding + root.rightPadding)
height: root.height - (root.topPadding + root.bottomPadding)
text: myTextField.placeholderText
font: myTextField.font
color: myTextField.placeholderTextColor
verticalAlignment: myTextField.verticalAlignment
visible: !myTextField.length && !myTextField.preeditText
&& (!myTextField.activeFocus || myTextField.horizontalAlignment !== Qt.AlignHCenter)
text: root.placeholderText
font: root.font
color: root.placeholderTextColor
verticalAlignment: root.verticalAlignment
visible: !root.length && !root.preeditText
&& (!root.activeFocus || root.horizontalAlignment !== Qt.AlignHCenter)
elide: Text.ElideRight
renderType: myTextField.renderType
renderType: root.renderType
}
background: Rectangle {
@@ -131,14 +140,14 @@ T.TextField {
border.color: StudioTheme.Values.themeControlOutline
border.width: StudioTheme.Values.border
x: actionIndicator.width
width: myTextField.width - actionIndicator.width
height: myTextField.height
width: root.width - actionIndicator.width
height: root.height
}
TranslationIndicator {
id: translationIndicator
myControl: myTextField
x: myTextField.width - translationIndicator.width
myControl: root
x: root.width - translationIndicator.width
width: translationIndicator.visible ? __translationIndicatorWidth : 0
height: translationIndicator.visible ? __translationIndicatorHeight : 0
}
@@ -146,15 +155,14 @@ T.TextField {
states: [
State {
name: "default"
when: myTextField.enabled && !myTextField.hover
&& !myTextField.edit
when: root.enabled && !root.hover && !root.edit
PropertyChanges {
target: textFieldBackground
color: StudioTheme.Values.themeControlBackground
border.color: StudioTheme.Values.themeControlOutline
}
PropertyChanges {
target: myTextField
target: root
color: StudioTheme.Values.themeTextColor
placeholderTextColor: StudioTheme.Values.themePlaceholderTextColor
}
@@ -165,15 +173,15 @@ T.TextField {
},
State {
name: "globalHover"
when: (actionIndicator.hover || translationIndicator.hover) && !myTextField.edit
&& myTextField.enabled
when: (actionIndicator.hover || translationIndicator.hover) && !root.edit
&& root.enabled
PropertyChanges {
target: textFieldBackground
color: StudioTheme.Values.themeControlBackgroundGlobalHover
border.color: StudioTheme.Values.themeControlOutline
}
PropertyChanges {
target: myTextField
target: root
color: StudioTheme.Values.themeTextColor
placeholderTextColor: StudioTheme.Values.themePlaceholderTextColor
}
@@ -181,28 +189,28 @@ T.TextField {
State {
name: "hover"
when: mouseArea.containsMouse && !actionIndicator.hover && !translationIndicator.hover
&& !myTextField.edit && myTextField.enabled
&& !root.edit && root.enabled
PropertyChanges {
target: textFieldBackground
color: StudioTheme.Values.themeControlBackgroundHover
border.color: StudioTheme.Values.themeControlOutline
}
PropertyChanges {
target: myTextField
target: root
color: StudioTheme.Values.themeTextColor
placeholderTextColor: StudioTheme.Values.themePlaceholderTextColor
}
},
State {
name: "edit"
when: myTextField.edit
when: root.edit
PropertyChanges {
target: textFieldBackground
color: StudioTheme.Values.themeControlBackgroundInteraction
border.color: StudioTheme.Values.themeControlOutlineInteraction
}
PropertyChanges {
target: myTextField
target: root
color: StudioTheme.Values.themeTextColor
placeholderTextColor: StudioTheme.Values.themePlaceholderTextColorInteraction
}
@@ -213,14 +221,14 @@ T.TextField {
},
State {
name: "disable"
when: !myTextField.enabled
when: !root.enabled
PropertyChanges {
target: textFieldBackground
color: StudioTheme.Values.themeControlBackgroundDisabled
border.color: StudioTheme.Values.themeControlOutlineDisabled
}
PropertyChanges {
target: myTextField
target: root
color: StudioTheme.Values.themeTextColorDisabled
placeholderTextColor: StudioTheme.Values.themeTextColorDisabled
}
@@ -228,7 +236,9 @@ T.TextField {
]
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape)
myTextField.focus = false
if (event.key === Qt.Key_Escape) {
root.text = root.preFocusText
root.focus = false
}
}
}