merge: integrate PR #724 hotkeys and Vim keymap

Resolve translation conflicts against pre-release and harden miniplayer dispatch, direct-output volume locks, preset collision state, and Spanish labels during review.

cx
This commit is contained in:
vicrodh committed 2026-08-28 07:02:33 -06:00
commit e993bc1e4d
16 files changed
+905 -78

No files matched your search

+8
View File
@@ -146,6 +146,14 @@ Rectangle {
Connections {
target: QbzHotkeys
function onFocusSearchRequested() { header.focusSearch() }
// playback.favorite — the player bar's own heart call, empty-id guard
// included. The now-playing track id is a QbzPlayer property, so the
// Rust dispatch signals and QML answers rather than resolving the id
// twice (PlayerBar.qml:769-773).
function onFavoriteRequested() {
var id = QbzPlayer.npTrackId
if (id !== "") QbzQueue.queueToggleFavorite("track", id)
}
}
// The §4.6 Ctrl+A + multi-select Escape-exit seam. Selection state is
@@ -197,6 +197,61 @@ Item {
font.weight: theme.weightMedium
}
}
// Keymap preset. Switching swaps the BASE shortcut table
// only — user overrides are kept and keep winning over it
// (hotkeys_qt::active_bindings_with), so Default <-> Vim is
// lossless. `keymapIndex` is republished with the groups, so
// the selection and the keycaps cannot disagree.
Row {
id: keymapPicker
anchors.right: closeX.left
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
spacing: 4
Text {
anchors.verticalCenter: parent.verticalCenter
rightPadding: 6
text: root.t("Keymap")
color: theme.textMuted
font.pixelSize: theme.fontLegal
}
Repeater {
model: [{ "label": root.t("Default"), "index": 0 },
{ "label": root.t("Vim"), "index": 1 }]
delegate: Rectangle {
id: seg
required property var modelData
readonly property bool current:
QbzHotkeys.keymapIndex === seg.modelData.index
width: segLabel.implicitWidth + 20
height: 26
radius: theme.radiusSm
color: seg.current
? theme.accent
: (segArea.containsMouse ? theme.surfaceHover
: "transparent")
border.width: 1
border.color: seg.current ? theme.accent
: theme.borderSubtle
Text {
id: segLabel
anchors.centerIn: parent
text: seg.modelData.label
color: seg.current ? theme.accentText
: theme.textPrimary
font.pixelSize: theme.fontLegal
font.weight: theme.weightMedium
}
MouseArea {
id: segArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: QbzHotkeys.setKeymap(seg.modelData.index)
}
}
}
}
Item {
id: closeX
anchors.right: parent.right
+20 -2
View File
@@ -871,9 +871,27 @@ Rectangle {
}
event.accepted = true
} else if (event.key === Qt.Key_Escape) {
if (QbzSearch.cortinillaOpen) {
// Escape must ALWAYS hand the keyboard back. With the
// dropdown shut this was a no-op, and since the central
// gate refuses every binding while a text input holds
// focus (AppShell.qml:133-139), the field was a trap with
// no keyboard way out — reachable from Ctrl+f, and one
// keystroke away under the Vim keymap's `/`.
//
// Focus is handed to the shell root by the same duck-walk
// the modals use, not merely cleared: a null
// activeFocusItem passes the gate but leaves AppShell's
// Keys handler receiving nothing at all.
if (QbzSearch.cortinillaOpen)
QbzSearch.cortinillaDismiss()
event.accepted = true
event.accepted = true
var p = searchInput
while (p.parent) {
if (p.parent.isQbzShellRoot === true) {
p.parent.forceActiveFocus()
return
}
p = p.parent
}
}
}