// A row whose trailing control is a free-text field that reports every // keystroke. // // LiveFieldRow { // label: "Username" // detail: root.userNameProblem !== "" ? root.userNameProblem : "Lowercase…" // invalid: root.userNameProblem !== "" // onEdited: value => root.newUserName = value // } // // TextFieldRow commits on Enter or blur, which is right when every commit is a // privileged write that prompts. It is wrong for a form that has to say whether // what is being typed is acceptable *while* it is being typed: the add-user // form used TextFieldRow and its Create button stayed dark until the field lost // focus, which reads as a broken button rather than as a field awaiting blur. // // So this reports twice. `edited` fires per keystroke, for validation and for // state a page holds until a submit button is pressed. `accepted` fires on // Enter or when focus leaves, for the values that go straight to a privileged // write and must not prompt once per character. import QtQuick import qs.config SettingRow { id: root property string text: "" property string placeholder: "" property bool enabled: true // Draws the field as rejected. The page decides what invalid means; this // only shows it, next to the detail line that says why. property bool invalid: false property int maximumLength: 0 signal edited(value: string) signal accepted(value: string) controlWidth: 220 function clear(): void { input.text = ""; } // An external change replaces what is shown, unless it would yank the field // out from under someone mid-edit. onTextChanged: if (!input.activeFocus && input.text !== root.text) input.text = root.text Rectangle { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter width: root.controlWidth height: 32 radius: 9 color: Theme.alpha(Theme.fg, root.enabled ? 0.06 : 0.03) border.width: input.activeFocus ? 2 : 1 border.color: root.invalid ? Theme.alpha(Theme.danger, 0.6) : (input.activeFocus ? Theme.alpha(Theme.accent, 0.55) : Theme.alpha(Theme.fg, 0.1)) opacity: root.enabled ? 1 : 0.5 TextInput { id: input anchors.fill: parent anchors.leftMargin: 11 anchors.rightMargin: 11 enabled: root.enabled activeFocusOnTab: true text: root.text color: root.invalid ? Theme.danger : Theme.fg selectByMouse: true selectionColor: Theme.alpha(Theme.accent, 0.5) selectedTextColor: Theme.fg font.family: Theme.fontFamily font.pixelSize: Theme.fontSize verticalAlignment: TextInput.AlignVCenter maximumLength: root.maximumLength > 0 ? root.maximumLength : 32767 clip: true onTextEdited: root.edited(input.text) onAccepted: if (input.text !== root.text) root.accepted(input.text) // Unchanged text is not re-committed: a page whose `accepted` // handler writes through polkit would otherwise prompt every time // the field was tabbed past. onActiveFocusChanged: { if (input.activeFocus) return; if (input.text !== root.text) root.accepted(input.text); else input.text = root.text; } Text { anchors.fill: parent visible: input.text === "" text: root.placeholder color: Theme.fgMuted font: input.font verticalAlignment: Text.AlignVCenter elide: Text.ElideRight } } } }