presentation/QuickCommandRouter.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "QuickCommandRouter.h" | ||
| 2 | #include <QScopedValueRollback> | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include "WorkspaceState.h" | ||
| 6 | #include "app/NamedCommandCatalog.h" | ||
| 7 | #include "GraphLinesItem.h" | ||
| 8 | #include "TextMarksItem.h" | ||
| 9 | #include "KeyHint.h" | ||
| 10 | #include <QCoreApplication> | ||
| 11 | #include <QKeyEvent> | ||
| 12 | #include <QLoggingCategory> | ||
| 13 | #include <QQuickItem> | ||
| 14 | #include <QQuickWindow> | ||
| 15 | |||
| 16 |
4/7✓ Branch 2 → 3 taken 18 times.
✓ Branch 2 → 8 taken 473 times.
✓ Branch 4 → 5 taken 18 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 18 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
|
491 | Q_LOGGING_CATEGORY(lcKeys, "mailjd.keys", QtWarningMsg) |
| 17 | |||
| 18 | namespace { | ||
| 19 | constexpr int kPrefixWindowMs = 1000; // D-14: one window for every "g …" | ||
| 20 | } // namespace | ||
| 21 | |||
| 22 |
4/4✓ Branch 9 → 10 taken 874 times.
✓ Branch 10 → 11 taken 874 times.
✓ Branch 13 → 14 taken 874 times.
✓ Branch 15 → 16 taken 874 times.
|
874 | QuickCommandRouter::QuickCommandRouter(QObject *parent) : QAbstractListModel(parent) { |
| 23 | // The QML module of the keys overlay must exist before any QML loads. | ||
| 24 |
1/1✓ Branch 17 → 18 taken 874 times.
|
874 | KeyHint::registerTypes(); |
| 25 |
1/1✓ Branch 18 → 19 taken 874 times.
|
874 | GraphLinesItem::registerTypes(); // Sprint 105 S105.4.2: MailJD.Graph |
| 26 |
1/1✓ Branch 19 → 20 taken 874 times.
|
874 | TextMarksItem::registerTypes(); // F108-20: MailJD.Text (wavy marks) |
| 27 |
3/6✓ Branch 20 → 21 taken 874 times.
✓ Branch 21 → 22 taken 874 times.
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 24 taken 874 times.
✗ Branch 30 → 31 not taken.
✗ Branch 30 → 32 not taken.
|
874 | m_overlay = new KeyOverlayController(this); |
| 28 |
1/1✓ Branch 24 → 25 taken 874 times.
|
874 | m_prefixTimeout.setSingleShot(true); |
| 29 |
1/1✓ Branch 25 → 26 taken 874 times.
|
874 | m_prefixTimeout.setInterval(kPrefixWindowMs); |
| 30 |
1/1✓ Branch 26 → 27 taken 874 times.
|
874 | connect(&m_prefixTimeout, &QTimer::timeout, this, &QuickCommandRouter::cancelSequence); |
| 31 |
1/1✓ Branch 28 → 29 taken 874 times.
|
874 | rebuild(); |
| 32 | 874 | } | |
| 33 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 22303 times.
|
22304 | int QuickCommandRouter::rowCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : m_commands.size(); } |
| 34 | 3971 | QString QuickCommandRouter::defaultShortcutFor(const QString &id) const { | |
| 35 | 3971 | const auto *command = CommandRegistry::find(id); | |
| 36 |
1/2✓ Branch 5 → 6 taken 3969 times.
✗ Branch 5 → 8 not taken.
|
3969 | return command && !command->defaultKeys.isEmpty() |
| 37 |
2/2✓ Branch 3 → 4 taken 3969 times.
✓ Branch 3 → 8 taken 2 times.
|
7940 | ? command->defaultKeys.first().toString(QKeySequence::PortableText) : QString{}; |
| 38 | } | ||
| 39 | 887 | QHash<int, QByteArray> QuickCommandRouter::roleNames() const { | |
| 40 | ✗ | return {{Qt::UserRole, "commandId"}, {Qt::UserRole + 1, "description"}, | |
| 41 | ✗ | {Qt::UserRole + 2, "keys"}, {Qt::UserRole + 3, "available"}, {Qt::UserRole + 4, "category"}, | |
| 42 |
2/4✓ Branch 11 → 12 taken 6209 times.
✓ Branch 11 → 13 taken 887 times.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
|
7096 | {Qt::UserRole + 5, "changedKey"}, {Qt::UserRole + 6, "explanation"}}; |
| 43 | 887 | } | |
| 44 | 9223 | QVariant QuickCommandRouter::data(const QModelIndex &index, int role) const { | |
| 45 |
6/8✓ Branch 3 → 4 taken 9218 times.
✓ Branch 3 → 9 taken 5 times.
✓ Branch 5 → 6 taken 9218 times.
✗ Branch 5 → 9 not taken.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 9218 times.
✓ Branch 11 → 12 taken 5 times.
✓ Branch 11 → 13 taken 9218 times.
|
9223 | if (!index.isValid() || index.row() < 0 || index.row() >= m_commands.size()) return {}; |
| 46 | 9218 | const auto &command = *m_commands[index.row()]; | |
| 47 |
8/8✓ Branch 15 → 16 taken 2 times.
✓ Branch 15 → 17 taken 1201 times.
✓ Branch 15 → 21 taken 1201 times.
✓ Branch 15 → 25 taken 2653 times.
✓ Branch 15 → 27 taken 1766 times.
✓ Branch 15 → 34 taken 1197 times.
✓ Branch 15 → 35 taken 1197 times.
✓ Branch 15 → 39 taken 1 time.
|
9218 | switch (role) { |
| 48 | 2 | case Qt::UserRole: return command.id; | |
| 49 |
1/1✓ Branch 17 → 18 taken 1201 times.
|
1201 | case Qt::UserRole + 1: return CommandRegistry::translatedDescription(command); |
| 50 |
1/1✓ Branch 21 → 22 taken 1201 times.
|
1201 | case Qt::UserRole + 2: return CommandRegistry::keyText(command); |
| 51 | 2653 | case Qt::UserRole + 3: return isAvailable(command.id); | |
| 52 | // S105.9.5: the help opens on the keys that work here and now. | ||
| 53 |
4/4✓ Branch 28 → 29 taken 394 times.
✓ Branch 28 → 30 taken 1372 times.
✓ Branch 29 → 31 taken 394 times.
✓ Branch 30 → 31 taken 1372 times.
|
3532 | case Qt::UserRole + 4: return index.row() < m_screenRows ? QCoreApplication::translate("QuickCommandRouter", "On this screen") |
| 54 | 1766 | : CommandRegistry::translatedCategory(command); | |
| 55 | 1197 | case Qt::UserRole + 5: return command.changedKey; | |
| 56 |
1/1✓ Branch 35 → 36 taken 1197 times.
|
1197 | case Qt::UserRole + 6: return CommandRegistry::translatedExplanation(command); |
| 57 | 1 | default: return {}; | |
| 58 | } | ||
| 59 | } | ||
| 60 | 904 | void QuickCommandRouter::rebuild() { | |
| 61 |
1/1✓ Branch 2 → 3 taken 904 times.
|
904 | beginResetModel(); |
| 62 |
1/1✓ Branch 3 → 4 taken 904 times.
|
904 | m_commands.clear(); |
| 63 | // F93-15: the help overlay searches id, description, key and category; | ||
| 64 | // every word of the query has to match somewhere. Grouped by category | ||
| 65 | // (stable, registry order inside a category) for the section headers. | ||
| 66 |
1/1✓ Branch 7 → 8 taken 904 times.
|
904 | const QStringList words = m_query.split(QLatin1Char(' '), Qt::SkipEmptyParts); |
| 67 |
3/3✓ Branch 8 → 9 taken 904 times.
✓ Branch 51 → 11 taken 185320 times.
✓ Branch 51 → 52 taken 904 times.
|
186224 | for (const auto &command : CommandRegistry::specs()) { |
| 68 |
3/3✓ Branch 18 → 19 taken 185320 times.
✓ Branch 21 → 22 taken 185320 times.
✓ Branch 22 → 23 taken 185320 times.
|
370640 | const QString haystack = command.id + QLatin1Char(' ') + CommandRegistry::translatedDescription(command) + |
| 69 |
4/4✓ Branch 15 → 16 taken 185320 times.
✓ Branch 23 → 24 taken 185320 times.
✓ Branch 24 → 25 taken 185320 times.
✓ Branch 25 → 26 taken 185320 times.
|
741280 | QLatin1Char(' ') + CommandRegistry::keyText(command) + QLatin1Char(' ') + |
| 70 |
2/2✓ Branch 12 → 13 taken 185320 times.
✓ Branch 26 → 27 taken 185320 times.
|
555960 | CommandRegistry::translatedCategory(command); |
| 71 | 185320 | bool matches = true; | |
| 72 |
7/7✓ Branch 38 → 39 taken 4364 times.
✓ Branch 38 → 42 taken 1581 times.
✓ Branch 39 → 40 taken 4364 times.
✓ Branch 40 → 41 taken 442 times.
✓ Branch 40 → 42 taken 3922 times.
✓ Branch 45 → 37 taken 5945 times.
✓ Branch 45 → 46 taken 185320 times.
|
191265 | for (const auto &word : words) matches = matches && haystack.contains(word, Qt::CaseInsensitive); |
| 73 |
3/3✓ Branch 46 → 47 taken 181398 times.
✓ Branch 46 → 48 taken 3922 times.
✓ Branch 47 → 48 taken 181398 times.
|
185320 | if (matches) m_commands.append(&command); |
| 74 | 185320 | } | |
| 75 | 904 | QStringList order; | |
| 76 |
4/4✓ Branch 52 → 53 taken 904 times.
✓ Branch 53 → 54 taken 904 times.
✓ Branch 68 → 55 taken 181398 times.
✓ Branch 68 → 69 taken 904 times.
|
182302 | for (const auto *command : m_commands) |
| 77 |
5/5✓ Branch 57 → 58 taken 181398 times.
✓ Branch 60 → 61 taken 14183 times.
✓ Branch 60 → 66 taken 167215 times.
✓ Branch 62 → 63 taken 14183 times.
✓ Branch 63 → 64 taken 14183 times.
|
181398 | if (!order.contains(QString::fromLatin1(command->category))) order.append(QString::fromLatin1(command->category)); |
| 78 |
3/3✓ Branch 69 → 70 taken 904 times.
✓ Branch 70 → 71 taken 904 times.
✓ Branch 71 → 72 taken 904 times.
|
904 | std::stable_sort(m_commands.begin(), m_commands.end(), [&order](const CommandSpec *a, const CommandSpec *b) { |
| 79 |
2/2✓ Branch 3 → 4 taken 783644 times.
✓ Branch 6 → 7 taken 783644 times.
|
783644 | return order.indexOf(QString::fromLatin1(a->category)) < order.indexOf(QString::fromLatin1(b->category)); |
| 80 | }); | ||
| 81 | // S105.9.5: without a query, "On this screen" first — the commands | ||
| 82 | // with a key that acts here now (the overlay's content as text). | ||
| 83 | 904 | QList<const CommandSpec *> screen; | |
| 84 |
2/2✓ Branch 73 → 74 taken 883 times.
✓ Branch 73 → 92 taken 21 times.
|
904 | if (words.isEmpty()) |
| 85 |
2/2✓ Branch 90 → 77 taken 181015 times.
✓ Branch 90 → 91 taken 883 times.
|
181898 | for (const auto *command : std::as_const(m_commands)) |
| 86 |
9/13✓ Branch 78 → 79 taken 181015 times.
✗ Branch 78 → 83 not taken.
✓ Branch 79 → 80 taken 181015 times.
✓ Branch 80 → 81 taken 181015 times.
✓ Branch 81 → 82 taken 601 times.
✓ Branch 81 → 83 taken 180414 times.
✓ Branch 84 → 85 taken 181015 times.
✗ Branch 84 → 86 not taken.
✓ Branch 86 → 87 taken 601 times.
✓ Branch 86 → 88 taken 180414 times.
✓ Branch 87 → 88 taken 601 times.
✗ Branch 146 → 147 not taken.
✗ Branch 146 → 148 not taken.
|
181015 | if (command->showInHelp && !effectiveKey(command->id).isEmpty()) screen.append(command); |
| 87 | 904 | m_screenRows = int(screen.size()); | |
| 88 |
1/1✓ Branch 93 → 94 taken 904 times.
|
904 | m_commands = screen + m_commands; |
| 89 |
1/1✓ Branch 96 → 97 taken 904 times.
|
904 | endResetModel(); |
| 90 | 904 | } | |
| 91 | 34 | void QuickCommandRouter::setQuery(const QString &query) { | |
| 92 |
2/2✓ Branch 3 → 4 taken 11 times.
✓ Branch 3 → 5 taken 23 times.
|
34 | if (m_query == query) return; |
| 93 |
1/1✓ Branch 5 → 6 taken 23 times.
|
23 | m_query = query.left(1024); |
| 94 | 23 | rebuild(); | |
| 95 | 23 | emit queryChanged(); | |
| 96 | } | ||
| 97 | 842 | void QuickCommandRouter::setSupported(const QSet<QString> &commands) { | |
| 98 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 841 times.
|
842 | if (m_supported == commands) return; |
| 99 | 841 | m_supported = commands; | |
| 100 | 841 | cancelSequence(); | |
| 101 | 841 | notifyAvailability(); | |
| 102 | } | ||
| 103 | 833 | void QuickCommandRouter::setNamedSupported(const QSet<QString> &commands) { | |
| 104 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 833 times.
|
833 | if (m_namedSupported == commands) return; |
| 105 | 833 | m_namedSupported = commands; | |
| 106 | 833 | notifyAvailability(); | |
| 107 | } | ||
| 108 | 2293 | QVariantMap QuickCommandRouter::namedTarget(const QString &text) const { | |
| 109 |
1/1✓ Branch 2 → 3 taken 2293 times.
|
2293 | const auto parsed = NamedCommandCatalog::parse(text); |
| 110 |
2/2✓ Branch 3 → 4 taken 951 times.
✓ Branch 3 → 5 taken 1342 times.
|
2293 | if (!parsed.spec) return {}; |
| 111 | 1342 | QStringList values; // D-15a: "filter unread important" → unread, important | |
| 112 |
7/7✓ Branch 5 → 6 taken 1342 times.
✓ Branch 6 → 7 taken 1342 times.
✓ Branch 7 → 8 taken 1342 times.
✓ Branch 11 → 12 taken 348 times.
✓ Branch 12 → 13 taken 348 times.
✓ Branch 16 → 9 taken 348 times.
✓ Branch 16 → 17 taken 1342 times.
|
1690 | for (const auto *value : parsed.values()) values << QString::fromLatin1(value->word); |
| 113 | 1342 | return {{"command", parsed.spec->name}, {"argument", parsed.argument}, | |
| 114 | 1342 | {"value", values.value(0)}, {"values", values}, {"words", parsed.words()}, | |
| 115 | 1342 | {"rest", parsed.rest()}, {"registryId", parsed.registryId()}, | |
| 116 |
1/1✓ Branch 31 → 33 taken 38 times.
|
2684 | {"stateKey", parsed.spec->stateKey ? QString::fromLatin1(parsed.spec->stateKey) : QString{}}, |
| 117 |
1/1✓ Branch 37 → 38 taken 1342 times.
|
2684 | {"complete", parsed.complete()}, {"syntax", QString::fromUtf8(parsed.spec->syntax)}, |
| 118 |
2/4✓ Branch 43 → 44 taken 14762 times.
✓ Branch 43 → 45 taken 1342 times.
✗ Branch 63 → 64 not taken.
✗ Branch 63 → 65 not taken.
|
16104 | {"inContext", parsed.spec->contexts.testFlag(m_context)}}; |
| 119 | 6319 | } | |
| 120 | 765 | void QuickCommandRouter::setCompletionSources(const CommandCompletion::Sources &sources) { | |
| 121 | 765 | m_sources = sources; | |
| 122 | 765 | m_sourcesInstalled = true; | |
| 123 | 765 | } | |
| 124 | 1075 | CommandCompletion::Sources QuickCommandRouter::effectiveSources(const QVariantMap &states) const { | |
| 125 | 1075 | CommandCompletion::Sources sources = m_sources; | |
| 126 | 1075 | sources.context = m_context; | |
| 127 | 1075 | sources.designPreview = !m_sourcesInstalled; | |
| 128 | 4092 | sources.connected = [this](const QString &name) { return m_namedSupported.contains(name); }; | |
| 129 | 2891 | sources.shortcut = [](const QString &id) { | |
| 130 | 1816 | const auto *command = CommandRegistry::find(id); | |
| 131 |
1/2✓ Branch 3 → 4 taken 1816 times.
✗ Branch 3 → 5 not taken.
|
1816 | return command ? CommandRegistry::keyText(*command) : QString{}; |
| 132 | 1075 | }; | |
| 133 |
1/2✓ Branch 6 → 7 taken 1075 times.
✗ Branch 6 → 9 not taken.
|
1539 | if (!sources.history) sources.history = [this] { return m_history.lines(); }; |
| 134 | 2150 | sources.state = [states](const QString &key) { | |
| 135 |
11/15✓ Branch 3 → 4 taken 308 times.
✓ Branch 3 → 11 taken 1 time.
✓ Branch 5 → 6 taken 308 times.
✓ Branch 6 → 7 taken 308 times.
✓ Branch 7 → 8 taken 57 times.
✓ Branch 7 → 9 taken 251 times.
✓ Branch 10 → 12 taken 308 times.
✓ Branch 13 → 14 taken 308 times.
✓ Branch 13 → 15 taken 1 time.
✓ Branch 15 → 16 taken 308 times.
✓ Branch 15 → 17 taken 1 time.
✗ Branch 19 → 20 not taken.
✗ Branch 19 → 21 not taken.
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 25 not taken.
|
309 | return states.contains(key) ? QString(states.value(key).toBool() ? "on" : "off") : QString{}; |
| 136 |
1/1✓ Branch 10 → 11 taken 1075 times.
|
1075 | }; |
| 137 | 1075 | return sources; | |
| 138 | ✗ | } | |
| 139 | 1060 | QVariantMap QuickCommandRouter::completions(const QString &text, int cursor, const QVariantMap &states) const { | |
| 140 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 1059 times.
|
1060 | if (text.size() > CommandCompletion::MaxInput) return {}; |
| 141 |
3/3✓ Branch 5 → 6 taken 1059 times.
✓ Branch 6 → 7 taken 1059 times.
✓ Branch 7 → 8 taken 1059 times.
|
1059 | return CommandCompletion::complete(text, cursor, effectiveSources(states)).toMap(); |
| 142 | } | ||
| 143 | 173 | bool QuickCommandRouter::recordCommand(const QString &line) { | |
| 144 |
2/2✓ Branch 3 → 4 taken 33 times.
✓ Branch 3 → 5 taken 140 times.
|
173 | if (!m_history.record(line)) return false; |
| 145 | 140 | m_clearedHistory.clear(); | |
| 146 | 140 | emit historyChanged(); | |
| 147 | 140 | return true; | |
| 148 | } | ||
| 149 | 3 | void QuickCommandRouter::clearHistory() { | |
| 150 |
7/31✗ Branch 3 → 4 not taken.
✓ Branch 3 → 12 taken 3 times.
✗ Branch 4 → 5 not taken.
✗ Branch 6 → 7 not taken.
✗ Branch 7 → 8 not taken.
✗ Branch 8 → 9 not taken.
✗ Branch 9 → 10 not taken.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 3 times.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 3 times.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 3 times.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 21 taken 3 times.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 3 times.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 3 times.
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 33 not taken.
✗ Branch 35 → 36 not taken.
✗ Branch 35 → 37 not taken.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
✗ Branch 43 → 44 not taken.
✗ Branch 43 → 45 not taken.
✗ Branch 47 → 48 not taken.
✗ Branch 47 → 49 not taken.
|
3 | if (m_history.isEmpty() && m_history.save().value("recentFolders").toMap().isEmpty()) return; |
| 151 |
1/1✓ Branch 25 → 26 taken 3 times.
|
3 | m_clearedHistory = m_history.save(); |
| 152 | 3 | m_history.clear(); | |
| 153 | 3 | emit historyChanged(); | |
| 154 | } | ||
| 155 | 4 | bool QuickCommandRouter::restoreHistory() { | |
| 156 |
2/2✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 5 taken 1 time.
|
4 | if (m_clearedHistory.isEmpty()) return false; |
| 157 | 1 | m_history.restore(m_clearedHistory); | |
| 158 | 1 | m_clearedHistory.clear(); | |
| 159 | 1 | emit historyChanged(); | |
| 160 | 1 | return true; | |
| 161 | } | ||
| 162 | 133 | void QuickCommandRouter::recordFolder(const QString &account, const QString &path) { | |
| 163 |
3/3✓ Branch 3 → 4 taken 133 times.
✓ Branch 7 → 8 taken 9 times.
✓ Branch 7 → 9 taken 124 times.
|
133 | if (m_history.recentFolders(account).value(0) == path) return; |
| 164 | 124 | m_history.recordFolder(account, path); | |
| 165 | 124 | emit historyChanged(); | |
| 166 | } | ||
| 167 | 16 | QString QuickCommandRouter::correction(const QString &text) const { | |
| 168 |
2/2✓ Branch 3 → 4 taken 16 times.
✓ Branch 4 → 5 taken 16 times.
|
16 | return CommandCompletion::correction(text, effectiveSources({})); |
| 169 | } | ||
| 170 | 190 | bool QuickCommandRouter::canInvokeNamed(const QString &text) const { | |
| 171 |
1/1✓ Branch 2 → 3 taken 190 times.
|
190 | const auto parsed = NamedCommandCatalog::parse(text); |
| 172 |
12/14✓ Branch 6 → 7 taken 188 times.
✓ Branch 7 → 8 taken 187 times.
✓ Branch 7 → 22 taken 1 time.
✓ Branch 9 → 10 taken 187 times.
✓ Branch 10 → 11 taken 186 times.
✓ Branch 10 → 22 taken 1 time.
✓ Branch 12 → 13 taken 186 times.
✓ Branch 13 → 14 taken 186 times.
✓ Branch 14 → 15 taken 185 times.
✓ Branch 14 → 22 taken 1 time.
✓ Branch 24 → 25 taken 186 times.
✓ Branch 24 → 26 taken 4 times.
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 31 not taken.
|
378 | return m_window && m_window->isVisible() && m_window->isActive() && m_window->property("modalDepth").toInt() == 0 |
| 173 |
4/4✓ Branch 16 → 17 taken 183 times.
✓ Branch 16 → 22 taken 2 times.
✓ Branch 18 → 19 taken 182 times.
✓ Branch 18 → 22 taken 1 time.
|
185 | && parsed.complete() && m_namedSupported.contains(parsed.spec->name) |
| 174 |
4/4✓ Branch 4 → 5 taken 188 times.
✓ Branch 4 → 22 taken 2 times.
✓ Branch 20 → 21 taken 181 times.
✓ Branch 20 → 22 taken 1 time.
|
568 | && parsed.spec->contexts.testFlag(m_context); |
| 175 | 190 | } | |
| 176 | 2 | QString QuickCommandRouter::namedCommandName(const QString &text) const { | |
| 177 |
1/1✓ Branch 2 → 3 taken 2 times.
|
2 | const auto parsed = NamedCommandCatalog::parse(text); |
| 178 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 1 time.
|
4 | return parsed.spec ? parsed.spec->name : QString{}; |
| 179 | 2 | } | |
| 180 | 11 | bool QuickCommandRouter::invokeNamed(const QString &text) { | |
| 181 |
3/3✓ Branch 2 → 3 taken 11 times.
✓ Branch 3 → 4 taken 8 times.
✓ Branch 3 → 5 taken 3 times.
|
11 | if (!canInvokeNamed(text)) return false; |
| 182 |
1/1✓ Branch 5 → 6 taken 3 times.
|
3 | const auto parsed = NamedCommandCatalog::parse(text); |
| 183 |
1/1✓ Branch 6 → 7 taken 3 times.
|
3 | cancelSequence(); |
| 184 |
1/1✓ Branch 7 → 8 taken 3 times.
|
3 | emit namedInvoked(parsed.spec->name, parsed.argument); |
| 185 | 3 | return true; | |
| 186 | 3 | } | |
| 187 | 2186 | void QuickCommandRouter::setContext(CommandContext context) { | |
| 188 |
2/2✓ Branch 2 → 3 taken 1173 times.
✓ Branch 2 → 4 taken 1013 times.
|
2186 | if (m_context == context) return; |
| 189 | 1013 | m_context = context; | |
| 190 | 1013 | cancelSequence(); | |
| 191 | 1013 | notifyAvailability(); | |
| 192 | } | ||
| 193 | 861 | void QuickCommandRouter::bindWorkspace(WorkspaceState *workspace) { | |
| 194 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 861 times.
|
861 | if (m_workspace == workspace) return; |
| 195 |
1/2✗ Branch 6 → 7 not taken.
✓ Branch 6 → 9 taken 861 times.
|
861 | if (m_workspace) disconnect(m_workspace, nullptr, this, nullptr); |
| 196 | 861 | m_workspace = workspace; | |
| 197 |
1/2✓ Branch 10 → 11 taken 861 times.
✗ Branch 10 → 20 not taken.
|
861 | if (workspace) { |
| 198 |
1/1✓ Branch 11 → 12 taken 861 times.
|
861 | connect(workspace, &WorkspaceState::activeTabChanged, this, &QuickCommandRouter::updateWorkspaceContext); |
| 199 |
1/1✓ Branch 13 → 14 taken 861 times.
|
861 | connect(workspace, &WorkspaceState::areaChanged, this, &QuickCommandRouter::updateWorkspaceContext); |
| 200 |
1/1✓ Branch 15 → 16 taken 861 times.
|
861 | connect(workspace, &WorkspaceState::selectionChanged, this, &QuickCommandRouter::cancelSequence); |
| 201 |
1/1✓ Branch 17 → 18 taken 861 times.
|
861 | connect(workspace, &QObject::destroyed, this, [this] { setContext(CommandContext::None); }); |
| 202 | } | ||
| 203 | 861 | updateWorkspaceContext(); | |
| 204 | } | ||
| 205 | 2169 | void QuickCommandRouter::updateWorkspaceContext() { | |
| 206 | 2169 | cancelSequence(); | |
| 207 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 7 taken 2169 times.
|
2169 | if (!m_workspace) { setContext(CommandContext::None); return; } |
| 208 |
2/2✓ Branch 11 → 12 taken 141 times.
✓ Branch 11 → 13 taken 2028 times.
|
2169 | if (m_workspace->area() == "calendar") setContext(CommandContext::Calendar); |
| 209 |
2/2✓ Branch 17 → 18 taken 89 times.
✓ Branch 17 → 19 taken 1939 times.
|
2028 | else if (m_workspace->area() == "tasks") setContext(CommandContext::Task); |
| 210 |
2/2✓ Branch 23 → 24 taken 88 times.
✓ Branch 23 → 25 taken 1851 times.
|
1939 | else if (m_workspace->area() == "contacts") setContext(CommandContext::Contact); |
| 211 |
2/2✓ Branch 29 → 30 taken 57 times.
✓ Branch 29 → 31 taken 1794 times.
|
1851 | else if (m_workspace->area() == "rules") |
| 212 | 57 | setContext(CommandContext::Rules); // F98-01: the rule manager's tab | |
| 213 |
2/2✓ Branch 35 → 36 taken 153 times.
✓ Branch 35 → 37 taken 1641 times.
|
1794 | else if (m_workspace->area() == "settings") setContext(CommandContext::Settings); // Sprint 100 |
| 214 |
1/2✗ Branch 41 → 42 not taken.
✓ Branch 41 → 43 taken 1641 times.
|
1641 | else if (m_workspace->area() != "mail") setContext(CommandContext::None); |
| 215 |
2/2✓ Branch 45 → 46 taken 1137 times.
✓ Branch 45 → 47 taken 504 times.
|
1641 | else if (m_workspace->activeTab() == 0) setContext(CommandContext::MainMail); |
| 216 |
2/2✓ Branch 49 → 50 taken 105 times.
✓ Branch 49 → 51 taken 399 times.
|
504 | else setContext(m_workspace->activeIsSearch() ? CommandContext::Search : CommandContext::MailTab); // Sprint 101 CB-06 |
| 217 | } | ||
| 218 | 872 | void QuickCommandRouter::setRulesOpen(bool open) { | |
| 219 |
2/2✓ Branch 2 → 3 taken 831 times.
✓ Branch 2 → 4 taken 41 times.
|
872 | if (m_rulesOpen == open) return; |
| 220 | 41 | m_rulesOpen = open; | |
| 221 | 41 | updateWorkspaceContext(); | |
| 222 | 41 | emit rulesOpenChanged(); | |
| 223 | } | ||
| 224 | 14930028 | bool QuickCommandRouter::isAvailable(const QString &id) const { | |
| 225 | 14930028 | const auto *spec = CommandRegistry::find(id); | |
| 226 |
5/6✓ Branch 3 → 4 taken 14930028 times.
✗ Branch 3 → 11 not taken.
✓ Branch 5 → 6 taken 14228485 times.
✓ Branch 5 → 11 taken 701543 times.
✓ Branch 9 → 10 taken 4877687 times.
✓ Branch 9 → 11 taken 9350798 times.
|
14930028 | return spec && m_supported.contains(id) && (spec->contexts & activeContexts()); |
| 227 | } | ||
| 228 | 14228485 | CommandContexts QuickCommandRouter::activeContexts() const { | |
| 229 |
2/2✓ Branch 3 → 4 taken 201066 times.
✓ Branch 3 → 5 taken 14027419 times.
|
14228485 | return exclusiveContext(m_focusContext) ? m_focusContext : (CommandContexts(m_context) | m_focusContext); |
| 230 | } | ||
| 231 | 66325 | QVariantMap QuickCommandRouter::availability() const { | |
| 232 | 66325 | QVariantMap result; | |
| 233 |
5/5✓ Branch 2 → 3 taken 66325 times.
✓ Branch 6 → 7 taken 13596625 times.
✓ Branch 8 → 9 taken 13596625 times.
✓ Branch 12 → 5 taken 13596625 times.
✓ Branch 12 → 13 taken 66325 times.
|
13662950 | for (const auto &spec : CommandRegistry::specs()) result.insert(spec.id, isAvailable(spec.id)); |
| 234 | 66325 | return result; | |
| 235 | ✗ | } | |
| 236 | 3148 | void QuickCommandRouter::notifyAvailability() { | |
| 237 |
5/6✓ Branch 3 → 4 taken 3148 times.
✗ Branch 3 → 13 not taken.
✓ Branch 4 → 5 taken 3148 times.
✓ Branch 7 → 8 taken 3148 times.
✓ Branch 9 → 10 taken 3148 times.
✓ Branch 10 → 11 taken 3148 times.
|
3148 | if (!m_commands.isEmpty()) emit dataChanged(index(0), index(m_commands.size() - 1), {Qt::UserRole + 3}); |
| 238 | 3148 | emit availabilityChanged(); | |
| 239 | 3148 | } | |
| 240 | 22096 | void QuickCommandRouter::cancelSequence() { | |
| 241 | 22096 | m_prefixTimeout.stop(); | |
| 242 |
2/2✓ Branch 4 → 5 taken 22015 times.
✓ Branch 4 → 6 taken 81 times.
|
22096 | if (m_prefix.isEmpty()) return; |
| 243 |
1/1✓ Branch 6 → 7 taken 81 times.
|
81 | m_prefix = {}; |
| 244 | 81 | emit prefixChanged(); | |
| 245 | } | ||
| 246 | 838 | void QuickCommandRouter::attach(QQuickWindow *window) { | |
| 247 | 838 | cancelSequence(); | |
| 248 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 9 taken 838 times.
|
838 | if (m_window) { |
| 249 | ✗ | m_window->removeEventFilter(this); | |
| 250 | ✗ | disconnect(m_window, nullptr, this, nullptr); | |
| 251 | } | ||
| 252 | 838 | m_window = window; | |
| 253 |
1/2✓ Branch 11 → 12 taken 838 times.
✗ Branch 11 → 19 not taken.
|
838 | if (m_window) { |
| 254 | 838 | m_window->installEventFilter(this); | |
| 255 |
1/1✓ Branch 14 → 15 taken 838 times.
|
838 | connect(window, &QQuickWindow::activeFocusItemChanged, this, [this] { |
| 256 | 9498 | cancelSequence(); | |
| 257 | 9498 | updateFocusContext(); | |
| 258 | 9498 | }); | |
| 259 |
1/1✓ Branch 16 → 17 taken 838 times.
|
838 | connect(window, &QWindow::activeChanged, this, [this] { |
| 260 | 933 | cancelSequence(); | |
| 261 |
2/2✓ Branch 5 → 6 taken 31 times.
✓ Branch 5 → 7 taken 902 times.
|
933 | if (!m_window->isActive()) m_overlay->windowDeactivated(); |
| 262 | 933 | }); | |
| 263 | } | ||
| 264 | 838 | } | |
| 265 | 2416 | bool QuickCommandRouter::invoke(const QString &id) { | |
| 266 |
11/14✓ Branch 5 → 6 taken 2414 times.
✓ Branch 6 → 7 taken 2413 times.
✓ Branch 6 → 16 taken 1 time.
✓ Branch 8 → 9 taken 2413 times.
✓ Branch 9 → 10 taken 2410 times.
✓ Branch 9 → 16 taken 3 times.
✓ Branch 11 → 12 taken 2410 times.
✓ Branch 12 → 13 taken 2410 times.
✓ Branch 13 → 14 taken 2410 times.
✗ Branch 13 → 16 not taken.
✓ Branch 20 → 21 taken 15 times.
✓ Branch 20 → 22 taken 2401 times.
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 30 not taken.
|
4830 | if (!m_window || !m_window->isVisible() || !m_window->isActive() || m_window->property("modalDepth").toInt() > 0 |
| 267 |
7/7✓ Branch 3 → 4 taken 2414 times.
✓ Branch 3 → 16 taken 2 times.
✓ Branch 14 → 15 taken 2410 times.
✓ Branch 15 → 16 taken 9 times.
✓ Branch 15 → 17 taken 2401 times.
✓ Branch 18 → 19 taken 2410 times.
✓ Branch 18 → 20 taken 6 times.
|
4845 | || !isAvailable(id)) return false; |
| 268 | 2401 | cancelSequence(); | |
| 269 |
2/2✓ Branch 23 → 24 taken 781 times.
✓ Branch 23 → 25 taken 1620 times.
|
2401 | if (m_dryRun) emit dryRunInvoked(id); |
| 270 | 1620 | else emit invoked(id); | |
| 271 | 2401 | return true; | |
| 272 | } | ||
| 273 | 7 | QString QuickCommandRouter::commandAt(int row) const { | |
| 274 |
3/4✓ Branch 2 → 3 taken 5 times.
✓ Branch 2 → 7 taken 2 times.
✓ Branch 4 → 5 taken 5 times.
✗ Branch 4 → 7 not taken.
|
7 | return row >= 0 && row < m_commands.size() ? m_commands[row]->id : QString{}; |
| 275 | } | ||
| 276 | 4 | QVariantMap QuickCommandRouter::entryAt(int row) const { | |
| 277 | ✗ | return {{"id", commandAt(row)}, | |
| 278 |
4/4✓ Branch 15 → 16 taken 4 times.
✓ Branch 16 → 17 taken 4 times.
✓ Branch 17 → 18 taken 4 times.
✓ Branch 18 → 19 taken 4 times.
|
8 | {"label", data(index(row), Qt::UserRole + 1).toString() + QStringLiteral(" [") + |
| 279 |
5/5✓ Branch 8 → 9 taken 4 times.
✓ Branch 9 → 10 taken 4 times.
✓ Branch 10 → 11 taken 4 times.
✓ Branch 19 → 20 taken 4 times.
✓ Branch 20 → 21 taken 4 times.
|
12 | data(index(row), Qt::UserRole + 2).toString() + QStringLiteral("]")}, |
| 280 |
4/6✓ Branch 23 → 24 taken 4 times.
✓ Branch 24 → 25 taken 4 times.
✓ Branch 28 → 29 taken 12 times.
✓ Branch 28 → 30 taken 4 times.
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 48 not taken.
|
24 | {"available", data(index(row), Qt::UserRole + 3)}}; |
| 281 | 12 | } | |
| 282 |
1/1✓ Branch 3 → 4 taken 1 time.
|
1 | bool QuickCommandRouter::invokeRow(int row) { return invoke(commandAt(row)); } |
| 283 | // F-32 (SPRINT-104): keypad keys carry the KeypadModifier and compare | ||
| 284 | // unequal to their registered plain equivalents — strip it so Del, 1–5 | ||
| 285 | // and Enter of the numeric keypad trigger the same commands (no | ||
| 286 | // Ctrl/Alt/Meta combination loses its modifier). Single-letter shortcuts | ||
| 287 | // must fire on every keyboard layout: QKeySequence(keyCombination()) | ||
| 288 | // compares the LAYOUT-DEPENDENT text, so a Russian or Greek layout never | ||
| 289 | // matched "D"/"R"/"A" — with no modifiers held, an unmodified letter | ||
| 290 | // matches its physical (US-layout) key. Entry punctuation (:, /, ?) | ||
| 291 | // stays layout-normalized as before. | ||
| 292 | 6772 | QKeySequence QuickCommandRouter::normalizedShortcut(const QKeyEvent *key) { | |
| 293 | 6772 | QKeySequence sequence(key->keyCombination()); | |
| 294 |
3/3✓ Branch 7 → 8 taken 6772 times.
✓ Branch 10 → 11 taken 2 times.
✓ Branch 10 → 24 taken 6770 times.
|
6772 | if (key->modifiers() & Qt::KeypadModifier) { |
| 295 |
1/1✓ Branch 11 → 12 taken 2 times.
|
2 | const auto combination = key->keyCombination(); |
| 296 | 2 | const auto modifiers = combination.keyboardModifiers() & ~Qt::KeypadModifier; | |
| 297 | 2 | sequence = modifiers | |
| 298 |
2/4✗ Branch 15 → 16 not taken.
✓ Branch 15 → 19 taken 2 times.
✗ Branch 18 → 21 not taken.
✓ Branch 20 → 21 taken 2 times.
|
6 | ? QKeySequence(combination.key() | int(modifiers)) |
| 299 | 4 | : QKeySequence(combination.key()); | |
| 300 | } | ||
| 301 |
5/5✓ Branch 26 → 27 taken 6772 times.
✓ Branch 29 → 30 taken 6202 times.
✓ Branch 29 → 40 taken 570 times.
✓ Branch 32 → 33 taken 5912 times.
✓ Branch 32 → 39 taken 290 times.
|
18886 | if (!(key->modifiers() & (Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier)) && |
| 302 |
12/18✓ Branch 35 → 36 taken 5878 times.
✓ Branch 35 → 39 taken 34 times.
✓ Branch 38 → 39 taken 3 times.
✓ Branch 38 → 40 taken 5875 times.
✓ Branch 41 → 42 taken 5878 times.
✓ Branch 41 → 43 taken 894 times.
✓ Branch 43 → 44 taken 5912 times.
✓ Branch 43 → 45 taken 860 times.
✓ Branch 45 → 46 taken 6202 times.
✓ Branch 45 → 47 taken 570 times.
✓ Branch 47 → 48 taken 327 times.
✓ Branch 47 → 54 taken 6445 times.
✗ Branch 98 → 99 not taken.
✗ Branch 98 → 100 not taken.
✗ Branch 103 → 104 not taken.
✗ Branch 103 → 105 not taken.
✗ Branch 108 → 109 not taken.
✗ Branch 108 → 110 not taken.
|
18886 | (key->text() == ":" || key->text() == "/" || key->text() == "?")) |
| 303 |
1/1✓ Branch 49 → 50 taken 327 times.
|
327 | sequence = QKeySequence(key->text()); |
| 304 | // Sprint 105 D-12: "?" needs Shift on most layouts; Ctrl+? is one key. | ||
| 305 |
7/7✓ Branch 55 → 56 taken 4 times.
✓ Branch 55 → 61 taken 6768 times.
✓ Branch 56 → 57 taken 4 times.
✓ Branch 59 → 60 taken 2 times.
✓ Branch 59 → 61 taken 2 times.
✓ Branch 62 → 63 taken 2 times.
✓ Branch 62 → 72 taken 6770 times.
|
6772 | if (key->key() == Qt::Key_Question && (key->modifiers() & Qt::ControlModifier)) |
| 306 |
1/1✓ Branch 68 → 69 taken 2 times.
|
2 | sequence = QKeySequence(QKeyCombination(Qt::ControlModifier, Qt::Key_Question)); |
| 307 | // AltGr (GroupSwitch) types a character (AltGr+Q is "@", AltGr+E "€" on | ||
| 308 | // a German layout): never the plain letter's command. | ||
| 309 |
1/1✓ Branch 76 → 77 taken 6772 times.
|
20316 | if (!(key->modifiers() & (Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier | Qt::ShiftModifier | |
| 310 |
2/2✓ Branch 81 → 82 taken 5627 times.
✓ Branch 81 → 85 taken 484 times.
|
19655 | Qt::GroupSwitchModifier)) && |
| 311 |
6/6✓ Branch 79 → 80 taken 6111 times.
✓ Branch 79 → 85 taken 661 times.
✓ Branch 83 → 84 taken 1324 times.
✓ Branch 83 → 85 taken 4303 times.
✓ Branch 86 → 87 taken 1324 times.
✓ Branch 86 → 92 taken 5448 times.
|
12883 | key->key() >= Qt::Key_A && key->key() <= Qt::Key_Z) |
| 312 |
1/1✓ Branch 88 → 89 taken 1324 times.
|
1324 | sequence = QKeySequence(key->key()); |
| 313 | 6772 | return sequence; | |
| 314 | ✗ | } | |
| 315 | |||
| 316 | 2199 | bool QuickCommandRouter::invokeFromKeyboard(const QString &id) { | |
| 317 | 2199 | const QScopedValueRollback<bool> keyboard(m_fromKeyboard, true); | |
| 318 |
1/1✓ Branch 3 → 4 taken 2199 times.
|
4398 | return invoke(id); |
| 319 | 2199 | } | |
| 320 |