// A free-text setting, bound to a schema key by name. // // TextEntryRow { setting: "keyboardOptions"; placeholder: "compose:ralt" } // // For the settings whose value is a short string with too many legal values to // enumerate -- XKB layouts and options, chiefly. Label, explanation, and // validation all come from PreferenceSchema, so a row cannot drift from the // setting it edits. // // Rejected input is shown as rejected rather than silently dropped or quietly // sanitised. Several of these strings are serialised into an hl.config payload, // where stripping an unexpected character would turn a typo into a different // working setting -- so the schema's pattern decides, the field turns red when // it fails, and nothing is committed until it passes. // // Committed on Enter or when focus leaves, not per keystroke: half a layout // name is a valid string that means something else, and each commit is a round // trip to the compositor. import QtQuick import qs.config import qs.services SettingRow { id: root required property string setting property string placeholder: "" readonly property var spec: PreferenceSchema.spec(root.setting) readonly property string stored: String(DesktopPreferences.get(root.setting) ?? "") // Empty is always allowed to be typed through, even where the pattern // forbids it, so a field can be cleared on the way to a new value. readonly property bool valid: input.text === "" || !root.spec?.pattern || new RegExp(root.spec.pattern).test(input.text) label: root.spec ? root.spec.label : root.setting detail: root.spec ? root.spec.detail : "" controlWidth: 210 function commit(): void { if (!root.valid || input.text === root.stored) return; SystemSettings.commitPreference(root.setting, input.text); } function revert(): void { input.text = root.stored; } // Follows the store when the value changes elsewhere -- a reset, a restored // backup -- but never while the field has focus, which would overwrite what // is being typed. onStoredChanged: if (!input.activeFocus) input.text = root.stored Rectangle { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter width: root.controlWidth height: 30 radius: 8 color: Theme.alpha(Theme.fg, 0.05) border.width: 1 border.color: !root.valid ? Theme.danger : (input.activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.12)) TextInput { id: input anchors.fill: parent anchors.leftMargin: 10 anchors.rightMargin: 10 verticalAlignment: TextInput.AlignVCenter clip: true text: root.stored color: root.valid ? Theme.fg : Theme.danger font.family: Theme.fontMono font.pixelSize: Theme.fontSizeSmall selectByMouse: true selectionColor: Theme.alpha(Theme.accent, 0.35) onAccepted: root.commit() // Commit, then show what is actually stored. If the compositor // refused the value, the field snaps back to the value in effect // rather than displaying a setting that was never applied; if it // accepted, onStoredChanged brings the field back up to date the // moment the write lands. onActiveFocusChanged: if (!activeFocus) { root.commit(); root.revert(); } Keys.onEscapePressed: { root.revert(); input.focus = false; } Text { anchors.verticalCenter: parent.verticalCenter visible: input.text === "" && !input.activeFocus text: root.placeholder color: Theme.fgMuted font: input.font } } } }