108 lines
3.8 KiB
QML
108 lines
3.8 KiB
QML
// 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
|
|
}
|
|
}
|
|
}
|
|
}
|