MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 97.2% 382 / 0 / 393
Functions: 100.0% 18 / 0 / 18
Branches: 87.3% 207 / 0 / 237

app/CommandRegistry.cpp
Line Branch Exec Source
1 #include "CommandRegistry.h"
2
3 #include <QAction>
4 #include <QCoreApplication>
5 #include <QHash>
6 #include <QSet>
7
8 namespace {
9 constexpr CommandContexts kMail = CommandContext::MainMail |
10 CommandContext::MailTab | CommandContext::Search;
11 // Sprint 101 CB-06: commands of the folder view that act on the selected
12 // mail or the list also act on the results of a search tab.
13 constexpr CommandContexts kList = CommandContext::MainMail | CommandContext::Search;
14 constexpr CommandContexts kViews = kMail | CommandContext::Calendar |
15 CommandContext::Task | CommandContext::Contact |
16 CommandContext::Rules;
17 // Sprint 100 (D-1): what works everywhere, including the settings area.
18 constexpr CommandContexts kAnywhere = kViews | CommandContext::Settings;
19 // Sprint 93 S93.4.6: the calendar keeps "t" for Today; adding a task stays
20 // on "t" in mail, tasks and contacts.
21 constexpr CommandContexts kTaskAdd = kMail | CommandContext::Task | CommandContext::Contact;
22
23 // Help sections: the headings of `?` and docs/user/KEYBOARD.md, in the
24 // order they first appear below (Sprint 105 keys.md §4).
25 constexpr const char *kAreas = QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Areas, panes and windows");
26 constexpr const char *kTabs = QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Tabs");
27 constexpr const char *kMailList = QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Mail list");
28 constexpr const char *kMailActions = QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Mail actions");
29 constexpr const char *kReading = QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Reading and answering");
30 constexpr const char *kFilters = QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Command bar, filter and search");
31 constexpr const char *kLabels = QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Labels");
32 constexpr const char *kFolders = QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Folders");
33 constexpr const char *kAgenda = QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Agenda beside the mail");
34 constexpr const char *kCalendar = QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Calendar");
35 constexpr const char *kTasks = QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Tasks");
36 constexpr const char *kContacts = QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Contacts");
37 constexpr const char *kRules = QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Rules");
38 constexpr const char *kSettings = QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Settings");
39 constexpr const char *kCompose = QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Writing a message");
40 constexpr const char *kResolver = QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Resolving a conflict");
41
42 // `keys`: one or more key sequences separated by " | " ("D | Del").
43 5945 CommandSpec spec(const char *id, const char *keys, CommandContexts contexts,
44 const char *category, const char *description) {
45 5945 CommandSpec command;
46
1/1
✓ Branch 3 → 4 taken 5945 times.
5945 command.id = QString::fromLatin1(id);
47
6/6
✓ Branch 7 → 8 taken 5945 times.
✓ Branch 12 → 13 taken 5945 times.
✓ Branch 16 → 17 taken 5945 times.
✓ Branch 17 → 18 taken 5945 times.
✓ Branch 25 → 19 taken 6293 times.
✓ Branch 25 → 26 taken 5945 times.
18183 for (const QString &key : QString::fromLatin1(keys).split(QStringLiteral(" | "), Qt::SkipEmptyParts))
48
2/2
✓ Branch 20 → 21 taken 6293 times.
✓ Branch 21 → 22 taken 6293 times.
12238 command.defaultKeys.append(QKeySequence(key));
49 5945 command.contexts = contexts;
50 5945 command.category = category;
51 5945 command.description = description;
52 5945 return command;
53 ✗ }
54 } // namespace
55
56 // Sprint 105: one table, in help order. Descriptions are in sentence case
57 // (K-9); `.typing()` keys act while a text field has focus (S105.7.4);
58 // `.explain()` texts are the overlay's second stage (keys.md §8).
59 154118 const QList<CommandSpec> &CommandRegistry::specs() {
60 static const QList<CommandSpec> commands = {
61 // Areas (D-8): F1–F5 and F11, everywhere, also while typing.
62 29 spec("app.mail", "F1", kAnywhere, kAreas, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Mail")).changed().typing().daily(),
63 58 spec("app.calendar", "F2", kAnywhere, kAreas, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Calendar")).changed().typing().daily(),
64 58 spec("app.tasks", "F3", kAnywhere, kAreas, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Tasks")).changed().typing().daily(),
65 58 spec("app.contacts", "F4", kAnywhere, kAreas, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Contacts")).changed().typing().daily(),
66 58 spec("app.rules", "F5", kAnywhere, kAreas, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Rules")).changed().typing().rare(),
67 58 spec("app.settings", "F11 | Ctrl+,", kAnywhere, kAreas, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Settings")).changed()
68 29 .typing().rare(),
69 // D-15 (revised by the user's feedback, D-25): one F-key per pane —
70 // F6 sidebar, F7 list or view, F8 reader or details, F9 agenda; the
71 // desktop's pane cycling stays on Ctrl+F6.
72 29 spec("app.pane_sidebar", "F6", kAnywhere, kAreas, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Go to the sidebar"))
73 29 .changed().typing().explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Focuses folders, calendars, lists or address books")),
74 29 spec("app.pane_main", "F7", kAnywhere, kAreas, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Go to the list or view"))
75 29 .changed().typing().daily()
76 29 .explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Focuses the mail list, the calendar view or the list of this area")),
77 29 spec("app.pane_detail", "F8", kAnywhere, kAreas, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Go to the reader or details"))
78 29 .changed().typing().explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Focuses the open mail, event, task, card or editor")),
79 29 spec("app.pane_side", "F9", kAnywhere, kAreas, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Go to the agenda"))
80 29 .changed().typing().explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Focuses the agenda beside the mail")),
81 29 spec("app.pane_next", "Ctrl+F6", kAnywhere, kAreas, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Next pane"))
82 29 .changed().typing().rare().explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Moves the focus to the next pane")),
83 29 spec("app.pane_previous", "Ctrl+Shift+F6", kAnywhere, kAreas, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Previous pane"))
84 29 .changed().typing().rare(),
85 58 spec("app.menu", "F10", kAnywhere, kAreas, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Open the menu")).changed().typing(),
86 // D-13: Ctrl+M writes a message from any area.
87 58 spec("app.compose", "Ctrl+M", kAnywhere, kAreas, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "New message from any area")).changed()
88 29 .typing().daily(),
89 // Sprint 108 D-28: also while writing — the focused composer moves into
90 // a window of its own; elsewhere it switches *Write in a separate window*.
91
1/1
✓ Branch 77 → 78 taken 29 times.
58 spec("compose.detach", "Ctrl+Shift+D", kViews | CommandContext::Compose, kAreas,
92 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Write the message in its own window")).typing().rare(),
93 58 spec("command.open", ":", kAnywhere, kAreas, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Open the command bar")).daily(),
94 spec("help.shortcuts", "?", kAnywhere, kAreas, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "All keys")),
95 // D-12: the keys overlay without holding Alt.
96 58 spec("app.key_overlay", "Ctrl+?", kAnywhere, kAreas, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Show or hide the keys overlay")).changed()
97 29 .typing().rare()
98 29 .explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Keeps the keys overlay open without holding Alt")),
99 ✗ spec("surface.close", "Esc", kAnywhere | CommandContext::TextInput | CommandContext::Modal, kAreas,
100 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Close or go back")),
101 58 spec("app.quit", "Ctrl+Q", kAnywhere, kAreas, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Quit")).typing().rare(),
102
103 // Tabs.
104 58 spec("tab.next", "Ctrl+Tab | Ctrl+PgDown", kViews, kTabs, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Next tab")).changed().typing().daily(),
105 58 spec("tab.previous", "Ctrl+Shift+Tab | Ctrl+PgUp", kViews, kTabs, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Previous tab")).changed()
106 29 .typing().daily(),
107 58 spec("tab.main", "Ctrl+0", kViews, kTabs, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Main view")).typing(),
108 58 spec("tab.1", "Ctrl+1", kViews, kTabs, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Tab 1")).typing(),
109 58 spec("tab.2", "Ctrl+2", kViews, kTabs, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Tab 2")).typing(),
110 58 spec("tab.3", "Ctrl+3", kViews, kTabs, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Tab 3")).typing(),
111 58 spec("tab.4", "Ctrl+4", kViews, kTabs, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Tab 4")).typing(),
112 58 spec("tab.5", "Ctrl+5", kViews, kTabs, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Tab 5")).typing(),
113 58 spec("tab.6", "Ctrl+6", kViews, kTabs, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Tab 6")).typing(),
114 58 spec("tab.7", "Ctrl+7", kViews, kTabs, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Tab 7")).typing(),
115 58 spec("tab.8", "Ctrl+8", kViews, kTabs, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Tab 8")).typing(),
116 58 spec("tab.9", "Ctrl+9", kViews, kTabs, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Tab 9")).typing(),
117 // Sprint 108 D-28: also while writing — a compose tab closes and keeps its draft.
118
1/1
✓ Branch 139 → 140 taken 29 times.
58 spec("tab.close", "Ctrl+W", kViews | CommandContext::Compose, kTabs, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Close the tab")).typing(),
119 spec("tab.open_mail", "Shift+T", kList, kTabs, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Open the mail in a tab")),
120
121 // Mail list: move and go to (D-14).
122 58 spec("mail.next", "J", kMail, kMailList, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Next mail")).daily(),
123 58 spec("mail.previous", "K", kMail, kMailList, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Previous mail")).daily(),
124 29 spec("mail.next_unread", "Space", kList, kMailList, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Next unread mail"))
125 29 .daily().explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Jumps to the next unread mail, also in other folders")),
126 spec("mail.page_down", "Shift+J", kList, kMailList, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Page down")),
127 spec("mail.page_up", "Shift+K", kList, kMailList, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Page up")),
128 spec("mail.first", "G, G", kList, kMailList, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "First mail")),
129 spec("mail.last", "Shift+G", kList, kMailList, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Last mail")),
130 spec("mail.open", "O", kList, kMailList, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Open the mail")),
131 29 spec("folder.switch", "B", kList, kMailList, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Switch folder"))
132 29 .explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Opens the folder list in the command bar")),
133 29 spec("folder.previous", "Shift+B", CommandContext::MainMail, kMailList,
134 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Previous folder")),
135 29 spec("mail.goto.inbox", "G, I", kList, kMailList, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Go to the inbox")).changed()
136 29 .explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Press g, then a letter to go to a folder or item")),
137 58 spec("mail.goto.sent", "G, S", kList, kMailList, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Go to sent mail")).changed(),
138 58 spec("mail.goto.drafts", "G, D", kList, kMailList, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Go to the drafts")).changed(),
139 58 spec("mail.goto.archive", "G, A", kList, kMailList, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Go to the archive")).changed(),
140 58 spec("mail.goto.junk", "G, J", kList, kMailList, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Go to junk")).changed(),
141 58 spec("mail.goto.task", "G, T", kMail, kMailList, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Open the linked task")).changed(),
142 58 spec("mail.goto.event", "G, E", kMail, kMailList, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Open the linked appointment")).changed(),
143 // F93-12: the agenda beside the mail list.
144 29 spec("view.agenda_panel", "Ctrl+Shift+A", kMail, kMailList,
145 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Agenda beside the mail on or off"))
146 29 .explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Shows your next appointments beside the mail")),
147
148 // Mail actions.
149 58 spec("compose.new", "N", kMail, kMailActions, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "New message")).daily(),
150 // D-13: Ctrl + the create key opens the editor in a window of its own.
151 58 spec("mail.new_window", "Ctrl+N", kMail, kMailActions, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "New message in a window")).changed(),
152 58 spec("mail.archive", "A", kList, kMailActions, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Archive")).daily(),
153 // F95-09: Del deletes like d.
154 58 spec("mail.delete", "D | Del", kList, kMailActions, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Delete")).daily(),
155 58 spec("mail.toggle_star", "M", kMail, kMailActions, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Mark as important")).daily(),
156 58 spec("mail.toggle_read", "R", kMail, kMailActions, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Mark as read or unread")).daily(),
157
1/1
✓ Branch 207 → 208 taken 29 times.
58 spec("mail.quick_move", "S", CommandContext::MainMail, kMailActions,
158 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Move to the suggested folder"))
159 29 .daily().explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Moves the mail to the folder suggested on the right")),
160
1/1
✓ Branch 212 → 213 taken 29 times.
58 spec("suggestion.alternate", "E", CommandContext::MainMail, kMailActions,
161 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Use the second suggestion"))
162 29 .explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Switches the suggestion to the second-best folder")),
163
1/1
✓ Branch 216 → 217 taken 29 times.
58 spec("suggestion.toggle_all", "Ctrl+S", CommandContext::MainMail, kMailActions,
164 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Suggestions for all mails"))
165 29 .explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Shows a folder suggestion for every mail")),
166 spec("mail.manual_move", "Shift+S", kList, kMailActions, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Move to a folder…")),
167 spec("mail.junk", "X", kList, kMailActions, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Mark as junk")),
168 29 spec("task.add", "T", kTaskAdd, kMailActions, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Create a task"))
169 29 .explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Creates a task that links back to this mail")),
170 // Sprint 95 S95.4.1: Thunderbird's "Create filter from message".
171 29 spec("rule.from_mail", "Shift+F", kList, kMailActions, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Create a rule from this mail"))
172 29 .explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Creates a server rule for mails like this one")),
173 29 spec("undo.normal", "U", kMail, kMailActions, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Undo"))
174 29 .daily().explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Takes back the last move, delete or mark")),
175 spec("undo.standard", "Ctrl+Z", kMail, kMailActions, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Undo (desktop key)")),
176
177 // Reading and answering.
178 58 spec("mail.reply", "Ctrl+R", kMail, kReading, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Reply")).daily(),
179 58 spec("mail.reply_all", "Ctrl+Shift+R", kMail, kReading, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Reply all")).daily(),
180 58 spec("mail.forward", "Ctrl+Shift+F", kMail, kReading, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Forward")).daily(),
181 // F108-18: the one-line quick reply field shows on Q only.
182 29 spec("mail.quick_reply", "Q", kMail, kReading, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Quick reply"))
183 29 .changed().explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "A reply field above the message; Esc closes it")),
184 29 spec("mail.reading_mode", "H", kMail, kReading, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Next reading mode"))
185 29 .explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Cycles Text, Light and HTML for this mail")),
186 58 spec("mail.reading_mode_back", "Shift+H", kMail, kReading, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Previous reading mode")).changed(),
187 29 spec("view.thread", "Ctrl+T", CommandContext::MainMail, kReading, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Thread view on or off")),
188
189 // Command bar, filter and search.
190 29 spec("filter.open", "/", kViews, kFilters, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Filter this view")).daily(),
191 ✗ spec("search.open", "F", kList | CommandContext::Calendar | CommandContext::Task, kFilters,
192 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Search this area")),
193 spec("view.filter_unread", "I", kList, kFilters, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Only unread mails")),
194 spec("view.filter_starred", "Shift+I", kList, kFilters, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Only important mails")),
195 spec("view.filter_attachments", "Ctrl+I", kList, kFilters, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Only mails with attachments")),
196 spec("view.filter_clear", "Ctrl+Shift+I", kList, kFilters, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Clear the mail filters")),
197 // Sprint 101 §6.2.3: the whole query of a search tab as text.
198 29 spec("search.edit_text", "Ctrl+E", CommandContext::Search, kFilters,
199 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Edit the search as text")),
200 // S101.6.4: the timeline of a search tab (Z as in "Zeitachse", zoom).
201 29 spec("search.toggle_timeline", "Shift+Z", CommandContext::Search, kFilters,
202 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Show or hide the timeline")),
203
204 // Labels.
205 spec("label.menu", "L", kList, kLabels, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Labels…")),
206 ✗ spec("label.1", "1", kList, kLabels, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Label 1 on or off"))
207 29 .explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Toggles the first five labels; 0 removes all")),
208 spec("label.2", "2", kList, kLabels, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Label 2 on or off")),
209 spec("label.3", "3", kList, kLabels, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Label 3 on or off")),
210 spec("label.4", "4", kList, kLabels, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Label 4 on or off")),
211 spec("label.5", "5", kList, kLabels, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Label 5 on or off")),
212 spec("label.clear", "0", kList, kLabels, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Remove all labels")),
213
214 // Folders (rare; D-10: rename on Shift+F2).
215
1/1
✓ Branch 277 → 278 taken 29 times.
58 spec("folder.create", "Ctrl+Shift+N", CommandContext::MainMail, kFolders, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "New subfolder"))
216 29 .rare(),
217
1/1
✓ Branch 281 → 282 taken 29 times.
58 spec("folder.rename", "Shift+F2", CommandContext::MainMail, kFolders, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Rename the folder")).changed()
218 29 .rare(),
219
1/1
✓ Branch 286 → 287 taken 29 times.
58 spec("folder.delete", "Shift+D", CommandContext::MainMail, kFolders, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Delete the folder"))
220 29 .rare(),
221
1/1
✓ Branch 290 → 291 taken 29 times.
58 spec("folder.move", "V", CommandContext::MainMail, kFolders, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Move the folder")).rare(),
222
223 // S105.9.2: the agenda beside the mail is a pane of its own.
224
1/1
✓ Branch 294 → 295 taken 29 times.
58 spec("agenda.next", "J", CommandContext::Agenda, kAgenda, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Next appointment")).changed()
225 29 .explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "F9 moves here; j and k pick an appointment")),
226
1/1
✓ Branch 299 → 300 taken 29 times.
58 spec("agenda.previous", "K", CommandContext::Agenda, kAgenda, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Previous appointment")).changed(),
227
1/1
✓ Branch 303 → 304 taken 29 times.
58 spec("agenda.open", "Return", CommandContext::Agenda, kAgenda, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Open the appointment")).changed(),
228
229 // Sprint 93 S93.4.6: calendar keyboard model.
230
1/1
✓ Branch 307 → 308 taken 29 times.
58 spec("calendar.today", "T | G, T", CommandContext::Calendar, kCalendar, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Go to today")).changed()
231 29 .daily(),
232
1/1
✓ Branch 312 → 313 taken 29 times.
58 spec("calendar.goto", "G, D", CommandContext::Calendar, kCalendar, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Go to a date")).changed(),
233 29 spec("calendar.view_day", "D", CommandContext::Calendar, kCalendar, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Day view")),
234 29 spec("calendar.view_week", "W", CommandContext::Calendar, kCalendar, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Week view")),
235 29 spec("calendar.view_month", "M", CommandContext::Calendar, kCalendar, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Month view")),
236 29 spec("calendar.view_year", "Y", CommandContext::Calendar, kCalendar, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Year view")),
237 29 spec("calendar.view_agenda", "A", CommandContext::Calendar, kCalendar, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Agenda view")),
238 29 spec("calendar.previous", "H | PgUp", CommandContext::Calendar, kCalendar,
239 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Previous period")),
240 29 spec("calendar.next", "L | PgDown", CommandContext::Calendar, kCalendar, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Next period")),
241
1/1
✓ Branch 330 → 331 taken 29 times.
29 spec("calendar.new", "N", CommandContext::Calendar, kCalendar, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "New appointment")).daily(),
242 // F95-18: Ctrl + the create key opens the editor in its own window.
243 29 spec("calendar.new_window", "Ctrl+N", CommandContext::Calendar, kCalendar,
244 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "New appointment in a window")),
245 29 spec("calendar.edit", "E", CommandContext::Calendar, kCalendar,
246 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Edit the selected appointment")),
247 29 spec("calendar.delete", "Del", CommandContext::Calendar, kCalendar,
248 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Delete the selected appointment")),
249
250 // Tasks.
251
1/1
✓ Branch 340 → 341 taken 29 times.
29 spec("task.next", "J", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Next task")).daily(),
252
1/1
✓ Branch 344 → 345 taken 29 times.
58 spec("task.previous", "K", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Previous task")).daily(),
253 29 spec("task.edit", "Return", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Edit the task")),
254
1/1
✓ Branch 350 → 351 taken 29 times.
29 spec("task.toggle", "Space", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Complete or reopen the task"))
255 29 .daily(),
256
1/1
✓ Branch 354 → 355 taken 29 times.
58 spec("task.new", "N", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "New task")).daily(),
257
1/1
✓ Branch 358 → 359 taken 29 times.
58 spec("task.new_window", "Ctrl+N", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "New task in a window")).changed(),
258
1/1
✓ Branch 362 → 363 taken 29 times.
58 spec("task.select", "X", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Select or deselect the task"))
259 29 .explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Adds the row to the selection for bulk actions")),
260
1/1
✓ Branch 366 → 367 taken 29 times.
58 spec("task.rename", "Shift+F2", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Rename the task")).changed()
261 29 .explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Renames in place; Enter keeps, Esc cancels")),
262 29 spec("task.expand", "Right", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Show the subtasks")),
263 29 spec("task.collapse", "Left", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Hide the subtasks")),
264 29 spec("task.indent", "Tab", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Make a subtask")),
265 29 spec("task.promote", "Shift+Tab", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Promote the subtask")),
266 29 spec("task.due", "D", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Change the due date")),
267 29 spec("task.move", "L", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Move to a list")),
268 29 spec("task.priority.high", "1", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "High priority")),
269 29 spec("task.priority.normal", "2", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Normal priority")),
270 29 spec("task.priority.low", "3", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Low priority")),
271 29 spec("task.priority.none", "0", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "No priority")),
272 29 spec("task.order_up", "Alt+Up", CommandContext::Task, kTasks,
273 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Move the task up (manual order)")),
274 29 spec("task.order_down", "Alt+Down", CommandContext::Task, kTasks,
275 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Move the task down (manual order)")),
276 29 spec("task.goto.today", "G, T", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Go to Today")),
277 29 spec("task.goto.week", "G, W", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Go to This week")),
278 29 spec("task.goto.planned", "G, P", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Go to Planned")),
279 29 spec("task.goto.completed", "G, C", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Go to Completed")),
280 29 spec("task.menu", "Shift+F10", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Task actions")),
281 29 spec("task.delete", "Del", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Delete the task")),
282 29 spec("task.undo", "Ctrl+Z", CommandContext::Task, kTasks, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Undo the task change")),
283
284 // Sprint 97 S97.4.3: contact keys.
285
1/1
✓ Branch 409 → 410 taken 29 times.
29 spec("contact.next", "J", CommandContext::Contact, kContacts, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Next contact")).daily(),
286
1/1
✓ Branch 413 → 414 taken 29 times.
58 spec("contact.previous", "K", CommandContext::Contact, kContacts, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Previous contact"))
287 29 .daily(),
288 29 spec("contact.open", "Return", CommandContext::Contact, kContacts, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Open the contact")),
289
1/1
✓ Branch 419 → 420 taken 29 times.
29 spec("contact.new", "N", CommandContext::Contact, kContacts, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "New contact")).daily(),
290 // Sprint 103 S103.3.5: a new contact with how you met.
291 29 spec("contact.met", "Shift+N", CommandContext::Contact, kContacts, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Met someone…")),
292 29 spec("contact.new_window", "Ctrl+N", CommandContext::Contact, kContacts,
293 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "New contact in a window")),
294 29 spec("contact.write", "C", CommandContext::Contact, kContacts,
295 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Write mail to the selected contacts")),
296 29 spec("contact.edit", "E", CommandContext::Contact, kContacts, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Edit the contact")),
297 29 spec("contact.select", "X", CommandContext::Contact, kContacts,
298 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Select or deselect the contact")),
299 29 spec("contact.move", "M", CommandContext::Contact, kContacts, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Move to an address book")),
300 // Sprint 103 S103.4.1: List · Map · Graph (D-12).
301 29 spec("contact.view.list", "1", CommandContext::Contact, kContacts,
302 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Show the contacts as a list")),
303 29 spec("contact.view.map", "2", CommandContext::Contact, kContacts,
304 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Show the contacts on the world map")),
305 29 spec("contact.view.graph", "3", CommandContext::Contact, kContacts,
306 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Show the relationship graph")),
307 29 spec("contact.goto.all", "G, A", CommandContext::Contact, kContacts, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Go to All contacts")),
308 29 spec("contact.goto.frequent", "G, F", CommandContext::Contact, kContacts,
309 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Go to Frequently contacted")),
310 29 spec("contact.goto.collected", "G, L", CommandContext::Contact, kContacts,
311 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Go to Collected addresses")),
312 29 spec("contact.menu", "Shift+F10", CommandContext::Contact, kContacts, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Contact actions")),
313 29 spec("contact.delete", "Del", CommandContext::Contact, kContacts, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Delete the contact")),
314 29 spec("contact.undo", "Ctrl+Z", CommandContext::Contact, kContacts,
315 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Undo the contact change")),
316
317 // Sprint 95 S95.3.7: the rule manager.
318 29 spec("rules.next", "J", CommandContext::Rules, kRules, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Next rule")),
319 29 spec("rules.previous", "K", CommandContext::Rules, kRules, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Previous rule")),
320 29 spec("rules.toggle", "Space", CommandContext::Rules, kRules, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Turn the rule on or off")),
321 29 spec("rules.edit", "Return", CommandContext::Rules, kRules, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Edit the rule")),
322
1/1
✓ Branch 461 → 462 taken 29 times.
29 spec("rules.new", "N | Ctrl+N", CommandContext::Rules, kRules, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "New rule")).changed(),
323
1/1
✓ Branch 465 → 466 taken 29 times.
58 spec("rules.rename", "Shift+F2", CommandContext::Rules, kRules, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Rename the rule")).changed().rare(),
324 29 spec("rules.move_up", "Alt+Up", CommandContext::Rules, kRules, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Move the rule up")),
325 29 spec("rules.move_down", "Alt+Down", CommandContext::Rules, kRules, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Move the rule down")),
326 29 spec("rules.delete", "Del", CommandContext::Rules, kRules, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Delete the rule")),
327 29 spec("rules.undo", "Ctrl+Z", CommandContext::Rules, kRules, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Undo the rule change")),
328
1/1
✓ Branch 478 → 479 taken 29 times.
29 spec("rules.script", "Ctrl+Shift+E", CommandContext::Rules, kRules,
329 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Edit the script")).rare(),
330
331 // S105.9.4: writing a message, in the main window and in a compose
332 // window of its own (ComposeWindow binds these keys there).
333
1/1
✓ Branch 482 → 483 taken 29 times.
58 spec("compose.send", "Ctrl+Return", CommandContext::Compose, kCompose, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Send the message"))
334 29 .changed().typing().daily(),
335
1/1
✓ Branch 488 → 489 taken 29 times.
58 spec("compose.save", "Ctrl+S", CommandContext::Compose, kCompose, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Save the draft"))
336 29 .changed().typing(),
337
1/1
✓ Branch 493 → 494 taken 29 times.
58 spec("compose.attach", "Ctrl+Shift+A", CommandContext::Compose, kCompose, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Attach files…"))
338 29 .changed().typing(),
339 // Bcc is one Tab after Cc: Ctrl+Shift+B was the old Contacts key (D-22).
340
1/1
✓ Branch 498 → 499 taken 29 times.
58 spec("compose.cc", "Ctrl+Shift+C", CommandContext::Compose, kCompose, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Go to Cc"))
341 29 .explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Tab then goes on to Bcc")).changed().typing().rare(),
342 // Sprint 108 D-28: the composer's keys — layout-safe letters only (no
343 // shifted digits or punctuation, no Alt). The old area keys of D-9
344 // return here, in the compose context only.
345
1/1
✓ Branch 505 → 506 taken 29 times.
58 spec("compose.bcc", "Ctrl+Shift+B", CommandContext::Compose, kCompose, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Go to Bcc"))
346 29 .changed().typing().rare(),
347
1/1
✓ Branch 511 → 512 taken 29 times.
58 spec("compose.send_later", "Ctrl+Shift+Return", CommandContext::Compose, kCompose,
348 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Send later…")).changed().typing().rare(),
349
1/1
✓ Branch 517 → 518 taken 29 times.
58 spec("compose.format", "Ctrl+Shift+M", CommandContext::Compose, kCompose,
350 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Formatted or plain text")).changed().typing().rare(),
351
1/1
✓ Branch 523 → 524 taken 29 times.
58 spec("compose.signature", "Ctrl+Shift+G", CommandContext::Compose, kCompose,
352 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Next signature")).changed().typing().rare(),
353
1/1
✓ Branch 529 → 530 taken 29 times.
58 spec("compose.next_misspelling", "Shift+F7", CommandContext::Compose, kCompose,
354 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Next misspelling")).changed().typing().rare(),
355
1/1
✓ Branch 535 → 536 taken 29 times.
58 spec("compose.invite", "Ctrl+Shift+E", CommandContext::Compose, kCompose,
356 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Invite to a meeting")).changed().typing().rare(),
357
1/1
✓ Branch 541 → 542 taken 29 times.
58 spec("compose.address_book", "Ctrl+Shift+K", CommandContext::Compose, kCompose,
358 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Address book")).changed().typing().rare(),
359
1/1
✓ Branch 547 → 548 taken 29 times.
58 spec("compose.template", "Ctrl+Shift+T", CommandContext::Compose, kCompose,
360 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Templates…")).changed().typing().rare(),
361
1/1
✓ Branch 553 → 554 taken 29 times.
58 spec("compose.bold", "Ctrl+B", CommandContext::Compose, kCompose, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Bold"))
362 29 .changed().typing(),
363
1/1
✓ Branch 558 → 559 taken 29 times.
58 spec("compose.italic", "Ctrl+I", CommandContext::Compose, kCompose, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Italic"))
364 29 .changed().typing(),
365
1/1
✓ Branch 563 → 564 taken 29 times.
58 spec("compose.underline", "Ctrl+U", CommandContext::Compose, kCompose, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Underline"))
366 29 .changed().typing(),
367
1/1
✓ Branch 568 → 569 taken 29 times.
58 spec("compose.strike", "Ctrl+Shift+X", CommandContext::Compose, kCompose,
368 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Strike through")).changed().typing().rare(),
369
1/1
✓ Branch 574 → 575 taken 29 times.
58 spec("compose.code", "Ctrl+E", CommandContext::Compose, kCompose, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Code"))
370 29 .changed().typing().rare(),
371
1/1
✓ Branch 580 → 581 taken 29 times.
58 spec("compose.link", "Ctrl+K", CommandContext::Compose, kCompose, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Insert or edit a link"))
372 29 .changed().typing(),
373
1/1
✓ Branch 585 → 586 taken 29 times.
58 spec("compose.bullets", "Ctrl+Shift+L", CommandContext::Compose, kCompose,
374 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Bulleted list")).changed().typing(),
375
1/1
✓ Branch 590 → 591 taken 29 times.
58 spec("compose.numbered", "Ctrl+Shift+O", CommandContext::Compose, kCompose,
376 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Numbered list")).changed().typing().rare(),
377
1/1
✓ Branch 596 → 597 taken 29 times.
58 spec("compose.quote", "Ctrl+Shift+Q", CommandContext::Compose, kCompose, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Quote"))
378 29 .changed().typing().rare(),
379
1/1
✓ Branch 602 → 603 taken 29 times.
58 spec("compose.paragraph", "Ctrl+Shift+S", CommandContext::Compose, kCompose,
380 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Paragraph style…"))
381 29 .explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Headings also by typing # at a line start")).changed().typing().rare(),
382
383 // S105.9.4: the settings' page list.
384
1/1
✓ Branch 609 → 610 taken 29 times.
58 spec("settings.next_page", "J", CommandContext::SettingsPages, kSettings, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Next page")).changed(),
385
1/1
✓ Branch 613 → 614 taken 29 times.
58 spec("settings.previous_page", "K", CommandContext::SettingsPages, kSettings,
386 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Previous page")).changed(),
387
388 // Sprint 107 D-17: the conflict resolver (focus context "resolver";
389 // left/right are the two sides as they stand on the screen, D-18).
390 29 spec("conflict.list", "Ctrl+F8", kAnywhere, kResolver, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Open conflicts"))
391 29 .rare().changed(),
392 29 spec("conflict.resolve", "Ctrl+Shift+F8", kAnywhere, kResolver,
393 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Resolve a conflict here"))
394 29 .explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "The first open conflict of this area"))
395 29 .rare()
396 29 .changed(),
397
1/1
✓ Branch 626 → 627 taken 29 times.
58 spec("conflict.next_row", "J", CommandContext::Resolver, kResolver, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Next row"))
398 29 .changed(),
399
1/1
✓ Branch 630 → 631 taken 29 times.
58 spec("conflict.previous_row", "K", CommandContext::Resolver, kResolver,
400 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Previous row")).changed(),
401
1/1
✓ Branch 634 → 635 taken 29 times.
58 spec("conflict.next_open", "N | F8", CommandContext::Resolver, kResolver,
402 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Next open question")).changed(),
403
1/1
✓ Branch 638 → 639 taken 29 times.
58 spec("conflict.previous_open", "Shift+N | Shift+F8", CommandContext::Resolver, kResolver,
404 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Previous open question")).changed(),
405
1/1
✓ Branch 642 → 643 taken 29 times.
58 spec("conflict.take_local", "Left | H", CommandContext::Resolver, kResolver,
406 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Take the version on this computer"))
407 29 .explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "The left column; for a text the focused change"))
408 29 .daily()
409 29 .changed(),
410
1/1
✓ Branch 648 → 649 taken 29 times.
58 spec("conflict.take_server", "Right | L", CommandContext::Resolver, kResolver,
411 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Take the version on the server"))
412 29 .explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "The right column; for a text the focused change"))
413 29 .daily()
414 29 .changed(),
415
1/1
✓ Branch 654 → 655 taken 29 times.
58 spec("conflict.take_both", "B", CommandContext::Resolver, kResolver,
416 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Keep the additions of both sides"))
417 29 .explain(QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Lists: every added value; texts: both lines")).changed(),
418
1/1
✓ Branch 659 → 660 taken 29 times.
58 spec("conflict.edit", "E", CommandContext::Resolver, kResolver, QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Edit the result"))
419 29 .changed(),
420
1/1
✓ Branch 663 → 664 taken 29 times.
58 spec("conflict.show_original", "O", CommandContext::Resolver, kResolver,
421 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Show the version before")).changed(),
422
1/1
✓ Branch 667 → 668 taken 29 times.
58 spec("conflict.show_source", "S", CommandContext::Resolver, kResolver,
423 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Show the source lines")).rare().changed(),
424
1/1
✓ Branch 672 → 673 taken 29 times.
58 spec("conflict.apply", "Ctrl+Return", CommandContext::Resolver, kResolver,
425 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Apply the choices")).changed(),
426
1/1
✓ Branch 676 → 677 taken 29 times.
58 spec("conflict.keep_mine", "Ctrl+Shift+Left", CommandContext::Resolver, kResolver,
427 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Keep all mine")).rare().changed(),
428
1/1
✓ Branch 681 → 682 taken 29 times.
58 spec("conflict.take_all_server", "Ctrl+Shift+Right", CommandContext::Resolver, kResolver,
429 29 QT_TRANSLATE_NOOP("ShortcutHelpOverlay", "Take all from the server")).rare().changed(),
430
5/10
✓ Branch 2 → 3 taken 29 times.
✓ Branch 2 → 817 taken 154089 times.
✓ Branch 4 → 5 taken 29 times.
✗ Branch 4 → 817 not taken.
✓ Branch 689 → 690 taken 5945 times.
✓ Branch 689 → 691 taken 29 times.
✗ Branch 820 → 821 not taken.
✗ Branch 820 → 822 not taken.
✗ Branch 1327 → 1328 not taken.
✗ Branch 1327 → 1329 not taken.
160121 };
431 154118 return commands;
432 3857 }
433
434 2 CommandRegistry::CommandRegistry(QObject *parent) : QObject(parent) {}
435
436 15280289 const CommandSpec *CommandRegistry::find(const QString &id) {
437 29 static const QHash<QString, const CommandSpec *> index = [] {
438 29 QHash<QString, const CommandSpec *> result;
439
3/3
✓ Branch 2 → 3 taken 29 times.
✓ Branch 9 → 5 taken 5945 times.
✓ Branch 9 → 10 taken 29 times.
5974 for (const CommandSpec &command : specs())
440
1/1
✓ Branch 6 → 7 taken 5945 times.
5945 result.insert(command.id, &command);
441 29 return result;
442
4/7
✓ Branch 2 → 3 taken 29 times.
✓ Branch 2 → 9 taken 15280260 times.
✓ Branch 4 → 5 taken 29 times.
✗ Branch 4 → 9 not taken.
✓ Branch 5 → 6 taken 29 times.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 15 not taken.
15280289 }();
443 15280289 return index.value(id, nullptr);
444 }
445
446 186897 QString CommandRegistry::translatedCategory(const CommandSpec &spec) {
447 186897 return QCoreApplication::translate("ShortcutHelpOverlay", spec.category);
448 }
449
450 188945 QString CommandRegistry::translatedDescription(const CommandSpec &spec) {
451 188945 return QCoreApplication::translate("ShortcutHelpOverlay", spec.description);
452 }
453
454 40350 QString CommandRegistry::translatedExplanation(const CommandSpec &spec) {
455
2/2
✓ Branch 2 → 3 taken 3724 times.
✓ Branch 2 → 4 taken 36626 times.
40350 return spec.explanation ? QCoreApplication::translate("ShortcutHelpOverlay", spec.explanation) : QString();
456 }
457
458 201503 QString CommandRegistry::keyText(const QKeySequence &key) {
459 // "G, I" → "g i": a sequence reads as the keys pressed one after another.
460 201503 QStringList parts;
461
3/3
✓ Branch 25 → 26 taken 418434 times.
✓ Branch 26 → 3 taken 216931 times.
✓ Branch 26 → 27 taken 201503 times.
418434 for (int index = 0; index < key.count(); ++index) {
462
3/3
✓ Branch 6 → 7 taken 216931 times.
✓ Branch 7 → 8 taken 216931 times.
✓ Branch 8 → 9 taken 216931 times.
216931 QString text = QKeySequence(key[index]).toString(QKeySequence::PortableText);
463
7/7
✓ Branch 11 → 12 taken 101536 times.
✓ Branch 11 → 16 taken 115395 times.
✓ Branch 12 → 13 taken 101536 times.
✓ Branch 14 → 15 taken 87024 times.
✓ Branch 14 → 16 taken 14512 times.
✓ Branch 17 → 18 taken 87024 times.
✓ Branch 17 → 22 taken 129907 times.
216931 if (text.size() == 1 && text.front().isLetter())
464
1/1
✓ Branch 18 → 19 taken 87024 times.
87024 text = text.toLower();
465
1/1
✓ Branch 22 → 23 taken 216931 times.
216931 parts.append(text);
466 216931 }
467
1/1
✓ Branch 29 → 30 taken 201503 times.
403006 return parts.join(QLatin1Char(' '));
468 201503 }
469
470 100549 QString CommandRegistry::nativeKeyText(const QKeySequence &key) {
471 100549 QStringList parts;
472
3/3
✓ Branch 25 → 26 taken 208834 times.
✓ Branch 26 → 3 taken 108285 times.
✓ Branch 26 → 27 taken 100549 times.
208834 for (int index = 0; index < key.count(); ++index) {
473
3/3
✓ Branch 6 → 7 taken 108285 times.
✓ Branch 7 → 8 taken 108285 times.
✓ Branch 8 → 9 taken 108285 times.
108285 QString text = QKeySequence(key[index]).toString(QKeySequence::NativeText);
474
7/7
✓ Branch 11 → 12 taken 74262 times.
✓ Branch 11 → 16 taken 34023 times.
✓ Branch 12 → 13 taken 74262 times.
✓ Branch 14 → 15 taken 56830 times.
✓ Branch 14 → 16 taken 17432 times.
✓ Branch 17 → 18 taken 56830 times.
✓ Branch 17 → 22 taken 51455 times.
108285 if (text.size() == 1 && text.front().isLetter())
475
1/1
✓ Branch 18 → 19 taken 56830 times.
56830 text = text.toLower();
476
1/1
✓ Branch 22 → 23 taken 108285 times.
108285 parts.append(text);
477 108285 }
478
1/1
✓ Branch 29 → 30 taken 100549 times.
201098 return parts.join(QLatin1Char(' '));
479 100549 }
480
481 188779 QString CommandRegistry::keyText(const CommandSpec &spec) {
482 188779 QStringList keys;
483
2/2
✓ Branch 10 → 4 taken 199751 times.
✓ Branch 10 → 11 taken 188779 times.
388530 for (const QKeySequence &key : spec.defaultKeys)
484
2/2
✓ Branch 5 → 6 taken 199751 times.
✓ Branch 6 → 7 taken 199751 times.
199751 keys.append(keyText(key));
485
1/1
✓ Branch 14 → 15 taken 188779 times.
377558 return keys.join(QStringLiteral(" / "));
486 188779 }
487
488 1 QString CommandRegistry::keyboardReference() {
489 // One section per help category, in registry order. Command IDs stay in
490 // an HTML comment so the generated page and the test can match rows; the
491 // explanation (S105.6.4) follows the action after a dash.
492 1 QStringList order;
493 1 QHash<QString, QString> rows;
494
3/3
✓ Branch 2 → 3 taken 1 time.
✓ Branch 48 → 5 taken 205 times.
✓ Branch 48 → 49 taken 1 time.
206 for (const CommandSpec &command : specs()) {
495
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 205 times.
205 if (!command.showInHelp)
496 ✗ continue;
497
1/1
✓ Branch 9 → 10 taken 205 times.
205 const QString category = QString::fromUtf8(command.category);
498
2/2
✓ Branch 11 → 12 taken 16 times.
✓ Branch 11 → 13 taken 189 times.
205 if (!order.contains(category))
499
1/1
✓ Branch 12 → 13 taken 16 times.
16 order.append(category);
500 205 const QString action = command.explanation
501
3/9
✓ Branch 20 → 21 taken 28 times.
✓ Branch 30 → 31 taken 28 times.
✓ Branch 30 → 32 taken 177 times.
✗ Branch 72 → 73 not taken.
✗ Branch 72 → 74 not taken.
✗ Branch 82 → 83 not taken.
✗ Branch 82 → 84 not taken.
✗ Branch 86 → 87 not taken.
✗ Branch 86 → 88 not taken.
289 ? QStringLiteral("%1 — %2").arg(QString::fromUtf8(command.description),
502
5/7
✓ Branch 18 → 19 taken 28 times.
✓ Branch 26 → 27 taken 28 times.
✓ Branch 26 → 28 taken 177 times.
✓ Branch 28 → 29 taken 28 times.
✓ Branch 28 → 30 taken 177 times.
✗ Branch 77 → 78 not taken.
✗ Branch 77 → 79 not taken.
233 QString::fromUtf8(command.explanation))
503
6/6
✓ Branch 13 → 14 taken 28 times.
✓ Branch 13 → 22 taken 177 times.
✓ Branch 21 → 24 taken 28 times.
✓ Branch 23 → 24 taken 177 times.
✓ Branch 24 → 25 taken 28 times.
✓ Branch 24 → 26 taken 177 times.
289 : QString::fromUtf8(command.description);
504
4/4
✓ Branch 35 → 36 taken 205 times.
✓ Branch 36 → 37 taken 205 times.
✓ Branch 37 → 38 taken 205 times.
✓ Branch 38 → 39 taken 205 times.
205 rows[category] += QStringLiteral("| `%1` | %2 <!-- %3 --> |\n").arg(keyText(command), action, command.id);
505 205 }
506 1 QString result;
507
4/4
✓ Branch 49 → 50 taken 1 time.
✓ Branch 50 → 51 taken 1 time.
✓ Branch 65 → 52 taken 16 times.
✓ Branch 65 → 66 taken 1 time.
17 for (const QString &category : order)
508
2/2
✓ Branch 57 → 58 taken 16 times.
✓ Branch 58 → 59 taken 16 times.
16 result += QStringLiteral("## %1\n\n| Key | Action |\n|---|---|\n%2\n").arg(category, rows.value(category));
509 1 return result;
510 1 }
511
512 1 QStringList CommandRegistry::duplicateSequences() {
513 1 QStringList duplicates;
514
1/1
✓ Branch 2 → 3 taken 1 time.
1 const QList<CommandSpec> &commands = specs();
515
2/2
✓ Branch 45 → 4 taken 205 times.
✓ Branch 45 → 46 taken 1 time.
206 for (int left = 0; left < commands.size(); ++left) {
516
2/2
✓ Branch 42 → 5 taken 20910 times.
✓ Branch 42 → 43 taken 205 times.
21115 for (int right = left + 1; right < commands.size(); ++right) {
517
2/2
✓ Branch 9 → 10 taken 12845 times.
✓ Branch 9 → 11 taken 8065 times.
20910 if (!(commands[left].contexts & commands[right].contexts))
518 12845 continue;
519
2/2
✓ Branch 38 → 14 taken 8619 times.
✓ Branch 38 → 39 taken 8065 times.
16684 for (const QKeySequence &leftKey : commands[left].defaultKeys) {
520
4/7
✓ Branch 15 → 16 taken 8619 times.
✓ Branch 16 → 17 taken 8619 times.
✗ Branch 16 → 21 not taken.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 21 taken 8619 times.
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 36 taken 8619 times.
8619 if (!leftKey.isEmpty() && commands[right].defaultKeys.contains(leftKey))
521 ✗ duplicates.append(QStringLiteral("%1/%2:%3")
522 ✗ .arg(commands[left].id, commands[right].id,
523 ✗ leftKey.toString(QKeySequence::PortableText)));
524 }
525 }
526 }
527 1 return duplicates;
528 ✗ }
529
530 1 QStringList CommandRegistry::altLetterViolations() {
531 1 QStringList violations;
532
3/3
✓ Branch 2 → 3 taken 1 time.
✓ Branch 38 → 5 taken 205 times.
✓ Branch 38 → 39 taken 1 time.
206 for (const CommandSpec &command : specs()) {
533
2/2
✓ Branch 35 → 8 taken 217 times.
✓ Branch 35 → 36 taken 205 times.
422 for (const QKeySequence &key : command.defaultKeys) {
534
3/3
✓ Branch 31 → 32 taken 451 times.
✓ Branch 32 → 10 taken 234 times.
✓ Branch 32 → 33 taken 217 times.
451 for (int index = 0; index < key.count(); ++index) {
535
1/1
✓ Branch 10 → 11 taken 234 times.
234 const QKeyCombination combination = key[index];
536
2/2
✓ Branch 14 → 15 taken 230 times.
✓ Branch 14 → 16 taken 4 times.
234 if (!(combination.keyboardModifiers() & Qt::AltModifier))
537 230 continue;
538 4 const Qt::Key code = combination.key();
539
3/4
✓ Branch 17 → 18 taken 2 times.
✓ Branch 17 → 29 taken 2 times.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 29 taken 2 times.
4 if (code != Qt::Key_Up && code != Qt::Key_Down)
540 ✗ violations.append(command.id + QLatin1Char(':') + key.toString(QKeySequence::PortableText));
541 }
542 }
543 }
544 1 return violations;
545 ✗ }
546
547 8 bool CommandRegistry::bind(const QString &id, QAction *action) {
548 8 const CommandSpec *command = find(id);
549
4/4
✓ Branch 3 → 4 taken 7 times.
✓ Branch 3 → 5 taken 1 time.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 6 times.
8 if (!command || !action)
550 2 return false;
551 // Runtime language changes rebuild the menu bar. Disable the shortcuts on
552 // the superseded QAction before replacing it so a menu awaiting deferred
553 // deletion can never leave a second active binding behind.
554
2/2
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 12 taken 5 times.
6 if (QAction *previous = m_actions.value(id).data();
555
1/2
✓ Branch 10 → 11 taken 1 time.
✗ Branch 10 → 12 not taken.
1 previous && previous != action)
556 1 previous->setShortcuts({});
557
2/2
✓ Branch 16 → 17 taken 6 times.
✓ Branch 17 → 18 taken 6 times.
18 action->setObjectName(QStringLiteral("command.%1").arg(id));
558 6 action->setShortcuts(command->defaultKeys);
559 6 action->setShortcutContext(Qt::WindowShortcut);
560
2/2
✓ Branch 23 → 24 taken 6 times.
✓ Branch 24 → 25 taken 6 times.
6 m_actions.insert(id, action);
561 6 applyContext();
562 6 return true;
563 }
564
565 6 void CommandRegistry::setContext(CommandContext context) {
566
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 5 times.
6 if (m_context == context)
567 1 return;
568 5 m_context = context;
569 5 applyContext();
570 }
571
572 3 QAction *CommandRegistry::action(const QString &id) const {
573 3 return m_actions.value(id).data();
574 }
575
576 11 void CommandRegistry::applyContext() {
577
2/2
✓ Branch 19 → 3 taken 22 times.
✓ Branch 19 → 20 taken 11 times.
33 for (auto it = m_actions.cbegin(); it != m_actions.cend(); ++it) {
578
1/1
✓ Branch 4 → 5 taken 22 times.
22 const CommandSpec *command = find(it.key());
579
2/2
✓ Branch 7 → 8 taken 21 times.
✓ Branch 7 → 16 taken 1 time.
22 if (it.value())
580
4/5
✓ Branch 10 → 11 taken 21 times.
✗ Branch 10 → 14 not taken.
✓ Branch 12 → 13 taken 13 times.
✓ Branch 12 → 14 taken 8 times.
✓ Branch 15 → 16 taken 21 times.
21 it.value()->setEnabled(command && command->contexts.testFlag(m_context));
581 }
582 11 }
583