diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-02 17:02:18 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-02 17:02:18 +0200 |
| commit | dd7845d847a3d6f961f0302551c33245d1f12bdc (patch) | |
| tree | f146a0bf84bbd3229e9bf18235314de0bc5f4c5f /src | |
| parent | 059fa151856293a8b6bdf9b16d1b72f415f59df3 (diff) | |
| download | qtmaildir-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')
| -rw-r--r-- | src/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/keymap.cpp | 82 | ||||
| -rw-r--r-- | src/keymap.h | 32 | ||||
| -rw-r--r-- | src/main_placeholder.cpp | 3 |
4 files changed, 115 insertions, 4 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e9e28e5..aeb425f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_library(qtmaildir_lib STATIC - main_placeholder.cpp + keymap.cpp ) target_include_directories(qtmaildir_lib 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); +} diff --git a/src/keymap.h b/src/keymap.h new file mode 100644 index 0000000..3f0f85c --- /dev/null +++ b/src/keymap.h @@ -0,0 +1,32 @@ +#pragma once + +#include <QHash> +#include <QKeySequence> +#include <QStringList> + +class QSettings; + +/// Maps key sequences to action names. Action names are plain strings so this +/// class has no dependency on the widgets that implement the actions. +class KeyMap +{ +public: + /// Every action name the application understands. loadOverrides() rejects + /// anything not in this set, so a typo in the config cannot bind silently. + static QStringList knownActions(); + + void loadDefaults(); + + /// Reads the [keys] group. Invalid sequences and unknown action names are + /// collected into warnings() rather than throwing or aborting. + void loadOverrides(QSettings &settings); + + /// Empty string when nothing is bound. + QString actionFor(const QKeySequence &sequence) const; + + QStringList warnings() const { return m_warnings; } + +private: + QHash<QKeySequence, QString> m_bindings; + QStringList m_warnings; +}; diff --git a/src/main_placeholder.cpp b/src/main_placeholder.cpp deleted file mode 100644 index 9ddb3f2..0000000 --- a/src/main_placeholder.cpp +++ /dev/null @@ -1,3 +0,0 @@ -// Placeholder so the library target has a source file before real code lands. -// Removed in Task 2. -namespace { int qtmaildir_placeholder = 0; } |
