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: 92.7% 294 / 0 / 317
Functions: 90.9% 30 / 0 / 33
Branches: 74.3% 430 / 0 / 579

presentation/QuickFolderActions.cpp
Line Branch Exec Source
1 #include "QuickFolderActions.h"
2
3 #include "FolderPresentationModel.h"
4 #include "app/core/AccountSession.h"
5 #include "app/core/MailSessionCore.h"
6 #include "app/core/SettingsSyncSession.h"
7 #include "controller/MailController.h"
8 #include "data/FolderPreferences.h"
9 #include "data/MailCache.h"
10 #include "service/ImapResponseParser.h"
11 #include "service/ImapService.h"
12 #include <QAction>
13 #include <QApplication>
14 #include <QMenu>
15 #include <QPointer>
16 #include <QRegularExpression>
17 #include <QSettings>
18 #include <QWindow>
19
20 795 QuickFolderActions::QuickFolderActions(MailSessionCore &session, FolderPresentationModel &folders,
21 const QList<FolderInfo> &folderInfos,
22 const QSet<QString> &availableFolders,
23 const QString &folderPath,
24 const QString &profileDirectory, Deps deps,
25 795 QObject *parent)
26 795 : QObject(parent), m_session(session), m_folders(folders), m_folderInfos(folderInfos),
27 795 m_availableFolders(availableFolders), m_folderPath(folderPath),
28
1/1
✓ Branch 7 → 8 taken 795 times.
795 m_profileDirectory(profileDirectory), m_deps(std::move(deps)) {}
29
30 namespace {
31 // F5: a folder name becomes exactly one hierarchy level. Reject the shapes
32 // that would silently land elsewhere: any occurrence of the active
33 // hierarchy delimiter (a dotted name under "." would nest), a bare ".."
34 // segment, IMAP LIST wildcards, quotes, and control characters.
35 19 static bool validFolderName(const QString &trimmed, const QString &delimiter) {
36
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 18 times.
19 if (trimmed.isEmpty()) return false;
37
6/6
✓ Branch 7 → 8 taken 18 times.
✓ Branch 8 → 9 taken 16 times.
✓ Branch 8 → 17 taken 2 times.
✓ Branch 11 → 12 taken 16 times.
✓ Branch 12 → 13 taken 15 times.
✓ Branch 12 → 17 taken 1 time.
33 if (trimmed.contains(QLatin1Char('"')) || trimmed.contains(QLatin1Char('%')) ||
38
5/5
✓ Branch 15 → 16 taken 15 times.
✓ Branch 16 → 17 taken 3 times.
✓ Branch 16 → 18 taken 12 times.
✓ Branch 19 → 20 taken 6 times.
✓ Branch 19 → 21 taken 12 times.
33 trimmed.contains(QLatin1Char('*')))
39 6 return false;
40
5/6
✓ Branch 22 → 23 taken 12 times.
✗ Branch 22 → 26 not taken.
✓ Branch 24 → 25 taken 5 times.
✓ Branch 24 → 26 taken 7 times.
✓ Branch 27 → 28 taken 5 times.
✓ Branch 27 → 29 taken 7 times.
12 if (!delimiter.isEmpty() && trimmed.contains(delimiter)) return false;
41
1/2
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 33 taken 7 times.
7 if (trimmed == QLatin1String("..")) return false;
42
2/2
✓ Branch 40 → 35 taken 50 times.
✓ Branch 40 → 41 taken 6 times.
56 for (const QChar &c : trimmed) {
43 50 const char16_t code = c.unicode();
44
3/4
✓ Branch 36 → 37 taken 49 times.
✓ Branch 36 → 38 taken 1 time.
✗ Branch 37 → 38 not taken.
✓ Branch 37 → 39 taken 49 times.
50 if (code < 0x20 || code == 0x7F) return false;
45 }
46 6 return true;
47 }
48
49 // S106-339: "folder/icon/<path>" keys nest like the folders on a "/"
50 // account, and QSettings::remove() takes the sub-keys along — a parent's
51 // reset deleted every descendant's icon. Only the key's own value goes.
52 5 void removeOwnValue(QSettings &settings, const QString &key) {
53
1/1
✓ Branch 3 → 4 taken 5 times.
5 settings.beginGroup(key);
54 5 QList<std::pair<QString, QVariant>> nested;
55
7/7
✓ Branch 4 → 5 taken 5 times.
✓ Branch 5 → 6 taken 5 times.
✓ Branch 6 → 7 taken 5 times.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 17 → 8 taken 1 time.
✓ Branch 17 → 18 taken 5 times.
6 for (const QString &child : settings.allKeys()) nested.append({child, settings.value(child)});
56
1/1
✓ Branch 19 → 20 taken 5 times.
5 settings.endGroup();
57
1/1
✓ Branch 21 → 22 taken 5 times.
5 settings.remove(key);
58
5/5
✓ Branch 30 → 31 taken 1 time.
✓ Branch 31 → 32 taken 1 time.
✓ Branch 33 → 34 taken 1 time.
✓ Branch 38 → 25 taken 1 time.
✓ Branch 38 → 39 taken 5 times.
6 for (const auto &[child, value] : std::as_const(nested)) settings.setValue(key + QLatin1Char('/') + child, value);
59 5 }
60 } // namespace
61
62 10332 bool QuickFolderActions::matchesSpecialName(const QString &path, const QString &delimiter,
63 std::initializer_list<const char *> names) {
64 10332 QString leaf = path;
65
1/2
✓ Branch 4 → 5 taken 10332 times.
✗ Branch 4 → 13 not taken.
10332 if (!delimiter.isEmpty()) {
66
1/1
✓ Branch 6 → 7 taken 10332 times.
10332 const QStringList components = path.split(delimiter, Qt::SkipEmptyParts);
67
1/2
✓ Branch 8 → 9 taken 10332 times.
✗ Branch 8 → 11 not taken.
10332 if (!components.isEmpty())
68 10332 leaf = components.last();
69 10332 }
70
2/2
✓ Branch 33 → 15 taken 41818 times.
✓ Branch 33 → 34 taken 10074 times.
51892 for (const char *name : names) {
71
1/1
✓ Branch 16 → 17 taken 41818 times.
41818 const QString candidate = QString::fromLatin1(name);
72
4/4
✓ Branch 18 → 19 taken 41561 times.
✓ Branch 18 → 23 taken 257 times.
✓ Branch 25 → 26 taken 258 times.
✓ Branch 25 → 27 taken 41560 times.
83379 if (path.compare(candidate, Qt::CaseInsensitive) == 0 ||
73
3/4
✓ Branch 20 → 21 taken 41561 times.
✗ Branch 20 → 24 not taken.
✓ Branch 22 → 23 taken 1 time.
✓ Branch 22 → 24 taken 41560 times.
41561 (!leaf.isEmpty() && leaf.compare(candidate, Qt::CaseInsensitive) == 0))
74 258 return true;
75 41818 }
76 10074 return false;
77 10332 }
78
79 4130 QString QuickFolderActions::resolveSpecialFolder(const QList<FolderInfo> &folders,
80 const QString &delimiter, const QString &account,
81 const QString &settingKey, bool (FolderInfo::*flag)() const,
82 std::initializer_list<const char *> names) {
83
1/1
✓ Branch 2 → 3 taken 4130 times.
4130 const QString assigned = QSettings()
84
2/2
✓ Branch 6 → 7 taken 4130 times.
✓ Branch 8 → 9 taken 4130 times.
8260 .value(QStringLiteral("folderRoles/%1/%2").arg(account, settingKey))
85
1/1
✓ Branch 9 → 10 taken 4130 times.
4130 .toString();
86
2/7
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 22 taken 4130 times.
✗ Branch 19 → 20 not taken.
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 22 not taken.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 26 taken 4130 times.
4130 if (!assigned.isEmpty() && std::any_of(folders.cbegin(), folders.cend(),
87 ✗ [&assigned](const FolderInfo &f) { return f.path == assigned; }))
88 ✗ return assigned;
89
2/2
✓ Branch 37 → 28 taken 11637 times.
✓ Branch 37 → 38 taken 4084 times.
15721 for (const auto &folder : folders)
90
4/5
✗ Branch 29 → 30 not taken.
✓ Branch 29 → 31 taken 11637 times.
✓ Branch 32 → 33 taken 11637 times.
✓ Branch 33 → 34 taken 46 times.
✓ Branch 33 → 35 taken 11591 times.
11637 if ((folder.*flag)()) return folder.path;
91
2/2
✓ Branch 47 → 41 taken 10332 times.
✓ Branch 47 → 48 taken 3826 times.
14158 for (const auto &folder : folders)
92
3/3
✓ Branch 42 → 43 taken 10332 times.
✓ Branch 43 → 44 taken 258 times.
✓ Branch 43 → 45 taken 10074 times.
10332 if (matchesSpecialName(folder.path, delimiter, names)) return folder.path;
93 3826 return QString();
94 4130 }
95
96 1796 QString QuickFolderActions::folderDelimiter() const {
97
2/2
✓ Branch 10 → 4 taken 4074 times.
✓ Branch 10 → 11 taken 1709 times.
5783 for (const auto &folder : m_folderInfos)
98
2/2
✓ Branch 6 → 7 taken 87 times.
✓ Branch 6 → 8 taken 3987 times.
4074 if (!folder.delimiter.isEmpty()) return folder.delimiter;
99 // F5: documented fallback — with no folder list yet there is no server
100 // delimiter to learn; "." matches the most common hierarchy and the next
101 // authoritative LIST corrects the projection. Only the active delimiter is
102 // ever rejected in names, so legal dotted names on "/" servers keep working.
103 1709 return QStringLiteral(".");
104 }
105
106 40 bool QuickFolderActions::folderIsProtected(const QString &folderPath) const {
107
2/2
✓ Branch 4 → 5 taken 21 times.
✓ Branch 4 → 6 taken 19 times.
40 if (folderPath.compare(QLatin1String("INBOX"), Qt::CaseInsensitive) == 0)
108 21 return true;
109 static const QStringList specialFlags = {
110 4 QStringLiteral("\\Sent"), QStringLiteral("\\Drafts"),
111 4 QStringLiteral("\\Trash"), QStringLiteral("\\Archive"),
112 4 QStringLiteral("\\Junk"), QStringLiteral("\\All"),
113
5/10
✓ Branch 6 → 7 taken 4 times.
✓ Branch 6 → 44 taken 15 times.
✓ Branch 8 → 9 taken 4 times.
✗ Branch 8 → 44 not taken.
✓ Branch 34 → 35 taken 28 times.
✓ Branch 34 → 36 taken 4 times.
✗ Branch 73 → 74 not taken.
✗ Branch 73 → 75 not taken.
✗ Branch 103 → 104 not taken.
✗ Branch 103 → 105 not taken.
55 QStringLiteral("\\Flagged")};
114
2/2
✓ Branch 69 → 46 taken 44 times.
✓ Branch 69 → 70 taken 19 times.
63 for (const auto &folder : m_folderInfos) {
115
2/2
✓ Branch 48 → 49 taken 25 times.
✓ Branch 48 → 50 taken 19 times.
44 if (folder.path != folderPath) continue;
116
1/2
✗ Branch 65 → 52 not taken.
✓ Branch 65 → 66 taken 19 times.
19 for (const auto &flag : folder.flags) {
117 ✗ for (const auto &special : specialFlags) {
118 ✗ if (flag.compare(special, Qt::CaseInsensitive) == 0) return true;
119 }
120 }
121 }
122 19 return false;
123 32 }
124
125 922 QStringList QuickFolderActions::childFolders(const QString &folderPath) const {
126
1/1
✓ Branch 2 → 3 taken 922 times.
922 const QString delimiter = folderDelimiter();
127 922 QStringList children;
128
2/2
✓ Branch 13 → 5 taken 2439 times.
✓ Branch 13 → 14 taken 922 times.
3361 for (const auto &folder : m_folderInfos)
129
5/5
✓ Branch 6 → 7 taken 2439 times.
✓ Branch 7 → 8 taken 2439 times.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 11 taken 2438 times.
✓ Branch 10 → 11 taken 1 time.
2439 if (folder.path.startsWith(folderPath + delimiter)) children.append(folder.path);
130 922 return children;
131 922 }
132
133 13 bool QuickFolderActions::createFolder(const QString &name) {
134
6/7
✓ Branch 2 → 3 taken 13 times.
✓ Branch 3 → 4 taken 12 times.
✓ Branch 3 → 6 taken 1 time.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 12 times.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 12 times.
13 if (!m_deps.configured() || !m_session.imap()) return false;
135
1/1
✓ Branch 10 → 11 taken 12 times.
12 const QString trimmed = name.trimmed();
136
4/4
✓ Branch 11 → 12 taken 12 times.
✓ Branch 12 → 13 taken 12 times.
✓ Branch 14 → 15 taken 9 times.
✓ Branch 14 → 19 taken 3 times.
12 if (!validFolderName(trimmed, folderDelimiter())) {
137
2/2
✓ Branch 15 → 16 taken 9 times.
✓ Branch 16 → 17 taken 9 times.
9 m_deps.setStatus(tr("Enter a valid folder name."));
138 9 return false;
139 }
140 // Paths are the server's wire form; the typed name is encoded once here
141 // (modified UTF-7: "Büro" → "B&APw-ro", "R&D" → "R&-D"; F-03, S106-968).
142
1/1
✓ Branch 19 → 20 taken 3 times.
3 const QString leaf = ImapResponseParser::encodeMailboxName(trimmed);
143 3 const QString fullPath = m_folderPath.isEmpty()
144 3 ? leaf
145
6/13
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 3 times.
✓ Branch 23 → 24 taken 3 times.
✓ Branch 24 → 25 taken 3 times.
✓ Branch 25 → 26 taken 3 times.
✓ Branch 26 → 27 taken 3 times.
✗ Branch 26 → 28 not taken.
✓ Branch 28 → 29 taken 3 times.
✗ Branch 28 → 30 not taken.
✗ Branch 48 → 49 not taken.
✗ Branch 48 → 50 not taken.
✗ Branch 52 → 53 not taken.
✗ Branch 52 → 54 not taken.
3 : m_folderPath + folderDelimiter() + leaf;
146 // S106-341: the deferred command holds the service it runs on (whose
147 // queue it lives in), not this object.
148
2/2
✓ Branch 33 → 34 taken 3 times.
✓ Branch 34 → 35 taken 3 times.
6 m_session.imap()->executeAfterIdle(
149 9 [imap = m_session.imap(), fullPath] { imap->createFolder(fullPath); });
150 3 return true;
151 12 }
152
153 10 bool QuickFolderActions::renameFolder(const QString &folderPath, const QString &newName) {
154
6/7
✓ Branch 2 → 3 taken 10 times.
✓ Branch 3 → 4 taken 9 times.
✓ Branch 3 → 6 taken 1 time.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 9 times.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 9 times.
10 if (!m_deps.configured() || !m_session.imap()) return false;
155
3/3
✓ Branch 10 → 11 taken 9 times.
✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 16 taken 7 times.
9 if (folderIsProtected(folderPath)) {
156
2/2
✓ Branch 12 → 13 taken 2 times.
✓ Branch 13 → 14 taken 2 times.
2 m_deps.setStatus(tr("Special folders cannot be renamed"));
157 2 return false;
158 }
159
1/1
✓ Branch 16 → 17 taken 7 times.
7 const QString trimmed = newName.trimmed();
160
4/4
✓ Branch 17 → 18 taken 7 times.
✓ Branch 18 → 19 taken 7 times.
✓ Branch 20 → 21 taken 4 times.
✓ Branch 20 → 25 taken 3 times.
7 if (!validFolderName(trimmed, folderDelimiter())) {
161
2/2
✓ Branch 21 → 22 taken 4 times.
✓ Branch 22 → 23 taken 4 times.
4 m_deps.setStatus(tr("Enter a valid folder name."));
162 4 return false;
163 }
164
1/1
✓ Branch 25 → 26 taken 3 times.
3 const QString delimiter = folderDelimiter();
165
1/1
✓ Branch 26 → 27 taken 3 times.
3 const qint64 split = folderPath.lastIndexOf(delimiter);
166
1/3
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 29 taken 3 times.
✗ Branch 28 → 30 not taken.
3 const QString parent = split > 0 ? folderPath.left(split) : QString{};
167
1/1
✓ Branch 30 → 31 taken 3 times.
3 const QString leaf = ImapResponseParser::encodeMailboxName(trimmed); // F-03, S106-968
168
2/8
✓ Branch 32 → 33 taken 3 times.
✗ Branch 32 → 34 not taken.
✗ Branch 34 → 35 not taken.
✗ Branch 35 → 36 not taken.
✗ Branch 36 → 37 not taken.
✓ Branch 36 → 38 taken 3 times.
✗ Branch 69 → 70 not taken.
✗ Branch 69 → 71 not taken.
3 const QString newPath = parent.isEmpty() ? leaf : parent + delimiter + leaf;
169
2/2
✓ Branch 39 → 40 taken 1 time.
✓ Branch 39 → 44 taken 2 times.
3 if (newPath == folderPath) {
170 // S106-376: the dialog shows the status as its error — a stale line was
171 // shown for an unchanged name.
172
2/2
✓ Branch 40 → 41 taken 1 time.
✓ Branch 41 → 42 taken 1 time.
1 m_deps.setStatus(tr("The folder already has this name."));
173 1 return false;
174 }
175
1/1
✓ Branch 49 → 50 taken 2 times.
4 m_session.imap()->executeAfterIdle(
176
1/3
✓ Branch 48 → 49 taken 2 times.
✗ Branch 80 → 81 not taken.
✗ Branch 80 → 82 not taken.
6 [imap = m_session.imap(), folderPath, newPath] { imap->renameFolder(folderPath, newPath); });
177 2 return true;
178 7 }
179
180 // The last path component in its wire form (modified UTF-7).
181 16 QString QuickFolderActions::wireLeaf(const QString &folderPath) const {
182
1/1
✓ Branch 2 → 3 taken 16 times.
16 const QString delimiter = folderDelimiter();
183
1/1
✓ Branch 3 → 4 taken 16 times.
16 const qint64 split = folderPath.lastIndexOf(delimiter);
184
3/3
✓ Branch 4 → 5 taken 8 times.
✓ Branch 4 → 7 taken 8 times.
✓ Branch 6 → 8 taken 8 times.
32 return split > 0 ? folderPath.mid(split + delimiter.size()) : folderPath;
185 16 }
186
187 // The leaf as the user reads it (rename prefill, notifications) — decoded,
188 // so "Entw&APw-rfe" shows as "Entwürfe" (S106-968).
189 14 QString QuickFolderActions::folderLeafName(const QString &folderPath) const {
190
2/2
✓ Branch 2 → 3 taken 14 times.
✓ Branch 3 → 4 taken 14 times.
14 return ImapResponseParser::decodeMailboxName(wireLeaf(folderPath));
191 }
192
193 8 bool QuickFolderActions::moveFolder(const QString &folderPath, const QString &newParentPath) {
194
6/7
✓ Branch 2 → 3 taken 8 times.
✓ Branch 3 → 4 taken 7 times.
✓ Branch 3 → 6 taken 1 time.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 7 times.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 7 times.
8 if (!m_deps.configured() || !m_session.imap()) return false;
195
3/3
✓ Branch 10 → 11 taken 7 times.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 16 taken 6 times.
7 if (folderIsProtected(folderPath)) {
196
2/2
✓ Branch 12 → 13 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
1 m_deps.setStatus(tr("Special folders cannot be moved"));
197 1 return false;
198 }
199
1/1
✓ Branch 16 → 17 taken 6 times.
6 const QString parent = newParentPath.trimmed();
200
1/1
✓ Branch 17 → 18 taken 6 times.
6 const QString delimiter = folderDelimiter();
201
1/2
✓ Branch 19 → 20 taken 6 times.
✗ Branch 19 → 45 not taken.
6 if (!parent.isEmpty()) {
202
6/7
✓ Branch 21 → 22 taken 4 times.
✓ Branch 21 → 24 taken 2 times.
✓ Branch 22 → 23 taken 4 times.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 4 times.
✓ Branch 26 → 27 taken 2 times.
✓ Branch 26 → 31 taken 4 times.
6 if (!m_availableFolders.contains(parent) || !m_folders.isSelectable(parent)) {
203
2/2
✓ Branch 27 → 28 taken 2 times.
✓ Branch 28 → 29 taken 2 times.
2 m_deps.setStatus(tr("The target folder is not available."));
204 2 return false;
205 }
206 // A folder cannot move into itself or one of its own descendants.
207
10/12
✓ Branch 32 → 33 taken 3 times.
✓ Branch 32 → 36 taken 1 time.
✓ Branch 33 → 34 taken 3 times.
✓ Branch 34 → 35 taken 3 times.
✓ Branch 35 → 36 taken 1 time.
✓ Branch 35 → 37 taken 2 times.
✓ Branch 38 → 39 taken 3 times.
✓ Branch 38 → 40 taken 1 time.
✓ Branch 40 → 41 taken 2 times.
✓ Branch 40 → 45 taken 2 times.
✗ Branch 78 → 79 not taken.
✗ Branch 78 → 80 not taken.
4 if (parent == folderPath || parent.startsWith(folderPath + delimiter)) {
208
2/2
✓ Branch 41 → 42 taken 2 times.
✓ Branch 42 → 43 taken 2 times.
2 m_deps.setStatus(tr("A folder cannot move below itself."));
209 2 return false;
210 }
211 }
212 2 const QString newPath = parent.isEmpty()
213 2 ? wireLeaf(folderPath)
214
6/14
✗ Branch 46 → 47 not taken.
✓ Branch 46 → 48 taken 2 times.
✗ Branch 47 → 51 not taken.
✓ Branch 48 → 49 taken 2 times.
✓ Branch 49 → 50 taken 2 times.
✓ Branch 50 → 51 taken 2 times.
✓ Branch 51 → 52 taken 2 times.
✗ Branch 51 → 53 not taken.
✓ Branch 53 → 54 taken 2 times.
✗ Branch 53 → 55 not taken.
✗ Branch 85 → 86 not taken.
✗ Branch 85 → 87 not taken.
✗ Branch 89 → 90 not taken.
✗ Branch 89 → 91 not taken.
2 : parent + delimiter + wireLeaf(folderPath);
215
1/2
✗ Branch 56 → 57 not taken.
✓ Branch 56 → 58 taken 2 times.
2 if (newPath == folderPath) return false;
216
1/1
✓ Branch 63 → 64 taken 2 times.
4 m_session.imap()->executeAfterIdle(
217
1/3
✓ Branch 62 → 63 taken 2 times.
✗ Branch 97 → 98 not taken.
✗ Branch 97 → 99 not taken.
6 [imap = m_session.imap(), folderPath, newPath] { imap->renameFolder(folderPath, newPath); });
218 2 return true;
219 6 }
220
221 3 bool QuickFolderActions::deleteFolder(const QString &folderPath) {
222
6/7
✓ Branch 2 → 3 taken 3 times.
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 6 taken 1 time.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 2 times.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 2 times.
3 if (!m_deps.configured() || !m_session.imap()) return false;
223
3/3
✓ Branch 10 → 11 taken 2 times.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 16 taken 1 time.
2 if (folderIsProtected(folderPath)) {
224
2/2
✓ Branch 12 → 13 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
1 m_deps.setStatus(tr("Special folders cannot be deleted"));
225 1 return false;
226 }
227 // Children first (bottom-up by path depth), then the folder itself.
228
1/1
✓ Branch 16 → 17 taken 1 time.
1 QStringList deleteList = childFolders(folderPath);
229 // S106-340: the protection holds for the folders the cascade deletes too.
230
1/2
✗ Branch 29 → 20 not taken.
✓ Branch 29 → 30 taken 1 time.
1 for (const QString &child : std::as_const(deleteList))
231 ✗ if (folderIsProtected(child)) {
232 ✗ m_deps.setStatus(tr("This folder holds a special folder and cannot be deleted"));
233 ✗ return false;
234 }
235
1/1
✓ Branch 30 → 31 taken 1 time.
1 const QString delimiter = folderDelimiter();
236
3/3
✓ Branch 31 → 32 taken 1 time.
✓ Branch 32 → 33 taken 1 time.
✓ Branch 33 → 34 taken 1 time.
1 std::sort(deleteList.begin(), deleteList.end(),
237 ✗ [&delimiter](const QString &a, const QString &b) {
238 ✗ return a.count(delimiter) > b.count(delimiter);
239 });
240
1/1
✓ Branch 34 → 35 taken 1 time.
1 deleteList.append(folderPath);
241
4/4
✓ Branch 35 → 36 taken 1 time.
✓ Branch 36 → 37 taken 1 time.
✓ Branch 48 → 38 taken 1 time.
✓ Branch 48 → 49 taken 1 time.
2 for (const QString &path : deleteList)
242
2/2
✓ Branch 42 → 43 taken 1 time.
✓ Branch 43 → 44 taken 1 time.
2 m_session.imap()->executeAfterIdle(
243 3 [imap = m_session.imap(), path] { imap->deleteFolder(path); });
244 1 return true;
245 1 }
246
247 830 void QuickFolderActions::refreshFolderAppearance() {
248
2/3
✓ Branch 5 → 6 taken 830 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 830 times.
1660 if (!m_iconCatalog.load()) return;
249
1/1
✓ Branch 10 → 11 taken 830 times.
830 QSettings settings;
250 830 QHash<QString, FolderPresentationModel::Appearance> appearance;
251
1/1
✓ Branch 12 → 13 taken 830 times.
830 appearance.reserve(m_folderInfos.size());
252
2/2
✓ Branch 52 → 16 taken 2346 times.
✓ Branch 52 → 53 taken 830 times.
3176 for (const auto &folder : std::as_const(m_folderInfos)) {
253
3/3
✓ Branch 20 → 21 taken 2346 times.
✓ Branch 22 → 23 taken 2346 times.
✓ Branch 23 → 24 taken 2346 times.
4692 const QString custom = settings.value(QStringLiteral("folder/icon/") + folder.path).toString();
254 2346 FolderPresentationModel::Appearance entry;
255 2346 entry.glyph = m_iconCatalog.codePoint(custom);
256
3/3
✓ Branch 29 → 30 taken 2344 times.
✓ Branch 29 → 34 taken 2 times.
✓ Branch 30 → 31 taken 2344 times.
2346 if (entry.glyph == 0) entry.glyph = m_iconCatalog.codePoint(FolderIconCatalog::defaultIconName(folder));
257
3/3
✓ Branch 37 → 38 taken 2346 times.
✓ Branch 39 → 40 taken 2346 times.
✓ Branch 40 → 41 taken 2346 times.
4692 entry.color = settings.value(QStringLiteral("folder/color/") + folder.path).toString();
258
1/1
✓ Branch 47 → 48 taken 2346 times.
2346 appearance.insert(folder.path, entry);
259 2346 }
260
1/1
✓ Branch 57 → 58 taken 830 times.
1660 m_folders.setAppearance(appearance, m_iconCatalog.codePoint(QStringLiteral("folder-outline")));
261 830 }
262
263 765 QAbstractItemModel *QuickFolderActions::folderIcons() {
264
1/2
✓ Branch 3 → 4 taken 765 times.
✗ Branch 3 → 14 not taken.
765 if (!m_iconList) {
265
1/1
✓ Branch 7 → 8 taken 765 times.
1530 m_iconCatalog.load();
266
1/1
✓ Branch 10 → 11 taken 765 times.
765 m_iconList = std::make_unique<FolderIconListModel>(&m_iconCatalog);
267 }
268 765 return m_iconList.get();
269 }
270
271 780 int QuickFolderActions::iconGlyph(const QString &name) {
272
1/1
✓ Branch 5 → 6 taken 780 times.
1560 m_iconCatalog.load();
273 780 return m_iconCatalog.codePoint(name);
274 }
275
276 2 QVariantMap QuickFolderActions::folderProperties(const QString &path) {
277 2 QVariantMap properties;
278 2 const auto &account = m_session.accounts()->primary();
279
4/7
✓ Branch 5 → 6 taken 2 times.
✗ Branch 5 → 8 not taken.
✓ Branch 6 → 7 taken 2 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 2 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 2 times.
2 if (!account || !m_folders.isSelectable(path)) return properties;
280 2 FolderInfo info;
281
1/2
✓ Branch 21 → 15 taken 3 times.
✗ Branch 21 → 22 not taken.
3 for (const auto &folder : std::as_const(m_folderInfos))
282
2/2
✓ Branch 17 → 18 taken 2 times.
✓ Branch 17 → 19 taken 1 time.
3 if (folder.path == path) { info = folder; break; }
283
1/1
✓ Branch 22 → 23 taken 2 times.
2 QSettings settings;
284 2 const auto subscribed = m_session.controller()->subscribedFolders();
285
1/1
✓ Branch 29 → 30 taken 2 times.
4 properties.insert(QStringLiteral("path"), path);
286
3/3
✓ Branch 33 → 34 taken 2 times.
✓ Branch 34 → 35 taken 2 times.
✓ Branch 38 → 39 taken 2 times.
4 properties.insert(QStringLiteral("name"), m_folders.data(m_folders.indexForPath(path),
287 FolderPresentationModel::NameRole));
288
4/4
✓ Branch 45 → 46 taken 2 times.
✓ Branch 47 → 48 taken 2 times.
✓ Branch 48 → 49 taken 2 times.
✓ Branch 53 → 54 taken 2 times.
4 properties.insert(QStringLiteral("icon"), settings.value(QStringLiteral("folder/icon/") + path).toString());
289
2/2
✓ Branch 62 → 63 taken 2 times.
✓ Branch 67 → 68 taken 2 times.
4 properties.insert(QStringLiteral("defaultIcon"), FolderIconCatalog::defaultIconName(info));
290
4/4
✓ Branch 75 → 76 taken 2 times.
✓ Branch 77 → 78 taken 2 times.
✓ Branch 78 → 79 taken 2 times.
✓ Branch 83 → 84 taken 2 times.
4 properties.insert(QStringLiteral("color"), settings.value(QStringLiteral("folder/color/") + path).toString());
291
1/1
✓ Branch 97 → 98 taken 2 times.
4 properties.insert(QStringLiteral("subscribed"), subscribed.contains(path));
292
2/2
✓ Branch 101 → 102 taken 2 times.
✓ Branch 107 → 108 taken 2 times.
4 properties.insert(QStringLiteral("hidden"), FolderPreferences::loadHidden(m_profileDirectory).contains(path));
293
1/1
✓ Branch 117 → 118 taken 2 times.
4 properties.insert(QStringLiteral("current"), path == m_folderPath);
294
2/2
✓ Branch 123 → 124 taken 2 times.
✓ Branch 128 → 129 taken 2 times.
4 properties.insert(QStringLiteral("flags"), info.flags.join(QLatin1Char(' ')));
295 2 return properties;
296 2 }
297
298 7 bool QuickFolderActions::saveFolderProperties(const QString &path, const QString &icon, const QString &color,
299 bool subscribed, bool hidden) {
300 7 const auto &account = m_session.accounts()->primary();
301
4/7
✓ Branch 5 → 6 taken 7 times.
✗ Branch 5 → 8 not taken.
✓ Branch 6 → 7 taken 7 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 7 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 7 times.
7 if (!account || !m_folders.isSelectable(path)) return false;
302 static const QRegularExpression colorPattern(
303
4/7
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 25 taken 6 times.
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 25 not taken.
✓ Branch 19 → 20 taken 1 time.
✗ Branch 174 → 175 not taken.
✗ Branch 174 → 176 not taken.
8 QStringLiteral("^#(?:[0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$"));
304
1/1
✓ Branch 28 → 29 taken 7 times.
14 m_iconCatalog.load();
305
4/4
✓ Branch 32 → 33 taken 4 times.
✓ Branch 32 → 35 taken 3 times.
✓ Branch 34 → 35 taken 3 times.
✓ Branch 34 → 41 taken 1 time.
13 if ((!icon.isEmpty() && !m_iconCatalog.contains(icon)) ||
306
10/12
✓ Branch 36 → 37 taken 2 times.
✓ Branch 36 → 42 taken 4 times.
✓ Branch 38 → 39 taken 2 times.
✓ Branch 39 → 40 taken 2 times.
✓ Branch 40 → 41 taken 1 time.
✓ Branch 40 → 42 taken 1 time.
✓ Branch 43 → 44 taken 2 times.
✓ Branch 43 → 45 taken 5 times.
✓ Branch 45 → 46 taken 2 times.
✓ Branch 45 → 50 taken 5 times.
✗ Branch 183 → 184 not taken.
✗ Branch 183 → 185 not taken.
15 (!color.isEmpty() && !colorPattern.match(color).hasMatch())) {
307
2/2
✓ Branch 46 → 47 taken 2 times.
✓ Branch 47 → 48 taken 2 times.
2 m_deps.reportError(tr("Invalid folder icon or color."));
308 2 return false;
309 }
310 // S106-338: the two files are written first; only when both are saved
311 // does anything apply (the appearance used to be stored and the
312 // subscription applied before a later failure returned).
313 5 const QStringList previousSubscriptions = m_session.controller()->subscribedFolders();
314 5 QStringList subscriptions = previousSubscriptions;
315 5 const bool subscriptionChanged = subscribed != subscriptions.contains(path);
316
2/2
✓ Branch 54 → 55 taken 1 time.
✓ Branch 54 → 66 taken 4 times.
5 if (subscriptionChanged) {
317
2/4
✗ Branch 55 → 56 not taken.
✓ Branch 55 → 57 taken 1 time.
✗ Branch 56 → 58 not taken.
✓ Branch 57 → 58 taken 1 time.
1 if (subscribed) subscriptions.append(path); else subscriptions.removeAll(path);
318
3/4
✓ Branch 58 → 59 taken 1 time.
✓ Branch 59 → 60 taken 1 time.
✗ Branch 61 → 62 not taken.
✓ Branch 61 → 66 taken 1 time.
1 if (!FolderPreferences::saveAccountSubscriptions(m_profileDirectory, m_deps.accountName(), subscriptions)) {
319 ✗ m_deps.reportError(tr("The folder subscriptions could not be saved locally."));
320 ✗ return false;
321 }
322 }
323
1/1
✓ Branch 66 → 67 taken 5 times.
5 QStringList hiddenFolders = FolderPreferences::loadHidden(m_profileDirectory);
324 5 const bool hiddenChanged = hidden != hiddenFolders.contains(path);
325
2/2
✓ Branch 68 → 69 taken 1 time.
✓ Branch 68 → 83 taken 4 times.
5 if (hiddenChanged) {
326
2/4
✓ Branch 69 → 70 taken 1 time.
✗ Branch 69 → 71 not taken.
✓ Branch 70 → 72 taken 1 time.
✗ Branch 71 → 72 not taken.
1 if (hidden) hiddenFolders.append(path); else hiddenFolders.removeAll(path);
327
2/3
✓ Branch 72 → 73 taken 1 time.
✓ Branch 73 → 74 taken 1 time.
✗ Branch 73 → 83 not taken.
1 if (!FolderPreferences::saveHidden(m_profileDirectory, hiddenFolders)) {
328
1/2
✓ Branch 74 → 75 taken 1 time.
✗ Branch 74 → 79 not taken.
1 if (subscriptionChanged)
329
2/2
✓ Branch 75 → 76 taken 1 time.
✓ Branch 76 → 77 taken 1 time.
1 FolderPreferences::saveAccountSubscriptions(m_profileDirectory, m_deps.accountName(), previousSubscriptions);
330
2/2
✓ Branch 79 → 80 taken 1 time.
✓ Branch 80 → 81 taken 1 time.
1 m_deps.reportError(tr("The hidden folders could not be saved locally."));
331 1 return false;
332 }
333 }
334
1/3
✗ Branch 83 → 84 not taken.
✓ Branch 83 → 86 taken 4 times.
✗ Branch 85 → 86 not taken.
4 if (subscriptionChanged) m_session.controller()->setSubscribedFolders(subscriptions);
335
1/1
✓ Branch 86 → 87 taken 4 times.
4 QSettings settings;
336
4/4
✓ Branch 88 → 89 taken 2 times.
✓ Branch 88 → 98 taken 2 times.
✓ Branch 92 → 93 taken 2 times.
✓ Branch 93 → 94 taken 2 times.
6 if (icon.isEmpty()) removeOwnValue(settings, QStringLiteral("folder/icon/") + path);
337
2/2
✓ Branch 102 → 103 taken 2 times.
✓ Branch 104 → 105 taken 2 times.
4 else settings.setValue(QStringLiteral("folder/icon/") + path, icon);
338
4/4
✓ Branch 111 → 112 taken 3 times.
✓ Branch 111 → 121 taken 1 time.
✓ Branch 115 → 116 taken 3 times.
✓ Branch 116 → 117 taken 3 times.
7 if (color.isEmpty()) removeOwnValue(settings, QStringLiteral("folder/color/") + path);
339
2/2
✓ Branch 125 → 126 taken 1 time.
✓ Branch 127 → 128 taken 1 time.
2 else settings.setValue(QStringLiteral("folder/color/") + path, color);
340
10/10
✓ Branch 133 → 134 taken 4 times.
✓ Branch 137 → 138 taken 4 times.
✓ Branch 138 → 139 taken 4 times.
✓ Branch 139 → 140 taken 4 times.
✓ Branch 140 → 141 taken 4 times.
✓ Branch 141 → 142 taken 4 times.
✓ Branch 142 → 143 taken 4 times.
✓ Branch 143 → 144 taken 4 times.
✓ Branch 151 → 135 taken 4 times.
✓ Branch 151 → 152 taken 4 times.
8 qCInfo(lcFolderIcons) << "Folder properties saved for" << path << "icon" << icon << "color" << color
341
4/4
✓ Branch 144 → 145 taken 4 times.
✓ Branch 145 → 146 taken 4 times.
✓ Branch 146 → 147 taken 4 times.
✓ Branch 147 → 148 taken 4 times.
4 << "subscribed" << subscribed << "hidden" << hidden;
342
1/3
✗ Branch 152 → 153 not taken.
✓ Branch 152 → 154 taken 4 times.
✗ Branch 153 → 155 not taken.
4 if (hiddenChanged) m_deps.reapplyFolderList();
343
1/1
✓ Branch 154 → 155 taken 4 times.
4 else refreshFolderAppearance();
344
2/3
✓ Branch 156 → 157 taken 4 times.
✗ Branch 156 → 158 not taken.
✓ Branch 157 → 158 taken 4 times.
4 if (auto *sync = m_session.settingsSync()) sync->localSettingsChanged();
345
2/2
✓ Branch 158 → 159 taken 4 times.
✓ Branch 159 → 160 taken 4 times.
4 m_deps.setStatus(tr("Folder properties saved."));
346 4 return true;
347 5 }
348
349 3 void QuickFolderActions::requestFolderStatistics(const QString &path) {
350 3 const auto &account = m_session.accounts()->primary();
351
4/7
✓ Branch 5 → 6 taken 3 times.
✗ Branch 5 → 8 not taken.
✓ Branch 6 → 7 taken 3 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 3 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 3 times.
3 if (!account || !m_folders.isSelectable(path)) return;
352 3 const quint64 sequence = ++m_folderStatsSequence;
353
1/1
✓ Branch 12 → 13 taken 3 times.
3 const QPointer<QuickFolderActions> owner(this);
354
1/1
✓ Branch 13 → 14 taken 3 times.
3 m_folderStatsPool.setMaxThreadCount(1);
355
1/7
✓ Branch 20 → 21 taken 3 times.
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 30 not taken.
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 33 not taken.
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 36 not taken.
6 m_folderStatsPool.start([owner, sequence, path, accountName = account->name,
356 3 database = m_session.cache()->databasePath()] {
357 3 QVariantMap statistics;
358
1/1
✓ Branch 2 → 3 taken 3 times.
3 MailCache reader;
359
2/3
✓ Branch 3 → 4 taken 3 times.
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 56 not taken.
3 if (reader.openReadOnly(database, 1000)) {
360
2/2
✓ Branch 6 → 7 taken 3 times.
✓ Branch 11 → 12 taken 3 times.
6 statistics.insert(QStringLiteral("headers"), reader.payloadLimits().cachedHeaderCount(accountName, path));
361
2/2
✓ Branch 16 → 17 taken 3 times.
✓ Branch 21 → 22 taken 3 times.
6 statistics.insert(QStringLiteral("bodies"), reader.payloadLimits().cachedBodyCount(accountName, path));
362
2/2
✓ Branch 26 → 27 taken 3 times.
✓ Branch 31 → 32 taken 3 times.
6 statistics.insert(QStringLiteral("diskUsage"), reader.payloadLimits().cachedDiskUsage(accountName, path));
363
2/2
✓ Branch 36 → 37 taken 3 times.
✓ Branch 41 → 42 taken 3 times.
6 statistics.insert(QStringLiteral("serverSize"), reader.payloadLimits().totalServerSize(accountName, path));
364
2/2
✓ Branch 46 → 47 taken 3 times.
✓ Branch 51 → 52 taken 3 times.
6 statistics.insert(QStringLiteral("averageSize"), reader.payloadLimits().averageMailSize(accountName, path));
365 } else {
366 ✗ statistics.insert(QStringLiteral("error"), reader.lastError());
367 }
368
1/5
✓ Branch 71 → 72 taken 3 times.
✗ Branch 135 → 136 not taken.
✗ Branch 135 → 137 not taken.
✗ Branch 138 → 139 not taken.
✗ Branch 138 → 140 not taken.
3 QMetaObject::invokeMethod(owner, [owner, sequence, path, statistics] {
369
3/6
✓ Branch 3 → 4 taken 2 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 2 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 2 times.
2 if (!owner || sequence != owner->m_folderStatsSequence) return;
370 2 emit owner->folderStatisticsReady(path, statistics);
371 }, Qt::QueuedConnection);
372 3 });
373 3 }
374
375 ✗ bool QuickFolderActions::clearFolderBodyCache(const QString &path) {
376 ✗ const auto &account = m_session.accounts()->primary();
377 ✗ if (!account || !m_folders.isSelectable(path)) return false;
378 ✗ if (!m_session.cache()->payloadLimits().purgeBodyCache(account->name, path)) {
379 ✗ qCWarning(lcFolderIcons) << "Clearing the body cache failed for" << path;
380 ✗ m_deps.setStatus(tr("The body cache could not be cleared."));
381 ✗ return false;
382 }
383 ✗ qCInfo(lcFolderIcons) << "Body cache cleared for" << path;
384 ✗ m_deps.setStatus(tr("Body cache cleared: %1").arg(path));
385 ✗ requestFolderStatistics(path);
386 ✗ return true;
387 }
388
389 2 bool QuickFolderActions::clearFolderCache(const QString &path) {
390 2 const auto &account = m_session.accounts()->primary();
391 // The open folder's list, selection and sync own its cached rows.
392
6/8
✓ Branch 5 → 6 taken 2 times.
✗ Branch 5 → 10 not taken.
✓ Branch 7 → 8 taken 2 times.
✗ Branch 7 → 10 not taken.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 11 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 14 taken 1 time.
2 if (!account || !m_folders.isSelectable(path) || path == m_folderPath) return false;
393 1 m_session.cache()->payloadLimits().purgeFolderByPath(account->name, path);
394 1 m_deps.forgetFolderProjection(path);
395
6/6
✓ Branch 19 → 20 taken 1 time.
✓ Branch 23 → 24 taken 1 time.
✓ Branch 24 → 25 taken 1 time.
✓ Branch 25 → 26 taken 1 time.
✓ Branch 29 → 21 taken 1 time.
✓ Branch 29 → 30 taken 1 time.
2 qCInfo(lcFolderIcons) << "Folder cache cleared for" << path;
396
3/3
✓ Branch 30 → 31 taken 1 time.
✓ Branch 32 → 33 taken 1 time.
✓ Branch 33 → 34 taken 1 time.
2 m_deps.setStatus(tr("Folder cache cleared: %1").arg(path));
397 1 requestFolderStatistics(path);
398 1 return true;
399 }
400
401 3 bool QuickFolderActions::showFolderContextMenu(const QString &path, QObject *window,
402 qreal sceneX, qreal sceneY) {
403 3 auto *quickWindow = qobject_cast<QWindow *>(window);
404
6/7
✓ Branch 3 → 4 taken 3 times.
✗ Branch 3 → 9 not taken.
✓ Branch 5 → 6 taken 3 times.
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 9 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 13 taken 2 times.
5 if (!quickWindow || !qobject_cast<QApplication *>(QCoreApplication::instance()) ||
405
2/3
✓ Branch 7 → 8 taken 2 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 2 times.
2 !m_folders.isSelectable(path))
406 1 return false;
407
3/6
✓ Branch 13 → 14 taken 2 times.
✓ Branch 14 → 15 taken 2 times.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 2 times.
✗ Branch 131 → 132 not taken.
✗ Branch 131 → 133 not taken.
2 auto *menu = new QMenu;
408
1/1
✓ Branch 17 → 18 taken 2 times.
2 menu->setAttribute(Qt::WA_DeleteOnClose);
409
1/1
✓ Branch 21 → 22 taken 2 times.
4 menu->setObjectName(QStringLiteral("nativeFolderContextMenu"));
410
1/1
✓ Branch 24 → 25 taken 2 times.
2 const QPointer<QuickFolderActions> owner(this);
411 13 const auto add = [&](const QString &action, const QString &icon, const QString &label) {
412
2/2
✓ Branch 2 → 3 taken 13 times.
✓ Branch 3 → 4 taken 13 times.
13 auto *entry = menu->addAction(QIcon::fromTheme(icon), label);
413
2/2
✓ Branch 8 → 9 taken 13 times.
✓ Branch 9 → 10 taken 13 times.
26 entry->setObjectName(QStringLiteral("folderContext-") + action);
414
1/5
✓ Branch 16 → 17 taken 13 times.
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 36 not taken.
✗ Branch 37 → 38 not taken.
✗ Branch 37 → 39 not taken.
13 connect(entry, &QAction::triggered, this, [owner, action, path] {
415
1/2
✓ Branch 3 → 4 taken 2 times.
✗ Branch 3 → 6 not taken.
2 if (owner) emit owner->folderContextActionRequested(action, path);
416 2 });
417 13 };
418
2/2
✓ Branch 25 → 26 taken 2 times.
✓ Branch 32 → 33 taken 2 times.
6 add(QStringLiteral("new"), QStringLiteral("folder-new"), tr("New subfolder"));
419
2/2
✓ Branch 38 → 39 taken 2 times.
✓ Branch 45 → 46 taken 2 times.
6 add(QStringLiteral("rename"), QStringLiteral("edit-rename"), tr("Rename"));
420
2/2
✓ Branch 51 → 52 taken 2 times.
✓ Branch 58 → 59 taken 2 times.
6 add(QStringLiteral("move"), QStringLiteral("folder-move"), tr("Move"));
421
2/2
✓ Branch 64 → 65 taken 2 times.
✓ Branch 71 → 72 taken 2 times.
6 add(QStringLiteral("delete"), QStringLiteral("edit-delete"), tr("Delete"));
422
1/1
✓ Branch 77 → 78 taken 2 times.
2 menu->addSeparator();
423
2/2
✓ Branch 78 → 79 taken 2 times.
✓ Branch 85 → 86 taken 2 times.
6 add(QStringLiteral("markRead"), QStringLiteral("mail-mark-read"), tr("Mark read"));
424
1/1
✓ Branch 91 → 92 taken 2 times.
2 menu->addSeparator();
425
2/2
✓ Branch 92 → 93 taken 1 time.
✓ Branch 92 → 107 taken 1 time.
2 if (m_rulesMenu)
426
2/2
✓ Branch 93 → 94 taken 1 time.
✓ Branch 100 → 101 taken 1 time.
3 add(QStringLiteral("rules"), QStringLiteral("view-filter"), tr("Rules for this folder…"));
427
2/2
✓ Branch 107 → 108 taken 2 times.
✓ Branch 114 → 115 taken 2 times.
6 add(QStringLiteral("properties"), QStringLiteral("document-properties"), tr("Properties…"));
428 // A popup needs its transient parent on Wayland (xdg_popup); the position
429 // is the pointer's scene position captured before the folder selection
430 // could scroll or relayout the tree.
431
1/1
✓ Branch 120 → 121 taken 2 times.
2 menu->winId();
432
3/4
✓ Branch 121 → 122 taken 2 times.
✓ Branch 122 → 123 taken 2 times.
✗ Branch 122 → 124 not taken.
✓ Branch 123 → 124 taken 2 times.
2 if (auto *handle = menu->windowHandle()) handle->setTransientParent(quickWindow);
433
2/2
✓ Branch 125 → 126 taken 2 times.
✓ Branch 127 → 128 taken 2 times.
2 menu->popup(quickWindow->mapToGlobal(QPointF(sceneX, sceneY)).toPoint());
434 2 return true;
435 2 }
436