presentation/QuickCommandRouter.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | #include "app/CommandRegistry.h" | ||
| 3 | #include "CommandCompletion.h" | ||
| 4 | #include "CommandHistory.h" | ||
| 5 | #include "CommandBarRowModel.h" | ||
| 6 | #include "KeyOverlayController.h" | ||
| 7 | #include <QAbstractListModel> | ||
| 8 | #include <QKeyEvent> | ||
| 9 | #include <QPointer> | ||
| 10 | #include <QSet> | ||
| 11 | #include <QElapsedTimer> | ||
| 12 | #include <QTimer> | ||
| 13 | #include <QVariantMap> | ||
| 14 | |||
| 15 | class QQuickItem; | ||
| 16 | class QQuickWindow; | ||
| 17 | class WorkspaceState; | ||
| 18 | |||
| 19 | // One instance per window. Shared specs; no second shortcut definition in QML. | ||
| 20 | class QuickCommandRouter final : public QAbstractListModel { | ||
| 21 | Q_OBJECT | ||
| 22 | Q_PROPERTY(QString query READ query WRITE setQuery NOTIFY queryChanged) | ||
| 23 | Q_PROPERTY(QVariantMap availability READ availability NOTIFY availabilityChanged) | ||
| 24 | // Sprint 95: the rule manager page switches the mail area to the Rules context. | ||
| 25 | Q_PROPERTY(bool rulesOpen READ rulesOpen WRITE setRulesOpen NOTIFY rulesOpenChanged) | ||
| 26 | // Sprint 98 G8: the rows of the command bar's panel, updated in place. | ||
| 27 | Q_PROPERTY(CommandBarRowModel *rowModel READ rowModel CONSTANT) | ||
| 28 | // Sprint 105: the keys overlay of this window (S105.8.5). | ||
| 29 | Q_PROPERTY(KeyOverlayController *overlay READ overlay CONSTANT) | ||
| 30 | // S105.7.3: a pending prefix ("g") and what may follow ([{key, text}]). | ||
| 31 | Q_PROPERTY(QString pendingPrefix READ pendingPrefix NOTIFY prefixChanged) | ||
| 32 | Q_PROPERTY(QVariantList prefixChoices READ prefixChoices NOTIFY prefixChanged) | ||
| 33 | public: | ||
| 34 | explicit QuickCommandRouter(QObject *parent = nullptr); | ||
| 35 | 1993 | CommandBarRowModel *rowModel() { return &m_rowModel; } | |
| 36 | 3417 | KeyOverlayController *overlay() { return m_overlay; } | |
| 37 | QQuickWindow *window() const; | ||
| 38 | QString pendingPrefix() const; | ||
| 39 | QVariantList prefixChoices() const; | ||
| 40 | // Sprint 105 rule 3: the key that runs `id` now, with the current focus | ||
| 41 | // (empty when no key does) — the overlay's badges come from here. | ||
| 42 | QKeySequence effectiveKey(const QString &id) const; | ||
| 43 | Q_INVOKABLE QString effectiveKeyText(const QString &id) const; | ||
| 44 | // S105.9.4: windows this router does not filter (a compose or editor | ||
| 45 | // window of its own) bind their keys with Shortcuts; their controls show | ||
| 46 | // the command's own key (`keyTextOf`) instead of the effective one. | ||
| 47 | Q_INVOKABLE bool filtersWindow(QObject *window) const; | ||
| 48 | Q_INVOKABLE QString keyTextOf(const QString &id) const; | ||
| 49 | // S105.6.4: the command's explanation in the interface language ("" = none). | ||
| 50 | Q_INVOKABLE QString explanationFor(const QString &id) const; | ||
| 51 | // A text field (not a read-only reader) has the focus: only keys marked | ||
| 52 | // whileTyping act (S105.7.4). | ||
| 53 | bool typingFocus() const; | ||
| 54 | // The name of the place the keys act on ("Mail list", "Calendar" …). | ||
| 55 | QString contextTitle() const; | ||
| 56 | // D-15: F6 / Shift+F6 — the next or previous pane (KeyHint.pane). | ||
| 57 | Q_INVOKABLE bool focusPane(int delta); | ||
| 58 | // D-25: F6–F9 — the visible pane with this KeyHint.paneOrder (the | ||
| 59 | // leftmost when several); false when the area shows none. | ||
| 60 | Q_INVOKABLE bool focusPaneAt(int order); | ||
| 61 | // D-25: the command (F6–F9) that focuses panes of this order. | ||
| 62 | static QString paneCommand(int order); | ||
| 63 | // KeyHint.context names → focus contexts ("agenda", "settingsPages", "compose"). | ||
| 64 | static CommandContexts focusContextOf(const QString &name); | ||
| 65 | // Sprint 107: a sheet whose focus context replaces the area's (the | ||
| 66 | // conflict resolver) — while it has the focus only its keys act and the | ||
| 67 | // keys overlay annotates only the sheet (the item returned here). | ||
| 68 | 14309486 | static bool exclusiveContext(CommandContexts context) { return context.testFlag(CommandContext::Resolver); } | |
| 69 | QQuickItem *exclusiveScope() const; | ||
| 70 | // The keys a pane's focus context adds while it has the focus (the | ||
| 71 | // agenda's j/k/Enter) — empty when that pane has the focus already. | ||
| 72 | QList<const CommandSpec *> paneCommands(const QString &context) const; | ||
| 73 | // G10 (S105.8.8): tests press every key the overlay shows and check what | ||
| 74 | // would run — a dry run reports the command instead of running it. | ||
| 75 | 122 | void setDryRun(bool dryRun) { m_dryRun = dryRun; } | |
| 76 | int rowCount(const QModelIndex &parent = {}) const override; | ||
| 77 | QVariant data(const QModelIndex &index, int role) const override; | ||
| 78 | QHash<int, QByteArray> roleNames() const override; | ||
| 79 | 1 | QString query() const { return m_query; } | |
| 80 | void setQuery(const QString &query); | ||
| 81 | void attach(QQuickWindow *window); | ||
| 82 | void setContext(CommandContext context); | ||
| 83 | void bindWorkspace(WorkspaceState *workspace); | ||
| 84 | void setSupported(const QSet<QString> &commands); | ||
| 85 | void setNamedSupported(const QSet<QString> &commands); | ||
| 86 | 831 | bool rulesOpen() const { return m_rulesOpen; } | |
| 87 | void setRulesOpen(bool open); | ||
| 88 | // Sprint 98 S98.1.4: the command bar's completion for `text` with the | ||
| 89 | // cursor at `cursor`; `states` maps toggle state keys to booleans (the | ||
| 90 | // window owns some of them, e.g. the list filters). The result's | ||
| 91 | // fields are documented in CommandCompletion.h. | ||
| 92 | Q_INVOKABLE QVariantMap completions(const QString &text, int cursor, const QVariantMap &states = {}) const; | ||
| 93 | // S98.4.8: the closest command line for a misspelled one ("" = none). | ||
| 94 | Q_INVOKABLE QString correction(const QString &text) const; | ||
| 95 | // The host installs the data sources (folders, labels, availability …); | ||
| 96 | // without them the bar completes command names as a design preview. | ||
| 97 | void setCompletionSources(const CommandCompletion::Sources &sources); | ||
| 98 | // S98.1.6 (D-9): after a successful run; free-text lines are refused. | ||
| 99 | Q_INVOKABLE bool recordCommand(const QString &line); | ||
| 100 | Q_INVOKABLE void clearHistory(); | ||
| 101 | // S98.5.2: Undo of the last clearHistory() (false when there is none). | ||
| 102 | Q_INVOKABLE bool restoreHistory(); | ||
| 103 | 21 | Q_INVOKABLE int historySize() const { return int(m_history.lines().size()); } | |
| 104 | 4 | Q_INVOKABLE QStringList historyLines() const { return m_history.lines(); } | |
| 105 | // D-20: folder switches and move targets of an account. | ||
| 106 | void recordFolder(const QString &account, const QString &path); | ||
| 107 | 1116 | CommandHistory &history() { return m_history; } | |
| 108 | // Sprint 98 S98.1.3: what a named line runs — {command, argument, value, | ||
| 109 | // values, words, rest, registryId, stateKey, complete, syntax, inContext} | ||
| 110 | // from the catalog (empty when the first word names no command); values | ||
| 111 | // and words list the parts of a line with several of them (D-15a). | ||
| 112 | Q_INVOKABLE QVariantMap namedTarget(const QString &text) const; | ||
| 113 | Q_INVOKABLE bool canInvokeNamed(const QString &text) const; | ||
| 114 | Q_INVOKABLE QString namedCommandName(const QString &text) const; | ||
| 115 | Q_INVOKABLE bool invokeNamed(const QString &text); | ||
| 116 | QVariantMap availability() const; | ||
| 117 | Q_INVOKABLE bool isAvailable(const QString &id) const; | ||
| 118 | Q_INVOKABLE void cancelSequence(); | ||
| 119 | Q_INVOKABLE bool invoke(const QString &id); | ||
| 120 | // True only while a command runs from its keyboard shortcut (Sprint 97: | ||
| 121 | // Ctrl+K focuses the contact search, the area button and the console do not). | ||
| 122 | 29 | Q_INVOKABLE bool invokedFromKeyboard() const { return m_fromKeyboard; } | |
| 123 | Q_INVOKABLE QString defaultShortcutFor(const QString &id) const; | ||
| 124 | Q_INVOKABLE bool invokeRow(int row); | ||
| 125 | // S105.9.5: recompute "On this screen" (the help opens). | ||
| 126 | 7 | Q_INVOKABLE void refreshHelp() { rebuild(); } | |
| 127 | Q_INVOKABLE QString commandAt(int row) const; | ||
| 128 | Q_INVOKABLE QVariantMap entryAt(int row) const; | ||
| 129 | signals: | ||
| 130 | void invoked(const QString &id); | ||
| 131 | void namedInvoked(const QString &command, const QString &argument); | ||
| 132 | void queryChanged(); | ||
| 133 | void availabilityChanged(); | ||
| 134 | void rulesOpenChanged(); | ||
| 135 | void historyChanged(); | ||
| 136 | void prefixChanged(); | ||
| 137 | // D-23: a key that means something elsewhere (once per key and session). | ||
| 138 | void keyHintRequested(const QString &text); | ||
| 139 | // D-15: the pane F6 moved to (for the status line). | ||
| 140 | void paneFocused(const QString &name); | ||
| 141 | void dryRunInvoked(const QString &id); | ||
| 142 | public: | ||
| 143 | // F-32 (SPRINT-104): the key event's effective shortcut — the | ||
| 144 | // KeypadModifier is stripped and an unmodified letter matches its | ||
| 145 | // physical (US-layout) key, so keypad keys and non-Latin layouts | ||
| 146 | // trigger the same single-key shortcuts. | ||
| 147 | static QKeySequence normalizedShortcut(const QKeyEvent *key); | ||
| 148 | |||
| 149 | protected: | ||
| 150 | bool eventFilter(QObject *watched, QEvent *event) override; | ||
| 151 | private: | ||
| 152 | bool invokeFromKeyboard(const QString &id); | ||
| 153 | void rebuild(); | ||
| 154 | void updateWorkspaceContext(); | ||
| 155 | void notifyAvailability(); | ||
| 156 | QString m_query; | ||
| 157 | QList<const CommandSpec *> m_commands; | ||
| 158 | int m_screenRows = 0; // the leading "On this screen" rows of m_commands | ||
| 159 | QPointer<QQuickWindow> m_window; | ||
| 160 | QSet<QString> m_supported; | ||
| 161 | QSet<QString> m_namedSupported; | ||
| 162 | QPointer<WorkspaceState> m_workspace; | ||
| 163 | const CommandSpec *commandFor(const QKeySequence &sequence, bool typing) const; | ||
| 164 | CommandContexts activeContexts() const; | ||
| 165 | bool isPrefix(const QKeySequence &sequence) const; | ||
| 166 | void updateFocusContext(); | ||
| 167 | void showWrongKeyHint(const QKeySequence &sequence); | ||
| 168 | bool handleKeyPress(QKeyEvent *key); | ||
| 169 | QKeySequence m_prefix; | ||
| 170 | QTimer m_prefixTimeout; | ||
| 171 | QSet<int> m_hintedKeys; | ||
| 172 | CommandContext m_context = CommandContext::MainMail; | ||
| 173 | CommandContexts m_focusContext; | ||
| 174 | KeyOverlayController *m_overlay = nullptr; | ||
| 175 | bool m_dryRun = false; | ||
| 176 | bool m_rulesOpen = false; | ||
| 177 | bool m_fromKeyboard = false; | ||
| 178 | CommandCompletion::Sources m_sources; | ||
| 179 | CommandHistory m_history; | ||
| 180 | CommandBarRowModel m_rowModel{this}; // parented: QML never takes ownership | ||
| 181 | QVariantMap m_clearedHistory; | ||
| 182 | bool m_sourcesInstalled = false; | ||
| 183 | CommandCompletion::Sources effectiveSources(const QVariantMap &states) const; | ||
| 184 | }; | ||
| 185 |