aboutsummaryrefslogtreecommitdiffstats
path: root/src/keymap.cpp
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-02 17:02:18 +0200
committerDanilo M. <danix@danix.xyz>2026-08-02 17:02:18 +0200
commitdd7845d847a3d6f961f0302551c33245d1f12bdc (patch)
treef146a0bf84bbd3229e9bf18235314de0bc5f4c5f /src/keymap.cpp
parent059fa151856293a8b6bdf9b16d1b72f415f59df3 (diff)
downloadqtmaildir-dd7845d847a3d6f961f0302551c33245d1f12bdc.tar.gz
qtmaildir-dd7845d847a3d6f961f0302551c33245d1f12bdc.zip
feat: add KeyMap with defaults and INI overrides
Maps key sequences to action name strings, with hardcoded vim-style defaults and QSettings-based [keys] overrides. Unknown actions and unparseable sequences are collected as warnings rather than treated as fatal, so a typo in the config cannot silently misbind or crash. Note: QKeySequence::fromString() on Qt 6.11 does not return an empty sequence for unparseable input (e.g. "NotAKey++") -- it returns a non-empty sequence whose toString() is empty. Detection uses that instead of isEmpty().
Diffstat (limited to 'src/keymap.cpp')
-rw-r--r--src/keymap.cpp82
1 files changed, 82 insertions, 0 deletions
diff --git a/src/keymap.cpp b/src/keymap.cpp
new file mode 100644
index 0000000..6e43f1b
--- /dev/null
+++ b/src/keymap.cpp
@@ -0,0 +1,82 @@
+#include "keymap.h"
+
+#include <QSettings>
+
+QStringList KeyMap::knownActions()
+{
+ // Keep in sync with the actions MainWindow registers.
+ return {
+ QStringLiteral("next_thread"),
+ QStringLiteral("prev_thread"),
+ QStringLiteral("open_thread"),
+ QStringLiteral("archive"),
+ QStringLiteral("delete"),
+ QStringLiteral("spam"),
+ QStringLiteral("toggle_unread"),
+ QStringLiteral("flag"),
+ QStringLiteral("focus_query"),
+ QStringLiteral("toggle_html"),
+ QStringLiteral("load_remote"),
+ QStringLiteral("undo"),
+ QStringLiteral("sync"),
+ QStringLiteral("quit"),
+ };
+}
+
+void KeyMap::loadDefaults()
+{
+ const QHash<QString, QString> defaults = {
+ { QStringLiteral("j"), QStringLiteral("next_thread") },
+ { QStringLiteral("k"), QStringLiteral("prev_thread") },
+ { QStringLiteral("Return"), QStringLiteral("open_thread") },
+ { QStringLiteral("a"), QStringLiteral("archive") },
+ { QStringLiteral("d"), QStringLiteral("delete") },
+ { QStringLiteral("N"), QStringLiteral("toggle_unread") },
+ { QStringLiteral("F"), QStringLiteral("flag") },
+ { QStringLiteral("/"), QStringLiteral("focus_query") },
+ { QStringLiteral("h"), QStringLiteral("toggle_html") },
+ { QStringLiteral("u"), QStringLiteral("undo") },
+ { QStringLiteral("G"), QStringLiteral("sync") },
+ { QStringLiteral("Ctrl+Q"), QStringLiteral("quit") },
+ };
+
+ for (auto it = defaults.cbegin(); it != defaults.cend(); ++it)
+ m_bindings.insert(QKeySequence::fromString(it.key()), it.value());
+}
+
+void KeyMap::loadOverrides(QSettings &settings)
+{
+ const QStringList known = knownActions();
+
+ settings.beginGroup(QStringLiteral("keys"));
+ const QStringList keys = settings.childKeys();
+ for (const QString &key : keys) {
+ const QString action = settings.value(key).toString();
+
+ const QKeySequence sequence = QKeySequence::fromString(key);
+ // QKeySequence::fromString() does not return an empty sequence for
+ // unparseable input; it returns a non-empty sequence whose
+ // toString() is empty (verified on Qt 6.11). Use that to detect
+ // garbage input instead.
+ if (sequence.isEmpty() || sequence.toString().isEmpty()) {
+ m_warnings.append(
+ QStringLiteral("Unparseable key sequence '%1' in [keys]").arg(key));
+ continue;
+ }
+
+ if (!known.contains(action)) {
+ m_warnings.append(
+ QStringLiteral("Unknown action '%1' bound to '%2' in [keys]")
+ .arg(action, key));
+ continue;
+ }
+
+ m_bindings.insert(sequence, action);
+ }
+ settings.endGroup();
+}
+
+QString KeyMap::actionFor(const QKeySequence &sequence) const
+{
+ return m_bindings.value(sequence);
+}