From b59b9ec616fb5c965cfd32385879162989589bb6 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Mon, 10 Aug 2026 08:49:21 +0200 Subject: feat(ui): step by index, and give thread stepping a second binding next_thread and prev_thread now walk with indexBelow/indexAbove, skipping message rows, so they keep meaning thread-to-thread whatever is expanded. Stepping message-to-message needs no code: QTreeView's own Up/Down walk VISIBLE rows and already enter an expanded thread, and being the view's key handling rather than a shortcut they stay inert when the message pane, a menu or an entry bar has focus. Item 60 turns out to have been fixed already, in 5487d58 on this branch, by threadRowOf() walking up to the containing thread before doing the arithmetic. The backlog entry was written against master, where that helper does not exist, so it described a defect this branch had resolved a commit earlier. Verified by writing both failing tests first and watching them pass: from the last reply of an expanded thread, and from a thread root with its replies showing. They are kept, because the property they assert is the one this change must not lose. What the rewrite buys is that nothing is keyed on a row number any more, which is the rule a deeper tree would break next. Alt+Up/Down added alongside Ctrl+J/K. That required KeyMap::sequencesFor and a move from setShortcut to setShortcuts, because the singular setter keeps only the last binding and the second one was silently unreachable. Alt because Shift+arrows is the built-in extend-selection that multi-row tagging depends on, and because a bare arrow cannot be a window shortcut without breaking every text field in the window, as Return already demonstrated. sequencesFor puts sequenceFor's own choice first so the menus advertise an unchanged binding, and sorts the tail, since QHash order is unspecified. --- src/keymap.cpp | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'src/keymap.cpp') diff --git a/src/keymap.cpp b/src/keymap.cpp index 29a5f71..cdd26a1 100644 --- a/src/keymap.cpp +++ b/src/keymap.cpp @@ -18,6 +18,8 @@ #include "keymap.h" +#include + #include QStringList KeyMap::knownActions() @@ -64,6 +66,18 @@ QList> KeyMap::defaultBindings() return { { QStringLiteral("Ctrl+J"), QStringLiteral("next_thread") }, { QStringLiteral("Ctrl+K"), QStringLiteral("prev_thread") }, + // Alt, because Shift+Up/Down is QTreeView's built-in extend-selection, + // which multi-row tagging depends on, and plain Up/Down is the view's + // own navigation, which already steps INTO an expanded thread's + // replies and is what gives message-to-message movement for free. + // + // These must stay chords. Every action is a QAction with + // WindowShortcut, dispatched before the focused widget sees the key, + // and Qt withholds only plain LETTERS from editable widgets: a bare + // Up bound here would break the arrow keys in the query bar, the tag + // dialog and the web view at once, exactly as Return did. + { QStringLiteral("Alt+Down"), QStringLiteral("next_thread") }, + { QStringLiteral("Alt+Up"), QStringLiteral("prev_thread") }, { QStringLiteral("Return"), QStringLiteral("open_thread") }, { QStringLiteral("Ctrl+E"), QStringLiteral("archive") }, { QStringLiteral("Ctrl+D"), QStringLiteral("delete") }, @@ -153,6 +167,28 @@ void KeyMap::loadDefaults() m_bindings.insert(normalizeSequence(binding.first), binding.second); } +QList KeyMap::sequencesFor(const QString &action) const +{ + const QKeySequence primary = sequenceFor(action); + if (primary.isEmpty()) + return {}; + + QList all{ primary }; + QList rest; + for (auto it = m_bindings.cbegin(); it != m_bindings.cend(); ++it) { + if (it.value() == action && it.key() != primary) + rest.append(it.key()); + } + // QHash iteration order is unspecified, so the tail is sorted rather than + // left to chance: an action's shortcut list must not reorder between runs. + std::sort(rest.begin(), rest.end(), + [](const QKeySequence &a, const QKeySequence &b) { + return a.toString() < b.toString(); + }); + all += rest; + return all; +} + QKeySequence KeyMap::sequenceFor(const QString &action) const { // Several sequences can reach one action: the built-in default, which -- cgit v1.2.3