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: 84.7% 166 / 0 / 196
Functions: 94.4% 17 / 0 / 18
Branches: 58.9% 155 / 0 / 263

presentation/QuickSourceViewer.cpp
Line Branch Exec Source
1 #include "QuickSourceViewer.h"
2
3 #include "WorkspaceState.h"
4 #include "app/core/MailSessionCore.h"
5 #include "data/MailCache.h"
6 #include "service/MailPayloadExportService.h"
7 #include "util/PdfAdmission.h"
8 #include <QDir>
9 #include <QStandardPaths>
10
11 // F6: viewer heuristic, not a security boundary — the extension/content
12 // type only selects the inline PDF viewer, never grants trust. Downloads
13 // and the sanitizer path treat every attachment as untrusted bytes.
14 namespace {
15 3 bool contentTypeIsPdf(const QString &contentType, const QString &filename) {
16
3/9
✓ Branch 5 → 6 taken 3 times.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 12 taken 3 times.
✓ Branch 20 → 21 taken 3 times.
✗ Branch 20 → 22 not taken.
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 34 not taken.
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 38 not taken.
9 return contentType.startsWith(QStringLiteral("application/pdf"), Qt::CaseInsensitive) ||
17
3/13
✗ Branch 10 → 11 not taken.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 13 not taken.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 3 times.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 3 times.
✓ Branch 18 → 19 taken 3 times.
✗ Branch 18 → 20 not taken.
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 26 not taken.
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 30 not taken.
6 filename.endsWith(QStringLiteral(".pdf"), Qt::CaseInsensitive);
18 }
19 }
20
21 // F-71 (SPRINT-104): the number of bytes at the END of `window` that form
22 // a possibly-incomplete multi-byte UTF-8 sequence (1..3), 0 when the tail
23 // is complete or malformed. Held back until the next window so a valid
24 // character crossing the source-window boundary decodes once, instead of
25 // two replacement characters from two independent decodes.
26 2 int trailingPartialUtf8(const QByteArray &window) {
27 2 const int size = window.size();
28
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 if (size == 0) return 0;
29
2/2
✓ Branch 28 → 7 taken 8 times.
✓ Branch 28 → 29 taken 1 time.
9 for (int back = qMin(3, size - 1); back >= 0; --back) {
30 8 const uchar lead = static_cast<uchar>(window.at(size - 1 - back));
31
3/4
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 7 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 1 time.
8 if (lead < 0xC0 || lead > 0xF4)
32 7 continue; // not a lead byte further back
33
2/4
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 16 not taken.
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 14 not taken.
1 const int length = lead >= 0xF0 ? 4 : lead >= 0xE0 ? 3 : 2;
34 1 const int available = back + 1;
35
1/2
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 1 time.
1 if (available >= length)
36 ✗ return 0; // the sequence is complete within this window
37 // every byte after the lead must be a continuation byte
38
1/2
✗ Branch 25 → 20 not taken.
✓ Branch 25 → 26 taken 1 time.
1 for (int k = 0; k < back; ++k) {
39 ✗ const uchar tail = static_cast<uchar>(window.at(size - 1 - k));
40 ✗ if (tail < 0x80 || tail >= 0xC0)
41 ✗ return 0; // malformed: let fromUtf8 produce the replacement
42 }
43 1 return length - available;
44 }
45 1 return 0;
46 }
47
48 795 QuickSourceViewer::QuickSourceViewer(MailSessionCore &session, WorkspaceState &workspace,
49 const QString &profileDirectory, Deps deps,
50 795 QObject *parent)
51 795 : QObject(parent), m_session(session), m_workspace(workspace),
52
2/2
✓ Branch 7 → 8 taken 795 times.
✓ Branch 9 → 10 taken 795 times.
795 m_profileDirectory(profileDirectory), m_deps(std::move(deps)) {}
53
54 // Source view and PDF preview close on selection change.
55 // Formerly QuickMailBackend::wireSourceView(); the backend calls this at the
56 // same constructor position, which keeps this handler the 6th receiver of
57 // WorkspaceState::selectionChanged (Sprint 91 rule 4).
58 795 void QuickSourceViewer::connectSignals() {
59 795 WorkspaceState &workspace = m_workspace;
60 // The raw-source view describes exactly the selected mail; any selection
61 // change (tab switch, new mail, account reload) closes it.
62
1/1
✓ Branch 2 → 3 taken 795 times.
795 connect(&workspace, &WorkspaceState::selectionChanged, this, [this] {
63
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 1569 times.
1569 if (m_sourceActive) closeSource();
64 // F3: stop a preview that is still in flight for the previous selection.
65 // The dedicated owner keeps attachment saves and source reads running.
66 1569 MailPayloadExportService::shared()->cancel(&m_pdfOwner);
67
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 10 taken 1569 times.
1569 if (!m_pdfRejectedAttachments.isEmpty()) {
68 ✗ m_pdfRejectedAttachments.clear();
69 ✗ emit pdfChanged();
70 }
71 1569 closePdfInternal();
72 1569 });
73 795 }
74
75 3 bool QuickSourceViewer::saveAttachment(qint64 attachmentId) { const auto id = m_workspace.mailId();
76
7/9
✓ Branch 5 → 6 taken 2 times.
✓ Branch 5 → 9 taken 1 time.
✓ Branch 6 → 7 taken 2 times.
✓ Branch 7 → 8 taken 2 times.
✗ Branch 7 → 9 not taken.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 2 times.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 13 taken 2 times.
3 if (!id.isValid() || !m_deps.configured() || attachmentId <= 0) return false;
77
1/1
✓ Branch 14 → 15 taken 2 times.
2 const auto epoch = m_session.cache()->uidValidity(id.folderId);
78
1/2
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 2 times.
2 if (epoch == 0) return false;
79 // Admission against the mail's cached metadata: an id that does not belong
80 // to this mail can never be exported.
81 2 QString suggestedName;
82
5/5
✓ Branch 19 → 20 taken 2 times.
✓ Branch 20 → 21 taken 2 times.
✓ Branch 21 → 22 taken 2 times.
✓ Branch 28 → 23 taken 2 times.
✓ Branch 28 → 29 taken 1 time.
3 for (const auto &attachment : m_session.cache()->attachments().attachments(id.folderId, id.uid)) {
83
2/2
✓ Branch 24 → 25 taken 1 time.
✓ Branch 24 → 26 taken 1 time.
2 if (attachment.id == attachmentId) { suggestedName = attachment.filename; break; }
84 2 }
85
2/2
✓ Branch 31 → 32 taken 1 time.
✓ Branch 31 → 33 taken 1 time.
2 if (suggestedName.isEmpty()) return false;
86 const QString directory =
87
1/1
✓ Branch 33 → 34 taken 1 time.
1 QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
88
1/2
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 40 taken 1 time.
1 if (directory.isEmpty()) {
89 ✗ m_deps.setStatus(tr("No download directory is available."));
90 ✗ return false;
91 }
92 1 MailPayloadRequest request{m_session.cache()->databasePath(), id, epoch,
93 1 MailPayloadRequest::Kind::Attachment, attachmentId};
94
3/3
✓ Branch 43 → 44 taken 1 time.
✓ Branch 45 → 46 taken 1 time.
✓ Branch 46 → 47 taken 1 time.
2 m_deps.setStatus(tr("Saving %1…").arg(suggestedName));
95
3/3
✓ Branch 49 → 50 taken 1 time.
✓ Branch 51 → 52 taken 1 time.
✓ Branch 55 → 56 taken 1 time.
2 MailPayloadExportService::shared()->saveInDirectory(
96 this, request, directory, suggestedName,
97 2 [this, suggestedName](MailPayloadReadResult result) {
98
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 1 time.
1 if (!result.completed) m_deps.setStatus(result.error);
99
3/3
✓ Branch 4 → 5 taken 1 time.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 7 → 8 taken 1 time.
2 else m_deps.setStatus(tr("Saved %1.").arg(suggestedName));
100 1 });
101 1 return true;
102 3 }
103
104 5 void QuickSourceViewer::openSource() {
105
5/6
✓ Branch 2 → 3 taken 5 times.
✗ Branch 2 → 6 not taken.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 4 times.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 4 times.
5 if (m_sourceActive || !m_workspace.mailId().isValid()) return;
106 4 m_sourceActive = true;
107 // F-70: a new view lifetime — completions of the previous lifetime
108 // (closed view, other message) can never publish into this one.
109 4 ++m_sourceGeneration;
110 4 m_sourceText.clear();
111 4 m_sourceSkip = 0;
112 4 m_sourceTotal = 0;
113 4 m_sourceFullyLoaded = false;
114 4 m_sourcePendingBytes.clear(); // F-71: decoder state resets with the view
115 4 emit sourceChanged();
116 4 requestSourceWindow();
117 }
118
119 2 void QuickSourceViewer::loadMoreSource() {
120
3/6
✓ Branch 2 → 3 taken 2 times.
✗ Branch 2 → 5 not taken.
✓ Branch 3 → 4 taken 2 times.
✗ Branch 3 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 2 times.
2 if (!m_sourceActive || m_sourcePending || m_sourceFullyLoaded) return;
121 2 requestSourceWindow();
122 }
123
124 2 void QuickSourceViewer::closeSource() {
125
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 2 times.
2 if (!m_sourceActive) return;
126 2 m_sourceActive = false;
127 // F-70: the closed lifetime's in-flight reads own nothing anymore.
128 2 ++m_sourceGeneration;
129 2 m_sourcePendingBytes.clear();
130 2 emit sourceChanged();
131 }
132
133 1 bool QuickSourceViewer::saveSource() {
134 1 const auto id = m_workspace.mailId();
135
4/7
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 8 not taken.
✓ Branch 6 → 7 taken 1 time.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 1 time.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 1 time.
1 if (!id.isValid() || !m_deps.configured()) return false;
136
1/1
✓ Branch 13 → 14 taken 1 time.
1 const auto epoch = m_session.cache()->uidValidity(id.folderId);
137
1/2
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 1 time.
1 if (epoch == 0) return false;
138 const QString directory =
139
1/1
✓ Branch 16 → 17 taken 1 time.
1 QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
140
1/2
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 23 taken 1 time.
1 if (directory.isEmpty()) {
141 ✗ m_deps.setStatus(tr("No download directory is available."));
142 ✗ return false;
143 }
144 1 MailPayloadRequest request{m_session.cache()->databasePath(), id, epoch,
145 1 MailPayloadRequest::Kind::RawSource, 0, 0, -1};
146
2/2
✓ Branch 26 → 27 taken 1 time.
✓ Branch 27 → 28 taken 1 time.
1 m_deps.setStatus(tr("Saving source…"));
147
2/2
✓ Branch 29 → 30 taken 1 time.
✓ Branch 36 → 37 taken 1 time.
2 MailPayloadExportService::shared()->save(
148
1/1
✓ Branch 34 → 35 taken 1 time.
2 this, request, directory + QStringLiteral("/source.eml"), true,
149 1 [this](MailPayloadReadResult result) {
150
3/4
✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
✓ Branch 5 → 6 taken 1 time.
1 m_deps.setStatus(result.completed ? tr("Source saved.") : result.error);
151 1 });
152 1 return true;
153 1 }
154
155 6 void QuickSourceViewer::requestSourceWindow() {
156 6 const auto id = m_workspace.mailId();
157
4/7
✓ Branch 5 → 6 taken 6 times.
✗ Branch 5 → 8 not taken.
✓ Branch 6 → 7 taken 6 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 6 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 6 times.
6 if (!id.isValid() || !m_deps.configured()) return;
158
1/1
✓ Branch 13 → 14 taken 6 times.
6 const auto epoch = m_session.cache()->uidValidity(id.folderId);
159 6 MailPayloadRequest request{m_session.cache()->databasePath(), id, epoch,
160 MailPayloadRequest::Kind::RawSource, 0,
161 6 m_sourceSkip, SourceWindowBytes};
162 6 m_sourcePending = true;
163 // F-70: a completion may only publish into the lifetime and byte
164 // position it was requested for — a reopened view (same or another
165 // message) must not admit an older read.
166 6 const quint64 generation = m_sourceGeneration;
167 6 const qint64 expectedSkip = m_sourceSkip;
168
1/1
✓ Branch 17 → 18 taken 6 times.
6 emit sourceChanged();
169
2/2
✓ Branch 18 → 19 taken 6 times.
✓ Branch 21 → 22 taken 6 times.
12 MailPayloadExportService::shared()->readWindow(
170
1/1
✓ Branch 19 → 20 taken 6 times.
6 this, request, [this, generation, expectedSkip](QByteArray bytes, MailPayloadReadResult result) {
171
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 5 times.
6 if (generation != m_sourceGeneration)
172 1 return; // a newer view lifetime owns the state
173 5 m_sourcePending = false;
174
2/4
✓ Branch 4 → 5 taken 5 times.
✗ Branch 4 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 12 taken 5 times.
5 if (!m_sourceActive || !result.completed) {
175 ✗ if (m_sourceActive && !result.completed) {
176 ✗ m_deps.setStatus(result.error);
177 ✗ m_sourceFullyLoaded = true;
178 }
179 ✗ emit sourceChanged();
180 ✗ return;
181 }
182
1/2
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 5 times.
5 if (m_sourceSkip != expectedSkip)
183 ✗ return; // not the window this state is built from
184
4/8
✓ Branch 14 → 15 taken 3 times.
✓ Branch 14 → 19 taken 2 times.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 19 taken 3 times.
✗ Branch 17 → 18 not taken.
✗ Branch 17 → 19 not taken.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 26 taken 5 times.
5 if (m_sourceSkip == 0 && bytes.isEmpty() && result.totalBytes == 0) {
185 ✗ m_deps.setStatus(tr("No source is stored for this message."));
186 ✗ m_sourceFullyLoaded = true;
187 ✗ emit sourceChanged();
188 ✗ return;
189 }
190 // F-71: hold a trailing partial UTF-8 sequence back until the
191 // next window (or the true end) so a character split across the
192 // window boundary decodes once. Byte offsets keep counting
193 // source bytes, not QString length.
194 5 m_sourceSkip += bytes.size();
195 5 m_sourceTotal = result.totalBytes;
196
2/2
✓ Branch 27 → 28 taken 2 times.
✓ Branch 27 → 29 taken 3 times.
7 m_sourceFullyLoaded = m_sourceSkip >= m_sourceTotal ||
197
1/2
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 2 times.
2 m_sourceSkip >= SourceMaximumLoadedBytes;
198
1/1
✓ Branch 31 → 32 taken 5 times.
5 QByteArray window = m_sourcePendingBytes + bytes;
199
1/1
✓ Branch 32 → 33 taken 5 times.
5 m_sourcePendingBytes.clear();
200 5 const int hold = m_sourceFullyLoaded
201
2/2
✓ Branch 33 → 34 taken 3 times.
✓ Branch 33 → 35 taken 2 times.
5 ? 0
202
1/1
✓ Branch 35 → 36 taken 2 times.
2 : trailingPartialUtf8(window);
203
2/2
✓ Branch 39 → 40 taken 5 times.
✓ Branch 40 → 41 taken 5 times.
5 m_sourceText += QString::fromUtf8(window.constData(), window.size() - hold);
204
2/2
✓ Branch 42 → 43 taken 1 time.
✓ Branch 42 → 47 taken 4 times.
5 if (hold > 0)
205
1/1
✓ Branch 43 → 44 taken 1 time.
1 m_sourcePendingBytes = window.right(hold);
206
1/1
✓ Branch 47 → 48 taken 5 times.
5 emit sourceChanged();
207 5 });
208 6 }
209
210 4 void QuickSourceViewer::viewAttachmentPdf(qint64 attachmentId) {
211 4 const auto id = m_workspace.mailId();
212
7/9
✓ Branch 5 → 6 taken 3 times.
✓ Branch 5 → 9 taken 1 time.
✓ Branch 6 → 7 taken 3 times.
✓ Branch 7 → 8 taken 3 times.
✗ Branch 7 → 9 not taken.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 3 times.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 13 taken 3 times.
4 if (!id.isValid() || !m_deps.configured() || attachmentId <= 0) return;
213
1/1
✓ Branch 14 → 15 taken 3 times.
3 const auto epoch = m_session.cache()->uidValidity(id.folderId);
214
1/2
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 3 times.
3 if (epoch == 0) return;
215 // Admission against the mail's cached metadata; only PDFs render inline.
216 3 QString filename;
217 3 bool isPdf = false;
218
4/5
✓ Branch 19 → 20 taken 3 times.
✓ Branch 20 → 21 taken 3 times.
✓ Branch 21 → 22 taken 3 times.
✓ Branch 40 → 23 taken 3 times.
✗ Branch 40 → 41 not taken.
3 for (const auto &attachment : m_session.cache()->attachments().attachments(id.folderId, id.uid)) {
219
1/2
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 26 taken 3 times.
3 if (attachment.id != attachmentId) continue;
220 3 filename = attachment.filename;
221
1/2
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 36 taken 3 times.
3 if (attachment.size > PdfAdmission::maximumBytes) {
222 ✗ m_pdfRejectedAttachments.append(attachmentId);
223 ✗ emit pdfChanged();
224 ✗ m_deps.setStatus(tr("This attachment is too large to preview. Save it to open it separately."));
225 ✗ return;
226 }
227
1/1
✓ Branch 36 → 37 taken 3 times.
3 isPdf = contentTypeIsPdf(attachment.contentType, attachment.filename);
228 3 break;
229 3 }
230
1/2
✗ Branch 45 → 47 not taken.
✓ Branch 45 → 51 taken 3 times.
3 if (!isPdf) {
231 ✗ m_deps.setStatus(tr("Only PDF attachments can be shown inline."));
232 ✗ return;
233 }
234
2/8
✗ Branch 52 → 53 not taken.
✓ Branch 52 → 57 taken 3 times.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 57 not taken.
✗ Branch 55 → 56 not taken.
✗ Branch 55 → 57 not taken.
✗ Branch 58 → 59 not taken.
✓ Branch 58 → 60 taken 3 times.
3 if (m_pdfPreview && m_pdfName == filename && m_pdfActive) return;
235 3 MailPayloadRequest request{m_session.cache()->databasePath(), id, epoch,
236 3 MailPayloadRequest::Kind::Attachment, attachmentId};
237
2/2
✓ Branch 63 → 64 taken 3 times.
✓ Branch 67 → 68 taken 3 times.
6 const QString cacheDirectory = QDir(m_profileDirectory).filePath(QStringLiteral("pdf-preview"));
238
3/3
✓ Branch 71 → 72 taken 3 times.
✓ Branch 73 → 74 taken 3 times.
✓ Branch 74 → 75 taken 3 times.
6 m_deps.setStatus(tr("Preparing %1…").arg(filename));
239
2/2
✓ Branch 77 → 78 taken 3 times.
✓ Branch 83 → 84 taken 3 times.
6 MailPayloadExportService::shared()->preparePreview(
240 &m_pdfOwner, request, cacheDirectory,
241
1/5
✓ Branch 80 → 81 taken 3 times.
✗ Branch 139 → 140 not taken.
✗ Branch 139 → 141 not taken.
✗ Branch 142 → 143 not taken.
✗ Branch 142 → 144 not taken.
6 [this, id, epoch, filename, attachmentId](std::shared_ptr<const MailPayloadPreview> preview,
242 MailPayloadReadResult result) {
243 // F3: only the request that still belongs to the current selection
244 // may publish or report. A stale result is dropped silently: its
245 // lease releases the snapshot file on scope exit, and it must not
246 // clobber the new selection's status.
247
1/2
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 9 not taken.
4 const bool current = m_workspace.mailId() == id &&
248
1/2
✓ Branch 7 → 8 taken 2 times.
✗ Branch 7 → 9 not taken.
2 m_session.cache()->uidValidity(id.folderId) == epoch;
249
4/8
✓ Branch 10 → 11 taken 2 times.
✗ Branch 10 → 17 not taken.
✓ Branch 12 → 13 taken 2 times.
✗ Branch 12 → 17 not taken.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 2 times.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 28 taken 2 times.
2 if (!result.completed || !preview || preview->path().isEmpty()) {
250 ✗ if (current)
251 ✗ m_deps.setStatus(!result.completed ? result.error : tr("The attachment could not be prepared."));
252 ✗ return;
253 }
254
1/2
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 2 times.
2 if (!current) return;
255 2 const auto admission = PdfAdmission::checkFile(preview->path());
256
2/2
✓ Branch 33 → 34 taken 1 time.
✓ Branch 33 → 44 taken 1 time.
2 if (admission != PdfAdmission::Result::Accepted) {
257
1/1
✓ Branch 35 → 36 taken 1 time.
1 m_pdfRejectedAttachments.append(attachmentId);
258 1 emit pdfChanged();
259
1/1
✓ Branch 41 → 42 taken 1 time.
1 m_deps.setStatus(admission == PdfAdmission::Result::TooLarge
260
2/4
✗ Branch 38 → 39 not taken.
✓ Branch 38 → 40 taken 1 time.
✗ Branch 39 → 41 not taken.
✓ Branch 40 → 41 taken 1 time.
2 ? tr("This attachment is too large to preview. Save it to open it separately.")
261 : tr("This attachment cannot be previewed as a PDF. Save it to open it separately."));
262 1 return;
263 }
264 1 m_pdfPreview = std::move(preview);
265 1 m_pdfActive = true;
266
1/1
✓ Branch 48 → 49 taken 1 time.
1 m_pdfSource = QUrl::fromLocalFile(m_pdfPreview->path());
267 1 m_pdfName = filename;
268 1 emit pdfChanged();
269 });
270 4 }
271
272 ✗ void QuickSourceViewer::closePdf() { closePdfInternal(); }
273
274 1569 void QuickSourceViewer::closePdfInternal() {
275 1569 m_pdfPreview.reset();
276
6/8
✓ Branch 3 → 4 taken 1568 times.
✓ Branch 3 → 9 taken 1 time.
✓ Branch 5 → 6 taken 1568 times.
✗ Branch 5 → 9 not taken.
✓ Branch 7 → 8 taken 1568 times.
✗ Branch 7 → 9 not taken.
✓ Branch 10 → 11 taken 1568 times.
✓ Branch 10 → 12 taken 1 time.
1569 if (!m_pdfActive && m_pdfSource.isEmpty() && m_pdfName.isEmpty()) return;
277 1 m_pdfActive = false;
278 1 m_pdfSource.clear();
279 1 m_pdfName.clear();
280 1 emit pdfChanged();
281 }
282