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: 90.2% 930 / 0 / 1031
Functions: 92.5% 86 / 0 / 93
Branches: 66.9% 1262 / 0 / 1887

presentation/ComposeSession.cpp
Line Branch Exec Source
1 #include "ComposeSession.h"
2 #include "ComposeLog.h"
3 #include "ComposeRecipients.h"
4 #include "SpellService.h"
5 #include <QJsonDocument>
6
7 #include "ComposeDraftFiles.h"
8 #include "ComposeHighlighter.h"
9 #include "data/ComposeDocumentFormat.h"
10 #include "data/ComposeDraftHeader.h"
11 #include "data/ImipInvitation.h"
12 #include "data/ComposeHtmlImport.h"
13 #include "data/MailHtmlWriter.h"
14 #include "data/MailTextWriter.h"
15 #include "data/MessageLanguageText.h"
16 #include "data/SendChecks.h"
17 #include "data/SignaturePlacement.h"
18 #include "service/Rfc2822Builder.h"
19
20 #include <QBuffer>
21 #include <QHash>
22 #include <QClipboard>
23 #include <QFileInfo>
24 #include <QGuiApplication>
25 #include <QImage>
26 #include <QMimeData>
27 #include <QJsonArray>
28 #include <QLocale>
29 #include <QLoggingCategory>
30 #include <QPointer>
31 #include <QQuickItem>
32 #include <QQuickTextDocument>
33 #include <QTextBlock>
34 #include <QTextCursor>
35 #include <QTextDocument>
36 #include <QUuid>
37 #include <QTimeZone>
38 #include <QtConcurrent>
39
40
41 using namespace ComposeDocumentFormat;
42
43 namespace {
44
45 19 QString quotedPlain(const QString &text) {
46 19 QStringList lines;
47
5/5
✓ Branch 5 → 6 taken 19 times.
✓ Branch 6 → 7 taken 19 times.
✓ Branch 7 → 8 taken 19 times.
✓ Branch 34 → 9 taken 52 times.
✓ Branch 34 → 35 taken 19 times.
71 for (const QString &line : text.split(QLatin1Char('\n')))
48
8/22
✓ Branch 12 → 13 taken 52 times.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 18 taken 52 times.
✗ Branch 17 → 22 not taken.
✓ Branch 21 → 22 taken 52 times.
✓ Branch 22 → 23 taken 52 times.
✓ Branch 24 → 25 taken 52 times.
✗ Branch 24 → 26 not taken.
✓ Branch 26 → 27 taken 52 times.
✗ Branch 26 → 28 not taken.
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 52 times.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 52 times.
✗ Branch 48 → 49 not taken.
✗ Branch 48 → 50 not taken.
✗ Branch 52 → 53 not taken.
✗ Branch 52 → 54 not taken.
✗ Branch 56 → 57 not taken.
✗ Branch 56 → 58 not taken.
✗ Branch 60 → 61 not taken.
✗ Branch 60 → 62 not taken.
123 lines.append(line.startsWith(QLatin1Char('>')) ? QStringLiteral(">") + line : QStringLiteral("> ") + line);
49
1/1
✓ Branch 38 → 39 taken 19 times.
38 return lines.join(QLatin1Char('\n'));
50 19 }
51
52 ✗ QString escapedParagraphs(const QString &text) {
53 ✗ QStringList paragraphs;
54 ✗ for (const QString &line : text.split(QLatin1Char('\n')))
55 ✗ paragraphs.append(QStringLiteral("<p style=\"margin:0\">") +
56 ✗ (line.isEmpty() ? QStringLiteral("<br>") : line.toHtmlEscaped()) + QStringLiteral("</p>"));
57 ✗ return paragraphs.join(QString());
58 ✗ }
59
60 constexpr qint64 IdleServerSaveAttachmentLimit = 10LL * 1024 * 1024;
61
62 } // namespace
63
64 193 ComposeSession::ComposeSession(const QString &sessionId, const QString &kind, Deps deps, QObject *parent)
65
4/4
✓ Branch 11 → 12 taken 193 times.
✓ Branch 12 → 13 taken 193 times.
✓ Branch 14 → 15 taken 193 times.
✓ Branch 15 → 16 taken 193 times.
386 : QObject(parent), m_id(sessionId), m_kind(kind), m_deps(std::move(deps)), m_to(tr("To")), m_cc(tr("Cc")),
66
8/8
✓ Branch 17 → 18 taken 193 times.
✓ Branch 18 → 19 taken 193 times.
✓ Branch 21 → 22 taken 193 times.
✓ Branch 40 → 41 taken 193 times.
✓ Branch 42 → 43 taken 193 times.
✓ Branch 48 → 49 taken 193 times.
✓ Branch 49 → 50 taken 193 times.
✓ Branch 50 → 51 taken 193 times.
772 m_bcc(tr("Bcc")) {
67
1/1
✓ Branch 53 → 54 taken 193 times.
386 m_attachments = std::make_unique<ComposeAttachmentModel>(
68
1/1
✓ Branch 52 → 53 taken 193 times.
579 ComposeDraftFiles::filesDirectory(m_deps.profileDirectory, m_id));
69
1/1
✓ Branch 57 → 58 taken 193 times.
193 m_savePool.setMaxThreadCount(1);
70
4/4
✓ Branch 59 → 60 taken 153 times.
✓ Branch 59 → 62 taken 40 times.
✓ Branch 60 → 61 taken 153 times.
✓ Branch 61 → 62 taken 153 times.
193 if (m_deps.markdownTyping) m_editor.setMarkdownTyping(m_deps.markdownTyping());
71
2/2
✓ Branch 75 → 64 taken 579 times.
✓ Branch 75 → 76 taken 193 times.
772 for (RecipientChipModel *field : {&m_to, &m_cc, &m_bcc}) {
72
2/3
✓ Branch 65 → 66 taken 579 times.
✗ Branch 65 → 70 not taken.
✓ Branch 66 → 67 taken 579 times.
579 if (m_deps.knownAddress) field->setKnownLookup(m_deps.knownAddress);
73
1/1
✓ Branch 70 → 71 taken 579 times.
579 connect(field, &RecipientChipModel::changed, this, &ComposeSession::fieldsMayHaveChanged);
74 579 connect(field, &RecipientChipModel::duplicateSkipped, this,
75
1/4
✗ Branch 2 → 3 not taken.
✗ Branch 4 → 5 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 72 → 73 taken 579 times.
1158 [this](const QString &address) { setNotice(tr("%1 is already a recipient.").arg(address)); });
76 }
77 // The models also signal progress, notices and contact lookups; only a
78 // change of what the draft holds makes it dirty.
79
1/1
✓ Branch 77 → 78 taken 193 times.
193 connect(m_attachments.get(), &ComposeAttachmentModel::changed, this, &ComposeSession::fieldsMayHaveChanged);
80
1/1
✓ Branch 79 → 80 taken 193 times.
193 m_localTimer.setSingleShot(true);
81
1/1
✓ Branch 80 → 81 taken 193 times.
193 m_localTimer.setInterval(m_deps.localSaveDelayMs);
82
1/1
✓ Branch 81 → 82 taken 193 times.
193 connect(&m_localTimer, &QTimer::timeout, this, &ComposeSession::saveLocalNow);
83 // D-12: the word at the caret is judged 400 ms after the last keystroke.
84
1/1
✓ Branch 83 → 84 taken 193 times.
193 m_typingPause.setSingleShot(true);
85
1/1
✓ Branch 84 → 85 taken 193 times.
193 m_typingPause.setInterval(400);
86
1/1
✓ Branch 85 → 86 taken 193 times.
193 connect(&m_typingPause, &QTimer::timeout, this, [this] {
87
2/2
✓ Branch 3 → 4 taken 27 times.
✓ Branch 3 → 6 taken 10 times.
37 if (m_highlighter) m_highlighter->typingPaused();
88 // D-12, F108-23: while the language is automatic, the text decides —
89 // from its first two words on, and again after every pause while the
90 // text changes (it was once, from 60 characters: a short English reply
91 // stayed German). Sonnet answers in well under a millisecond once its
92 // data is loaded (125 ms the first time, once per process).
93
5/6
✓ Branch 7 → 8 taken 29 times.
✓ Branch 7 → 11 taken 8 times.
✓ Branch 9 → 10 taken 29 times.
✗ Branch 9 → 11 not taken.
✓ Branch 12 → 13 taken 29 times.
✓ Branch 12 → 44 taken 8 times.
37 if (m_spellLanguage.isEmpty() && spellAvailable()) {
94 // The start is enough (a 1 MiB paste is not copied for it).
95
1/1
✓ Branch 13 → 14 taken 29 times.
29 const QString text = newText(1000);
96
2/2
✓ Branch 15 → 16 taken 28 times.
✓ Branch 15 → 42 taken 1 time.
29 if (text != m_guessedFrom) {
97 28 m_guessedFrom = text;
98
1/1
✓ Branch 17 → 18 taken 28 times.
28 const QString guessed = m_deps.spell->guess(text);
99
6/6
✓ Branch 19 → 20 taken 24 times.
✓ Branch 19 → 23 taken 4 times.
✓ Branch 21 → 22 taken 19 times.
✓ Branch 21 → 23 taken 5 times.
✓ Branch 24 → 25 taken 19 times.
✓ Branch 24 → 40 taken 9 times.
28 if (!guessed.isEmpty() && guessed != m_guessedLanguage) {
100 19 m_guessedLanguage = guessed;
101
2/7
✓ Branch 26 → 27 taken 19 times.
✗ Branch 30 → 31 not taken.
✗ Branch 31 → 32 not taken.
✗ Branch 32 → 33 not taken.
✗ Branch 33 → 34 not taken.
✗ Branch 37 → 28 not taken.
✓ Branch 37 → 38 taken 19 times.
19 qCInfo(lcCompose) << "spelling language from the text" << m_id << m_guessedLanguage;
102 // The check starts on the next turn (G11, one stall at a time).
103
1/1
✓ Branch 38 → 39 taken 19 times.
19 QTimer::singleShot(0, this, [this] {
104 19 applySpellCheck();
105 19 emit languageChanged();
106 19 });
107 }
108 28 }
109 29 }
110 37 });
111
1/1
✓ Branch 87 → 88 taken 193 times.
193 m_serverTimer.setSingleShot(true);
112
1/1
✓ Branch 88 → 89 taken 193 times.
193 m_serverTimer.setInterval(m_deps.serverSaveIdleMs);
113
1/1
✓ Branch 89 → 90 taken 193 times.
193 connect(&m_serverTimer, &QTimer::timeout, this, [this] {
114 // D-21: the idle server save skips large attachment sets (close and
115 // Ctrl+S still save them).
116 ✗ if (m_attachments->totalBytes() <= IdleServerSaveAttachmentLimit) queueServerDraft();
117 ✗ });
118
1/1
✓ Branch 91 → 92 taken 193 times.
193 m_editor.setFormatted(false);
119 // A refused format (a key, or rich content pasted into plain text).
120
1/1
✓ Branch 92 → 93 taken 193 times.
193 connect(&m_editor, &RichTextController::formattingRefused, this, &ComposeSession::showFormatHint);
121 193 }
122
123 352 ComposeSession::~ComposeSession() { m_savePool.waitForDone(); }
124
125 193 void ComposeSession::setPlace(const QString &place, const QVariantMap &mailReference) {
126 193 const bool moved = m_place != place;
127 193 m_place = place;
128
7/7
✓ Branch 4 → 5 taken 193 times.
✓ Branch 5 → 6 taken 157 times.
✓ Branch 5 → 9 taken 36 times.
✓ Branch 8 → 9 taken 156 times.
✓ Branch 8 → 10 taken 1 time.
✓ Branch 11 → 12 taken 192 times.
✓ Branch 11 → 13 taken 1 time.
193 if (!mailReference.isEmpty() || place != QLatin1String("inline")) m_mailReference = mailReference;
129 193 emit placeChanged();
130 // Where the draft is open belongs to the draft (a restart reopens it there).
131
8/8
✓ Branch 14 → 15 taken 90 times.
✓ Branch 14 → 22 taken 103 times.
✓ Branch 17 → 18 taken 63 times.
✓ Branch 17 → 22 taken 27 times.
✓ Branch 20 → 21 taken 60 times.
✓ Branch 20 → 22 taken 3 times.
✓ Branch 23 → 24 taken 60 times.
✓ Branch 23 → 25 taken 133 times.
193 if (moved && place != QLatin1String("outbox") && place != QLatin1String("discarded")) m_localTimer.start();
132 193 }
133
134 935 QString ComposeSession::title() const {
135
1/1
✓ Branch 2 → 3 taken 935 times.
935 const QString subject = m_subject.trimmed();
136
3/3
✓ Branch 4 → 5 taken 444 times.
✓ Branch 4 → 6 taken 491 times.
✓ Branch 5 → 7 taken 444 times.
1870 return subject.isEmpty() ? tr("New message") : subject;
137 935 }
138
139 107 void ComposeSession::setSubject(const QString &subject) {
140
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 106 times.
107 if (m_subject == subject) return;
141 106 m_subject = subject;
142 106 emit subjectChanged();
143 106 changed();
144 }
145
146 1 void ComposeSession::setPriority(int priority) {
147
1/4
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 1 time.
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 5 not taken.
1 priority = (priority == 1 || priority == 5) ? priority : 3;
148
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 1 time.
1 if (m_priority == priority) return;
149 1 m_priority = priority;
150 1 emit optionsChanged();
151 1 changed();
152 }
153
154 ✗ void ComposeSession::setReadReceipt(bool on) {
155 ✗ if (m_readReceipt == on) return;
156 ✗ m_readReceipt = on;
157 ✗ emit optionsChanged();
158 ✗ changed();
159 }
160
161 7 void ComposeSession::setNotice(const QString &notice) {
162 7 m_notice = notice;
163 7 emit noticeChanged();
164 7 }
165
166 2092 std::optional<Identity> ComposeSession::currentIdentity() const {
167
2/3
✓ Branch 3 → 4 taken 2092 times.
✗ Branch 3 → 5 not taken.
✓ Branch 4 → 6 taken 2092 times.
2092 const QList<Identity> identities = m_deps.identities ? m_deps.identities() : QList<Identity>();
168
2/2
✓ Branch 14 → 8 taken 2127 times.
✓ Branch 14 → 15 taken 5 times.
2132 for (const Identity &identity : identities)
169
2/2
✓ Branch 10 → 11 taken 2087 times.
✓ Branch 10 → 12 taken 40 times.
2127 if (identity.id == m_identityId) return identity;
170
1/2
✗ Branch 16 → 18 not taken.
✓ Branch 16 → 19 taken 5 times.
5 return identities.isEmpty() ? std::nullopt : std::optional<Identity>(identities.first());
171 2092 }
172
173 911 QVariantMap ComposeSession::identityMap() const {
174
1/1
✓ Branch 2 → 3 taken 911 times.
911 const auto identity = currentIdentity();
175
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 911 times.
911 if (!identity) return {};
176 1822 return {{QStringLiteral("id"), identity->id},
177 1822 {QStringLiteral("name"), identity->name},
178 1822 {QStringLiteral("email"), identity->email},
179 1822 {QStringLiteral("accountId"), identity->accountId},
180
1/2
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 34 taken 911 times.
2733 {QStringLiteral("label"), identity->name.isEmpty() ? identity->email
181
5/13
✓ Branch 39 → 40 taken 911 times.
✓ Branch 43 → 44 taken 4555 times.
✓ Branch 43 → 45 taken 911 times.
✓ Branch 46 → 47 taken 911 times.
✗ Branch 46 → 48 not taken.
✓ Branch 48 → 49 taken 911 times.
✗ Branch 48 → 50 not taken.
✗ Branch 65 → 66 not taken.
✗ Branch 65 → 67 not taken.
✗ Branch 70 → 71 not taken.
✗ Branch 70 → 72 not taken.
✗ Branch 74 → 75 not taken.
✗ Branch 74 → 76 not taken.
8199 : QStringLiteral("%1 <%2>").arg(identity->name, identity->email)}};
182 6377 }
183
184 226 QVariantList ComposeSession::identityChoices() const {
185 226 QVariantList out;
186
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 226 times.
226 if (!m_deps.identities) return out;
187
5/5
✓ Branch 5 → 6 taken 226 times.
✓ Branch 6 → 7 taken 226 times.
✓ Branch 7 → 8 taken 226 times.
✓ Branch 44 → 9 taken 280 times.
✓ Branch 44 → 45 taken 226 times.
506 for (const Identity &identity : m_deps.identities())
188
1/1
✓ Branch 28 → 29 taken 280 times.
840 out.append(QVariantMap{{QStringLiteral("id"), identity.id},
189 560 {QStringLiteral("name"), identity.name},
190 560 {QStringLiteral("email"), identity.email},
191
2/4
✓ Branch 32 → 33 taken 1120 times.
✓ Branch 32 → 34 taken 280 times.
✗ Branch 53 → 54 not taken.
✗ Branch 53 → 55 not taken.
2186 {QStringLiteral("accountId"), identity.accountId}});
192 226 return out;
193 1400 }
194
195 749 QVariantList ComposeSession::signatureChoices() const {
196 749 QVariantList out;
197
1/1
✓ Branch 2 → 3 taken 749 times.
749 const auto identity = currentIdentity();
198
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 749 times.
749 if (!identity) return out;
199
2/2
✓ Branch 32 → 9 taken 1150 times.
✓ Branch 32 → 33 taken 749 times.
1899 for (const Signature &signature : identity->signatures)
200
3/5
✓ Branch 20 → 21 taken 1150 times.
✓ Branch 24 → 25 taken 2300 times.
✓ Branch 24 → 26 taken 1150 times.
✗ Branch 42 → 43 not taken.
✗ Branch 42 → 44 not taken.
5750 out.append(QVariantMap{{QStringLiteral("id"), signature.id}, {QStringLiteral("name"), signature.name}});
201 749 return out;
202 4199 }
203
204 158 void ComposeSession::attachEditor(QObject *quickTextDocument) {
205 158 auto *quick = qobject_cast<QQuickTextDocument *>(quickTextDocument);
206
3/6
✓ Branch 3 → 4 taken 158 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 158 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 158 times.
158 if (!quick || !quick->textDocument()) return;
207 // G11: a TextEdit draws only the visible part of its text when it observes
208 // its viewport. Qt turns that on only for long text given through setText,
209 // never for the composer's cursor edits, so a long message was redrawn in
210 // full on every keystroke. The TextEdit owns its QQuickTextDocument.
211
1/2
✓ Branch 12 → 13 taken 158 times.
✗ Branch 12 → 14 not taken.
158 if (auto *textEdit = qobject_cast<QQuickItem *>(quick->parent()))
212 158 textEdit->setFlag(QQuickItem::ItemObservesViewport, true);
213 158 attachDocument(quick->textDocument());
214 }
215
216 // The text lives in the view's QTextDocument. When the view goes away (the
217 // composer moves between tab, reply pane and window, or the scene ends)
218 // the session keeps its own copy, so nothing typed is lost and a later
219 // save or the next view gets it back.
220 156 void ComposeSession::keepTextOf(QTextDocument *document) {
221
5/6
✓ Branch 2 → 3 taken 156 times.
✗ Branch 2 → 5 not taken.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 155 times.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 155 times.
156 if (!document || document != m_document) return;
222
1/1
✓ Branch 9 → 10 taken 155 times.
155 m_pendingRestore = snapshotText();
223 155 disconnect(m_document, nullptr, this, nullptr);
224 155 m_document = nullptr;
225 155 m_editor.setDocument(nullptr);
226
1/2
✓ Branch 17 → 18 taken 155 times.
✗ Branch 17 → 22 not taken.
155 if (m_highlighter) {
227 155 m_highlighter->setDocument(nullptr); // off the document now, not at the next event
228 155 m_highlighter->deleteLater();
229 }
230 155 m_highlighter = nullptr;
231 }
232
233 155 void ComposeSession::detachEditor(QObject *quickTextDocument) {
234 155 auto *quick = qobject_cast<QQuickTextDocument *>(quickTextDocument);
235
1/2
✓ Branch 3 → 4 taken 155 times.
✗ Branch 3 → 6 not taken.
155 if (quick) keepTextOf(quick->textDocument());
236 155 }
237
238 336 QJsonObject ComposeSession::snapshotText() const {
239
2/2
✓ Branch 3 → 4 taken 104 times.
✓ Branch 3 → 5 taken 232 times.
336 if (!m_document) return m_pendingRestore;
240 232 MailHtmlWriter::Options options;
241 232 QHash<QString, QString> contentIds;
242
6/6
✓ Branch 8 → 9 taken 232 times.
✓ Branch 9 → 10 taken 232 times.
✓ Branch 12 → 13 taken 48 times.
✓ Branch 13 → 14 taken 48 times.
✓ Branch 17 → 11 taken 48 times.
✓ Branch 17 → 18 taken 232 times.
280 for (const auto &item : m_attachments->items()) contentIds.insert(ImageScheme + item.id, item.contentId);
243 242 options.contentIdFor = [&contentIds](const QString &name) { return contentIds.value(name); };
244
2/2
✓ Branch 24 → 25 taken 232 times.
✓ Branch 25 → 26 taken 232 times.
464 return {{QStringLiteral("html"), MailHtmlWriter::wrapDocument(MailHtmlWriter::writeBody(*m_document, options).html)},
245
1/1
✓ Branch 31 → 32 taken 232 times.
464 {QStringLiteral("body"), m_document->toPlainText()},
246
3/5
✓ Branch 37 → 38 taken 232 times.
✓ Branch 41 → 42 taken 696 times.
✓ Branch 41 → 43 taken 232 times.
✗ Branch 64 → 65 not taken.
✗ Branch 64 → 66 not taken.
1160 {QStringLiteral("text"), m_document->toPlainText()}};
247 1160 }
248
249 192 void ComposeSession::attachDocument(QTextDocument *document) {
250
3/6
✓ Branch 2 → 3 taken 192 times.
✗ Branch 2 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 192 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 192 times.
192 if (!document || m_document == document) return;
251
2/2
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 13 taken 191 times.
192 if (m_document) keepTextOf(m_document);
252 192 m_document = document;
253 // A reused view's document may still hold another session's text.
254
2/2
✓ Branch 15 → 16 taken 34 times.
✓ Branch 15 → 17 taken 158 times.
192 if (m_pendingRestore.isEmpty()) document->clear();
255 192 m_editor.setDocument(document);
256
1/2
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 24 taken 192 times.
192 if (m_highlighter) {
257 ✗ m_highlighter->setDocument(nullptr);
258 ✗ m_highlighter->deleteLater();
259 }
260
3/6
✓ Branch 25 → 26 taken 192 times.
✓ Branch 26 → 27 taken 192 times.
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 29 taken 192 times.
✗ Branch 44 → 45 not taken.
✗ Branch 44 → 46 not taken.
192 m_highlighter = new ComposeHighlighter(document);
261 192 applySpellCheck();
262 192 m_editor.setFormatted(m_formatted);
263 192 applyPendingRestore();
264
1/3
✗ Branch 33 → 34 not taken.
✓ Branch 33 → 40 taken 192 times.
✗ Branch 36 → 37 not taken.
192 if (!m_startText.isEmpty()) startWithText(std::exchange(m_startText, {}));
265 // A highlighter pass (spelling, regions, dark-theme colours) reports a
266 // change of 0 characters; only real edits make the draft dirty.
267
1/1
✓ Branch 40 → 41 taken 192 times.
192 connect(document, &QTextDocument::contentsChange, this, [this](int, int removed, int added) {
268
3/4
✓ Branch 2 → 3 taken 438 times.
✓ Branch 2 → 5 taken 223 times.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 438 times.
661 if (removed == 0 && added == 0) return;
269 // Written over the signature: those blocks are the user's text now.
270 // Deferred: the document is still in the middle of the edit here.
271
7/8
✓ Branch 6 → 7 taken 661 times.
✗ Branch 6 → 12 not taken.
✓ Branch 8 → 9 taken 661 times.
✓ Branch 9 → 10 taken 661 times.
✓ Branch 10 → 11 taken 10 times.
✓ Branch 10 → 12 taken 651 times.
✓ Branch 13 → 14 taken 10 times.
✓ Branch 13 → 16 taken 651 times.
661 if (m_document && region(m_document->begin()) == SignatureRegion)
272
1/1
✓ Branch 14 → 15 taken 10 times.
10 QMetaObject::invokeMethod(this, [this] {
273
3/6
✓ Branch 3 → 4 taken 9 times.
✗ Branch 3 → 8 not taken.
✓ Branch 6 → 7 taken 9 times.
✗ Branch 6 → 8 not taken.
✓ Branch 9 → 10 taken 9 times.
✗ Branch 9 → 25 not taken.
9 if (m_document && SignaturePlacement::releaseOverwritten(*m_document)) {
274 9 m_placedSignatureText.clear();
275 9 m_signatureId.clear();
276 9 emit signatureChanged();
277
2/6
✓ Branch 13 → 14 taken 9 times.
✗ Branch 17 → 18 not taken.
✗ Branch 18 → 19 not taken.
✗ Branch 19 → 20 not taken.
✗ Branch 23 → 15 not taken.
✓ Branch 23 → 24 taken 9 times.
9 qCInfo(lcCompose) << "signature written over; its blocks are text now" << m_id;
278 }
279 9 }, Qt::QueuedConnection);
280 661 changed();
281 });
282 }
283
284 2 void ComposeSession::startWithText(const QString &text) {
285
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 if (text.isEmpty()) return;
286
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 9 taken 2 times.
2 if (!m_document) {
287 ✗ m_startText += text;
288 ✗ return;
289 }
290 2 m_editor.setSelection(0, 0, 0);
291 2 m_startCaret = m_editor.insertPlainText(text);
292
2/8
✓ Branch 11 → 12 taken 2 times.
✗ Branch 15 → 16 not taken.
✗ Branch 16 → 17 not taken.
✗ Branch 17 → 18 not taken.
✗ Branch 18 → 19 not taken.
✗ Branch 20 → 21 not taken.
✗ Branch 24 → 13 not taken.
✓ Branch 24 → 25 taken 2 times.
2 qCInfo(lcCompose) << "quick reply text placed" << m_id << "characters" << text.size();
293 }
294
295 385 void ComposeSession::applyPendingRestore() {
296
7/7
✓ Branch 3 → 4 taken 226 times.
✓ Branch 3 → 6 taken 159 times.
✓ Branch 4 → 5 taken 226 times.
✓ Branch 5 → 6 taken 34 times.
✓ Branch 5 → 7 taken 192 times.
✓ Branch 8 → 9 taken 193 times.
✓ Branch 8 → 10 taken 192 times.
385 if (!m_document || m_pendingRestore.isEmpty()) return;
297
1/1
✓ Branch 10 → 11 taken 192 times.
192 const QJsonObject pending = std::exchange(m_pendingRestore, {});
298
2/2
✓ Branch 16 → 17 taken 192 times.
✓ Branch 17 → 18 taken 192 times.
192 const QString html = pending.value(QStringLiteral("html")).toString();
299
2/2
✓ Branch 24 → 25 taken 192 times.
✓ Branch 25 → 26 taken 192 times.
192 const QString body = pending.value(QStringLiteral("body")).toString();
300
1/1
✓ Branch 30 → 31 taken 192 times.
192 m_document->clear();
301
2/2
✓ Branch 32 → 33 taken 16 times.
✓ Branch 32 → 76 taken 176 times.
192 if (!html.isEmpty()) {
302
1/1
✓ Branch 33 → 34 taken 16 times.
16 ComposeHtmlImport::Options options;
303
4/4
✓ Branch 36 → 37 taken 16 times.
✓ Branch 37 → 38 taken 16 times.
✓ Branch 47 → 39 taken 13 times.
✓ Branch 47 → 48 taken 16 times.
29 for (const auto &item : m_attachments->items())
304
4/4
✓ Branch 40 → 41 taken 4 times.
✓ Branch 40 → 45 taken 9 times.
✓ Branch 41 → 42 taken 4 times.
✓ Branch 42 → 43 taken 4 times.
29 if (item.image) options.contentIdImages.insert(item.contentId, ImageScheme + item.id);
305
2/2
✓ Branch 49 → 50 taken 16 times.
✓ Branch 50 → 51 taken 16 times.
16 const auto imported = ComposeHtmlImport::import(html.toUtf8(), options);
306
1/2
✓ Branch 53 → 54 taken 16 times.
✗ Branch 53 → 61 not taken.
16 if (imported.ok()) {
307
1/1
✓ Branch 55 → 56 taken 16 times.
16 QTextCursor cursor(m_document);
308
1/1
✓ Branch 56 → 57 taken 16 times.
16 cursor.insertFragment(imported.fragment);
309
1/1
✓ Branch 58 → 59 taken 16 times.
16 RichTextController::decorateQuotes(*m_document);
310 16 } else {
311 ✗ m_document->setPlainText(pending.value(QStringLiteral("text")).toString());
312 }
313 16 } else {
314
1/1
✓ Branch 77 → 78 taken 176 times.
176 m_document->setPlainText(body);
315 }
316
5/5
✓ Branch 81 → 82 taken 192 times.
✓ Branch 82 → 83 taken 192 times.
✓ Branch 86 → 87 taken 37 times.
✓ Branch 86 → 88 taken 155 times.
✓ Branch 87 → 88 taken 37 times.
192 if (pending.value(QStringLiteral("placeSignature")).toBool()) placeSignature(m_signatureId);
317 // Placed images show from their tray copies.
318
4/4
✓ Branch 90 → 91 taken 192 times.
✓ Branch 91 → 92 taken 192 times.
✓ Branch 106 → 93 taken 14 times.
✓ Branch 106 → 107 taken 192 times.
206 for (const auto &item : m_attachments->items())
319
6/7
✓ Branch 94 → 95 taken 4 times.
✓ Branch 94 → 104 taken 10 times.
✓ Branch 95 → 96 taken 4 times.
✗ Branch 95 → 104 not taken.
✓ Branch 97 → 98 taken 4 times.
✓ Branch 98 → 99 taken 4 times.
✓ Branch 99 → 100 taken 4 times.
206 if (item.image && item.inlinePlaced) m_editor.addImageResource(ImageScheme + item.id, QImage(item.sentPath()));
320
1/1
✓ Branch 109 → 110 taken 192 times.
192 m_document->setModified(false);
321
1/1
✓ Branch 111 → 112 taken 192 times.
192 m_document->clearUndoRedoStacks();
322 192 }
323
324 187 void ComposeSession::prefill(const QVariantMap &draft, const Quote &quote) {
325
3/3
✓ Branch 6 → 7 taken 187 times.
✓ Branch 7 → 8 taken 187 times.
✓ Branch 8 → 9 taken 187 times.
374 m_to.setText(draft.value(QStringLiteral("to")).toString());
326
3/3
✓ Branch 18 → 19 taken 187 times.
✓ Branch 19 → 20 taken 187 times.
✓ Branch 20 → 21 taken 187 times.
374 m_cc.setText(draft.value(QStringLiteral("cc")).toString());
327
3/3
✓ Branch 30 → 31 taken 187 times.
✓ Branch 31 → 32 taken 187 times.
✓ Branch 32 → 33 taken 187 times.
374 m_bcc.setText(draft.value(QStringLiteral("bcc")).toString());
328
2/2
✓ Branch 42 → 43 taken 187 times.
✓ Branch 43 → 44 taken 187 times.
374 m_subject = draft.value(QStringLiteral("subject")).toString();
329
2/2
✓ Branch 54 → 55 taken 187 times.
✓ Branch 55 → 56 taken 187 times.
374 m_inReplyTo = draft.value(QStringLiteral("inReplyTo")).toString();
330
2/2
✓ Branch 66 → 67 taken 187 times.
✓ Branch 67 → 68 taken 187 times.
374 m_references = draft.value(QStringLiteral("references")).toStringList();
331
2/2
✓ Branch 78 → 79 taken 187 times.
✓ Branch 79 → 80 taken 187 times.
374 m_originalSentTo = draft.value(QStringLiteral("originalSentTo")).toString();
332 187 m_quote = quote;
333
2/2
✓ Branch 91 → 92 taken 187 times.
✓ Branch 92 → 93 taken 187 times.
374 QString identity = draft.value(QStringLiteral("identityId")).toString();
334
2/3
✓ Branch 98 → 99 taken 187 times.
✗ Branch 98 → 100 not taken.
✓ Branch 99 → 101 taken 187 times.
187 const QList<Identity> identities = m_deps.identities ? m_deps.identities() : QList<Identity>();
335
2/2
✓ Branch 102 → 103 taken 155 times.
✓ Branch 102 → 121 taken 32 times.
187 if (identity.isEmpty()) {
336
1/2
✓ Branch 111 → 105 taken 155 times.
✗ Branch 111 → 112 not taken.
155 for (const Identity &candidate : identities)
337
1/2
✓ Branch 106 → 107 taken 155 times.
✗ Branch 106 → 109 not taken.
155 if (candidate.isDefault) {
338 155 identity = candidate.id;
339 155 break;
340 }
341
2/6
✗ Branch 113 → 114 not taken.
✓ Branch 113 → 117 taken 155 times.
✗ Branch 115 → 116 not taken.
✗ Branch 115 → 117 not taken.
✗ Branch 118 → 119 not taken.
✓ Branch 118 → 121 taken 155 times.
155 if (identity.isEmpty() && !identities.isEmpty()) identity = identities.first().id;
342 }
343 187 m_identityId = identity;
344
1/1
✓ Branch 122 → 123 taken 187 times.
187 const auto chosen = currentIdentity();
345 // D-4: a reply takes the original's format; a new mail the identity's.
346
5/5
✓ Branch 126 → 127 taken 187 times.
✓ Branch 129 → 130 taken 57 times.
✓ Branch 129 → 141 taken 130 times.
✓ Branch 134 → 135 taken 57 times.
✓ Branch 135 → 136 taken 57 times.
244 if (draft.contains(QStringLiteral("formatted"))) m_formatted = draft.value(QStringLiteral("formatted")).toBool();
347
3/4
✓ Branch 142 → 143 taken 130 times.
✗ Branch 142 → 146 not taken.
✓ Branch 144 → 145 taken 4 times.
✓ Branch 144 → 146 taken 126 times.
130 else m_formatted = chosen && chosen->formattedByDefault;
348
1/1
✓ Branch 148 → 149 taken 187 times.
187 m_editor.setFormatted(m_formatted);
349
4/4
✓ Branch 151 → 152 taken 35 times.
✓ Branch 151 → 156 taken 152 times.
✓ Branch 154 → 155 taken 33 times.
✓ Branch 154 → 156 taken 2 times.
187 const bool reply = m_kind != QLatin1String("new") && m_kind != QLatin1String("resumed");
350
3/4
✓ Branch 158 → 159 taken 187 times.
✗ Branch 158 → 165 not taken.
✓ Branch 159 → 160 taken 33 times.
✓ Branch 159 → 162 taken 154 times.
187 m_signatureId = chosen ? (reply ? chosen->replySignatureId : chosen->newMailSignatureId) : QString();
351 // A resumed draft already holds its signature as text.
352
2/2
✓ Branch 170 → 171 taken 2 times.
✓ Branch 170 → 172 taken 185 times.
187 if (m_kind == QLatin1String("resumed")) m_signatureId.clear();
353
2/2
✓ Branch 179 → 180 taken 187 times.
✓ Branch 180 → 181 taken 187 times.
748 m_pendingRestore = QJsonObject{{QStringLiteral("body"), draft.value(QStringLiteral("body")).toString()},
354
2/2
✓ Branch 189 → 190 taken 187 times.
✓ Branch 190 → 191 taken 187 times.
561 {QStringLiteral("html"), draft.value(QStringLiteral("html")).toString()},
355
2/4
✓ Branch 201 → 202 taken 561 times.
✓ Branch 201 → 203 taken 187 times.
✗ Branch 358 → 359 not taken.
✗ Branch 358 → 360 not taken.
1122 {QStringLiteral("placeSignature"), !m_signatureId.isEmpty()}};
356
1/1
✓ Branch 219 → 220 taken 187 times.
187 applyPendingRestore();
357
1/1
✓ Branch 220 → 221 taken 187 times.
187 emit subjectChanged();
358
1/1
✓ Branch 221 → 222 taken 187 times.
187 emit formattedChanged();
359
1/1
✓ Branch 222 → 223 taken 187 times.
187 emit identityChanged();
360
1/1
✓ Branch 223 → 224 taken 187 times.
187 emit signatureChanged();
361
1/1
✓ Branch 224 → 225 taken 187 times.
187 emit quoteChanged();
362 187 m_revision = m_savedRevision = 0;
363
1/1
✓ Branch 225 → 226 taken 187 times.
187 emit saveStateChanged();
364 935 }
365
366 2 void ComposeSession::setIdentity(const QString &identityId) {
367
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 if (identityId == m_identityId) return;
368 2 const QString previousSignature = m_signatureId;
369 2 m_identityId = identityId;
370
1/1
✓ Branch 7 → 8 taken 2 times.
2 const auto identity = currentIdentity();
371
1/1
✓ Branch 8 → 9 taken 2 times.
2 emit identityChanged();
372 2 const bool reply = m_kind != QLatin1String("new");
373
2/4
✓ Branch 12 → 13 taken 2 times.
✗ Branch 12 → 19 not taken.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 16 taken 2 times.
2 const QString wanted = identity ? (reply ? identity->replySignatureId : identity->newMailSignatureId) : QString();
374 // D-16: an unchanged signature is swapped in place, an edited one only on request.
375
5/7
✓ Branch 21 → 22 taken 2 times.
✗ Branch 21 → 29 not taken.
✓ Branch 23 → 24 taken 2 times.
✓ Branch 24 → 25 taken 1 time.
✓ Branch 24 → 29 taken 1 time.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 2 times.
3 if (m_document && SignaturePlacement::hasSignature(*m_document) &&
376
2/3
✓ Branch 26 → 27 taken 1 time.
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 29 taken 1 time.
1 SignaturePlacement::isEdited(*m_document, m_placedSignatureText)) {
377 ✗ emit signatureReplaceRequested(wanted);
378
7/9
✓ Branch 33 → 34 taken 1 time.
✓ Branch 33 → 39 taken 1 time.
✓ Branch 35 → 36 taken 1 time.
✗ Branch 35 → 40 not taken.
✓ Branch 37 → 38 taken 1 time.
✗ Branch 38 → 39 not taken.
✓ Branch 38 → 40 taken 1 time.
✓ Branch 41 → 42 taken 1 time.
✓ Branch 41 → 43 taken 1 time.
2 } else if (wanted != previousSignature || (m_document && !SignaturePlacement::hasSignature(*m_document))) {
379
1/1
✓ Branch 42 → 43 taken 1 time.
1 placeSignature(wanted);
380 }
381
5/6
✓ Branch 44 → 45 taken 2 times.
✗ Branch 44 → 53 not taken.
✓ Branch 47 → 48 taken 1 time.
✓ Branch 47 → 51 taken 1 time.
✓ Branch 49 → 50 taken 1 time.
✓ Branch 52 → 53 taken 2 times.
2 if (identity) m_attachments->setServerLimit(m_deps.serverSizeLimit ? m_deps.serverSizeLimit(identity->accountId) : 0);
382
5/9
✓ Branch 54 → 55 taken 2 times.
✗ Branch 54 → 59 not taken.
✓ Branch 55 → 56 taken 2 times.
✓ Branch 58 → 60 taken 2 times.
✓ Branch 60 → 61 taken 2 times.
✓ Branch 62 → 63 taken 2 times.
✗ Branch 62 → 64 not taken.
✗ Branch 74 → 75 not taken.
✗ Branch 74 → 76 not taken.
2 setNotice(identity ? tr("Sending as %1").arg(identity->email) : QString());
383
1/1
✓ Branch 64 → 65 taken 2 times.
2 changed();
384 2 }
385
386 45 void ComposeSession::placeSignature(const QString &signatureId) {
387 45 m_signatureId = signatureId;
388
1/1
✓ Branch 3 → 4 taken 45 times.
45 emit signatureChanged();
389
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 45 times.
45 if (!m_document) return;
390
1/1
✓ Branch 7 → 8 taken 45 times.
45 const auto identity = currentIdentity();
391
1/1
✓ Branch 8 → 9 taken 45 times.
45 SignaturePlacement::Content content;
392
1/2
✓ Branch 10 → 11 taken 45 times.
✗ Branch 10 → 40 not taken.
45 if (identity) {
393
2/2
✓ Branch 38 → 14 taken 69 times.
✓ Branch 38 → 39 taken 45 times.
114 for (const Signature &signature : identity->signatures) {
394
2/2
✓ Branch 16 → 17 taken 29 times.
✓ Branch 16 → 18 taken 40 times.
69 if (signature.id != signatureId) continue;
395 40 content.text = signature.text;
396
3/4
✓ Branch 19 → 20 taken 23 times.
✓ Branch 19 → 23 taken 17 times.
✓ Branch 21 → 22 taken 23 times.
✗ Branch 21 → 23 not taken.
40 content.formatted = m_formatted && !signature.html.isEmpty();
397
2/2
✓ Branch 24 → 25 taken 23 times.
✓ Branch 24 → 36 taken 17 times.
40 if (content.formatted) {
398
1/1
✓ Branch 27 → 28 taken 23 times.
23 const auto imported = ComposeHtmlImport::import(signature.html.toUtf8());
399
2/3
✓ Branch 31 → 32 taken 23 times.
✗ Branch 31 → 33 not taken.
✓ Branch 32 → 34 taken 23 times.
23 if (imported.ok()) content.fragment = imported.fragment;
400 ✗ else content.formatted = false;
401 23 }
402 }
403 }
404
1/1
✓ Branch 41 → 42 taken 45 times.
45 SignaturePlacement::place(*m_document, content);
405
1/1
✓ Branch 43 → 44 taken 45 times.
45 m_placedSignatureText = SignaturePlacement::currentText(*m_document);
406 68 }
407
408 12 void ComposeSession::setSignature(const QString &signatureId, bool replaceEdited) {
409
2/2
✓ Branch 6 → 7 taken 7 times.
✓ Branch 6 → 12 taken 5 times.
24 if (m_document && SignaturePlacement::hasSignature(*m_document) &&
410
7/8
✓ Branch 3 → 4 taken 12 times.
✗ Branch 3 → 12 not taken.
✓ Branch 9 → 10 taken 6 times.
✓ Branch 9 → 12 taken 1 time.
✓ Branch 10 → 11 taken 5 times.
✓ Branch 10 → 12 taken 1 time.
✓ Branch 13 → 14 taken 5 times.
✓ Branch 13 → 16 taken 7 times.
24 SignaturePlacement::isEdited(*m_document, m_placedSignatureText) && !replaceEdited) {
411 5 emit signatureReplaceRequested(signatureId);
412 5 return;
413 }
414 7 placeSignature(signatureId);
415 7 changed();
416 }
417
418 2 void ComposeSession::cycleSignature() {
419
1/1
✓ Branch 2 → 3 taken 2 times.
2 const QVariantList choices = signatureChoices();
420
3/5
✓ Branch 3 → 4 taken 2 times.
✓ Branch 5 → 6 taken 2 times.
✓ Branch 5 → 7 taken 2 times.
✗ Branch 35 → 36 not taken.
✗ Branch 35 → 37 not taken.
4 QStringList ids{QString()};
421
6/6
✓ Branch 10 → 11 taken 4 times.
✓ Branch 15 → 16 taken 4 times.
✓ Branch 16 → 17 taken 4 times.
✓ Branch 17 → 18 taken 4 times.
✓ Branch 26 → 9 taken 4 times.
✓ Branch 26 → 27 taken 2 times.
6 for (const QVariant &choice : choices) ids.append(choice.toMap().value(QStringLiteral("id")).toString());
422 2 const int next = (int(ids.indexOf(m_signatureId)) + 1) % int(ids.size());
423
1/1
✓ Branch 30 → 31 taken 2 times.
2 setSignature(ids.at(next), true);
424 2 }
425
426
1/2
✓ Branch 2 → 3 taken 3 times.
✗ Branch 2 → 4 not taken.
3 QStringList ComposeSession::formatLosses() const { return m_formatted ? m_editor.plainTextLosses() : QStringList(); }
427
428 14 void ComposeSession::setFormatted(bool formatted, bool confirmed) {
429
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 14 times.
15 if (formatted == m_formatted) return;
430
1/1
✓ Branch 4 → 5 taken 14 times.
14 finishPaste();
431
10/13
✓ Branch 5 → 6 taken 3 times.
✓ Branch 5 → 11 taken 11 times.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 11 taken 2 times.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 11 not taken.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 14 taken 13 times.
✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 16 taken 13 times.
✗ Branch 75 → 76 not taken.
✗ Branch 75 → 77 not taken.
14 if (!formatted && !confirmed && !formatLosses().isEmpty()) return;
432
5/6
✓ Branch 16 → 17 taken 2 times.
✓ Branch 16 → 20 taken 11 times.
✓ Branch 18 → 19 taken 2 times.
✗ Branch 18 → 20 not taken.
✓ Branch 21 → 22 taken 2 times.
✓ Branch 21 → 36 taken 11 times.
13 if (!formatted && m_document) {
433 // Placed images stay in the mail as attachments.
434
3/4
✓ Branch 24 → 25 taken 2 times.
✓ Branch 25 → 26 taken 2 times.
✗ Branch 33 → 27 not taken.
✓ Branch 33 → 34 taken 2 times.
2 for (const auto &item : m_attachments->items())
435
0/3
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 31 not taken.
✗ Branch 30 → 31 not taken.
2 if (item.inlinePlaced) m_attachments->setInline(item.id, false);
436
1/1
✓ Branch 35 → 36 taken 2 times.
2 m_editor.convertToPlainText();
437 }
438 13 m_formatted = formatted;
439
1/1
✓ Branch 36 → 37 taken 13 times.
13 m_editor.setFormatted(formatted);
440
1/1
✓ Branch 37 → 38 taken 13 times.
13 emit formattedChanged();
441
1/1
✓ Branch 38 → 39 taken 13 times.
13 dismissFormatHint();
442 13 const QString pending = std::exchange(m_pendingFormat, QString());
443
7/8
✓ Branch 42 → 43 taken 11 times.
✓ Branch 42 → 48 taken 2 times.
✓ Branch 44 → 45 taken 5 times.
✓ Branch 44 → 48 taken 6 times.
✓ Branch 46 → 47 taken 5 times.
✗ Branch 46 → 48 not taken.
✓ Branch 49 → 50 taken 5 times.
✓ Branch 49 → 70 taken 8 times.
13 if (formatted && !pending.isEmpty() && m_document) {
444
1/1
✓ Branch 51 → 52 taken 5 times.
5 const int last = qMax(0, m_document->characterCount() - 1);
445 5 const int start = qBound(0, m_pendingFormatStart, last);
446 5 const int end = qBound(start, m_pendingFormatEnd, last);
447
1/1
✓ Branch 55 → 56 taken 5 times.
5 m_editor.setSelection(start, end, end);
448
1/1
✓ Branch 56 → 57 taken 5 times.
5 applyFormat(pending);
449
2/7
✓ Branch 57 → 58 taken 5 times.
✗ Branch 61 → 62 not taken.
✗ Branch 62 → 63 not taken.
✗ Branch 63 → 64 not taken.
✗ Branch 64 → 65 not taken.
✗ Branch 68 → 59 not taken.
✓ Branch 68 → 69 taken 5 times.
5 qCInfo(lcCompose) << "format key applied after the switch" << m_id << pending;
450 }
451
1/1
✓ Branch 70 → 71 taken 13 times.
13 changed();
452 13 }
453
454 12 bool ComposeSession::applyFormat(const QString &action) {
455 using Toggle = void (RichTextController::*)();
456 static const QHash<QString, Toggle> toggles{
457 4 {QStringLiteral("bold"), &RichTextController::toggleBold},
458 4 {QStringLiteral("italic"), &RichTextController::toggleItalic},
459 4 {QStringLiteral("underline"), &RichTextController::toggleUnderline},
460 4 {QStringLiteral("strike"), &RichTextController::toggleStrikeOut},
461 4 {QStringLiteral("code"), &RichTextController::toggleCode},
462
5/10
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 48 taken 10 times.
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 48 not taken.
✓ Branch 33 → 34 taken 12 times.
✓ Branch 33 → 35 taken 2 times.
✗ Branch 83 → 84 not taken.
✗ Branch 83 → 85 not taken.
✗ Branch 134 → 135 not taken.
✗ Branch 134 → 136 not taken.
30 {QStringLiteral("quote"), &RichTextController::toggleQuote}};
463
2/2
✓ Branch 49 → 50 taken 11 times.
✓ Branch 49 → 55 taken 1 time.
12 if (const auto toggle = toggles.value(action)) {
464
2/3
✗ Branch 50 → 51 not taken.
✓ Branch 50 → 52 taken 11 times.
✓ Branch 53 → 54 taken 11 times.
11 (m_editor.*toggle)();
465 11 return true;
466 }
467
2/6
✗ Branch 57 → 58 not taken.
✓ Branch 57 → 61 taken 1 time.
✗ Branch 60 → 61 not taken.
✗ Branch 60 → 62 not taken.
✓ Branch 63 → 64 taken 1 time.
✗ Branch 63 → 80 not taken.
1 if (action == QLatin1String("bullets") || action == QLatin1String("numbered")) {
468
4/11
✓ Branch 66 → 67 taken 1 time.
✗ Branch 66 → 70 not taken.
✓ Branch 73 → 74 taken 1 time.
✗ Branch 75 → 76 not taken.
✓ Branch 75 → 77 taken 1 time.
✓ Branch 77 → 78 taken 1 time.
✗ Branch 77 → 79 not taken.
✗ Branch 140 → 141 not taken.
✗ Branch 140 → 142 not taken.
✗ Branch 144 → 145 not taken.
✗ Branch 144 → 146 not taken.
2 m_editor.toggleList(action == QLatin1String("bullets") ? QStringLiteral("disc") : QStringLiteral("decimal"));
469 1 return true;
470 }
471 ✗ return false;
472 14 }
473
474 13 void ComposeSession::format(const QString &action) {
475
2/2
✓ Branch 2 → 3 taken 7 times.
✓ Branch 2 → 5 taken 6 times.
13 if (m_formatted) {
476 7 applyFormat(action);
477 7 return;
478 }
479 6 m_pendingFormat = action;
480
2/2
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 5 times.
6 m_pendingFormatStart = m_editor.selectionStart() < m_editor.selectionEnd() ? m_editor.selectionStart()
481 5 : m_editor.cursorPosition();
482 6 m_pendingFormatEnd = qMax(m_editor.selectionEnd(), m_pendingFormatStart);
483 6 showFormatHint();
484 }
485
486 6 void ComposeSession::showFormatHint() {
487
3/4
✓ Branch 2 → 3 taken 6 times.
✗ Branch 2 → 4 not taken.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 5 times.
6 if (m_formatted || m_formatHintShown) return;
488 5 m_formatHintShown = true;
489 5 m_formatHint = true;
490 5 emit formatHintChanged();
491 }
492
493 13 void ComposeSession::dismissFormatHint() {
494
2/2
✓ Branch 2 → 3 taken 8 times.
✓ Branch 2 → 4 taken 5 times.
13 if (!m_formatHint) return;
495 5 m_formatHint = false;
496 5 emit formatHintChanged();
497 }
498
499 1 void ComposeSession::includeQuote() {
500
4/8
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 7 not taken.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 1 time.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 1 time.
1 if (!m_document || m_quoteInEditor || !hasQuote()) return;
501
1/1
✓ Branch 12 → 13 taken 1 time.
1 QTextCursor cursor(m_document);
502
1/1
✓ Branch 13 → 14 taken 1 time.
1 cursor.beginEditBlock();
503
1/1
✓ Branch 14 → 15 taken 1 time.
1 cursor.movePosition(QTextCursor::End);
504
1/1
✓ Branch 15 → 16 taken 1 time.
1 QTextBlockFormat region;
505
1/1
✓ Branch 17 → 18 taken 1 time.
1 region.setProperty(RegionProperty, QuoteRegion);
506
3/3
✓ Branch 19 → 20 taken 1 time.
✓ Branch 20 → 21 taken 1 time.
✓ Branch 21 → 22 taken 1 time.
1 cursor.insertBlock(QTextBlockFormat(), QTextCharFormat());
507
2/2
✓ Branch 24 → 25 taken 1 time.
✓ Branch 25 → 26 taken 1 time.
1 cursor.insertBlock(region, QTextCharFormat());
508
1/1
✓ Branch 27 → 28 taken 1 time.
1 cursor.insertText(m_quote.attribution);
509
3/6
✓ Branch 28 → 29 taken 1 time.
✗ Branch 28 → 32 not taken.
✓ Branch 30 → 31 taken 1 time.
✗ Branch 30 → 32 not taken.
✓ Branch 33 → 34 taken 1 time.
✗ Branch 33 → 69 not taken.
1 if (m_formatted && !m_quote.html.isEmpty()) {
510
2/2
✓ Branch 34 → 35 taken 1 time.
✓ Branch 35 → 36 taken 1 time.
1 cursor.insertBlock(region, QTextCharFormat());
511
1/1
✓ Branch 37 → 38 taken 1 time.
1 const int start = cursor.position();
512
1/1
✓ Branch 40 → 41 taken 1 time.
1 const auto imported = ComposeHtmlImport::import(m_quote.html.toUtf8());
513
2/3
✓ Branch 44 → 45 taken 1 time.
✗ Branch 44 → 46 not taken.
✓ Branch 45 → 46 taken 1 time.
1 if (imported.ok()) cursor.insertFragment(imported.fragment);
514
5/5
✓ Branch 47 → 48 taken 1 time.
✓ Branch 62 → 63 taken 2 times.
✓ Branch 65 → 66 taken 3 times.
✓ Branch 66 → 49 taken 2 times.
✓ Branch 66 → 67 taken 1 time.
3 for (QTextBlock block = m_document->findBlock(start); block.isValid(); block = block.next()) {
515
1/1
✓ Branch 49 → 50 taken 2 times.
2 QTextCursor blockCursor(block);
516
1/1
✓ Branch 50 → 51 taken 2 times.
2 QTextBlockFormat format = block.blockFormat();
517
1/1
✓ Branch 52 → 53 taken 2 times.
2 format.setProperty(RegionProperty, QuoteRegion);
518
2/2
✓ Branch 54 → 55 taken 2 times.
✓ Branch 57 → 58 taken 2 times.
2 format.setProperty(QTextFormat::BlockQuoteLevel, qMax(1, quoteLevel(block) + 1));
519
1/1
✓ Branch 59 → 60 taken 2 times.
2 blockCursor.setBlockFormat(format);
520 2 }
521 1 } else {
522 ✗ for (const QString &line : quotedPlain(m_quote.text).split(QLatin1Char('\n'))) {
523 ✗ cursor.insertBlock(region, QTextCharFormat());
524 ✗ cursor.insertText(line);
525 ✗ }
526 }
527
1/1
✓ Branch 88 → 89 taken 1 time.
1 cursor.endEditBlock();
528
1/1
✓ Branch 90 → 91 taken 1 time.
1 RichTextController::decorateQuotes(*m_document);
529 1 m_quoteInEditor = true;
530
1/1
✓ Branch 91 → 92 taken 1 time.
1 emit quoteChanged();
531
1/1
✓ Branch 92 → 93 taken 1 time.
1 changed();
532 2 }
533
534 111 QString ComposeSession::plainText() const {
535
11/17
✓ Branch 3 → 4 taken 109 times.
✓ Branch 3 → 6 taken 2 times.
✓ Branch 5 → 11 taken 109 times.
✓ Branch 9 → 10 taken 2 times.
✓ Branch 10 → 11 taken 2 times.
✓ Branch 12 → 13 taken 2 times.
✓ Branch 12 → 14 taken 109 times.
✓ Branch 14 → 15 taken 2 times.
✓ Branch 14 → 16 taken 109 times.
✓ Branch 16 → 17 taken 2 times.
✓ Branch 16 → 18 taken 109 times.
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 22 not taken.
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 26 not taken.
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 30 not taken.
113 return m_document ? m_document->toPlainText() : m_pendingRestore.value(QStringLiteral("body")).toString();
536 }
537
538 89 QString ComposeSession::newText(int limit) const {
539
2/2
✓ Branch 3 → 4 taken 15 times.
✓ Branch 3 → 5 taken 74 times.
89 if (!m_document) return {};
540 74 QStringList lines;
541 74 qsizetype length = 0;
542
8/9
✓ Branch 6 → 7 taken 74 times.
✓ Branch 20 → 21 taken 1775 times.
✓ Branch 23 → 24 taken 1849 times.
✓ Branch 24 → 25 taken 1775 times.
✓ Branch 24 → 27 taken 74 times.
✓ Branch 25 → 26 taken 1775 times.
✗ Branch 25 → 27 not taken.
✓ Branch 28 → 8 taken 1775 times.
✓ Branch 28 → 29 taken 74 times.
1849 for (QTextBlock block = m_document->begin(); block.isValid() && length < limit; block = block.next())
543
7/8
✓ Branch 8 → 9 taken 1775 times.
✓ Branch 9 → 10 taken 1766 times.
✓ Branch 9 → 13 taken 9 times.
✓ Branch 10 → 11 taken 1766 times.
✓ Branch 11 → 12 taken 1766 times.
✗ Branch 11 → 13 not taken.
✓ Branch 14 → 15 taken 1766 times.
✓ Branch 14 → 20 taken 9 times.
1775 if (region(block) == NoRegion && quoteLevel(block) == 0) {
544
2/2
✓ Branch 15 → 16 taken 1766 times.
✓ Branch 16 → 17 taken 1766 times.
1766 lines.append(block.text());
545
1/1
✓ Branch 18 → 19 taken 1766 times.
1766 length += block.length();
546 }
547
1/1
✓ Branch 31 → 32 taken 74 times.
74 return lines.join(QLatin1Char('\n'));
548 74 }
549
550
3/4
✓ Branch 2 → 3 taken 1629 times.
✓ Branch 2 → 6 taken 34 times.
✓ Branch 4 → 5 taken 1629 times.
✗ Branch 4 → 6 not taken.
1663 bool ComposeSession::spellAvailable() const { return m_deps.spell && m_deps.spell->available(); }
551
552 // D-12: the chip's choice, else the language the text is written in, else
553 // the one last used with the first To recipient, else the message's (UI) one.
554 1276 QString ComposeSession::checkedLanguage() const {
555
7/7
✓ Branch 2 → 3 taken 1276 times.
✓ Branch 3 → 4 taken 1242 times.
✓ Branch 3 → 7 taken 34 times.
✓ Branch 6 → 7 taken 4 times.
✓ Branch 6 → 8 taken 1238 times.
✓ Branch 9 → 10 taken 38 times.
✓ Branch 9 → 11 taken 1238 times.
1276 if (!spellAvailable() || m_spellLanguage == QLatin1String("off")) return {};
556
3/3
✓ Branch 12 → 13 taken 82 times.
✓ Branch 12 → 15 taken 1156 times.
✓ Branch 13 → 14 taken 82 times.
1238 if (!m_spellLanguage.isEmpty()) return m_deps.spell->bestLanguage(m_spellLanguage);
557
2/2
✓ Branch 16 → 17 taken 236 times.
✓ Branch 16 → 18 taken 920 times.
1156 if (!m_guessedLanguage.isEmpty()) return m_guessedLanguage;
558
1/2
✓ Branch 19 → 20 taken 920 times.
✗ Branch 19 → 42 not taken.
920 if (m_deps.rememberedLanguage) {
559
2/2
✓ Branch 20 → 21 taken 920 times.
✓ Branch 21 → 22 taken 920 times.
920 const QString remembered = m_deps.rememberedLanguage(firstRecipient());
560
2/2
✓ Branch 24 → 25 taken 37 times.
✓ Branch 24 → 36 taken 883 times.
920 if (!remembered.isEmpty()) {
561
1/1
✓ Branch 25 → 26 taken 37 times.
37 const QString best = m_deps.spell->bestLanguage(remembered);
562
1/2
✓ Branch 27 → 28 taken 37 times.
✗ Branch 27 → 30 not taken.
37 if (!best.isEmpty()) return best;
563 37 }
564 920 }
565
3/5
✓ Branch 43 → 44 taken 883 times.
✗ Branch 43 → 48 not taken.
✓ Branch 45 → 46 taken 883 times.
✗ Branch 45 → 48 not taken.
✓ Branch 46 → 47 taken 883 times.
883 const QString message = m_language.isEmpty() && m_deps.uiLanguage ? m_deps.uiLanguage() : m_language;
566
3/7
✗ Branch 50 → 51 not taken.
✓ Branch 50 → 54 taken 883 times.
✓ Branch 55 → 56 taken 883 times.
✗ Branch 57 → 58 not taken.
✓ Branch 57 → 59 taken 883 times.
✗ Branch 85 → 86 not taken.
✗ Branch 85 → 87 not taken.
883 const QString best = m_deps.spell->bestLanguage(message.isEmpty() ? QStringLiteral("en") : message);
567 // A program language without a dictionary (the "C" locale) checks English.
568
7/11
✓ Branch 60 → 61 taken 823 times.
✓ Branch 60 → 65 taken 60 times.
✓ Branch 64 → 66 taken 823 times.
✓ Branch 67 → 68 taken 823 times.
✓ Branch 67 → 69 taken 60 times.
✓ Branch 69 → 70 taken 823 times.
✓ Branch 69 → 71 taken 60 times.
✗ Branch 90 → 91 not taken.
✗ Branch 90 → 92 not taken.
✗ Branch 94 → 95 not taken.
✗ Branch 94 → 96 not taken.
1706 return best.isEmpty() ? m_deps.spell->bestLanguage(QStringLiteral("en")) : best;
569 883 }
570
571 947 QString ComposeSession::firstRecipient() const {
572
1/1
✓ Branch 2 → 3 taken 947 times.
947 const QStringList addresses = m_to.addresses();
573
2/2
✓ Branch 4 → 5 taken 558 times.
✓ Branch 4 → 6 taken 389 times.
1894 return addresses.isEmpty() ? QString() : addresses.first();
574 947 }
575
576 12 void ComposeSession::setSpellLanguage(const QString &language) {
577
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 12 times.
12 if (language == m_spellLanguage) return;
578 12 m_spellLanguage = language;
579 12 m_guessedFrom.clear(); // back to automatic: the text is judged again at the next pause
580 12 applySpellCheck();
581 12 emit languageChanged();
582 }
583
584 223 void ComposeSession::applySpellCheck() {
585
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 223 times.
258 if (!m_highlighter) return;
586
1/1
✓ Branch 5 → 6 taken 223 times.
223 const QString language = checkedLanguage();
587
2/2
✓ Branch 7 → 8 taken 35 times.
✓ Branch 7 → 15 taken 188 times.
223 if (language.isEmpty()) {
588
1/1
✓ Branch 11 → 12 taken 35 times.
35 m_highlighter->setSpellCheck({}, {});
589 35 return;
590 }
591 188 SpellService *spell = m_deps.spell;
592
1/1
✓ Branch 15 → 16 taken 188 times.
188 QPointer<ComposeSession> self(this);
593
1/1
✓ Branch 19 → 20 taken 188 times.
376 m_highlighter->setSpellCheck(
594
1/3
✓ Branch 18 → 19 taken 188 times.
✗ Branch 40 → 41 not taken.
✗ Branch 40 → 42 not taken.
376 [self, spell](const QString &word, const QString &checked) {
595
8/12
✓ Branch 3 → 4 taken 3040 times.
✗ Branch 3 → 11 not taken.
✓ Branch 5 → 6 taken 3040 times.
✓ Branch 7 → 8 taken 3038 times.
✓ Branch 7 → 11 taken 2 times.
✓ Branch 8 → 9 taken 3038 times.
✓ Branch 9 → 10 taken 424 times.
✓ Branch 9 → 11 taken 2614 times.
✓ Branch 13 → 14 taken 3040 times.
✗ Branch 13 → 15 not taken.
✗ Branch 17 → 18 not taken.
✗ Branch 17 → 19 not taken.
3040 return self && !self->m_ignoredWords.contains(word.toLower()) && spell->isMisspelled(word, checked);
596 },
597 language);
598 223 }
599
600 32 QVariantMap ComposeSession::misspellingAt(int position) const {
601
3/6
✓ Branch 3 → 4 taken 32 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 32 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 32 times.
32 if (!m_highlighter || !m_document) return {};
602
1/1
✓ Branch 11 → 12 taken 32 times.
32 const QTextBlock block = m_document->findBlock(position);
603
2/3
✓ Branch 12 → 13 taken 32 times.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 32 times.
32 if (!block.isValid()) return {};
604
5/5
✓ Branch 16 → 17 taken 32 times.
✓ Branch 17 → 18 taken 32 times.
✓ Branch 18 → 19 taken 32 times.
✓ Branch 67 → 20 taken 23 times.
✓ Branch 67 → 68 taken 12 times.
35 for (const ComposeHighlighter::Word &word : m_highlighter->misspellings(block)) {
605
1/1
✓ Branch 21 → 22 taken 23 times.
23 const int start = block.position() + word.start;
606
4/4
✓ Branch 22 → 23 taken 21 times.
✓ Branch 22 → 24 taken 2 times.
✓ Branch 23 → 24 taken 1 time.
✓ Branch 23 → 25 taken 20 times.
23 if (position < start || position > start + word.length) continue;
607
2/2
✓ Branch 25 → 26 taken 20 times.
✓ Branch 26 → 27 taken 20 times.
20 const QString text = block.text().mid(word.start, word.length);
608 40 return {{QStringLiteral("start"), start},
609 40 {QStringLiteral("length"), word.length},
610 40 {QStringLiteral("word"), text},
611
6/12
✓ Branch 43 → 44 taken 20 times.
✗ Branch 43 → 46 not taken.
✓ Branch 44 → 45 taken 20 times.
✓ Branch 45 → 47 taken 20 times.
✓ Branch 50 → 51 taken 80 times.
✓ Branch 50 → 52 taken 20 times.
✓ Branch 53 → 54 taken 20 times.
✗ Branch 53 → 55 not taken.
✗ Branch 80 → 81 not taken.
✗ Branch 80 → 82 not taken.
✗ Branch 85 → 86 not taken.
✗ Branch 85 → 87 not taken.
120 {QStringLiteral("suggestions"), m_deps.spell ? m_deps.spell->suggestions(text, checkedLanguage()) : QStringList()}};
612 52 }
613 12 return {};
614 100 }
615
616 55 QVariantMap ComposeSession::nextMisspelling(int position) const {
617
3/6
✓ Branch 3 → 4 taken 55 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 55 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 55 times.
55 if (!m_highlighter || !m_document) return {};
618
2/2
✓ Branch 55 → 11 taken 82 times.
✓ Branch 55 → 56 taken 22 times.
104 for (int pass = 0; pass < 2; ++pass)
619
5/5
✓ Branch 12 → 13 taken 82 times.
✓ Branch 48 → 50 taken 49 times.
✓ Branch 52 → 53 taken 131 times.
✓ Branch 53 → 14 taken 82 times.
✓ Branch 53 → 54 taken 49 times.
131 for (QTextBlock block = m_document->begin(); block.isValid(); block = block.next())
620
5/5
✓ Branch 15 → 16 taken 82 times.
✓ Branch 16 → 17 taken 82 times.
✓ Branch 17 → 18 taken 82 times.
✓ Branch 43 → 19 taken 38 times.
✓ Branch 43 → 44 taken 49 times.
87 for (const ComposeHighlighter::Word &word : m_highlighter->misspellings(block)) {
621
1/1
✓ Branch 20 → 21 taken 38 times.
38 const int start = block.position() + word.start;
622
4/4
✓ Branch 21 → 22 taken 33 times.
✓ Branch 21 → 24 taken 5 times.
✓ Branch 22 → 23 taken 5 times.
✓ Branch 22 → 24 taken 28 times.
38 if (pass == 0 && start <= position) continue;
623
2/4
✓ Branch 34 → 35 taken 66 times.
✓ Branch 34 → 36 taken 33 times.
✗ Branch 59 → 60 not taken.
✗ Branch 59 → 61 not taken.
165 return {{QStringLiteral("start"), start}, {QStringLiteral("length"), word.length}};
624 82 }
625 22 return {};
626 99 }
627
628 4 void ComposeSession::replaceRange(int start, int length, const QString &text) {
629
5/10
✓ Branch 3 → 4 taken 4 times.
✗ Branch 3 → 9 not taken.
✓ Branch 4 → 5 taken 4 times.
✗ Branch 4 → 9 not taken.
✓ Branch 5 → 6 taken 4 times.
✗ Branch 5 → 9 not taken.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 4 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 4 times.
4 if (!m_document || start < 0 || length < 0 || start + length > m_document->characterCount()) return;
630 4 m_editor.setSelection(start, start + length, start + length);
631 4 m_editor.insertPlainText(text);
632 }
633
634 1 void ComposeSession::ignoreWord(const QString &word) {
635
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (word.isEmpty()) return;
636
2/2
✓ Branch 5 → 6 taken 1 time.
✓ Branch 6 → 7 taken 1 time.
1 m_ignoredWords.insert(word.toLower());
637
1/2
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 12 not taken.
1 if (m_highlighter) m_highlighter->rehighlightAll();
638 }
639
640 ✗ void ComposeSession::addToDictionary(const QString &word) {
641 ✗ if (word.isEmpty() || !m_deps.spell) return;
642 ✗ m_deps.spell->addToDictionary(word, checkedLanguage());
643 ✗ if (m_highlighter) m_highlighter->rehighlightAll();
644 ✗ qCInfo(lcCompose) << "word added to the personal dictionary";
645 }
646
647 438 void ComposeSession::caretMoved(int position, bool typing) {
648
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 438 times.
438 if (!m_highlighter) return;
649 438 m_highlighter->setCaret(position, typing);
650
1/2
✓ Branch 7 → 8 taken 438 times.
✗ Branch 7 → 9 not taken.
438 if (typing) m_typingPause.start();
651 }
652
653 20 void ComposeSession::setMeeting(const QVariantMap &meeting) {
654 // §4.15: a new meeting needs a calendar the user can write to (Ctrl+Shift+E
655 // reaches here even when the *Invite* button is hidden for that reason).
656
11/16
✓ Branch 2 → 3 taken 20 times.
✓ Branch 3 → 4 taken 8 times.
✓ Branch 3 → 18 taken 12 times.
✓ Branch 7 → 8 taken 8 times.
✓ Branch 8 → 9 taken 8 times.
✗ Branch 8 → 18 not taken.
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 18 taken 7 times.
✓ Branch 31 → 32 taken 8 times.
✓ Branch 31 → 33 taken 12 times.
✓ Branch 33 → 34 taken 1 time.
✓ Branch 33 → 38 taken 19 times.
✗ Branch 157 → 158 not taken.
✗ Branch 157 → 159 not taken.
✗ Branch 161 → 162 not taken.
✗ Branch 161 → 163 not taken.
56 if (m_meeting.isEmpty() && meeting.contains(QStringLiteral("calendarAccount")) &&
657
14/24
✓ Branch 13 → 14 taken 8 times.
✓ Branch 14 → 15 taken 8 times.
✓ Branch 19 → 20 taken 8 times.
✓ Branch 19 → 21 taken 12 times.
✓ Branch 21 → 22 taken 8 times.
✓ Branch 21 → 23 taken 12 times.
✓ Branch 23 → 24 taken 8 times.
✓ Branch 23 → 25 taken 12 times.
✓ Branch 25 → 26 taken 8 times.
✓ Branch 25 → 27 taken 12 times.
✓ Branch 27 → 28 taken 8 times.
✓ Branch 27 → 29 taken 12 times.
✓ Branch 29 → 30 taken 8 times.
✓ Branch 29 → 31 taken 12 times.
✗ Branch 137 → 138 not taken.
✗ Branch 137 → 139 not taken.
✗ Branch 141 → 142 not taken.
✗ Branch 141 → 143 not taken.
✗ Branch 145 → 146 not taken.
✗ Branch 145 → 147 not taken.
✗ Branch 149 → 150 not taken.
✗ Branch 149 → 151 not taken.
✗ Branch 153 → 154 not taken.
✗ Branch 153 → 155 not taken.
36 meeting.value(QStringLiteral("calendarAccount")).toString().isEmpty()) {
658
2/2
✓ Branch 34 → 35 taken 1 time.
✓ Branch 35 → 36 taken 1 time.
1 setNotice(tr("Invitations need a calendar you can write to — add one under Settings → Accounts → Calendars and contacts."));
659 2 return;
660 }
661 19 QVariantMap next = meeting;
662
1/1
✓ Branch 43 → 44 taken 19 times.
38 next.insert(QStringLiteral("active"), true);
663 // The uid stays across edits; an invitation for a stored event (the
664 // event editor's *Save and invite…*) brings that event's uid.
665
2/2
✓ Branch 51 → 52 taken 19 times.
✓ Branch 52 → 53 taken 19 times.
38 QString uid = m_meeting.value(QStringLiteral("uid")).toString();
666
5/5
✓ Branch 58 → 59 taken 7 times.
✓ Branch 58 → 74 taken 12 times.
✓ Branch 63 → 64 taken 7 times.
✓ Branch 64 → 65 taken 7 times.
✓ Branch 65 → 66 taken 7 times.
26 if (uid.isEmpty()) uid = meeting.value(QStringLiteral("uid")).toString().left(255);
667
4/4
✓ Branch 75 → 76 taken 13 times.
✓ Branch 75 → 77 taken 6 times.
✓ Branch 82 → 83 taken 6 times.
✓ Branch 87 → 88 taken 19 times.
44 next.insert(QStringLiteral("uid"), !uid.isEmpty() ? uid
668
8/14
✓ Branch 80 → 81 taken 6 times.
✓ Branch 81 → 82 taken 6 times.
✓ Branch 92 → 93 taken 6 times.
✓ Branch 92 → 94 taken 13 times.
✓ Branch 94 → 95 taken 6 times.
✓ Branch 94 → 96 taken 13 times.
✓ Branch 96 → 97 taken 6 times.
✓ Branch 96 → 98 taken 13 times.
✗ Branch 215 → 216 not taken.
✗ Branch 215 → 217 not taken.
✗ Branch 220 → 221 not taken.
✗ Branch 220 → 222 not taken.
✗ Branch 224 → 225 not taken.
✗ Branch 224 → 226 not taken.
25 : QUuid::createUuid().toString(QUuid::WithoutBraces) + QStringLiteral("@mailjd"));
669
2/2
✓ Branch 102 → 103 taken 19 times.
✓ Branch 103 → 104 taken 19 times.
38 const int minutes = next.value(QStringLiteral("durationMinutes")).toInt();
670
2/3
✓ Branch 108 → 109 taken 19 times.
✗ Branch 108 → 111 not taken.
✓ Branch 116 → 117 taken 19 times.
38 next.insert(QStringLiteral("durationMinutes"), minutes > 0 ? qBound(5, minutes, 24 * 60) : 60);
671
3/3
✓ Branch 120 → 121 taken 19 times.
✓ Branch 121 → 122 taken 1 time.
✓ Branch 121 → 123 taken 18 times.
19 if (next == m_meeting) return;
672 18 m_meeting = next;
673
1/1
✓ Branch 124 → 125 taken 18 times.
18 emit meetingChanged();
674
1/1
✓ Branch 125 → 126 taken 18 times.
18 changed();
675 20 }
676
677 4 void ComposeSession::clearMeeting() {
678
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 4 times.
4 if (m_meeting.isEmpty()) return;
679 4 m_meeting.clear();
680 4 emit meetingChanged();
681 4 changed();
682 }
683
684 4 QByteArray ComposeSession::meetingRequest() const {
685
2/3
✓ Branch 2 → 3 taken 4 times.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 4 times.
4 if (!isInvitation()) return {};
686
1/1
✓ Branch 5 → 6 taken 4 times.
4 const auto identity = currentIdentity();
687
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 4 times.
4 if (!identity) return {};
688 4 Imip::Meeting meeting;
689
2/2
✓ Branch 14 → 15 taken 4 times.
✓ Branch 15 → 16 taken 4 times.
8 meeting.uid = m_meeting.value(QStringLiteral("uid")).toString();
690 4 meeting.summary = m_subject;
691
2/2
✓ Branch 27 → 28 taken 4 times.
✓ Branch 28 → 29 taken 4 times.
8 meeting.location = m_meeting.value(QStringLiteral("location")).toString();
692
2/2
✓ Branch 36 → 37 taken 4 times.
✓ Branch 37 → 38 taken 4 times.
4 meeting.description = newText().trimmed();
693
2/2
✓ Branch 45 → 46 taken 4 times.
✓ Branch 46 → 47 taken 4 times.
8 meeting.start = m_meeting.value(QStringLiteral("start")).toDateTime();
694
4/5
✓ Branch 53 → 54 taken 4 times.
✓ Branch 54 → 55 taken 4 times.
✗ Branch 54 → 61 not taken.
✓ Branch 55 → 56 taken 4 times.
✓ Branch 56 → 57 taken 4 times.
4 if (meeting.start.timeSpec() == Qt::LocalTime) meeting.start = meeting.start.toTimeZone(QTimeZone::systemTimeZone());
695
3/3
✓ Branch 65 → 66 taken 4 times.
✓ Branch 66 → 67 taken 4 times.
✓ Branch 67 → 68 taken 4 times.
8 meeting.end = meeting.start.addSecs(qint64(m_meeting.value(QStringLiteral("durationMinutes")).toInt()) * 60);
696 4 meeting.organizerEmail = identity->email;
697 4 meeting.organizerName = identity->name;
698 // A stored event's series and its next SEQUENCE (from the event editor).
699
2/2
✓ Branch 82 → 83 taken 4 times.
✓ Branch 83 → 84 taken 4 times.
8 meeting.rrule = m_meeting.value(QStringLiteral("rrule")).toString();
700
2/2
✓ Branch 94 → 95 taken 4 times.
✓ Branch 95 → 96 taken 4 times.
8 meeting.sequence = m_meeting.value(QStringLiteral("sequence")).toInt();
701 8 const auto attendeesOf = [&meeting](const RecipientChipModel &field, const QString &role) {
702
5/5
✓ Branch 2 → 3 taken 8 times.
✓ Branch 3 → 4 taken 8 times.
✓ Branch 4 → 5 taken 8 times.
✓ Branch 51 → 6 taken 9 times.
✓ Branch 51 → 52 taken 8 times.
17 for (const QString &header : field.headers()) {
703
1/1
✓ Branch 7 → 8 taken 9 times.
9 const auto parsed = ComposeRecipients::parse(header);
704
3/6
✓ Branch 9 → 10 taken 9 times.
✗ Branch 9 → 13 not taken.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 9 times.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 9 times.
9 if (!parsed || parsed->envelope.isEmpty()) continue;
705 9 QString name = header;
706 9 const qsizetype angle = name.lastIndexOf(QLatin1Char('<'));
707
9/13
✓ Branch 21 → 22 taken 3 times.
✓ Branch 21 → 28 taken 6 times.
✓ Branch 22 → 23 taken 3 times.
✓ Branch 23 → 24 taken 3 times.
✓ Branch 26 → 27 taken 3 times.
✓ Branch 31 → 32 taken 3 times.
✓ Branch 31 → 33 taken 6 times.
✓ Branch 33 → 34 taken 3 times.
✓ Branch 33 → 35 taken 6 times.
✗ Branch 56 → 57 not taken.
✗ Branch 56 → 58 not taken.
✗ Branch 60 → 61 not taken.
✗ Branch 60 → 62 not taken.
9 name = angle > 0 ? name.left(angle).trimmed().remove(QLatin1Char('"')) : QString();
708 9 meeting.attendees.append({parsed->envelope.first(), name, {}, role, {}});
709 17 }
710
1/9
✓ Branch 40 → 41 taken 9 times.
✗ Branch 67 → 68 not taken.
✗ Branch 67 → 69 not taken.
✗ Branch 70 → 71 not taken.
✗ Branch 70 → 72 not taken.
✗ Branch 73 → 74 not taken.
✗ Branch 73 → 75 not taken.
✗ Branch 76 → 77 not taken.
✗ Branch 76 → 78 not taken.
21 };
711
1/1
✓ Branch 103 → 104 taken 4 times.
4 attendeesOf(m_to, QStringLiteral("REQ-PARTICIPANT"));
712
1/1
✓ Branch 109 → 110 taken 4 times.
4 attendeesOf(m_cc, QStringLiteral("OPT-PARTICIPANT"));
713
2/2
✓ Branch 112 → 113 taken 4 times.
✓ Branch 116 → 117 taken 4 times.
8 return Imip::buildRequest(meeting);
714 4 }
715
716 3 QVariantMap ComposeSession::templateContent() const {
717
2/4
✓ Branch 8 → 9 taken 3 times.
✓ Branch 8 → 10 taken 3 times.
✗ Branch 108 → 109 not taken.
✗ Branch 108 → 110 not taken.
9 QVariantMap content{{QStringLiteral("subject"), m_subject}};
718
1/2
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 42 taken 3 times.
3 if (!m_document) {
719 ✗ content.insert(QStringLiteral("text"), m_pendingRestore.value(QStringLiteral("text")).toString());
720 ✗ content.insert(QStringLiteral("html"), QString());
721 ✗ return content;
722 }
723 // A copy without the signature and quote blocks (last to first, so the
724 // positions of the blocks still to visit stay valid).
725
1/1
✓ Branch 43 → 44 taken 3 times.
3 std::unique_ptr<QTextDocument> copy(m_document->clone());
726
1/1
✓ Branch 46 → 47 taken 3 times.
3 QTextBlock block = copy->lastBlock();
727
3/3
✓ Branch 66 → 67 taken 9 times.
✓ Branch 67 → 48 taken 6 times.
✓ Branch 67 → 68 taken 3 times.
9 while (block.isValid()) {
728
1/1
✓ Branch 48 → 49 taken 6 times.
6 const QTextBlock previous = block.previous();
729
5/8
✓ Branch 49 → 50 taken 6 times.
✓ Branch 50 → 51 taken 6 times.
✗ Branch 50 → 53 not taken.
✓ Branch 51 → 52 taken 6 times.
✗ Branch 52 → 53 not taken.
✓ Branch 52 → 54 taken 6 times.
✗ Branch 55 → 56 not taken.
✓ Branch 55 → 64 taken 6 times.
6 if (region(block) != NoRegion || quoteLevel(block) > 0) {
730 ✗ QTextCursor cursor(block);
731 ✗ cursor.select(QTextCursor::BlockUnderCursor);
732 ✗ if (!previous.isValid()) cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor);
733 ✗ cursor.removeSelectedText();
734 ✗ }
735 6 block = previous;
736 }
737
1/1
✓ Branch 69 → 70 taken 3 times.
3 QString text = copy->toPlainText();
738
2/4
✗ Branch 71 → 72 not taken.
✓ Branch 74 → 75 taken 3 times.
✗ Branch 75 → 71 not taken.
✓ Branch 75 → 76 taken 3 times.
3 while (text.endsWith(QLatin1Char('\n'))) text.chop(1);
739
1/1
✓ Branch 80 → 81 taken 3 times.
6 content.insert(QStringLiteral("text"), text);
740
4/12
✗ Branch 84 → 85 not taken.
✓ Branch 84 → 89 taken 3 times.
✗ Branch 87 → 88 not taken.
✓ Branch 94 → 95 taken 3 times.
✗ Branch 99 → 100 not taken.
✓ Branch 99 → 101 taken 3 times.
✗ Branch 101 → 102 not taken.
✓ Branch 101 → 103 taken 3 times.
✗ Branch 181 → 182 not taken.
✗ Branch 181 → 183 not taken.
✗ Branch 185 → 186 not taken.
✗ Branch 185 → 187 not taken.
6 content.insert(QStringLiteral("html"), m_formatted ? MailHtmlWriter::writeBody(*copy, MailHtmlWriter::Options{}).html : QString());
741 3 return content;
742 9 }
743
744 3 bool ComposeSession::insertTemplate(const QVariantMap &content) {
745
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 3 times.
3 if (!m_document) return false;
746
1/1
✓ Branch 5 → 6 taken 3 times.
3 finishPaste();
747 // D-27: an empty subject takes the template's; a written one stays and
748 // the notice names the template's.
749
2/2
✓ Branch 10 → 11 taken 3 times.
✓ Branch 11 → 12 taken 3 times.
6 const QString subject = content.value(QStringLiteral("subject")).toString();
750 3 QString notice;
751
4/4
✓ Branch 16 → 17 taken 3 times.
✓ Branch 19 → 20 taken 2 times.
✓ Branch 19 → 21 taken 1 time.
✓ Branch 20 → 38 taken 2 times.
3 if (m_subject.trimmed().isEmpty()) setSubject(subject);
752
5/11
✓ Branch 21 → 22 taken 1 time.
✓ Branch 23 → 24 taken 1 time.
✗ Branch 23 → 27 not taken.
✓ Branch 25 → 26 taken 1 time.
✗ Branch 25 → 27 not taken.
✓ Branch 28 → 29 taken 1 time.
✗ Branch 28 → 30 not taken.
✓ Branch 30 → 31 taken 1 time.
✗ Branch 30 → 38 not taken.
✗ Branch 119 → 120 not taken.
✗ Branch 119 → 121 not taken.
1 else if (!subject.trimmed().isEmpty() && subject != m_subject)
753
2/2
✓ Branch 31 → 32 taken 1 time.
✓ Branch 33 → 34 taken 1 time.
2 notice = tr("Your subject stays — the template's was \u201c%1\u201d.").arg(subject);
754
2/2
✓ Branch 42 → 43 taken 3 times.
✓ Branch 43 → 44 taken 3 times.
6 const QString html = content.value(QStringLiteral("html")).toString();
755 3 int caret = -1;
756
2/8
✗ Branch 48 → 49 not taken.
✓ Branch 48 → 52 taken 3 times.
✗ Branch 50 → 51 not taken.
✗ Branch 50 → 52 not taken.
✗ Branch 53 → 54 not taken.
✓ Branch 53 → 58 taken 3 times.
✗ Branch 54 → 55 not taken.
✗ Branch 55 → 56 not taken.
3 if (m_formatted && !html.isEmpty()) caret = insertHtml(html.toUtf8());
757
4/5
✓ Branch 58 → 59 taken 3 times.
✗ Branch 58 → 72 not taken.
✓ Branch 63 → 64 taken 3 times.
✓ Branch 64 → 65 taken 3 times.
✓ Branch 65 → 66 taken 3 times.
6 if (caret < 0) m_editor.insertPlainText(content.value(QStringLiteral("text")).toString());
758
3/6
✓ Branch 72 → 73 taken 3 times.
✗ Branch 72 → 76 not taken.
✗ Branch 74 → 75 not taken.
✓ Branch 74 → 76 taken 3 times.
✗ Branch 77 → 78 not taken.
✓ Branch 77 → 82 taken 3 times.
3 if (!m_formatted && !html.isEmpty())
759 ✗ notice = tr("The template is formatted; this message is plain text, so it went in as text.");
760
1/1
✓ Branch 82 → 83 taken 3 times.
3 setNotice(notice);
761
2/12
✓ Branch 83 → 84 taken 3 times.
✗ Branch 87 → 88 not taken.
✗ Branch 88 → 89 not taken.
✗ Branch 89 → 90 not taken.
✗ Branch 90 → 91 not taken.
✗ Branch 91 → 92 not taken.
✗ Branch 91 → 95 not taken.
✗ Branch 93 → 94 not taken.
✗ Branch 93 → 95 not taken.
✗ Branch 96 → 97 not taken.
✗ Branch 100 → 85 not taken.
✓ Branch 100 → 101 taken 3 times.
3 qCInfo(lcCompose) << "template inserted" << m_id << "formatted" << (m_formatted && !html.isEmpty());
762 3 return true;
763 3 }
764
765 147 ComposeMessage ComposeSession::message(ComposeMessage::Form form) const {
766 147 ComposeMessage message;
767
1/1
✓ Branch 2 → 3 taken 147 times.
147 const auto identity = currentIdentity();
768
1/2
✓ Branch 4 → 5 taken 147 times.
✗ Branch 4 → 11 not taken.
147 if (identity) {
769 147 message.fromName = identity->name;
770 147 message.fromAddress = identity->email;
771 147 message.replyTo = identity->replyTo;
772 }
773
1/1
✓ Branch 11 → 12 taken 147 times.
147 message.to = m_to.headers();
774
1/1
✓ Branch 14 → 15 taken 147 times.
147 message.cc = m_cc.headers();
775
1/1
✓ Branch 17 → 18 taken 147 times.
147 message.bcc = m_bcc.headers();
776
3/10
✓ Branch 21 → 22 taken 147 times.
✗ Branch 21 → 28 not taken.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 28 taken 147 times.
✗ Branch 25 → 26 not taken.
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 28 not taken.
✗ Branch 29 → 30 not taken.
✓ Branch 29 → 32 taken 147 times.
✗ Branch 31 → 32 not taken.
147 if (identity && identity->bccSelf && !m_bcc.containsAddress(identity->email)) message.bcc.append(identity->email);
777 147 message.subject = m_subject;
778 147 message.formatted = m_formatted;
779 147 message.inReplyTo = m_inReplyTo;
780 147 message.references = m_references;
781 147 message.priority = m_priority;
782 147 message.readReceipt = m_readReceipt;
783
3/3
✓ Branch 35 → 36 taken 147 times.
✓ Branch 36 → 37 taken 3 times.
✓ Branch 36 → 47 taken 144 times.
147 if (isInvitation()) {
784
1/1
✓ Branch 37 → 38 taken 3 times.
3 message.calendar = meetingRequest();
785 3 message.calendarMethod = QStringLiteral("REQUEST");
786 }
787
4/6
✓ Branch 48 → 49 taken 147 times.
✗ Branch 48 → 53 not taken.
✓ Branch 50 → 51 taken 147 times.
✗ Branch 50 → 53 not taken.
✓ Branch 51 → 52 taken 147 times.
✓ Branch 54 → 55 taken 147 times.
147 const MessageLanguageText language(m_language.isEmpty() && m_deps.uiLanguage ? m_deps.uiLanguage() : m_language);
788
4/4
✓ Branch 57 → 58 taken 20 times.
✓ Branch 57 → 60 taken 127 times.
✓ Branch 58 → 59 taken 19 times.
✓ Branch 58 → 60 taken 1 time.
147 const bool quote = hasQuote() && !m_quoteInEditor;
789 // Inline images: the document names them; the tray holds the files.
790 147 QHash<QString, ComposeAttachmentModel::Item> byImageName;
791
6/6
✓ Branch 63 → 64 taken 147 times.
✓ Branch 64 → 65 taken 147 times.
✓ Branch 67 → 68 taken 43 times.
✓ Branch 68 → 69 taken 43 times.
✓ Branch 72 → 66 taken 43 times.
✓ Branch 72 → 73 taken 147 times.
190 for (const auto &item : m_attachments->items()) byImageName.insert(ImageScheme + item.id, item);
792 147 QSet<QString> placed;
793
2/2
✓ Branch 76 → 77 taken 98 times.
✓ Branch 76 → 105 taken 49 times.
147 if (m_document) {
794
5/5
✓ Branch 78 → 79 taken 98 times.
✓ Branch 99 → 100 taken 1928 times.
✓ Branch 102 → 103 taken 2026 times.
✓ Branch 103 → 80 taken 1928 times.
✓ Branch 103 → 104 taken 98 times.
2026 for (QTextBlock block = m_document->begin(); block.isValid(); block = block.next())
795
4/4
✓ Branch 80 → 81 taken 1928 times.
✓ Branch 96 → 97 taken 4850 times.
✓ Branch 98 → 82 taken 4850 times.
✓ Branch 98 → 99 taken 1928 times.
6778 for (auto it = block.begin(); !it.atEnd(); ++it)
796
5/5
✓ Branch 82 → 83 taken 4850 times.
✓ Branch 83 → 84 taken 4850 times.
✓ Branch 84 → 85 taken 4850 times.
✓ Branch 86 → 87 taken 6 times.
✓ Branch 86 → 96 taken 4844 times.
4850 if (it.fragment().charFormat().isImageFormat())
797
5/5
✓ Branch 87 → 88 taken 6 times.
✓ Branch 88 → 89 taken 6 times.
✓ Branch 89 → 90 taken 6 times.
✓ Branch 90 → 91 taken 6 times.
✓ Branch 91 → 92 taken 6 times.
6 placed.insert(it.fragment().charFormat().toImageFormat().name());
798 }
799
6/6
✓ Branch 105 → 106 taken 54 times.
✓ Branch 105 → 109 taken 93 times.
✓ Branch 107 → 108 taken 46 times.
✓ Branch 107 → 109 taken 8 times.
✓ Branch 110 → 111 taken 46 times.
✓ Branch 110 → 165 taken 101 times.
147 if (m_formatted && m_document) {
800 46 MailHtmlWriter::Options options;
801 98 options.contentIdFor = [&byImageName](const QString &name) {
802
2/4
✓ Branch 3 → 4 taken 6 times.
✗ Branch 3 → 6 not taken.
✓ Branch 8 → 9 taken 6 times.
✗ Branch 8 → 10 not taken.
6 return byImageName.contains(name) ? byImageName.value(name).contentId : QString();
803 46 };
804
1/1
✓ Branch 114 → 115 taken 46 times.
46 const auto body = MailHtmlWriter::writeBody(*m_document, options);
805 46 QString html = body.html;
806
2/2
✓ Branch 116 → 117 taken 12 times.
✓ Branch 116 → 126 taken 34 times.
46 if (quote) {
807
1/3
✗ Branch 118 → 119 not taken.
✓ Branch 118 → 120 taken 12 times.
✗ Branch 119 → 121 not taken.
12 const QString quoted = m_quote.html.isEmpty() ? escapedParagraphs(m_quote.text) : m_quote.html;
808
2/2
✓ Branch 121 → 122 taken 12 times.
✓ Branch 122 → 123 taken 12 times.
12 html += MailHtmlWriter::quoteContainer(m_quote.attribution, quoted);
809 12 }
810
2/2
✓ Branch 127 → 128 taken 46 times.
✓ Branch 128 → 129 taken 46 times.
46 const bool rtl = m_document->begin().textDirection() == Qt::RightToLeft;
811
1/1
✓ Branch 129 → 130 taken 46 times.
46 message.html = MailHtmlWriter::wrapDocument(html, rtl);
812
1/1
✓ Branch 132 → 133 taken 46 times.
46 MailTextWriter::Options textOptions;
813
1/1
✓ Branch 133 → 134 taken 46 times.
46 textOptions.imageMarker = language.imageMarker();
814
1/1
✓ Branch 137 → 138 taken 46 times.
46 message.text = MailTextWriter::write(*m_document, textOptions);
815
2/2
✓ Branch 159 → 143 taken 6 times.
✓ Branch 159 → 160 taken 46 times.
52 for (const QString &name : std::as_const(placed)) {
816
1/2
✗ Branch 145 → 146 not taken.
✓ Branch 145 → 147 taken 6 times.
6 if (!byImageName.contains(name)) continue;
817 6 const auto &item = byImageName.value(name);
818 6 message.inlineImages.append({item.sentPath(), item.name, item.mimeType, item.contentId});
819 6 }
820
2/2
✓ Branch 166 → 167 taken 52 times.
✓ Branch 166 → 182 taken 49 times.
147 } else if (m_document) {
821
1/1
✓ Branch 168 → 169 taken 52 times.
52 message.text = m_document->toPlainText();
822
3/7
✓ Branch 172 → 173 taken 52 times.
✗ Branch 172 → 176 not taken.
✗ Branch 174 → 175 not taken.
✓ Branch 174 → 176 taken 52 times.
✗ Branch 177 → 178 not taken.
✓ Branch 177 → 182 taken 52 times.
✗ Branch 178 → 179 not taken.
52 if (identity && identity->wrapPlainText) message.text = ComposeMessage::wrapPlainText(message.text);
823 }
824
7/7
✓ Branch 182 → 183 taken 19 times.
✓ Branch 182 → 200 taken 128 times.
✓ Branch 183 → 184 taken 19 times.
✓ Branch 189 → 190 taken 19 times.
✓ Branch 190 → 191 taken 19 times.
✓ Branch 191 → 192 taken 19 times.
✓ Branch 192 → 193 taken 19 times.
166 if (quote) message.text += QStringLiteral("\n\n") + m_quote.attribution + QLatin1Char('\n') + quotedPlain(m_quote.text);
825
4/4
✓ Branch 202 → 203 taken 147 times.
✓ Branch 203 → 204 taken 147 times.
✓ Branch 228 → 205 taken 43 times.
✓ Branch 228 → 229 taken 147 times.
190 for (const auto &item : m_attachments->items()) {
826
1/2
✗ Branch 208 → 209 not taken.
✓ Branch 208 → 210 taken 43 times.
43 if (item.state != QLatin1String("ready")) continue;
827
9/11
✓ Branch 210 → 211 taken 29 times.
✓ Branch 210 → 215 taken 14 times.
✓ Branch 211 → 212 taken 29 times.
✓ Branch 213 → 214 taken 6 times.
✓ Branch 213 → 215 taken 23 times.
✓ Branch 216 → 217 taken 29 times.
✓ Branch 216 → 218 taken 14 times.
✓ Branch 218 → 219 taken 6 times.
✓ Branch 218 → 220 taken 37 times.
✗ Branch 333 → 334 not taken.
✗ Branch 333 → 335 not taken.
43 if (m_formatted && placed.contains(ImageScheme + item.id)) continue;
828 37 message.attachments.append({item.sentPath(), item.name, item.mimeType, {}});
829 147 }
830
2/2
✓ Branch 230 → 231 taken 77 times.
✓ Branch 230 → 239 taken 70 times.
147 if (form == ComposeMessage::Form::Draft)
831
1/5
✓ Branch 234 → 235 taken 77 times.
✗ Branch 354 → 355 not taken.
✗ Branch 354 → 356 not taken.
✗ Branch 357 → 358 not taken.
✗ Branch 357 → 359 not taken.
77 message.draftHeader = ComposeDraftHeader{m_id, m_identityId, m_formatted}.toString();
832 147 return message;
833 190 }
834
835 27 QString ComposeSession::checkSentence(const QVariantMap &finding) const {
836 using SendChecks::Kind;
837
2/2
✓ Branch 6 → 7 taken 27 times.
✓ Branch 7 → 8 taken 27 times.
54 const auto kind = Kind(finding.value(QStringLiteral("kind")).toInt());
838
2/2
✓ Branch 16 → 17 taken 27 times.
✓ Branch 17 → 18 taken 27 times.
54 const QString value = finding.value(QStringLiteral("value")).toString();
839
4/12
✓ Branch 22 → 23 taken 2 times.
✓ Branch 22 → 25 taken 15 times.
✓ Branch 22 → 30 taken 7 times.
✓ Branch 22 → 32 taken 3 times.
✗ Branch 22 → 34 not taken.
✗ Branch 22 → 39 not taken.
✗ Branch 22 → 55 not taken.
✗ Branch 22 → 71 not taken.
✗ Branch 22 → 76 not taken.
✗ Branch 22 → 78 not taken.
✗ Branch 22 → 80 not taken.
✗ Branch 22 → 82 not taken.
27 switch (kind) {
840
1/1
✓ Branch 23 → 24 taken 2 times.
2 case Kind::NoRecipient: return tr("Nobody would receive this mail — add a recipient.");
841
2/2
✓ Branch 25 → 26 taken 15 times.
✓ Branch 27 → 28 taken 15 times.
30 case Kind::InvalidRecipient: return tr("%1 is not a valid address.").arg(value);
842
1/1
✓ Branch 30 → 31 taken 7 times.
7 case Kind::EmptySubject: return tr("The subject is empty.");
843
1/1
✓ Branch 32 → 33 taken 3 times.
3 case Kind::ForgottenAttachment: return tr("You mention an attachment, but nothing is attached.");
844 ✗ case Kind::FileTooLarge: return tr("%1 is larger than the server accepts.").arg(value);
845 ✗ case Kind::ManyWithoutBcc:
846 ✗ return tr("%1 recipients see each other's addresses. Use Bcc?").arg(finding.value(QStringLiteral("count")).toInt());
847 ✗ case Kind::IdentityDiffers:
848 ✗ return tr("The mail was sent to %1; you answer from %2.").arg(value, finding.value(QStringLiteral("other")).toString());
849 ✗ case Kind::Placeholder: return tr("A time placeholder is still in the text: %1").arg(value);
850 ✗ case Kind::InvitationBcc: return tr("An invitation cannot have Bcc recipients.");
851 ✗ case Kind::ServerInvitationLoses:
852 ✗ return tr("Your server sends the invitation as plain text without attachments.");
853 ✗ case Kind::QuoteUnavailable: return tr("The original could not be loaded — send without the quote?");
854 }
855 ✗ return {};
856 27 }
857
858 39 QString ComposeSession::send(bool anyway) {
859
5/6
✓ Branch 2 → 3 taken 39 times.
✓ Branch 4 → 5 taken 39 times.
✗ Branch 4 → 7 not taken.
✓ Branch 5 → 6 taken 39 times.
✓ Branch 9 → 10 taken 39 times.
✓ Branch 10 → 11 taken 39 times.
39 return submit(anyway, QDateTime::currentDateTimeUtc().addSecs(qBound(0, m_deps.undoDelaySeconds ? m_deps.undoDelaySeconds() : 10, 30)), false);
860 }
861
862 9 QString ComposeSession::sendLater(const QDateTime &when, bool anyway) {
863
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 9 taken 9 times.
9 if (!when.isValid()) return QStringLiteral("error");
864 // D-23: the event is written when an invitation leaves, so it is not scheduled.
865
2/2
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 19 taken 8 times.
9 if (isInvitation()) {
866
2/2
✓ Branch 11 → 12 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
1 setNotice(tr("Invitations are sent right away — Send later is not available for them."));
867 1 return QStringLiteral("error");
868 }
869
2/2
✓ Branch 19 → 20 taken 8 times.
✓ Branch 20 → 21 taken 8 times.
8 return submit(anyway, when.toUTC(), true);
870 }
871
872 47 QString ComposeSession::submit(bool anyway, const QDateTime &due, bool scheduled) {
873
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 8 taken 47 times.
47 if (m_sending) return QStringLiteral("error");
874
1/1
✓ Branch 8 → 9 taken 47 times.
47 finishPaste();
875
2/3
✓ Branch 10 → 11 taken 47 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 20 taken 47 times.
47 if (m_attachments->busy()) {
876 ✗ setNotice(tr("An attachment is still being copied."));
877 ✗ return QStringLiteral("error");
878 }
879 47 SendChecks::Input input;
880
1/1
✓ Branch 21 → 22 taken 47 times.
47 input.newText = newText();
881 47 input.subject = m_subject;
882
1/1
✓ Branch 25 → 26 taken 47 times.
47 input.to = m_to.addresses();
883
1/1
✓ Branch 28 → 29 taken 47 times.
47 input.cc = m_cc.addresses();
884
1/1
✓ Branch 31 → 32 taken 47 times.
47 input.bcc = m_bcc.addresses();
885
5/5
✓ Branch 34 → 35 taken 47 times.
✓ Branch 35 → 36 taken 47 times.
✓ Branch 36 → 37 taken 47 times.
✓ Branch 37 → 38 taken 47 times.
✓ Branch 38 → 39 taken 47 times.
47 input.invalidRecipients = m_to.invalidTexts() + m_cc.invalidTexts() + m_bcc.invalidTexts();
886
3/3
✓ Branch 46 → 47 taken 43 times.
✓ Branch 46 → 48 taken 4 times.
✓ Branch 47 → 49 taken 43 times.
47 input.ownDomains = m_deps.ownDomains ? m_deps.ownDomains() : QStringList();
887 47 input.attachmentCount = int(m_attachments->items().size());
888
4/4
✓ Branch 57 → 58 taken 47 times.
✓ Branch 58 → 59 taken 47 times.
✓ Branch 67 → 60 taken 29 times.
✓ Branch 67 → 68 taken 47 times.
76 for (const auto &item : m_attachments->items())
889
1/3
✗ Branch 63 → 64 not taken.
✓ Branch 63 → 65 taken 29 times.
✗ Branch 64 → 65 not taken.
76 if (item.state == QLatin1String("failed")) input.filesOverLimit.append(item.name);
890
3/3
✓ Branch 70 → 71 taken 43 times.
✓ Branch 70 → 72 taken 4 times.
✓ Branch 71 → 73 taken 43 times.
47 input.attachmentWords = m_deps.attachmentWords ? m_deps.attachmentWords() : QStringList();
891
2/3
✓ Branch 75 → 76 taken 47 times.
✓ Branch 77 → 78 taken 47 times.
✗ Branch 77 → 80 not taken.
47 if (const auto identity = currentIdentity()) input.identityAddress = identity->email;
892 47 input.originalSentTo = m_originalSentTo;
893
1/1
✓ Branch 82 → 83 taken 47 times.
47 const auto findings = SendChecks::check(input);
894 47 bool blocking = false;
895 47 QVariantList checks;
896
2/2
✓ Branch 136 → 85 taken 27 times.
✓ Branch 136 → 137 taken 47 times.
74 for (const auto &finding : findings) {
897 54 QVariantMap entry{{QStringLiteral("kind"), int(finding.kind)},
898 54 {QStringLiteral("value"), finding.value},
899 54 {QStringLiteral("other"), finding.other},
900 54 {QStringLiteral("count"), finding.count},
901
2/4
✓ Branch 108 → 109 taken 135 times.
✓ Branch 108 → 110 taken 27 times.
✗ Branch 252 → 253 not taken.
✗ Branch 252 → 254 not taken.
189 {QStringLiteral("blocking"), finding.blocking}};
902
2/2
✓ Branch 120 → 121 taken 27 times.
✓ Branch 125 → 126 taken 27 times.
54 entry.insert(QStringLiteral("text"), checkSentence(entry));
903 27 blocking |= finding.blocking;
904
1/1
✓ Branch 131 → 132 taken 27 times.
27 checks.append(entry);
905 27 }
906
8/8
✓ Branch 138 → 139 taken 19 times.
✓ Branch 138 → 142 taken 28 times.
✓ Branch 139 → 140 taken 3 times.
✓ Branch 139 → 141 taken 16 times.
✓ Branch 140 → 141 taken 2 times.
✓ Branch 140 → 142 taken 1 time.
✓ Branch 143 → 144 taken 18 times.
✓ Branch 143 → 165 taken 29 times.
47 if (!checks.isEmpty() && (blocking || !anyway)) {
907 18 m_checks = checks;
908
1/1
✓ Branch 145 → 146 taken 18 times.
18 emit checksChanged();
909
2/8
✓ Branch 146 → 147 taken 18 times.
✗ Branch 150 → 151 not taken.
✗ Branch 151 → 152 not taken.
✗ Branch 153 → 154 not taken.
✗ Branch 154 → 155 not taken.
✗ Branch 155 → 156 not taken.
✗ Branch 159 → 148 not taken.
✓ Branch 159 → 160 taken 18 times.
18 qCInfo(lcCompose) << "send checks" << checks.size() << "blocking" << blocking;
910 18 return QStringLiteral("checks");
911 }
912
1/1
✓ Branch 165 → 166 taken 29 times.
29 m_checks.clear();
913
1/1
✓ Branch 166 → 167 taken 29 times.
29 emit checksChanged();
914
1/1
✓ Branch 167 → 168 taken 29 times.
29 const ComposeMessage smtp = message(ComposeMessage::Form::Smtp);
915
1/1
✓ Branch 168 → 169 taken 29 times.
29 saveLocalNow();
916
4/7
✓ Branch 170 → 171 taken 29 times.
✗ Branch 170 → 173 not taken.
✓ Branch 171 → 172 taken 29 times.
✗ Branch 172 → 173 not taken.
✓ Branch 172 → 174 taken 29 times.
✗ Branch 175 → 176 not taken.
✓ Branch 175 → 184 taken 29 times.
29 if (!m_deps.enqueue || !m_deps.enqueue(this, smtp, due, scheduled)) {
917 ✗ setNotice(tr("The message could not be put into the Outbox."));
918 ✗ return QStringLiteral("error");
919 }
920 29 m_sending = true;
921
1/1
✓ Branch 184 → 185 taken 29 times.
29 emit sendingChanged();
922
11/14
✓ Branch 186 → 187 taken 27 times.
✓ Branch 186 → 191 taken 2 times.
✓ Branch 187 → 188 taken 27 times.
✓ Branch 189 → 190 taken 27 times.
✗ Branch 189 → 191 not taken.
✓ Branch 192 → 193 taken 27 times.
✓ Branch 192 → 194 taken 2 times.
✓ Branch 194 → 195 taken 27 times.
✓ Branch 194 → 201 taken 2 times.
✓ Branch 195 → 196 taken 27 times.
✓ Branch 196 → 197 taken 27 times.
✓ Branch 197 → 198 taken 27 times.
✗ Branch 319 → 320 not taken.
✗ Branch 319 → 321 not taken.
29 if (m_deps.rememberLanguage && !checkedLanguage().isEmpty()) m_deps.rememberLanguage(firstRecipient(), checkedLanguage());
923
7/11
✓ Branch 201 → 202 taken 8 times.
✓ Branch 201 → 205 taken 21 times.
✓ Branch 208 → 209 taken 29 times.
✓ Branch 210 → 211 taken 21 times.
✓ Branch 210 → 212 taken 8 times.
✓ Branch 212 → 213 taken 8 times.
✓ Branch 212 → 214 taken 21 times.
✗ Branch 331 → 332 not taken.
✗ Branch 331 → 333 not taken.
✗ Branch 335 → 336 not taken.
✗ Branch 335 → 337 not taken.
58 emit finished(scheduled ? QStringLiteral("scheduled") : QStringLiteral("sent"));
924 29 return QStringLiteral("queued");
925 209 }
926
927 4 void ComposeSession::reopenAfterUndo() {
928 4 m_sending = false;
929 4 emit sendingChanged();
930 4 }
931
932 1 void ComposeSession::dismissCheck(int index) {
933
3/6
✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1 time.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 1 time.
1 if (index < 0 || index >= m_checks.size()) return;
934 1 m_checks.removeAt(index);
935 1 emit checksChanged();
936 }
937
938 4 void ComposeSession::clearChecks() {
939 4 m_checks.clear();
940 4 emit checksChanged();
941 4 }
942
943 ✗ void ComposeSession::moveRecipientsToBcc() {
944 ✗ for (RecipientChipModel *field : {&m_to, &m_cc}) {
945 ✗ while (field->rowCount() > 0) {
946 ✗ const QVariantMap chip = field->chipAt(0);
947 ✗ field->remove(0);
948 ✗ m_bcc.insertChip(m_bcc.rowCount(), chip);
949 ✗ }
950 }
951 ✗ if (const auto identity = currentIdentity()) m_to.addMailbox(identity->name, identity->email);
952 ✗ qCInfo(lcCompose) << "session" << m_id << "moved the recipients to Bcc:" << m_bcc.rowCount();
953 ✗ }
954
955 932 void ComposeSession::fieldsMayHaveChanged() {
956
7/7
✓ Branch 9 → 10 taken 932 times.
✓ Branch 12 → 13 taken 932 times.
✓ Branch 15 → 16 taken 932 times.
✓ Branch 16 → 17 taken 932 times.
✓ Branch 17 → 18 taken 932 times.
✓ Branch 18 → 19 taken 932 times.
✓ Branch 19 → 20 taken 932 times.
1864 const QString key = m_to.draftText() + QLatin1Char('\n') + m_cc.draftText() + QLatin1Char('\n') + m_bcc.draftText() +
957
6/6
✓ Branch 3 → 4 taken 932 times.
✓ Branch 4 → 5 taken 932 times.
✓ Branch 5 → 6 taken 932 times.
✓ Branch 6 → 7 taken 932 times.
✓ Branch 20 → 21 taken 932 times.
✓ Branch 21 → 22 taken 932 times.
2796 QLatin1Char('\n') + QString::fromUtf8(QJsonDocument(m_attachments->manifest()).toJson(QJsonDocument::Compact));
958
2/2
✓ Branch 35 → 36 taken 617 times.
✓ Branch 35 → 37 taken 315 times.
932 if (key == m_fieldsKey) return;
959 315 m_fieldsKey = key;
960
1/1
✓ Branch 38 → 39 taken 315 times.
315 changed();
961 932 }
962
963 1128 void ComposeSession::changed() {
964 1128 ++m_revision;
965 1128 m_localTimer.start();
966 1128 m_serverTimer.start();
967 1128 emit saveStateChanged();
968 1128 }
969
970 181 QJsonObject ComposeSession::snapshot() const {
971 362 QJsonObject draft{{QStringLiteral("kind"), m_kind},
972 362 {QStringLiteral("place"), m_place},
973 362 {QStringLiteral("identityId"), m_identityId},
974 362 {QStringLiteral("signatureId"), m_signatureId},
975 362 {QStringLiteral("placedSignature"), m_placedSignatureText},
976
1/1
✓ Branch 25 → 26 taken 181 times.
362 {QStringLiteral("to"), m_to.draftText()},
977
1/1
✓ Branch 30 → 31 taken 181 times.
362 {QStringLiteral("cc"), m_cc.draftText()},
978
1/1
✓ Branch 35 → 36 taken 181 times.
362 {QStringLiteral("bcc"), m_bcc.draftText()},
979 362 {QStringLiteral("subject"), m_subject},
980 362 {QStringLiteral("formatted"), m_formatted},
981 362 {QStringLiteral("language"), m_language},
982 362 {QStringLiteral("priority"), m_priority},
983 362 {QStringLiteral("readReceipt"), m_readReceipt},
984 362 {QStringLiteral("inReplyTo"), m_inReplyTo},
985
1/1
✓ Branch 64 → 65 taken 181 times.
362 {QStringLiteral("references"), QJsonArray::fromStringList(m_references)},
986 362 {QStringLiteral("originalSentTo"), m_originalSentTo},
987
1/1
✓ Branch 73 → 74 taken 181 times.
362 {QStringLiteral("mailReference"), QJsonObject::fromVariantMap(m_mailReference)},
988 724 {QStringLiteral("quote"), QJsonObject{{QStringLiteral("attribution"), m_quote.attribution},
989 362 {QStringLiteral("text"), m_quote.text},
990 362 {QStringLiteral("html"), m_quote.html},
991
2/4
✓ Branch 147 → 148 taken 724 times.
✓ Branch 147 → 149 taken 181 times.
✗ Branch 286 → 287 not taken.
✗ Branch 286 → 288 not taken.
1267 {QStringLiteral("inEditor"), m_quoteInEditor}}},
992
1/1
✓ Branch 100 → 101 taken 181 times.
362 {QStringLiteral("attachments"), m_attachments->manifest()},
993 362 {QStringLiteral("closed"), m_closed},
994
1/1
✓ Branch 109 → 110 taken 181 times.
362 {QStringLiteral("serverDraftUid"), QString::number(m_serverDraftUid)},
995
1/1
✓ Branch 114 → 115 taken 181 times.
362 {QStringLiteral("serverDraftUidValidity"), QString::number(m_serverDraftUidValidity)},
996
1/1
✓ Branch 119 → 120 taken 181 times.
362 {QStringLiteral("meeting"), QJsonObject::fromVariantMap(m_meeting)},
997
2/4
✓ Branch 127 → 128 taken 4344 times.
✓ Branch 127 → 129 taken 181 times.
✗ Branch 232 → 233 not taken.
✗ Branch 232 → 234 not taken.
4706 {QStringLiteral("spellLanguage"), m_spellLanguage}};
998
1/1
✓ Branch 198 → 199 taken 181 times.
181 const QJsonObject text = snapshotText();
999
2/3
✓ Branch 199 → 200 taken 181 times.
✓ Branch 200 → 201 taken 181 times.
✗ Branch 200 → 228 not taken.
181 if (!text.isEmpty()) {
1000
2/2
✓ Branch 204 → 205 taken 181 times.
✓ Branch 208 → 209 taken 181 times.
362 draft.insert(QStringLiteral("html"), text.value(QStringLiteral("html")));
1001
2/2
✓ Branch 217 → 218 taken 181 times.
✓ Branch 221 → 222 taken 181 times.
362 draft.insert(QStringLiteral("text"), text.value(QStringLiteral("body")));
1002 }
1003 181 return draft;
1004 5430 }
1005
1006 6 bool ComposeSession::restore(const QJsonObject &draft) {
1007
2/2
✓ Branch 5 → 6 taken 6 times.
✓ Branch 9 → 10 taken 6 times.
12 m_place = draft.value(QStringLiteral("place")).toString(QStringLiteral("tab"));
1008
3/3
✓ Branch 20 → 21 taken 6 times.
✓ Branch 21 → 22 taken 6 times.
✓ Branch 22 → 23 taken 6 times.
6 m_serverDraftUid = qMax<qint64>(0, draft.value(QStringLiteral("serverDraftUid")).toString().toLongLong());
1009
3/3
✓ Branch 31 → 32 taken 6 times.
✓ Branch 32 → 33 taken 6 times.
✓ Branch 33 → 34 taken 6 times.
6 m_serverDraftUidValidity = draft.value(QStringLiteral("serverDraftUidValidity")).toString().toUInt();
1010
2/2
✓ Branch 41 → 42 taken 6 times.
✓ Branch 42 → 43 taken 6 times.
6 m_identityId = draft.value(QStringLiteral("identityId")).toString();
1011
2/2
✓ Branch 51 → 52 taken 6 times.
✓ Branch 52 → 53 taken 6 times.
6 m_signatureId = draft.value(QStringLiteral("signatureId")).toString();
1012
2/2
✓ Branch 61 → 62 taken 6 times.
✓ Branch 62 → 63 taken 6 times.
6 m_placedSignatureText = draft.value(QStringLiteral("placedSignature")).toString();
1013
3/3
✓ Branch 71 → 72 taken 6 times.
✓ Branch 72 → 73 taken 6 times.
✓ Branch 73 → 74 taken 6 times.
12 m_to.setText(draft.value(QStringLiteral("to")).toString());
1014
3/3
✓ Branch 81 → 82 taken 6 times.
✓ Branch 82 → 83 taken 6 times.
✓ Branch 83 → 84 taken 6 times.
12 m_cc.setText(draft.value(QStringLiteral("cc")).toString());
1015
3/3
✓ Branch 91 → 92 taken 6 times.
✓ Branch 92 → 93 taken 6 times.
✓ Branch 93 → 94 taken 6 times.
12 m_bcc.setText(draft.value(QStringLiteral("bcc")).toString());
1016
2/2
✓ Branch 101 → 102 taken 6 times.
✓ Branch 102 → 103 taken 6 times.
6 m_subject = draft.value(QStringLiteral("subject")).toString();
1017
2/2
✓ Branch 111 → 112 taken 6 times.
✓ Branch 112 → 113 taken 6 times.
6 m_formatted = draft.value(QStringLiteral("formatted")).toBool();
1018
1/1
✓ Branch 116 → 117 taken 6 times.
6 m_editor.setFormatted(m_formatted);
1019
2/2
✓ Branch 120 → 121 taken 6 times.
✓ Branch 121 → 122 taken 6 times.
6 m_language = draft.value(QStringLiteral("language")).toString();
1020
2/2
✓ Branch 130 → 131 taken 6 times.
✓ Branch 131 → 132 taken 6 times.
6 m_priority = draft.value(QStringLiteral("priority")).toInt(3);
1021
2/2
✓ Branch 138 → 139 taken 6 times.
✓ Branch 139 → 140 taken 6 times.
6 m_readReceipt = draft.value(QStringLiteral("readReceipt")).toBool();
1022
2/2
✓ Branch 146 → 147 taken 6 times.
✓ Branch 147 → 148 taken 6 times.
6 m_inReplyTo = draft.value(QStringLiteral("inReplyTo")).toString();
1023
1/1
✓ Branch 153 → 154 taken 6 times.
6 m_references.clear();
1024
5/9
✓ Branch 157 → 158 taken 6 times.
✓ Branch 158 → 159 taken 6 times.
✓ Branch 162 → 163 taken 6 times.
✓ Branch 163 → 164 taken 6 times.
✗ Branch 166 → 167 not taken.
✗ Branch 167 → 168 not taken.
✗ Branch 168 → 169 not taken.
✗ Branch 173 → 165 not taken.
✓ Branch 173 → 174 taken 6 times.
6 for (const QJsonValue &value : draft.value(QStringLiteral("references")).toArray()) m_references.append(value.toString());
1025
2/2
✓ Branch 178 → 179 taken 6 times.
✓ Branch 179 → 180 taken 6 times.
6 m_originalSentTo = draft.value(QStringLiteral("originalSentTo")).toString();
1026
3/3
✓ Branch 188 → 189 taken 6 times.
✓ Branch 189 → 190 taken 6 times.
✓ Branch 190 → 191 taken 6 times.
6 m_mailReference = draft.value(QStringLiteral("mailReference")).toObject().toVariantMap();
1027
3/3
✓ Branch 200 → 201 taken 6 times.
✓ Branch 201 → 202 taken 6 times.
✓ Branch 202 → 203 taken 6 times.
6 m_meeting = draft.value(QStringLiteral("meeting")).toObject().toVariantMap();
1028
3/3
✓ Branch 212 → 213 taken 6 times.
✓ Branch 213 → 214 taken 6 times.
✓ Branch 214 → 215 taken 6 times.
6 m_spellLanguage = draft.value(QStringLiteral("spellLanguage")).toString().left(32);
1029
2/3
✓ Branch 221 → 222 taken 6 times.
✗ Branch 222 → 223 not taken.
✓ Branch 222 → 245 taken 6 times.
6 if (!m_meeting.isEmpty()) // QDateTime travels as ISO text
1030 ✗ m_meeting.insert(QStringLiteral("start"), QDateTime::fromString(m_meeting.value(QStringLiteral("start")).toString(), Qt::ISODate));
1031
2/2
✓ Branch 248 → 249 taken 6 times.
✓ Branch 249 → 250 taken 6 times.
6 const QJsonObject quote = draft.value(QStringLiteral("quote")).toObject();
1032
2/2
✓ Branch 256 → 257 taken 6 times.
✓ Branch 261 → 262 taken 6 times.
18 m_quote = {quote.value(QStringLiteral("attribution")).toString(), quote.value(QStringLiteral("text")).toString(),
1033
1/1
✓ Branch 266 → 267 taken 6 times.
18 quote.value(QStringLiteral("html")).toString()};
1034
2/2
✓ Branch 282 → 283 taken 6 times.
✓ Branch 283 → 284 taken 6 times.
6 m_quoteInEditor = quote.value(QStringLiteral("inEditor")).toBool();
1035
3/3
✓ Branch 291 → 292 taken 6 times.
✓ Branch 292 → 293 taken 6 times.
✓ Branch 293 → 294 taken 6 times.
12 m_attachments->restore(draft.value(QStringLiteral("attachments")).toArray());
1036
1/1
✓ Branch 304 → 305 taken 6 times.
24 m_pendingRestore = QJsonObject{{QStringLiteral("html"), draft.value(QStringLiteral("html"))},
1037
1/1
✓ Branch 312 → 313 taken 6 times.
18 {QStringLiteral("text"), draft.value(QStringLiteral("text"))},
1038
3/5
✓ Branch 320 → 321 taken 6 times.
✓ Branch 326 → 327 taken 18 times.
✓ Branch 326 → 328 taken 6 times.
✗ Branch 682 → 683 not taken.
✗ Branch 682 → 684 not taken.
42 {QStringLiteral("body"), draft.value(QStringLiteral("text"))}};
1039
1/1
✓ Branch 343 → 344 taken 6 times.
6 applyPendingRestore();
1040
1/1
✓ Branch 344 → 345 taken 6 times.
6 emit placeChanged();
1041
1/1
✓ Branch 345 → 346 taken 6 times.
6 emit subjectChanged();
1042
1/1
✓ Branch 346 → 347 taken 6 times.
6 emit formattedChanged();
1043
1/1
✓ Branch 347 → 348 taken 6 times.
6 emit identityChanged();
1044
1/1
✓ Branch 348 → 349 taken 6 times.
6 emit signatureChanged();
1045
1/1
✓ Branch 349 → 350 taken 6 times.
6 emit optionsChanged();
1046
1/1
✓ Branch 350 → 351 taken 6 times.
6 emit quoteChanged();
1047 6 m_revision = m_savedRevision = 0;
1048
1/1
✓ Branch 351 → 352 taken 6 times.
6 m_saveState = tr("Opened from your drafts");
1049
1/1
✓ Branch 354 → 355 taken 6 times.
6 emit saveStateChanged();
1050 6 return true;
1051 48 }
1052
1053 41 void ComposeSession::saveLocalNow() {
1054
1/1
✓ Branch 2 → 3 taken 41 times.
41 m_localTimer.stop();
1055
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 41 times.
41 if (m_deps.profileDirectory.isEmpty()) return;
1056
1/1
✓ Branch 6 → 7 taken 41 times.
41 const QJsonObject draft = snapshot();
1057 41 const quint64 revision = m_revision;
1058
1/1
✓ Branch 7 → 8 taken 41 times.
41 QPointer<ComposeSession> self(this);
1059 41 const QString profile = m_deps.profileDirectory, id = m_id;
1060
1/7
✓ Branch 14 → 15 taken 41 times.
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 27 not taken.
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 30 not taken.
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 33 not taken.
41 m_savePool.start([self, draft, revision, profile, id] {
1061 41 QString error;
1062
1/1
✓ Branch 2 → 3 taken 41 times.
41 const bool ok = ComposeDraftFiles::save(profile, id, draft, &error);
1063
1/5
✓ Branch 6 → 7 taken 41 times.
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 14 not taken.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 17 not taken.
41 QMetaObject::invokeMethod(self, [self, ok, error, revision] {
1064
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 31 times.
31 if (!self) return;
1065 31 self->m_saveFailed = !ok;
1066
1/2
✓ Branch 6 → 7 taken 31 times.
✗ Branch 6 → 24 not taken.
31 if (ok) {
1067
1/2
✓ Branch 8 → 9 taken 31 times.
✗ Branch 8 → 11 not taken.
31 if (revision >= self->m_savedRevision) self->m_savedRevision = revision;
1068
5/5
✓ Branch 11 → 12 taken 31 times.
✓ Branch 13 → 14 taken 31 times.
✓ Branch 14 → 15 taken 31 times.
✓ Branch 15 → 16 taken 31 times.
✓ Branch 16 → 17 taken 31 times.
62 self->m_saveState = tr("Saved %1").arg(QLocale().toString(QTime::currentTime(), QLocale::ShortFormat));
1069 } else {
1070 ✗ self->m_saveState = tr("Could not save on this computer — %1").arg(error);
1071 ✗ qCWarning(lcCompose) << "local draft save failed";
1072 }
1073 31 emit self->saveStateChanged();
1074 }, Qt::QueuedConnection);
1075 41 });
1076 41 }
1077
1078 139 void ComposeSession::queueServerDraft() {
1079 139 m_serverTimer.stop();
1080
5/6
✓ Branch 3 → 4 taken 83 times.
✓ Branch 3 → 6 taken 56 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 83 times.
✓ Branch 8 → 9 taken 56 times.
✓ Branch 8 → 10 taken 83 times.
139 if (m_serverRevision == m_revision || !m_deps.saveServerDraft) return;
1081
13/18
✓ Branch 13 → 14 taken 15 times.
✓ Branch 13 → 22 taken 68 times.
✓ Branch 15 → 16 taken 6 times.
✓ Branch 15 → 22 taken 9 times.
✓ Branch 17 → 18 taken 6 times.
✓ Branch 18 → 19 taken 6 times.
✓ Branch 20 → 21 taken 6 times.
✗ Branch 20 → 22 not taken.
✓ Branch 23 → 24 taken 6 times.
✓ Branch 23 → 25 taken 77 times.
✓ Branch 25 → 26 taken 6 times.
✓ Branch 25 → 27 taken 77 times.
✓ Branch 27 → 28 taken 6 times.
✓ Branch 27 → 29 taken 77 times.
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 36 not taken.
✗ Branch 38 → 39 not taken.
✗ Branch 38 → 40 not taken.
83 if (m_to.count() + m_cc.count() + m_bcc.count() == 0 && m_subject.isEmpty() && newText().trimmed().isEmpty()) return;
1082 77 m_serverRevision = m_revision;
1083
2/2
✓ Branch 29 → 30 taken 77 times.
✓ Branch 30 → 31 taken 77 times.
77 m_deps.saveServerDraft(this, message(ComposeMessage::Form::Draft));
1084 }
1085
1086 // Explicit saves (Ctrl+S, quit, account switch, close) write in the calling
1087 // thread after any autosave still in flight, so the newest text is the one
1088 // on disk and the caller learns whether it got there.
1089 140 bool ComposeSession::saveLocalSync() {
1090
1/1
✓ Branch 2 → 3 taken 140 times.
140 m_localTimer.stop();
1091
1/1
✓ Branch 4 → 5 taken 140 times.
140 m_savePool.waitForDone();
1092
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 140 times.
140 if (m_deps.profileDirectory.isEmpty()) return true;
1093 140 QString error;
1094
2/2
✓ Branch 8 → 9 taken 140 times.
✓ Branch 9 → 10 taken 140 times.
140 const bool ok = ComposeDraftFiles::save(m_deps.profileDirectory, m_id, snapshot(), &error);
1095 140 m_saveFailed = !ok;
1096
2/2
✓ Branch 11 → 12 taken 137 times.
✓ Branch 11 → 24 taken 3 times.
140 if (ok) {
1097 137 m_savedRevision = m_revision;
1098
5/5
✓ Branch 12 → 13 taken 137 times.
✓ Branch 14 → 15 taken 137 times.
✓ Branch 15 → 16 taken 137 times.
✓ Branch 16 → 17 taken 137 times.
✓ Branch 17 → 18 taken 137 times.
274 m_saveState = tr("Saved %1").arg(QLocale().toString(QTime::currentTime(), QLocale::ShortFormat));
1099 } else {
1100
2/2
✓ Branch 24 → 25 taken 3 times.
✓ Branch 26 → 27 taken 3 times.
6 m_saveState = tr("Could not save on this computer — %1").arg(error);
1101
5/5
✓ Branch 30 → 31 taken 3 times.
✓ Branch 34 → 35 taken 3 times.
✓ Branch 35 → 36 taken 3 times.
✓ Branch 39 → 32 taken 3 times.
✓ Branch 39 → 40 taken 3 times.
6 qCWarning(lcCompose) << "local draft save failed";
1102 }
1103
1/1
✓ Branch 41 → 42 taken 140 times.
140 emit saveStateChanged();
1104 140 return ok;
1105 140 }
1106
1107 120 bool ComposeSession::save() {
1108 120 const bool ok = saveLocalSync();
1109 120 m_serverRevision = 0;
1110 120 queueServerDraft();
1111 120 return ok;
1112 }
1113
1114 23 bool ComposeSession::closeKeepingDraft() {
1115 23 finishPaste();
1116 // An untouched new mail leaves no draft behind (F100-29: Esc closes it).
1117
9/13
✓ Branch 7 → 8 taken 10 times.
✓ Branch 7 → 25 taken 13 times.
✓ Branch 11 → 12 taken 3 times.
✓ Branch 11 → 25 taken 7 times.
✓ Branch 12 → 13 taken 3 times.
✓ Branch 14 → 15 taken 3 times.
✗ Branch 14 → 25 not taken.
✓ Branch 19 → 20 taken 3 times.
✗ Branch 19 → 25 not taken.
✓ Branch 34 → 35 taken 3 times.
✓ Branch 34 → 37 taken 20 times.
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 63 not taken.
49 if (m_document && m_kind == QLatin1String("new") && m_to.count() + m_cc.count() + m_bcc.count() == 0 && m_subject.trimmed().isEmpty() &&
1118
12/20
✓ Branch 4 → 5 taken 23 times.
✗ Branch 4 → 25 not taken.
✓ Branch 16 → 17 taken 3 times.
✓ Branch 17 → 18 taken 3 times.
✓ Branch 23 → 24 taken 3 times.
✗ Branch 23 → 25 not taken.
✓ Branch 26 → 27 taken 3 times.
✓ Branch 26 → 28 taken 20 times.
✓ Branch 28 → 29 taken 3 times.
✓ Branch 28 → 30 taken 20 times.
✓ Branch 30 → 31 taken 3 times.
✓ Branch 30 → 32 taken 20 times.
✓ Branch 32 → 33 taken 3 times.
✓ Branch 32 → 34 taken 20 times.
✗ Branch 49 → 50 not taken.
✗ Branch 49 → 51 not taken.
✗ Branch 53 → 54 not taken.
✗ Branch 53 → 55 not taken.
✗ Branch 57 → 58 not taken.
✗ Branch 57 → 59 not taken.
49 newText().trimmed().isEmpty() && m_attachments->items().isEmpty()) {
1119 3 discard();
1120 3 return true;
1121 }
1122 20 m_closed = true;
1123
2/2
✓ Branch 38 → 39 taken 1 time.
✓ Branch 38 → 40 taken 19 times.
20 if (!saveLocalSync()) {
1124 1 m_closed = false; // the composer stays open: its text is nowhere else
1125 1 return false;
1126 }
1127 19 queueServerDraft();
1128
1/1
✓ Branch 44 → 45 taken 19 times.
19 emit finished(QStringLiteral("closed"));
1129 19 return true;
1130 }
1131
1132 24 void ComposeSession::discard() {
1133 24 m_localTimer.stop();
1134 24 m_serverTimer.stop();
1135
1/1
✓ Branch 5 → 6 taken 24 times.
24 m_savePool.waitForDone();
1136 24 m_attachments->removeAll();
1137 24 ComposeDraftFiles::remove(m_deps.profileDirectory, m_id);
1138
1/1
✓ Branch 12 → 13 taken 24 times.
24 emit finished(QStringLiteral("discarded"));
1139 24 }
1140
1141 159 void ComposeSession::setDisplayPalette(const QVariantMap &palette, qreal basePointSize) {
1142
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 159 times.
159 if (!m_highlighter) return;
1143 159 ComposeHighlighter::Palette value;
1144
2/2
✓ Branch 9 → 10 taken 159 times.
✓ Branch 10 → 11 taken 159 times.
318 value.text = QColor(palette.value(QStringLiteral("text")).toString());
1145
2/2
✓ Branch 21 → 22 taken 159 times.
✓ Branch 22 → 23 taken 159 times.
318 value.secondary = QColor(palette.value(QStringLiteral("secondary")).toString());
1146
2/2
✓ Branch 33 → 34 taken 159 times.
✓ Branch 34 → 35 taken 159 times.
318 value.surface = QColor(palette.value(QStringLiteral("surface")).toString());
1147
2/2
✓ Branch 45 → 46 taken 159 times.
✓ Branch 46 → 47 taken 159 times.
318 value.codeBackground = QColor(palette.value(QStringLiteral("codeBackground")).toString());
1148
2/2
✓ Branch 57 → 58 taken 159 times.
✓ Branch 58 → 59 taken 159 times.
318 value.spellError = QColor(palette.value(QStringLiteral("spellError")).toString());
1149
3/4
✓ Branch 66 → 67 taken 159 times.
✗ Branch 66 → 70 not taken.
✓ Branch 68 → 69 taken 12 times.
✓ Branch 68 → 70 taken 147 times.
159 value.dark = value.surface.isValid() && value.surface.lightnessF() < 0.5;
1150
1/1
✓ Branch 72 → 73 taken 159 times.
159 m_highlighter->setBaseFontPointSize(basePointSize);
1151
1/1
✓ Branch 74 → 75 taken 159 times.
159 m_highlighter->setPalette(value);
1152 }
1153
1154 5 int ComposeSession::insertImageData(const QString &name, const QByteArray &data, const QString &mimeType) {
1155
1/1
✓ Branch 4 → 5 taken 5 times.
5 const QString id = m_attachments->addData(name, data, mimeType);
1156
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 5 times.
5 if (id.isEmpty()) return -1;
1157
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 5 times.
5 if (!m_formatted) return -1; // plain-text mails attach the image
1158 5 QImage image;
1159
1/1
✓ Branch 12 → 13 taken 5 times.
5 image.loadFromData(data);
1160
1/1
✓ Branch 13 → 14 taken 5 times.
5 const QString resource = ImageScheme + id;
1161
1/1
✓ Branch 14 → 15 taken 5 times.
5 m_editor.addImageResource(resource, image);
1162
3/4
✓ Branch 15 → 16 taken 5 times.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 5 times.
✓ Branch 18 → 19 taken 5 times.
5 const int width = image.isNull() ? 0 : qMin(image.width(), 640);
1163
1/1
✓ Branch 21 → 22 taken 5 times.
5 m_editor.insertImage(resource, width, name);
1164
1/1
✓ Branch 23 → 24 taken 5 times.
5 m_attachments->setInline(id, true);
1165 5 return m_editor.cursorPosition();
1166 5 }
1167
1168 22 int ComposeSession::insertHtml(const QByteArray &html) {
1169
1/1
✓ Branch 2 → 3 taken 22 times.
22 ComposeHtmlImport::Options options;
1170
1/1
✓ Branch 3 → 4 taken 22 times.
22 const auto imported = ComposeHtmlImport::import(html, options);
1171
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 22 times.
22 if (!imported.ok()) return -1;
1172
1/1
✓ Branch 7 → 8 taken 22 times.
22 adoptImported(imported);
1173
1/1
✓ Branch 8 → 9 taken 22 times.
22 return m_editor.insertFragment(imported.fragment);
1174 22 }
1175
1176 24 void ComposeSession::adoptImported(const ComposeHtmlImport::Result &imported) {
1177 // data: images become tray items under the names the fragment uses: the
1178 // item's id is the name after ImageScheme, the key the message builder
1179 // looks pictures up by (a new id left the picture out of the HTML part
1180 // and sent it as a plain attachment — S108.10.2 rendering test).
1181
2/2
✓ Branch 42 → 4 taken 2 times.
✓ Branch 42 → 43 taken 24 times.
26 for (const auto &image : imported.images) {
1182
3/4
✓ Branch 5 → 6 taken 2 times.
✓ Branch 6 → 7 taken 2 times.
✗ Branch 6 → 9 not taken.
✓ Branch 8 → 10 taken 2 times.
2 const QString wanted = image.name.startsWith(ImageScheme) ? image.name.mid(ImageScheme.size()) : QString();
1183
2/6
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 16 taken 2 times.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 21 taken 2 times.
✗ Branch 53 → 54 not taken.
✗ Branch 53 → 55 not taken.
4 const QString id = m_attachments->addData(image.alternativeText.isEmpty() ? QStringLiteral("image.png")
1184 2 : image.alternativeText,
1185
1/1
✓ Branch 17 → 18 taken 2 times.
4 image.data, image.mimeType, wanted);
1186
1/2
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 24 taken 2 times.
2 if (id.isEmpty()) continue;
1187 2 QImage decoded;
1188
1/1
✓ Branch 25 → 26 taken 2 times.
2 decoded.loadFromData(image.data);
1189
1/1
✓ Branch 26 → 27 taken 2 times.
2 m_editor.addImageResource(image.name, decoded);
1190
1/1
✓ Branch 28 → 29 taken 2 times.
2 m_attachments->setInline(id, true);
1191 2 }
1192
1/2
✗ Branch 43 → 44 not taken.
✓ Branch 43 → 50 taken 24 times.
24 if (imported.remoteImagesLeftOut > 0)
1193 ✗ setNotice(imported.remoteImagesLeftOut == 1 ? tr("1 remote image was left out")
1194 ✗ : tr("%n remote images were left out", nullptr, imported.remoteImagesLeftOut));
1195 24 }
1196
1197 25 int ComposeSession::insertPastedHtml(const QByteArray &html, const QString &text) {
1198 25 finishPaste();
1199
2/2
✓ Branch 4 → 5 taken 22 times.
✓ Branch 4 → 7 taken 3 times.
25 if (html.size() <= ComposePasteJob::AsyncBytes) return insertHtml(html);
1200
1/2
✓ Branch 7 → 8 taken 3 times.
✗ Branch 7 → 19 not taken.
3 if (!m_pasteJob) {
1201
2/5
✓ Branch 9 → 10 taken 3 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 3 times.
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 28 not taken.
3 m_pasteJob = new ComposePasteJob(m_editor, this);
1202
1/1
✓ Branch 12 → 13 taken 3 times.
3 connect(m_pasteJob, &ComposePasteJob::imported, this, &ComposeSession::adoptImported);
1203
1/1
✓ Branch 14 → 15 taken 3 times.
3 connect(m_pasteJob, &ComposePasteJob::finished, this, [this](int caret) {
1204 2 emit pastingChanged();
1205
1/2
✓ Branch 3 → 4 taken 2 times.
✗ Branch 3 → 5 not taken.
2 if (caret >= 0) emit caretRequested(caret);
1206 2 });
1207
1/1
✓ Branch 16 → 17 taken 3 times.
3 connect(m_pasteJob, &ComposePasteJob::failed, this, [this] {
1208 1 emit pastingChanged();
1209
1/2
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 7 not taken.
1 if (!m_pasteText.isEmpty()) emit caretRequested(m_editor.insertPlainText(m_pasteText));
1210 1 });
1211 }
1212 3 m_pasteText = text;
1213 3 m_pasteJob->start(html, {});
1214 3 emit pastingChanged();
1215 3 return -1;
1216 3 }
1217
1218 112 void ComposeSession::finishPaste() {
1219
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 111 times.
112 if (m_pasteJob) m_pasteJob->finishNow();
1220 112 }
1221
1222 6 int ComposeSession::paste(bool plainTextOnly) {
1223 6 const QMimeData *data = QGuiApplication::clipboard()->mimeData();
1224
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 6 times.
6 if (!data) return -1;
1225
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 31 taken 6 times.
6 if (data->hasUrls()) {
1226 ✗ QList<QUrl> files;
1227 ✗ for (const QUrl &url : data->urls())
1228 ✗ if (url.isLocalFile()) files.append(url);
1229 ✗ if (!files.isEmpty()) {
1230 ✗ m_attachments->addUrls(files);
1231 ✗ return -1;
1232 }
1233 ✗ }
1234
5/6
✓ Branch 32 → 33 taken 5 times.
✓ Branch 32 → 36 taken 1 time.
✓ Branch 34 → 35 taken 5 times.
✗ Branch 34 → 36 not taken.
✓ Branch 37 → 38 taken 5 times.
✓ Branch 37 → 77 taken 1 time.
6 if (data->hasImage() && !data->hasText()) {
1235
2/2
✓ Branch 38 → 39 taken 5 times.
✓ Branch 39 → 40 taken 5 times.
5 const QImage image = qvariant_cast<QImage>(data->imageData());
1236 5 QByteArray png;
1237
1/1
✓ Branch 41 → 42 taken 5 times.
5 QBuffer buffer(&png);
1238
1/1
✓ Branch 43 → 44 taken 5 times.
5 buffer.open(QIODevice::WriteOnly);
1239
1/1
✓ Branch 44 → 45 taken 5 times.
5 image.save(&buffer, "PNG");
1240
3/3
✓ Branch 49 → 50 taken 5 times.
✓ Branch 53 → 54 taken 5 times.
✓ Branch 54 → 55 taken 5 times.
15 const QString name = QStringLiteral("image-%1.png").arg(QDateTime::currentDateTime().toString(QStringLiteral("yyyyMMdd-HHmmss")));
1241
1/1
✓ Branch 64 → 65 taken 5 times.
5 const int caret = insertImageData(name, png, QStringLiteral("image/png"));
1242
1/4
✗ Branch 67 → 68 not taken.
✓ Branch 67 → 72 taken 5 times.
✗ Branch 68 → 69 not taken.
✗ Branch 69 → 70 not taken.
5 if (!m_formatted) setNotice(tr("The image was attached (plain-text mails carry no images in the text)."));
1243 5 return caret;
1244 5 }
1245
3/6
✓ Branch 77 → 78 taken 1 time.
✗ Branch 77 → 81 not taken.
✓ Branch 79 → 80 taken 1 time.
✗ Branch 79 → 81 not taken.
✓ Branch 82 → 83 taken 1 time.
✗ Branch 82 → 102 not taken.
1 if (!plainTextOnly && data->hasHtml()) {
1246
1/2
✗ Branch 83 → 84 not taken.
✓ Branch 83 → 85 taken 1 time.
1 if (!m_formatted) {
1247 ✗ emit m_editor.formattingRefused();
1248 } else {
1249
6/7
✓ Branch 85 → 86 taken 1 time.
✓ Branch 86 → 87 taken 1 time.
✗ Branch 86 → 88 not taken.
✓ Branch 87 → 89 taken 1 time.
✓ Branch 89 → 90 taken 1 time.
✓ Branch 90 → 91 taken 1 time.
✓ Branch 91 → 92 taken 1 time.
1 const int caret = insertPastedHtml(data->html().toUtf8(), data->hasText() ? data->text() : QString());
1250
2/6
✗ Branch 95 → 96 not taken.
✓ Branch 95 → 98 taken 1 time.
✗ Branch 97 → 98 not taken.
✗ Branch 97 → 99 not taken.
✓ Branch 100 → 101 taken 1 time.
✗ Branch 100 → 102 not taken.
1 if (caret >= 0 || pasting()) return caret;
1251 }
1252 }
1253 ✗ if (data->hasText()) return m_editor.insertPlainText(data->text());
1254 ✗ return -1;
1255 }
1256
1257 30 int ComposeSession::dropContent(const QList<QUrl> &urls, const QString &html, const QString &text, bool placeImages) {
1258 30 QList<QUrl> files;
1259
2/2
✓ Branch 10 → 4 taken 6 times.
✓ Branch 10 → 11 taken 30 times.
36 for (const QUrl &url : urls)
1260
3/4
✓ Branch 5 → 6 taken 6 times.
✓ Branch 6 → 7 taken 6 times.
✗ Branch 6 → 8 not taken.
✓ Branch 7 → 8 taken 6 times.
6 if (url.isLocalFile()) files.append(url);
1261
2/2
✓ Branch 12 → 13 taken 6 times.
✓ Branch 12 → 66 taken 24 times.
30 if (!files.isEmpty()) {
1262
2/4
✓ Branch 13 → 14 taken 6 times.
✗ Branch 13 → 63 not taken.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 63 taken 6 times.
6 if (placeImages && m_formatted) {
1263 ✗ int caret = -1;
1264 ✗ for (const QUrl &url : files) {
1265 ✗ QFile file(url.toLocalFile());
1266 ✗ const QString mime = QString::fromLatin1(Rfc2822Builder::mimeTypeForFile(url.toLocalFile()));
1267 ✗ if (mime.startsWith(QLatin1String("image/")) && file.size() <= ComposeAttachmentModel::PerFileLimit &&
1268 ✗ file.open(QIODevice::ReadOnly))
1269 ✗ caret = insertImageData(QFileInfo(file).fileName(), file.readAll(), mime);
1270 else
1271 ✗ m_attachments->addUrls({url});
1272 ✗ }
1273 ✗ return caret;
1274 }
1275
1/1
✓ Branch 64 → 65 taken 6 times.
6 m_attachments->addUrls(files);
1276 6 return -1;
1277 }
1278
3/6
✓ Branch 67 → 68 taken 24 times.
✗ Branch 67 → 70 not taken.
✓ Branch 68 → 69 taken 24 times.
✗ Branch 68 → 70 not taken.
✓ Branch 71 → 72 taken 24 times.
✗ Branch 71 → 82 not taken.
24 if (!html.isEmpty() && m_formatted) {
1279
2/2
✓ Branch 72 → 73 taken 24 times.
✓ Branch 73 → 74 taken 24 times.
24 const int caret = insertPastedHtml(html.toUtf8(), text);
1280
4/6
✓ Branch 75 → 76 taken 3 times.
✓ Branch 75 → 78 taken 21 times.
✓ Branch 77 → 78 taken 3 times.
✗ Branch 77 → 79 not taken.
✓ Branch 80 → 81 taken 24 times.
✗ Branch 80 → 82 not taken.
24 if (caret >= 0 || pasting()) return caret;
1281 }
1282 ✗ if (!text.isEmpty()) return m_editor.insertPlainText(text);
1283 ✗ return -1;
1284 30 }
1285
1286 ✗ void ComposeSession::placeImage(const QString &attachmentId) {
1287 ✗ const auto item = m_attachments->item(attachmentId);
1288 ✗ if (!item || !item->image || !m_formatted) return;
1289 ✗ QImage image(item->sentPath());
1290 ✗ const QString resource = ImageScheme + item->id;
1291 ✗ m_editor.addImageResource(resource, image);
1292 ✗ m_editor.insertImage(resource, image.isNull() ? 0 : qMin(image.width(), 640), item->name);
1293 ✗ m_attachments->setInline(attachmentId, true);
1294 ✗ }
1295