Extract PanelKeyCatcher, share key dispatch across wifi/audio/bluetooth
This commit is contained in:
@@ -0,0 +1,73 @@
|
|||||||
|
import QtQuick
|
||||||
|
|
||||||
|
// Drop-in key dispatcher for keyboard-driven panels. Wraps panel content
|
||||||
|
// and emits semantic signals so each panel keeps its own state machine
|
||||||
|
// (focusSection, selectedIndex, activation rules) while the boilerplate
|
||||||
|
// key handling lives here.
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// Common.KeyboardPanel {
|
||||||
|
// ...
|
||||||
|
// PanelKeyCatcher {
|
||||||
|
// anchors.fill: parent
|
||||||
|
// onMoveRequested: function(dx, dy) { root.moveCursor(dx, dy) }
|
||||||
|
// onActivateRequested: root.activateCursor()
|
||||||
|
// onCloseRequested: root.closePopout()
|
||||||
|
// onDeleteRequested: root.deleteSelected()
|
||||||
|
// onTextKey: function(t) { if (t === "r") root.refresh() }
|
||||||
|
//
|
||||||
|
// Column { ... panel content ... }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Keys.priority: Keys.AfterItem means a focused descendant (e.g. a
|
||||||
|
// TextField inside an inline password prompt) gets the event first. Only
|
||||||
|
// events the focused subtree ignores reach this handler — that's what
|
||||||
|
// lets j/k/Esc keep working in the panel while an input field consumes
|
||||||
|
// typing.
|
||||||
|
//
|
||||||
|
// blocked: when true, ALL keys are forwarded to descendants without
|
||||||
|
// triggering signals. Useful when an inline editor is open and the
|
||||||
|
// caller wants the cursor model frozen.
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool blocked: false
|
||||||
|
|
||||||
|
signal moveRequested(int dx, int dy)
|
||||||
|
signal activateRequested()
|
||||||
|
signal closeRequested()
|
||||||
|
signal deleteRequested()
|
||||||
|
signal textKey(string text)
|
||||||
|
|
||||||
|
focus: true
|
||||||
|
Keys.priority: Keys.AfterItem
|
||||||
|
Keys.onPressed: function(event) {
|
||||||
|
if (blocked) return
|
||||||
|
|
||||||
|
if (event.key === Qt.Key_Escape) {
|
||||||
|
closeRequested(); event.accepted = true; return
|
||||||
|
}
|
||||||
|
if (event.key === Qt.Key_Down || event.text === "j") {
|
||||||
|
moveRequested(0, 1); event.accepted = true; return
|
||||||
|
}
|
||||||
|
if (event.key === Qt.Key_Up || event.text === "k") {
|
||||||
|
moveRequested(0, -1); event.accepted = true; return
|
||||||
|
}
|
||||||
|
if (event.key === Qt.Key_Right || event.text === "l") {
|
||||||
|
moveRequested(1, 0); event.accepted = true; return
|
||||||
|
}
|
||||||
|
if (event.key === Qt.Key_Left || event.text === "h") {
|
||||||
|
moveRequested(-1, 0); event.accepted = true; return
|
||||||
|
}
|
||||||
|
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_Space) {
|
||||||
|
activateRequested(); event.accepted = true; return
|
||||||
|
}
|
||||||
|
if (event.text === "x" || event.text === "X") {
|
||||||
|
deleteRequested(); event.accepted = true; return
|
||||||
|
}
|
||||||
|
if (event.text && event.text.length === 1) {
|
||||||
|
textKey(event.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ ChoiceButton 1.0 ChoiceButton.qml
|
|||||||
CursorSurface 1.0 CursorSurface.qml
|
CursorSurface 1.0 CursorSurface.qml
|
||||||
KeyboardPanel 1.0 KeyboardPanel.qml
|
KeyboardPanel 1.0 KeyboardPanel.qml
|
||||||
PanelActionButton 1.0 PanelActionButton.qml
|
PanelActionButton 1.0 PanelActionButton.qml
|
||||||
|
PanelKeyCatcher 1.0 PanelKeyCatcher.qml
|
||||||
PanelSectionHeader 1.0 PanelSectionHeader.qml
|
PanelSectionHeader 1.0 PanelSectionHeader.qml
|
||||||
PanelSeparator 1.0 PanelSeparator.qml
|
PanelSeparator 1.0 PanelSeparator.qml
|
||||||
PanelSlider 1.0 PanelSlider.qml
|
PanelSlider 1.0 PanelSlider.qml
|
||||||
|
|||||||
@@ -375,37 +375,19 @@ Item {
|
|||||||
contentWidth: 370
|
contentWidth: 370
|
||||||
contentHeight: Math.min(560, panelColumn.implicitHeight + 28)
|
contentHeight: Math.min(560, panelColumn.implicitHeight + 28)
|
||||||
|
|
||||||
Item {
|
PanelKeyCatcher {
|
||||||
id: keyCatcher
|
id: keyCatcher
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
focus: true
|
onMoveRequested: function(dx, dy) {
|
||||||
Keys.priority: Keys.AfterItem
|
if (dy !== 0) root.moveCursor(dy)
|
||||||
Keys.onPressed: function(event) {
|
else if (dx !== 0) root.adjustVolume(dx * 0.05)
|
||||||
if (event.key === Qt.Key_Escape) {
|
}
|
||||||
root.closePopout()
|
onActivateRequested: root.activateCursor()
|
||||||
event.accepted = true
|
onCloseRequested: root.closePopout()
|
||||||
return
|
onTextKey: function(t) {
|
||||||
}
|
// 'm' mutes whatever the cursor is on: focused section's slider
|
||||||
if (event.key === Qt.Key_Down || event.text === "j") {
|
// for output/input, the focused stream for streams.
|
||||||
root.moveCursor(1); event.accepted = true; return
|
if (t === "m" || t === "M") {
|
||||||
}
|
|
||||||
if (event.key === Qt.Key_Up || event.text === "k") {
|
|
||||||
root.moveCursor(-1); event.accepted = true; return
|
|
||||||
}
|
|
||||||
if (event.key === Qt.Key_Right || event.text === "l") {
|
|
||||||
root.adjustVolume(0.05); event.accepted = true; return
|
|
||||||
}
|
|
||||||
if (event.key === Qt.Key_Left || event.text === "h") {
|
|
||||||
root.adjustVolume(-0.05); event.accepted = true; return
|
|
||||||
}
|
|
||||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_Space) {
|
|
||||||
root.activateCursor(); event.accepted = true; return
|
|
||||||
}
|
|
||||||
// 'm' mutes whatever the cursor is on: focused section's slider for
|
|
||||||
// output/input, the focused stream for streams. Without the stream
|
|
||||||
// branch, m would mute the global output while the cursor sat on a
|
|
||||||
// per-app row — surprising and inconsistent with Enter behaviour.
|
|
||||||
if (event.text === "m" || event.text === "M") {
|
|
||||||
if (root.focusSection === "streams" && root.selectedIndex >= 0
|
if (root.focusSection === "streams" && root.selectedIndex >= 0
|
||||||
&& root.selectedIndex < root.audioStreams.length) {
|
&& root.selectedIndex < root.audioStreams.length) {
|
||||||
var s = root.audioStreams[root.selectedIndex]
|
var s = root.audioStreams[root.selectedIndex]
|
||||||
@@ -415,7 +397,6 @@ Item {
|
|||||||
} else {
|
} else {
|
||||||
root.toggleOutputMute()
|
root.toggleOutputMute()
|
||||||
}
|
}
|
||||||
event.accepted = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -331,34 +331,16 @@ Item {
|
|||||||
contentWidth: 320
|
contentWidth: 320
|
||||||
contentHeight: column.implicitHeight + 28
|
contentHeight: column.implicitHeight + 28
|
||||||
|
|
||||||
Item {
|
PanelKeyCatcher {
|
||||||
id: keyCatcher
|
id: keyCatcher
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
focus: true
|
onMoveRequested: function(dx, dy) {
|
||||||
Keys.priority: Keys.AfterItem
|
if (dy !== 0) root.moveCursor(dy)
|
||||||
Keys.onPressed: function(event) {
|
else if (dx !== 0) root.moveCursorH(dx)
|
||||||
if (event.key === Qt.Key_Escape) {
|
|
||||||
root.closePopout(); event.accepted = true; return
|
|
||||||
}
|
|
||||||
if (event.key === Qt.Key_Down || event.text === "j") {
|
|
||||||
root.moveCursor(1); event.accepted = true; return
|
|
||||||
}
|
|
||||||
if (event.key === Qt.Key_Up || event.text === "k") {
|
|
||||||
root.moveCursor(-1); event.accepted = true; return
|
|
||||||
}
|
|
||||||
if (event.key === Qt.Key_Right || event.text === "l") {
|
|
||||||
root.moveCursorH(1); event.accepted = true; return
|
|
||||||
}
|
|
||||||
if (event.key === Qt.Key_Left || event.text === "h") {
|
|
||||||
root.moveCursorH(-1); event.accepted = true; return
|
|
||||||
}
|
|
||||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_Space) {
|
|
||||||
root.activateCursor(); event.accepted = true; return
|
|
||||||
}
|
|
||||||
if (event.text === "x" || event.text === "X") {
|
|
||||||
root.deleteSelected(); event.accepted = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
onActivateRequested: root.activateCursor()
|
||||||
|
onCloseRequested: root.closePopout()
|
||||||
|
onDeleteRequested: root.deleteSelected()
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: column
|
id: column
|
||||||
|
|||||||
@@ -552,62 +552,42 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\
|
|||||||
// Catches all unhandled keys for keyboard navigation. AfterItem priority
|
// Catches all unhandled keys for keyboard navigation. AfterItem priority
|
||||||
// lets the passphrase TextField (a child via focus chain) get its keys
|
// lets the passphrase TextField (a child via focus chain) get its keys
|
||||||
// first; only events the focused subtree ignores bubble back here.
|
// first; only events the focused subtree ignores bubble back here.
|
||||||
Item {
|
PanelKeyCatcher {
|
||||||
id: keyCatcher
|
id: keyCatcher
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
focus: true
|
// Freeze the cursor model while the inline password prompt is open;
|
||||||
Keys.priority: Keys.AfterItem
|
// the TextField inside owns input until Esc/Enter/Cancel.
|
||||||
Keys.onPressed: function(event) {
|
blocked: root.passwordSsid !== ""
|
||||||
if (root.passwordSsid !== "") return
|
|
||||||
if (event.key === Qt.Key_Escape) {
|
onMoveRequested: function(dx, dy) {
|
||||||
root.closePopout()
|
if (dy !== 0) {
|
||||||
event.accepted = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.text === "r" || event.text === "R") {
|
|
||||||
root.refresh()
|
|
||||||
event.accepted = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (root.focusSection === "dns") {
|
if (root.focusSection === "dns") {
|
||||||
if (event.key === Qt.Key_Left || event.text === "h") {
|
// j from DNS drops into the wifi list if there's anywhere to
|
||||||
root.selectDnsByDelta(-1)
|
// land; otherwise hold position so j isn't a no-op surprise.
|
||||||
event.accepted = true
|
if (dy > 0 && root.wifiNetworks.length > 0) {
|
||||||
} else if (event.key === Qt.Key_Right || event.text === "l") {
|
root.focusSection = "wifi"
|
||||||
root.selectDnsByDelta(1)
|
if (root.selectedIndex < 0) root.selectedIndex = 0
|
||||||
event.accepted = true
|
|
||||||
} else if (event.key === Qt.Key_Down || event.text === "j") {
|
|
||||||
// Drop into the wifi list if there's anything to land on;
|
|
||||||
// otherwise hold position so j isn't a no-op surprise.
|
|
||||||
if (root.wifiNetworks.length > 0) {
|
|
||||||
root.focusSection = "wifi"
|
|
||||||
if (root.selectedIndex < 0) root.selectedIndex = 0
|
|
||||||
}
|
|
||||||
event.accepted = true
|
|
||||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_Space) {
|
|
||||||
root.activateDns()
|
|
||||||
event.accepted = true
|
|
||||||
}
|
}
|
||||||
return
|
} else { // wifi
|
||||||
}
|
|
||||||
// focusSection === "wifi"
|
|
||||||
if (event.key === Qt.Key_Down || event.text === "j") {
|
|
||||||
root.selectByDelta(1)
|
|
||||||
event.accepted = true
|
|
||||||
} else if (event.key === Qt.Key_Up || event.text === "k") {
|
|
||||||
// k from the top row escapes back up into the DNS row rather
|
// k from the top row escapes back up into the DNS row rather
|
||||||
// than wrapping around to the bottom of the list.
|
// than wrapping around to the bottom of the list.
|
||||||
if (root.selectedIndex <= 0) root.focusSection = "dns"
|
if (dy < 0 && root.selectedIndex <= 0) root.focusSection = "dns"
|
||||||
else root.selectByDelta(-1)
|
else root.selectByDelta(dy)
|
||||||
event.accepted = true
|
|
||||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_Space) {
|
|
||||||
root.activateSelected()
|
|
||||||
event.accepted = true
|
|
||||||
} else if (event.text === "x" || event.text === "X") {
|
|
||||||
root.forgetSelected()
|
|
||||||
event.accepted = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (dx !== 0 && root.focusSection === "dns") root.selectDnsByDelta(dx)
|
||||||
|
}
|
||||||
|
onActivateRequested: {
|
||||||
|
if (root.focusSection === "dns") root.activateDns()
|
||||||
|
else root.activateSelected()
|
||||||
|
}
|
||||||
|
onCloseRequested: root.closePopout()
|
||||||
|
onDeleteRequested: {
|
||||||
|
if (root.focusSection === "wifi") root.forgetSelected()
|
||||||
|
}
|
||||||
|
onTextKey: function(t) {
|
||||||
|
if (t === "r" || t === "R") root.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: column
|
id: column
|
||||||
|
|||||||
Reference in New Issue
Block a user