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.3% 874 / 0 / 968
Functions: 92.1% 93 / 0 / 101
Branches: 77.0% 1202 / 0 / 1562

presentation/RichTextController.cpp
Line Branch Exec Source
1 #include "RichTextController.h"
2
3 #include "data/ComposeDocumentFormat.h"
4 #include "data/MailTextWriter.h"
5
6 #include <QImage>
7 #include <QQuickTextDocument>
8 #include <QTextBlock>
9 #include <QTextDocument>
10 #include <QTextDocumentFragment>
11 #include <QTextList>
12 #include <QTextTable>
13 #include <QRegularExpression>
14 #include <QTimer>
15 #include <QUrl>
16
17 using namespace ComposeDocumentFormat;
18
19 namespace {
20
21 constexpr qreal QuoteDisplayMargin = 18.0;
22
23 8 QTextListFormat::Style listStyleFor(const QString &name) {
24
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 8 times.
8 if (name == QLatin1String("circle")) return QTextListFormat::ListCircle;
25
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 8 times.
8 if (name == QLatin1String("square")) return QTextListFormat::ListSquare;
26
2/2
✓ Branch 12 → 13 taken 3 times.
✓ Branch 12 → 14 taken 5 times.
8 if (name == QLatin1String("decimal")) return QTextListFormat::ListDecimal;
27
1/2
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 5 times.
5 if (name == QLatin1String("lower-alpha")) return QTextListFormat::ListLowerAlpha;
28
1/2
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 5 times.
5 if (name == QLatin1String("upper-alpha")) return QTextListFormat::ListUpperAlpha;
29
1/2
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 26 taken 5 times.
5 if (name == QLatin1String("lower-roman")) return QTextListFormat::ListLowerRoman;
30
1/2
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 5 times.
5 if (name == QLatin1String("upper-roman")) return QTextListFormat::ListUpperRoman;
31 5 return QTextListFormat::ListDisc;
32 }
33
34 86 QString listStyleName(QTextListFormat::Style style) {
35
2/8
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 8 not taken.
✓ Branch 2 → 13 taken 1 time.
✗ Branch 2 → 18 not taken.
✗ Branch 2 → 23 not taken.
✗ Branch 2 → 28 not taken.
✗ Branch 2 → 33 not taken.
✓ Branch 2 → 38 taken 85 times.
86 switch (style) {
36 ✗ case QTextListFormat::ListCircle: return QStringLiteral("circle");
37 ✗ case QTextListFormat::ListSquare: return QStringLiteral("square");
38 2 case QTextListFormat::ListDecimal: return QStringLiteral("decimal");
39 ✗ case QTextListFormat::ListLowerAlpha: return QStringLiteral("lower-alpha");
40 ✗ case QTextListFormat::ListUpperAlpha: return QStringLiteral("upper-alpha");
41 ✗ case QTextListFormat::ListLowerRoman: return QStringLiteral("lower-roman");
42 ✗ case QTextListFormat::ListUpperRoman: return QStringLiteral("upper-roman");
43 170 default: return QStringLiteral("disc");
44 }
45 }
46
47 22 bool numbered(QTextListFormat::Style style) {
48
2/4
✓ Branch 3 → 4 taken 10 times.
✗ Branch 3 → 7 not taken.
✓ Branch 4 → 5 taken 10 times.
✗ Branch 4 → 7 not taken.
10 return style == QTextListFormat::ListDecimal || style == QTextListFormat::ListLowerAlpha ||
49
4/6
✓ Branch 2 → 3 taken 10 times.
✓ Branch 2 → 7 taken 12 times.
✓ Branch 5 → 6 taken 10 times.
✗ Branch 5 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 10 times.
32 style == QTextListFormat::ListUpperAlpha || style == QTextListFormat::ListLowerRoman ||
50 22 style == QTextListFormat::ListUpperRoman;
51 }
52
53 // Nested lists change their marker per level, as mail clients show them.
54 3 QTextListFormat::Style nestedStyle(QTextListFormat::Style style, int level) {
55
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 7 taken 2 times.
3 if (numbered(style)) {
56 static const QTextListFormat::Style levels[] = {QTextListFormat::ListDecimal, QTextListFormat::ListLowerAlpha,
57 QTextListFormat::ListLowerRoman};
58
1/1
✓ Branch 4 → 5 taken 1 time.
1 return levels[qBound(1, level, 3) - 1];
59 }
60 static const QTextListFormat::Style levels[] = {QTextListFormat::ListDisc, QTextListFormat::ListCircle,
61 QTextListFormat::ListSquare};
62
1/1
✓ Branch 7 → 8 taken 2 times.
2 return levels[qBound(1, level, 3) - 1];
63 }
64
65 6 QString normalizedLink(QString url) {
66
1/1
✓ Branch 2 → 3 taken 6 times.
6 url = url.trimmed();
67
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 6 times.
6 if (url.isEmpty()) return {};
68
4/7
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 21 taken 4 times.
✓ Branch 10 → 11 taken 2 times.
✗ Branch 10 → 21 not taken.
✓ Branch 15 → 16 taken 2 times.
✗ Branch 65 → 66 not taken.
✗ Branch 65 → 67 not taken.
8 static const QRegularExpression scheme(QStringLiteral("^[A-Za-z][A-Za-z0-9+.-]*:"));
69
4/4
✓ Branch 22 → 23 taken 6 times.
✓ Branch 23 → 24 taken 6 times.
✓ Branch 25 → 26 taken 2 times.
✓ Branch 25 → 55 taken 4 times.
6 if (!scheme.match(url).hasMatch()) {
70
8/9
✓ Branch 28 → 29 taken 2 times.
✓ Branch 29 → 30 taken 1 time.
✓ Branch 29 → 35 taken 1 time.
✓ Branch 32 → 33 taken 1 time.
✓ Branch 33 → 34 taken 1 time.
✗ Branch 33 → 35 not taken.
✓ Branch 36 → 37 taken 1 time.
✓ Branch 36 → 46 taken 1 time.
✓ Branch 40 → 41 taken 1 time.
3 if (url.contains(QLatin1Char('@')) && !url.contains(QLatin1Char('/'))) url = QStringLiteral("mailto:") + url;
71
1/1
✓ Branch 49 → 50 taken 1 time.
1 else url = QStringLiteral("https://") + url;
72 }
73 6 return url;
74 }
75
76 } // namespace
77
78
1/1
✓ Branch 5 → 6 taken 220 times.
220 RichTextController::RichTextController(QObject *parent) : QObject(parent) {}
79
80 ✗ void RichTextController::setTextDocumentObject(QObject *document) {
81 ✗ if (m_quickDocument == document) return;
82 ✗ m_quickDocument = document;
83 ✗ auto *quick = qobject_cast<QQuickTextDocument *>(document);
84 ✗ setDocument(quick ? quick->textDocument() : nullptr);
85 ✗ emit textDocumentChanged();
86 }
87
88 374 void RichTextController::setDocument(QTextDocument *document) {
89
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 374 times.
374 if (m_document == document) return;
90
2/2
✓ Branch 6 → 7 taken 155 times.
✓ Branch 6 → 9 taken 219 times.
374 if (m_document) disconnect(m_document, nullptr, this, nullptr);
91 374 m_document = document;
92 374 m_pendingValid = false;
93
2/2
✓ Branch 11 → 12 taken 219 times.
✓ Branch 11 → 18 taken 155 times.
374 if (m_document) {
94 // QTextDocument reports contentsChange only once it has a layout (the
95 // TextEdit's has one; a bare document creates it here).
96 219 (void)m_document->documentLayout();
97
1/1
✓ Branch 15 → 16 taken 219 times.
219 connect(m_document, &QTextDocument::contentsChange, this, &RichTextController::onContentsChange);
98 }
99 374 emitState();
100 }
101
102 ✗ void RichTextController::setCursorPosition(int position) {
103 ✗ if (m_cursorPosition == position) return;
104 ✗ m_cursorPosition = position;
105 // A pending caret format belongs to the place where it was chosen.
106 ✗ if (m_pendingValid && position != m_pendingPosition) m_pendingValid = false;
107 ✗ emit selectionChanged();
108 ✗ emitState();
109 }
110
111 ✗ void RichTextController::setSelectionStart(int position) {
112 ✗ if (m_selectionStart == position) return;
113 ✗ m_selectionStart = position;
114 ✗ emit selectionChanged();
115 ✗ emitState();
116 }
117
118 ✗ void RichTextController::setSelectionEnd(int position) {
119 ✗ if (m_selectionEnd == position) return;
120 ✗ m_selectionEnd = position;
121 ✗ emit selectionChanged();
122 ✗ emitState();
123 }
124
125 1567 void RichTextController::setSelection(int start, int end, int cursor) {
126 1567 m_selectionStart = qMin(start, end);
127 1567 m_selectionEnd = qMax(start, end);
128 1567 m_cursorPosition = cursor;
129
4/4
✓ Branch 4 → 5 taken 36 times.
✓ Branch 4 → 7 taken 1531 times.
✓ Branch 5 → 6 taken 8 times.
✓ Branch 5 → 7 taken 28 times.
1567 if (m_pendingValid && cursor != m_pendingPosition) m_pendingValid = false;
130 1567 emit selectionChanged();
131 1567 emitState();
132 1567 }
133
134 620 void RichTextController::setFormatted(bool formatted) {
135
2/2
✓ Branch 2 → 3 taken 544 times.
✓ Branch 2 → 4 taken 76 times.
620 if (m_formatted == formatted) return;
136 76 m_formatted = formatted;
137 76 m_pendingValid = false;
138 76 emit formattedChanged();
139 76 emitState();
140 }
141
142 154 void RichTextController::setMarkdownTyping(bool on) {
143
2/2
✓ Branch 2 → 3 taken 153 times.
✓ Branch 2 → 4 taken 1 time.
154 if (m_markdownTyping == on) return;
144 1 m_markdownTyping = on;
145 1 emit markdownTypingChanged();
146 }
147
148 3 void RichTextController::setComposing(bool composing) {
149
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 3 times.
3 if (m_composing == composing) return;
150 3 m_composing = composing;
151 3 emit composingChanged();
152 }
153
154 80 bool RichTextController::refuseInPlainMode() {
155
5/6
✓ Branch 2 → 3 taken 77 times.
✓ Branch 2 → 6 taken 3 times.
✓ Branch 4 → 5 taken 77 times.
✗ Branch 4 → 6 not taken.
✓ Branch 7 → 8 taken 77 times.
✓ Branch 7 → 9 taken 3 times.
80 if (m_formatted && m_document) return false;
156 3 emit formattingRefused();
157 3 return true;
158 }
159
160 545 QTextCursor RichTextController::selectionCursor() const {
161
1/1
✓ Branch 3 → 4 taken 545 times.
545 QTextCursor cursor(m_document);
162
1/1
✓ Branch 5 → 6 taken 545 times.
545 const int last = qMax(0, m_document->characterCount() - 1);
163
2/2
✓ Branch 8 → 9 taken 97 times.
✓ Branch 8 → 14 taken 448 times.
545 if (hasSelection()) {
164
2/2
✓ Branch 9 → 10 taken 97 times.
✓ Branch 10 → 11 taken 97 times.
97 cursor.setPosition(qBound(0, m_selectionStart, last));
165
2/2
✓ Branch 11 → 12 taken 97 times.
✓ Branch 12 → 13 taken 97 times.
97 cursor.setPosition(qBound(0, m_selectionEnd, last), QTextCursor::KeepAnchor);
166 } else {
167
2/2
✓ Branch 14 → 15 taken 448 times.
✓ Branch 15 → 16 taken 448 times.
448 cursor.setPosition(qBound(0, m_cursorPosition, last));
168 }
169 545 return cursor;
170 ✗ }
171
172 509 QTextCharFormat RichTextController::formatAtCaret() const {
173
1/3
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 509 times.
✗ Branch 4 → 5 not taken.
509 if (!m_document) return {};
174
8/9
✓ Branch 7 → 8 taken 408 times.
✓ Branch 7 → 11 taken 101 times.
✓ Branch 8 → 9 taken 19 times.
✓ Branch 8 → 11 taken 389 times.
✓ Branch 9 → 10 taken 19 times.
✗ Branch 9 → 11 not taken.
✓ Branch 12 → 13 taken 19 times.
✓ Branch 12 → 15 taken 490 times.
✓ Branch 13 → 14 taken 19 times.
509 if (!hasSelection() && m_pendingValid && m_pendingPosition == m_cursorPosition) return m_pendingFormat;
175
1/1
✓ Branch 16 → 17 taken 490 times.
490 QTextCursor cursor(m_document);
176
1/1
✓ Branch 18 → 19 taken 490 times.
490 const int last = qMax(0, m_document->characterCount() - 1);
177
3/3
✓ Branch 21 → 22 taken 101 times.
✓ Branch 21 → 24 taken 389 times.
✓ Branch 24 → 25 taken 389 times.
490 const int position = hasSelection() ? qMin(m_selectionStart + 1, last) : qBound(0, m_cursorPosition, last);
178
1/1
✓ Branch 26 → 27 taken 490 times.
490 cursor.setPosition(position);
179 // At a block start the caret takes the format of the character after it.
180
10/10
✓ Branch 28 → 29 taken 389 times.
✓ Branch 28 → 34 taken 101 times.
✓ Branch 29 → 30 taken 389 times.
✓ Branch 30 → 31 taken 101 times.
✓ Branch 30 → 34 taken 288 times.
✓ Branch 31 → 32 taken 101 times.
✓ Branch 32 → 33 taken 2 times.
✓ Branch 32 → 34 taken 99 times.
✓ Branch 35 → 36 taken 2 times.
✓ Branch 35 → 42 taken 488 times.
490 if (!hasSelection() && cursor.atBlockStart() && !cursor.atBlockEnd()) {
181
1/1
✓ Branch 36 → 37 taken 2 times.
2 QTextCursor next(cursor);
182
1/1
✓ Branch 37 → 38 taken 2 times.
2 next.movePosition(QTextCursor::NextCharacter);
183
1/1
✓ Branch 38 → 39 taken 2 times.
2 return next.charFormat();
184 2 }
185
1/1
✓ Branch 42 → 43 taken 488 times.
488 return cursor.charFormat();
186 490 }
187
188 37 void RichTextController::changeCharFormat(const std::function<void(QTextCharFormat &)> &change) {
189
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 37 times.
45 if (!m_document) return;
190
2/2
✓ Branch 6 → 7 taken 8 times.
✓ Branch 6 → 19 taken 29 times.
37 if (!hasSelection()) {
191
1/1
✓ Branch 7 → 8 taken 8 times.
8 QTextCharFormat format = formatAtCaret();
192
2/5
✓ Branch 8 → 9 taken 8 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 14 taken 8 times.
✗ Branch 10 → 11 not taken.
✗ Branch 11 → 12 not taken.
8 if (format.isImageFormat()) format = QTextCharFormat();
193
1/1
✓ Branch 14 → 15 taken 8 times.
8 change(format);
194
1/1
✓ Branch 15 → 16 taken 8 times.
8 m_pendingFormat = format;
195 8 m_pendingValid = true;
196 8 m_pendingPosition = m_cursorPosition;
197
1/1
✓ Branch 16 → 17 taken 8 times.
8 emitState();
198 8 return;
199 8 }
200 struct Span { int start, end; QTextCharFormat format; };
201 29 QList<Span> spans;
202 29 const int start = m_selectionStart, end = m_selectionEnd;
203
10/10
✓ Branch 20 → 21 taken 29 times.
✓ Branch 51 → 52 taken 29 times.
✓ Branch 54 → 55 taken 58 times.
✓ Branch 55 → 56 taken 35 times.
✓ Branch 55 → 59 taken 23 times.
✓ Branch 56 → 57 taken 35 times.
✓ Branch 57 → 58 taken 29 times.
✓ Branch 57 → 59 taken 6 times.
✓ Branch 60 → 22 taken 29 times.
✓ Branch 60 → 61 taken 29 times.
58 for (QTextBlock block = m_document->findBlock(start); block.isValid() && block.position() < end; block = block.next()) {
204
4/4
✓ Branch 22 → 23 taken 29 times.
✓ Branch 48 → 49 taken 47 times.
✓ Branch 50 → 24 taken 47 times.
✓ Branch 50 → 51 taken 29 times.
76 for (auto it = block.begin(); !it.atEnd(); ++it) {
205
1/1
✓ Branch 24 → 25 taken 47 times.
47 const QTextFragment fragment = it.fragment();
206
1/2
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 28 taken 47 times.
47 if (!fragment.isValid()) continue;
207
1/1
✓ Branch 28 → 29 taken 47 times.
47 const int from = qMax(fragment.position(), start);
208
2/2
✓ Branch 30 → 31 taken 47 times.
✓ Branch 31 → 32 taken 47 times.
47 const int to = qMin(fragment.position() + fragment.length(), end);
209
10/13
✓ Branch 33 → 34 taken 31 times.
✓ Branch 33 → 38 taken 16 times.
✓ Branch 34 → 35 taken 31 times.
✓ Branch 35 → 36 taken 31 times.
✓ Branch 36 → 37 taken 31 times.
✗ Branch 36 → 38 not taken.
✓ Branch 39 → 40 taken 31 times.
✓ Branch 39 → 41 taken 16 times.
✓ Branch 41 → 42 taken 31 times.
✓ Branch 41 → 46 taken 16 times.
✓ Branch 43 → 44 taken 31 times.
✗ Branch 93 → 94 not taken.
✗ Branch 93 → 95 not taken.
78 if (from < to && !fragment.charFormat().isImageFormat()) spans.append({from, to, fragment.charFormat()});
210 }
211 }
212 29 m_applying = true;
213
1/1
✓ Branch 62 → 63 taken 29 times.
29 QTextCursor edit(m_document);
214
1/1
✓ Branch 63 → 64 taken 29 times.
29 edit.beginEditBlock();
215
4/4
✓ Branch 64 → 65 taken 29 times.
✓ Branch 65 → 66 taken 29 times.
✓ Branch 77 → 67 taken 31 times.
✓ Branch 77 → 78 taken 29 times.
60 for (Span &span : spans) {
216
1/1
✓ Branch 68 → 69 taken 31 times.
31 change(span.format);
217
1/1
✓ Branch 70 → 71 taken 31 times.
31 QTextCursor cursor(m_document);
218
1/1
✓ Branch 71 → 72 taken 31 times.
31 cursor.setPosition(span.start);
219
1/1
✓ Branch 72 → 73 taken 31 times.
31 cursor.setPosition(span.end, QTextCursor::KeepAnchor);
220
1/1
✓ Branch 73 → 74 taken 31 times.
31 cursor.setCharFormat(span.format);
221 31 }
222
1/1
✓ Branch 78 → 79 taken 29 times.
29 edit.endEditBlock();
223 29 m_applying = false;
224
1/1
✓ Branch 79 → 80 taken 29 times.
29 emitState();
225 60 }
226
227 26 void RichTextController::toggleCharProperty(const std::function<bool(const QTextCharFormat &)> &isOn,
228 const std::function<void(QTextCharFormat &, bool)> &set,
229 const QString &name) {
230
3/3
✓ Branch 2 → 3 taken 26 times.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 25 times.
26 if (refuseInPlainMode()) return;
231
2/2
✓ Branch 5 → 6 taken 25 times.
✓ Branch 6 → 7 taken 25 times.
25 const bool on = !isOn(formatAtCaret());
232
1/1
✓ Branch 9 → 10 taken 25 times.
51 changeCharFormat([&](QTextCharFormat &format) { set(format, on); });
233
11/15
✓ Branch 11 → 12 taken 23 times.
✓ Branch 11 → 15 taken 2 times.
✓ Branch 12 → 13 taken 23 times.
✓ Branch 14 → 18 taken 23 times.
✓ Branch 15 → 16 taken 2 times.
✓ Branch 17 → 18 taken 2 times.
✓ Branch 18 → 19 taken 25 times.
✓ Branch 20 → 21 taken 2 times.
✓ Branch 20 → 22 taken 23 times.
✓ Branch 22 → 23 taken 23 times.
✓ Branch 22 → 24 taken 2 times.
✗ Branch 37 → 38 not taken.
✗ Branch 37 → 39 not taken.
✗ Branch 42 → 43 not taken.
✗ Branch 42 → 44 not taken.
25 emit announcement(on ? tr("%1 on").arg(name) : tr("%1 off").arg(name));
234 }
235
236 20 void RichTextController::toggleBold() {
237
1/1
✓ Branch 5 → 6 taken 20 times.
39 toggleCharProperty([](const QTextCharFormat &f) { return f.fontWeight() >= QFont::Bold; },
238
2/2
✓ Branch 2 → 3 taken 38 times.
✓ Branch 2 → 4 taken 2 times.
60 [](QTextCharFormat &f, bool on) { f.setFontWeight(on ? QFont::Bold : QFont::Normal); }, tr("Bold"));
239 20 }
240
241 2 void RichTextController::toggleItalic() {
242
1/1
✓ Branch 5 → 6 taken 2 times.
4 toggleCharProperty([](const QTextCharFormat &f) { return f.fontItalic(); },
243
1/1
✓ Branch 2 → 3 taken 2 times.
6 [](QTextCharFormat &f, bool on) { f.setFontItalic(on); }, tr("Italic"));
244 2 }
245
246 1 void RichTextController::toggleUnderline() {
247
1/1
✓ Branch 5 → 6 taken 1 time.
2 toggleCharProperty([](const QTextCharFormat &f) { return f.fontUnderline(); },
248
1/1
✓ Branch 2 → 3 taken 1 time.
3 [](QTextCharFormat &f, bool on) { f.setFontUnderline(on); }, tr("Underline"));
249 1 }
250
251 1 void RichTextController::toggleStrikeOut() {
252
1/1
✓ Branch 5 → 6 taken 1 time.
2 toggleCharProperty([](const QTextCharFormat &f) { return f.fontStrikeOut(); },
253
1/1
✓ Branch 2 → 3 taken 1 time.
3 [](QTextCharFormat &f, bool on) { f.setFontStrikeOut(on); }, tr("Strike-through"));
254 1 }
255
256 1 void RichTextController::toggleSuperscript() {
257
1/1
✓ Branch 5 → 6 taken 1 time.
1 toggleCharProperty(
258 2 [](const QTextCharFormat &f) { return f.verticalAlignment() == QTextCharFormat::AlignSuperScript; },
259 ✗ [](QTextCharFormat &f, bool on) {
260
1/2
✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 4 not taken.
1 f.setVerticalAlignment(on ? QTextCharFormat::AlignSuperScript : QTextCharFormat::AlignNormal);
261 1 },
262
1/1
✓ Branch 2 → 3 taken 1 time.
2 tr("Superscript"));
263 1 }
264
265 ✗ void RichTextController::toggleSubscript() {
266 ✗ toggleCharProperty(
267 ✗ [](const QTextCharFormat &f) { return f.verticalAlignment() == QTextCharFormat::AlignSubScript; },
268 ✗ [](QTextCharFormat &f, bool on) {
269 ✗ f.setVerticalAlignment(on ? QTextCharFormat::AlignSubScript : QTextCharFormat::AlignNormal);
270 ✗ },
271 ✗ tr("Subscript"));
272 ✗ }
273
274 1 void RichTextController::toggleCode() {
275
1/1
✓ Branch 5 → 6 taken 1 time.
2 toggleCharProperty([](const QTextCharFormat &f) { return isInlineCode(f); },
276 ✗ [](QTextCharFormat &f, bool on) {
277
1/2
✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 18 not taken.
1 if (on) {
278
1/1
✓ Branch 4 → 5 taken 1 time.
1 f.setProperty(CodeProperty, true);
279 1 f.setFontFixedPitch(true);
280
3/5
✓ Branch 11 → 12 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 16 taken 1 time.
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 30 not taken.
2 f.setFontFamilies({QStringLiteral("monospace")});
281 } else {
282 ✗ f.clearProperty(CodeProperty);
283 ✗ f.clearProperty(QTextFormat::FontFixedPitch);
284 ✗ f.clearProperty(QTextFormat::FontFamilies);
285 }
286
1/5
✓ Branch 10 → 11 taken 1 time.
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 38 not taken.
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 38 not taken.
3 },
287
1/1
✓ Branch 2 → 3 taken 1 time.
2 tr("Code"));
288 1 }
289
290 2 void RichTextController::setFontFamily(const QString &key) {
291
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 if (refuseInPlainMode()) return;
292 2 const FontFamily *family = fontFamilyFor(key);
293
1/1
✓ Branch 7 → 8 taken 2 times.
2 changeCharFormat([family](QTextCharFormat &format) {
294
5/7
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 11 taken 1 time.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 1 time.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
4 if (family) format.setFontFamilies({family->family});
295 1 else format.clearProperty(QTextFormat::FontFamilies);
296
1/5
✓ Branch 4 → 5 taken 1 time.
✗ Branch 19 → 20 not taken.
✗ Branch 19 → 23 not taken.
✗ Branch 21 → 22 not taken.
✗ Branch 21 → 23 not taken.
3 });
297 }
298
299 2 void RichTextController::setFontPointSize(int points) {
300
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 if (refuseInPlainMode()) return;
301
1/1
✓ Branch 6 → 7 taken 2 times.
2 changeCharFormat([points](QTextCharFormat &format) {
302
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 1 time.
2 if (points <= 0) format.clearProperty(QTextFormat::FontPointSize);
303 1 else format.setFontPointSize(qBound(FontPointSizeMin, points, FontPointSizeMax));
304 2 });
305 }
306
307 5 void RichTextController::setTextColor(const QString &color) {
308
3/3
✓ Branch 2 → 3 taken 5 times.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 4 times.
5 if (refuseInPlainMode()) return;
309 4 const QColor value(color);
310
1/1
✓ Branch 7 → 8 taken 4 times.
4 changeCharFormat([value](QTextCharFormat &format) {
311
4/4
✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 8 taken 1 time.
✓ Branch 4 → 5 taken 3 times.
✓ Branch 5 → 6 taken 3 times.
4 if (value.isValid()) format.setForeground(value);
312 1 else format.clearForeground();
313 4 });
314 }
315
316 2 void RichTextController::setHighlightColor(const QString &color) {
317
2/3
✓ Branch 2 → 3 taken 2 times.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 if (refuseInPlainMode()) return;
318 2 const QColor value(color);
319
1/1
✓ Branch 7 → 8 taken 2 times.
2 changeCharFormat([value](QTextCharFormat &format) {
320
4/4
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 8 taken 1 time.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 5 → 6 taken 1 time.
2 if (value.isValid()) format.setBackground(value);
321 1 else format.clearBackground();
322 2 });
323 }
324
325 1 void RichTextController::clearFormatting() {
326
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (refuseInPlainMode()) return;
327 // Links stay links; everything else returns to the default.
328
1/1
✓ Branch 6 → 7 taken 1 time.
1 changeCharFormat([](QTextCharFormat &format) {
329
1/1
✓ Branch 2 → 3 taken 2 times.
2 QTextCharFormat clean;
330
3/3
✓ Branch 3 → 4 taken 2 times.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 10 taken 1 time.
2 if (format.isAnchor()) {
331
1/1
✓ Branch 5 → 6 taken 1 time.
1 clean.setAnchor(true);
332
2/2
✓ Branch 6 → 7 taken 1 time.
✓ Branch 7 → 8 taken 1 time.
1 clean.setAnchorHref(format.anchorHref());
333 }
334
1/1
✓ Branch 10 → 11 taken 2 times.
2 format = clean;
335 2 });
336
1/1
✓ Branch 9 → 10 taken 1 time.
1 changeBlocks([](QTextCursor &cursor, QTextBlock block) {
337
1/1
✓ Branch 2 → 3 taken 1 time.
1 QTextBlockFormat format = block.blockFormat();
338
1/1
✓ Branch 3 → 4 taken 1 time.
1 format.setHeadingLevel(0);
339
1/1
✓ Branch 4 → 5 taken 1 time.
1 format.clearProperty(QTextFormat::BlockCodeFence);
340
1/1
✓ Branch 5 → 6 taken 1 time.
1 format.clearProperty(QTextFormat::BlockAlignment);
341
1/1
✓ Branch 6 → 7 taken 1 time.
1 cursor.setBlockFormat(format);
342 1 });
343
2/2
✓ Branch 11 → 12 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
1 emit announcement(tr("Formatting cleared"));
344 }
345
346 12 void RichTextController::changeBlocks(const std::function<void(QTextCursor &, QTextBlock)> &change) {
347
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 12 times.
12 if (!m_document) return;
348
1/1
✓ Branch 5 → 6 taken 12 times.
12 const QTextCursor selection = selectionCursor();
349
2/2
✓ Branch 7 → 8 taken 12 times.
✓ Branch 8 → 9 taken 12 times.
12 const QTextBlock first = m_document->findBlock(selection.selectionStart());
350
2/2
✓ Branch 10 → 11 taken 12 times.
✓ Branch 11 → 12 taken 12 times.
12 const QTextBlock last = m_document->findBlock(selection.selectionEnd());
351 12 QList<QTextBlock> blocks;
352
2/4
✗ Branch 17 → 18 not taken.
✓ Branch 20 → 21 taken 12 times.
✓ Branch 21 → 13 taken 12 times.
✗ Branch 21 → 22 not taken.
12 for (QTextBlock block = first; block.isValid(); block = block.next()) {
353
1/1
✓ Branch 13 → 14 taken 12 times.
12 blocks.append(block);
354
1/2
✓ Branch 15 → 16 taken 12 times.
✗ Branch 15 → 17 not taken.
12 if (block == last) break;
355 }
356 12 m_applying = true;
357
1/1
✓ Branch 23 → 24 taken 12 times.
12 QTextCursor edit(m_document);
358
1/1
✓ Branch 24 → 25 taken 12 times.
12 edit.beginEditBlock();
359
2/2
✓ Branch 35 → 28 taken 12 times.
✓ Branch 35 → 36 taken 12 times.
24 for (const QTextBlock &block : std::as_const(blocks)) {
360
1/1
✓ Branch 29 → 30 taken 12 times.
12 QTextCursor cursor(block);
361
1/1
✓ Branch 31 → 32 taken 12 times.
12 change(cursor, block);
362 12 }
363
1/1
✓ Branch 36 → 37 taken 12 times.
12 edit.endEditBlock();
364 12 m_applying = false;
365
1/1
✓ Branch 37 → 38 taken 12 times.
12 emitState();
366 12 }
367
368 2 void RichTextController::setParagraphStyle(const QString &style) {
369
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 if (refuseInPlainMode()) return;
370
3/4
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 1 time.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 1 time.
3 const int heading = style == QLatin1String("h1") ? 1 : style == QLatin1String("h2") ? 2
371
1/2
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 1 time.
1 : style == QLatin1String("h3") ? 3 : 0;
372 2 const bool code = style == QLatin1String("code");
373
1/1
✓ Branch 23 → 24 taken 2 times.
2 changeBlocks([heading, code](QTextCursor &cursor, QTextBlock block) {
374
2/8
✓ Branch 2 → 3 taken 2 times.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 7 taken 2 times.
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 6 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 7 not taken.
✗ Branch 6 → 7 not taken.
2 if (QTextList *list = block.textList(); list && (heading > 0 || code)) list->remove(block);
375
1/1
✓ Branch 7 → 8 taken 2 times.
2 QTextBlockFormat format = cursor.blockFormat();
376
1/1
✓ Branch 8 → 9 taken 2 times.
2 format.setHeadingLevel(heading);
377
3/3
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 15 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
2 if (code) format.setProperty(QTextFormat::BlockCodeFence, QVariant(QChar(u'`')));
378
1/1
✓ Branch 15 → 16 taken 1 time.
1 else format.clearProperty(QTextFormat::BlockCodeFence);
379
1/1
✓ Branch 16 → 17 taken 2 times.
2 format.clearProperty(QTextFormat::ObjectIndex);
380
3/12
✓ Branch 17 → 18 taken 2 times.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 22 taken 2 times.
✗ Branch 19 → 20 not taken.
✗ Branch 19 → 22 not taken.
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 22 not taken.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 27 taken 2 times.
✗ Branch 24 → 25 not taken.
✗ Branch 25 → 26 not taken.
✗ Branch 26 → 27 not taken.
2 if (block.textList() && heading == 0 && !code) format.setObjectIndex(block.textList()->objectIndex());
381
1/1
✓ Branch 27 → 28 taken 2 times.
2 cursor.setBlockFormat(format);
382 2 });
383
9/13
✓ Branch 25 → 26 taken 1 time.
✓ Branch 25 → 27 taken 1 time.
✓ Branch 26 → 32 taken 1 time.
✓ Branch 27 → 28 taken 1 time.
✗ Branch 27 → 31 not taken.
✓ Branch 28 → 29 taken 1 time.
✓ Branch 30 → 32 taken 1 time.
✗ Branch 31 → 32 not taken.
✓ Branch 32 → 33 taken 2 times.
✓ Branch 34 → 35 taken 1 time.
✓ Branch 34 → 36 taken 1 time.
✗ Branch 45 → 46 not taken.
✗ Branch 45 → 47 not taken.
2 emit announcement(code ? tr("Code block") : heading > 0 ? tr("Heading %1").arg(heading) : tr("Paragraph"));
384 }
385
386 ✗ QTextList *RichTextController::listAt(const QTextBlock &block) const {
387 ✗ return block.isValid() ? block.textList() : nullptr;
388 }
389
390 9 void RichTextController::toggleList(const QString &style) {
391
3/3
✓ Branch 2 → 3 taken 9 times.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 8 times.
9 if (refuseInPlainMode()) return;
392 8 const QTextListFormat::Style wanted = listStyleFor(style);
393
1/1
✓ Branch 6 → 7 taken 8 times.
8 const QTextCursor selection = selectionCursor();
394
2/2
✓ Branch 8 → 9 taken 8 times.
✓ Branch 9 → 10 taken 8 times.
8 const QTextBlock first = m_document->findBlock(selection.selectionStart());
395
2/2
✓ Branch 11 → 12 taken 8 times.
✓ Branch 12 → 13 taken 8 times.
8 const QTextBlock last = m_document->findBlock(selection.selectionEnd());
396 8 bool allWanted = true;
397
1/1
✓ Branch 13 → 14 taken 8 times.
8 QTextList *common = first.textList();
398
3/4
✓ Branch 31 → 32 taken 9 times.
✓ Branch 34 → 35 taken 17 times.
✓ Branch 35 → 15 taken 17 times.
✗ Branch 35 → 36 not taken.
17 for (QTextBlock block = first; block.isValid(); block = block.next()) {
399
1/1
✓ Branch 15 → 16 taken 17 times.
17 QTextList *list = block.textList();
400
8/10
✓ Branch 16 → 17 taken 6 times.
✓ Branch 16 → 23 taken 11 times.
✓ Branch 17 → 18 taken 6 times.
✓ Branch 18 → 19 taken 6 times.
✓ Branch 21 → 22 taken 3 times.
✓ Branch 21 → 23 taken 3 times.
✓ Branch 24 → 25 taken 6 times.
✓ Branch 24 → 26 taken 11 times.
✗ Branch 109 → 110 not taken.
✗ Branch 109 → 111 not taken.
17 allWanted &= list && numbered(list->format().style()) == numbered(wanted);
401
1/2
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 28 taken 17 times.
17 if (list != common) common = nullptr;
402
2/2
✓ Branch 29 → 30 taken 8 times.
✓ Branch 29 → 31 taken 9 times.
17 if (block == last) break;
403 }
404 8 m_applying = true;
405
1/1
✓ Branch 37 → 38 taken 8 times.
8 QTextCursor edit(m_document);
406
1/1
✓ Branch 38 → 39 taken 8 times.
8 edit.beginEditBlock();
407
2/2
✓ Branch 39 → 40 taken 1 time.
✓ Branch 39 → 69 taken 7 times.
8 if (allWanted) {
408
3/4
✓ Branch 59 → 61 taken 2 times.
✓ Branch 63 → 64 taken 3 times.
✓ Branch 64 → 41 taken 3 times.
✗ Branch 64 → 65 not taken.
3 for (QTextBlock block = first; block.isValid(); block = block.next()) {
409
3/4
✓ Branch 41 → 42 taken 3 times.
✓ Branch 42 → 43 taken 3 times.
✗ Branch 42 → 44 not taken.
✓ Branch 43 → 44 taken 3 times.
3 if (QTextList *list = block.textList()) list->remove(block);
410
1/1
✓ Branch 44 → 45 taken 3 times.
3 QTextCursor cursor(block);
411
1/1
✓ Branch 45 → 46 taken 3 times.
3 QTextBlockFormat format = block.blockFormat();
412
1/1
✓ Branch 46 → 47 taken 3 times.
3 format.setIndent(0);
413
1/1
✓ Branch 47 → 48 taken 3 times.
3 cursor.setBlockFormat(format);
414
2/2
✓ Branch 49 → 50 taken 1 time.
✓ Branch 49 → 51 taken 2 times.
3 if (block == last) break;
415 4 }
416
2/2
✓ Branch 65 → 66 taken 1 time.
✓ Branch 66 → 67 taken 1 time.
1 emit announcement(tr("List removed"));
417
2/2
✓ Branch 69 → 70 taken 1 time.
✓ Branch 69 → 83 taken 6 times.
7 } else if (common) {
418 // One list in another style: the whole list changes its style.
419
1/1
✓ Branch 70 → 71 taken 1 time.
1 QTextListFormat format = common->format();
420
3/3
✓ Branch 71 → 72 taken 1 time.
✓ Branch 72 → 73 taken 1 time.
✓ Branch 73 → 74 taken 1 time.
1 format.setStyle(nestedStyle(wanted, format.indent()));
421
1/1
✓ Branch 74 → 75 taken 1 time.
1 common->setFormat(format);
422
3/5
✓ Branch 76 → 77 taken 1 time.
✗ Branch 76 → 78 not taken.
✓ Branch 77 → 79 taken 1 time.
✗ Branch 78 → 79 not taken.
✓ Branch 79 → 80 taken 1 time.
1 emit announcement(numbered(wanted) ? tr("Numbered list") : tr("Bulleted list"));
423 1 } else {
424
1/1
✓ Branch 83 → 84 taken 6 times.
6 QTextListFormat format;
425
1/1
✓ Branch 84 → 85 taken 6 times.
6 format.setStyle(wanted);
426
1/1
✓ Branch 85 → 86 taken 6 times.
6 format.setIndent(1);
427
1/1
✓ Branch 86 → 87 taken 6 times.
6 QTextCursor cursor = selection;
428
1/1
✓ Branch 87 → 88 taken 6 times.
6 QTextBlockFormat plain = cursor.blockFormat();
429
1/1
✓ Branch 88 → 89 taken 6 times.
6 plain.setHeadingLevel(0);
430
1/1
✓ Branch 89 → 90 taken 6 times.
6 plain.clearProperty(QTextFormat::BlockCodeFence);
431
1/1
✓ Branch 90 → 91 taken 6 times.
6 cursor.mergeBlockFormat(plain);
432
1/1
✓ Branch 91 → 92 taken 6 times.
6 cursor.createList(format);
433
5/5
✓ Branch 93 → 94 taken 1 time.
✓ Branch 93 → 95 taken 5 times.
✓ Branch 94 → 96 taken 1 time.
✓ Branch 95 → 96 taken 5 times.
✓ Branch 96 → 97 taken 6 times.
6 emit announcement(numbered(wanted) ? tr("Numbered list") : tr("Bulleted list"));
434 6 }
435
1/1
✓ Branch 102 → 103 taken 8 times.
8 edit.endEditBlock();
436 8 m_applying = false;
437
1/1
✓ Branch 103 → 104 taken 8 times.
8 emitState();
438 8 }
439
440 2 void RichTextController::toggleQuote() {
441
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 if (refuseInPlainMode()) return;
442
4/4
✓ Branch 6 → 7 taken 2 times.
✓ Branch 7 → 8 taken 2 times.
✓ Branch 8 → 9 taken 2 times.
✓ Branch 9 → 10 taken 2 times.
2 const bool quoted = quoteLevel(m_document->findBlock(selectionCursor().selectionStart())) > 0;
443
1/1
✓ Branch 12 → 13 taken 2 times.
2 changeBlocks([quoted](QTextCursor &cursor, QTextBlock) {
444
1/1
✓ Branch 2 → 3 taken 2 times.
2 QTextBlockFormat format = cursor.blockFormat();
445
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 6 taken 1 time.
2 if (quoted) {
446
1/1
✓ Branch 4 → 5 taken 1 time.
1 format.clearProperty(QTextFormat::BlockQuoteLevel);
447
1/1
✓ Branch 5 → 10 taken 1 time.
1 format.setLeftMargin(0);
448 } else {
449
1/1
✓ Branch 7 → 8 taken 1 time.
1 format.setProperty(QTextFormat::BlockQuoteLevel, 1);
450
1/1
✓ Branch 9 → 10 taken 1 time.
1 format.setLeftMargin(QuoteDisplayMargin);
451 }
452
1/1
✓ Branch 10 → 11 taken 2 times.
2 cursor.setBlockFormat(format);
453 2 });
454
5/5
✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 16 taken 1 time.
✓ Branch 15 → 17 taken 1 time.
✓ Branch 16 → 17 taken 1 time.
✓ Branch 17 → 18 taken 2 times.
2 emit announcement(quoted ? tr("Quote removed") : tr("Quote"));
455 }
456
457 2 void RichTextController::indent() {
458
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 if (refuseInPlainMode()) return;
459
1/1
✓ Branch 6 → 7 taken 2 times.
2 changeBlocks([](QTextCursor &cursor, QTextBlock block) {
460
1/1
✓ Branch 2 → 3 taken 2 times.
2 QTextList *list = block.textList();
461
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 11 taken 2 times.
2 if (!list) {
462 ✗ QTextBlockFormat format = block.blockFormat();
463 ✗ format.setIndent(qMin(8, format.indent() + 1));
464 ✗ cursor.setBlockFormat(format);
465 ✗ return;
466 ✗ }
467
2/2
✓ Branch 11 → 12 taken 2 times.
✓ Branch 12 → 13 taken 2 times.
2 const int level = list->format().indent();
468
1/2
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 2 times.
2 if (level >= MaxListLevel) return;
469 // Join the nested list right above, else start one.
470
3/5
✓ Branch 16 → 17 taken 2 times.
✗ Branch 35 → 36 not taken.
✓ Branch 38 → 39 taken 2 times.
✓ Branch 39 → 18 taken 2 times.
✗ Branch 39 → 40 not taken.
2 for (QTextBlock previous = block.previous(); previous.isValid(); previous = previous.previous()) {
471
1/1
✓ Branch 18 → 19 taken 2 times.
2 QTextList *candidate = previous.textList();
472
6/12
✓ Branch 19 → 20 taken 2 times.
✗ Branch 19 → 23 not taken.
✓ Branch 20 → 21 taken 2 times.
✓ Branch 21 → 22 taken 2 times.
✓ Branch 22 → 23 taken 2 times.
✗ Branch 22 → 24 not taken.
✓ Branch 25 → 26 taken 2 times.
✗ Branch 25 → 27 not taken.
✓ Branch 27 → 28 taken 2 times.
✗ Branch 27 → 29 not taken.
✗ Branch 59 → 60 not taken.
✗ Branch 59 → 61 not taken.
2 if (!candidate || candidate->format().indent() <= level) break;
473 ✗ if (candidate->format().indent() == level + 1) {
474 ✗ candidate->add(block);
475 ✗ return;
476 }
477 }
478
1/1
✓ Branch 41 → 42 taken 2 times.
2 QTextListFormat format = list->format();
479
1/1
✓ Branch 42 → 43 taken 2 times.
2 format.setIndent(level + 1);
480
3/3
✓ Branch 43 → 44 taken 2 times.
✓ Branch 44 → 45 taken 2 times.
✓ Branch 45 → 46 taken 2 times.
2 format.setStyle(nestedStyle(format.style(), level + 1));
481
1/1
✓ Branch 46 → 47 taken 2 times.
2 cursor.createList(format);
482 2 });
483 }
484
485 2 void RichTextController::outdent() {
486
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 if (refuseInPlainMode()) return;
487
1/1
✓ Branch 6 → 7 taken 2 times.
2 changeBlocks([](QTextCursor &cursor, QTextBlock block) {
488
1/1
✓ Branch 2 → 3 taken 2 times.
2 QTextList *list = block.textList();
489
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 11 taken 2 times.
2 if (!list) {
490 ✗ QTextBlockFormat format = block.blockFormat();
491 ✗ format.setIndent(qMax(0, format.indent() - 1));
492 ✗ cursor.setBlockFormat(format);
493 ✗ return;
494 ✗ }
495
2/2
✓ Branch 11 → 12 taken 2 times.
✓ Branch 12 → 13 taken 2 times.
2 const int level = list->format().indent();
496
1/2
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 21 taken 2 times.
2 if (level <= 1) {
497 ✗ list->remove(block);
498 ✗ QTextBlockFormat format = block.blockFormat();
499 ✗ format.setIndent(0);
500 ✗ cursor.setBlockFormat(format);
501 ✗ return;
502 ✗ }
503
3/5
✓ Branch 21 → 22 taken 2 times.
✗ Branch 32 → 33 not taken.
✓ Branch 35 → 36 taken 2 times.
✓ Branch 36 → 23 taken 2 times.
✗ Branch 36 → 37 not taken.
2 for (QTextBlock previous = block.previous(); previous.isValid(); previous = previous.previous()) {
504
1/1
✓ Branch 23 → 24 taken 2 times.
2 QTextList *candidate = previous.textList();
505
1/2
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 26 taken 2 times.
2 if (!candidate) break;
506
3/4
✓ Branch 26 → 27 taken 2 times.
✓ Branch 27 → 28 taken 2 times.
✓ Branch 29 → 30 taken 2 times.
✗ Branch 29 → 32 not taken.
2 if (candidate->format().indent() == level - 1) {
507
1/1
✓ Branch 30 → 31 taken 2 times.
2 candidate->add(block);
508 2 return;
509 }
510 }
511 ✗ QTextListFormat format = list->format();
512 ✗ format.setIndent(level - 1);
513 ✗ format.setStyle(nestedStyle(format.style(), level - 1));
514 ✗ cursor.createList(format);
515 ✗ });
516 }
517
518 2 void RichTextController::setAlignment(const QString &alignment) {
519
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 if (refuseInPlainMode()) return;
520
2/2
✓ Branch 6 → 7 taken 2 times.
✓ Branch 7 → 8 taken 2 times.
2 changeBlocks([alignment](QTextCursor &cursor, QTextBlock) {
521
1/1
✓ Branch 2 → 3 taken 2 times.
2 QTextBlockFormat format = cursor.blockFormat();
522
3/3
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 9 taken 1 time.
✓ Branch 7 → 8 taken 1 time.
2 if (alignment == QLatin1String("center")) format.setAlignment(Qt::AlignHCenter);
523
1/3
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 15 taken 1 time.
✗ Branch 13 → 14 not taken.
1 else if (alignment == QLatin1String("end")) format.setAlignment(Qt::AlignRight);
524
1/3
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 21 taken 1 time.
✗ Branch 19 → 20 not taken.
1 else if (alignment == QLatin1String("justify")) format.setAlignment(Qt::AlignJustify);
525
1/1
✓ Branch 21 → 22 taken 1 time.
1 else format.clearProperty(QTextFormat::BlockAlignment);
526
1/1
✓ Branch 22 → 23 taken 2 times.
2 cursor.setBlockFormat(format);
527 2 });
528 }
529
530 1 void RichTextController::setDirection(const QString &direction) {
531
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (!m_document) return;
532 // Direction is structure, not formatting: plain-text mails keep it too.
533
2/2
✓ Branch 6 → 7 taken 1 time.
✓ Branch 7 → 8 taken 1 time.
1 changeBlocks([direction](QTextCursor &cursor, QTextBlock) {
534
1/1
✓ Branch 2 → 3 taken 1 time.
1 QTextBlockFormat format = cursor.blockFormat();
535
2/3
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 7 not taken.
✓ Branch 6 → 12 taken 1 time.
1 if (direction == QLatin1String("rtl")) format.setLayoutDirection(Qt::RightToLeft);
536 ✗ else if (direction == QLatin1String("ltr")) format.setLayoutDirection(Qt::LeftToRight);
537 ✗ else format.clearProperty(QTextFormat::LayoutDirection);
538
1/1
✓ Branch 12 → 13 taken 1 time.
1 cursor.setBlockFormat(format);
539 1 });
540 }
541
542 1 void RichTextController::insertHorizontalRule() {
543
2/3
✓ Branch 2 → 3 taken 1 time.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (refuseInPlainMode()) return;
544
1/1
✓ Branch 5 → 6 taken 1 time.
1 QTextCursor cursor = selectionCursor();
545 1 m_applying = true;
546
1/1
✓ Branch 6 → 7 taken 1 time.
1 cursor.beginEditBlock();
547
1/1
✓ Branch 7 → 8 taken 1 time.
1 cursor.removeSelectedText();
548
6/7
✓ Branch 8 → 9 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 19 not taken.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
✓ Branch 15 → 16 taken 1 time.
1 if (!cursor.block().text().isEmpty()) cursor.insertBlock(QTextBlockFormat(), QTextCharFormat());
549
1/1
✓ Branch 19 → 20 taken 1 time.
1 QTextBlockFormat rule;
550
2/2
✓ Branch 21 → 22 taken 1 time.
✓ Branch 22 → 23 taken 1 time.
1 rule.setProperty(QTextFormat::BlockTrailingHorizontalRulerWidth, QTextLength(QTextLength::PercentageLength, 100));
551
1/1
✓ Branch 24 → 25 taken 1 time.
1 cursor.setBlockFormat(rule);
552
3/3
✓ Branch 25 → 26 taken 1 time.
✓ Branch 26 → 27 taken 1 time.
✓ Branch 27 → 28 taken 1 time.
1 cursor.insertBlock(QTextBlockFormat(), QTextCharFormat());
553
1/1
✓ Branch 30 → 31 taken 1 time.
1 cursor.endEditBlock();
554 1 m_applying = false;
555
1/1
✓ Branch 31 → 32 taken 1 time.
1 emitState();
556 1 }
557
558 6 QVariantMap RichTextController::linkAtCursor() const {
559
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 6 times.
6 if (!m_document) return {};
560
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 6 times.
6 const int position = hasSelection() ? m_selectionStart : m_cursorPosition;
561
1/1
✓ Branch 10 → 11 taken 6 times.
6 const QTextBlock block = m_document->findBlock(position);
562
3/3
✓ Branch 11 → 12 taken 6 times.
✓ Branch 107 → 13 taken 9 times.
✓ Branch 107 → 108 taken 3 times.
12 for (auto it = block.begin(); !it.atEnd(); ++it) {
563
1/1
✓ Branch 13 → 14 taken 9 times.
9 const QTextFragment fragment = it.fragment();
564
9/13
✓ Branch 15 → 16 taken 9 times.
✗ Branch 15 → 19 not taken.
✓ Branch 16 → 17 taken 9 times.
✓ Branch 17 → 18 taken 9 times.
✓ Branch 18 → 19 taken 6 times.
✓ Branch 18 → 20 taken 3 times.
✓ Branch 21 → 22 taken 9 times.
✗ Branch 21 → 23 not taken.
✓ Branch 23 → 24 taken 6 times.
✓ Branch 23 → 25 taken 3 times.
✓ Branch 105 → 106 taken 6 times.
✗ Branch 111 → 112 not taken.
✗ Branch 111 → 113 not taken.
9 if (!fragment.isValid() || !fragment.charFormat().isAnchor()) continue;
565
2/2
✓ Branch 25 → 26 taken 3 times.
✓ Branch 26 → 27 taken 3 times.
3 const int start = fragment.position(), end = start + fragment.length();
566
2/4
✓ Branch 27 → 28 taken 3 times.
✗ Branch 27 → 29 not taken.
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 3 times.
3 if (position < start || position > end) continue;
567 // Neighbouring fragments with the same target belong to the link.
568
2/2
✓ Branch 30 → 31 taken 3 times.
✓ Branch 31 → 32 taken 3 times.
3 const QString href = fragment.charFormat().anchorHref();
569 3 int linkStart = start, linkEnd = end;
570
4/4
✓ Branch 33 → 34 taken 3 times.
✓ Branch 65 → 66 taken 9 times.
✓ Branch 67 → 35 taken 9 times.
✓ Branch 67 → 68 taken 3 times.
12 for (auto other = block.begin(); !other.atEnd(); ++other) {
571
1/1
✓ Branch 35 → 36 taken 9 times.
9 const QTextFragment piece = other.fragment();
572
14/22
✓ Branch 36 → 37 taken 9 times.
✓ Branch 37 → 38 taken 9 times.
✓ Branch 38 → 39 taken 3 times.
✓ Branch 38 → 43 taken 6 times.
✓ Branch 39 → 40 taken 3 times.
✓ Branch 40 → 41 taken 3 times.
✗ Branch 42 → 43 not taken.
✓ Branch 42 → 44 taken 3 times.
✓ Branch 45 → 46 taken 3 times.
✓ Branch 45 → 47 taken 6 times.
✓ Branch 47 → 48 taken 3 times.
✓ Branch 47 → 49 taken 6 times.
✓ Branch 49 → 50 taken 9 times.
✗ Branch 49 → 51 not taken.
✓ Branch 51 → 52 taken 6 times.
✓ Branch 51 → 53 taken 3 times.
✗ Branch 118 → 119 not taken.
✗ Branch 118 → 120 not taken.
✗ Branch 122 → 123 not taken.
✗ Branch 122 → 124 not taken.
✗ Branch 126 → 127 not taken.
✗ Branch 126 → 128 not taken.
9 if (!piece.charFormat().isAnchor() || piece.charFormat().anchorHref() != href) continue;
573
3/5
✓ Branch 53 → 54 taken 3 times.
✓ Branch 54 → 55 taken 3 times.
✗ Branch 55 → 56 not taken.
✓ Branch 55 → 58 taken 3 times.
✗ Branch 56 → 57 not taken.
3 if (piece.position() + piece.length() == linkStart) linkStart = piece.position();
574
2/5
✓ Branch 58 → 59 taken 3 times.
✗ Branch 59 → 60 not taken.
✓ Branch 59 → 63 taken 3 times.
✗ Branch 60 → 61 not taken.
✗ Branch 61 → 62 not taken.
3 if (piece.position() == linkEnd) linkEnd = piece.position() + piece.length();
575 }
576
1/1
✓ Branch 69 → 70 taken 3 times.
3 QTextCursor cursor(m_document);
577
1/1
✓ Branch 70 → 71 taken 3 times.
3 cursor.setPosition(linkStart);
578
1/1
✓ Branch 71 → 72 taken 3 times.
3 cursor.setPosition(linkEnd, QTextCursor::KeepAnchor);
579
1/1
✓ Branch 75 → 76 taken 3 times.
9 return {{QStringLiteral("text"), cursor.selectedText()}, {QStringLiteral("href"), href},
580
2/4
✓ Branch 91 → 92 taken 12 times.
✓ Branch 91 → 93 taken 3 times.
✗ Branch 133 → 134 not taken.
✗ Branch 133 → 135 not taken.
21 {QStringLiteral("start"), linkStart}, {QStringLiteral("end"), linkEnd}};
581 3 }
582 3 return {};
583 15 }
584
585 6 bool RichTextController::setLink(const QString &url, const QString &text) {
586
2/3
✓ Branch 2 → 3 taken 6 times.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 6 times.
6 if (refuseInPlainMode()) return false;
587
1/1
✓ Branch 6 → 7 taken 6 times.
6 const QString href = normalizedLink(url);
588
3/3
✓ Branch 8 → 9 taken 6 times.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 11 taken 5 times.
6 if (!isAllowedLink(href)) return false;
589
1/1
✓ Branch 11 → 12 taken 5 times.
5 QTextCharFormat link = formatAtCaret();
590
1/1
✓ Branch 12 → 13 taken 5 times.
5 link.setAnchor(true);
591
1/1
✓ Branch 13 → 14 taken 5 times.
5 link.setAnchorHref(href);
592
1/1
✓ Branch 14 → 15 taken 5 times.
5 QTextCursor cursor = selectionCursor();
593
3/3
✓ Branch 16 → 17 taken 2 times.
✓ Branch 16 → 18 taken 3 times.
✓ Branch 18 → 19 taken 3 times.
5 const QVariantMap existing = hasSelection() ? QVariantMap() : linkAtCursor();
594
3/3
✓ Branch 19 → 20 taken 5 times.
✓ Branch 20 → 21 taken 1 time.
✓ Branch 20 → 44 taken 4 times.
5 if (!existing.isEmpty()) {
595
3/3
✓ Branch 25 → 26 taken 1 time.
✓ Branch 26 → 27 taken 1 time.
✓ Branch 27 → 28 taken 1 time.
2 cursor.setPosition(existing.value(QStringLiteral("start")).toInt());
596
3/3
✓ Branch 36 → 37 taken 1 time.
✓ Branch 37 → 38 taken 1 time.
✓ Branch 38 → 39 taken 1 time.
2 cursor.setPosition(existing.value(QStringLiteral("end")).toInt(), QTextCursor::KeepAnchor);
597 }
598 5 m_applying = true;
599
1/1
✓ Branch 44 → 45 taken 5 times.
5 cursor.beginEditBlock();
600
7/7
✓ Branch 46 → 47 taken 2 times.
✓ Branch 46 → 48 taken 3 times.
✓ Branch 48 → 49 taken 3 times.
✓ Branch 49 → 50 taken 2 times.
✓ Branch 49 → 51 taken 1 time.
✓ Branch 50 → 52 taken 2 times.
✓ Branch 51 → 52 taken 1 time.
5 const QString shown = !text.isEmpty() ? text : cursor.hasSelection() ? cursor.selectedText() : url.trimmed();
601
7/7
✓ Branch 52 → 53 taken 5 times.
✓ Branch 53 → 54 taken 3 times.
✓ Branch 53 → 57 taken 2 times.
✓ Branch 55 → 56 taken 2 times.
✓ Branch 55 → 57 taken 1 time.
✓ Branch 58 → 59 taken 2 times.
✓ Branch 58 → 60 taken 3 times.
5 if (cursor.hasSelection() && text.isEmpty()) {
602
1/1
✓ Branch 59 → 62 taken 2 times.
2 cursor.mergeCharFormat(link);
603 } else {
604
1/1
✓ Branch 60 → 61 taken 3 times.
3 cursor.removeSelectedText();
605
1/1
✓ Branch 61 → 62 taken 3 times.
3 cursor.insertText(shown, link);
606 }
607
1/1
✓ Branch 62 → 63 taken 5 times.
5 cursor.endEditBlock();
608 5 m_applying = false;
609 // Typing after a new link is plain text again.
610
1/1
✓ Branch 63 → 64 taken 5 times.
5 QTextCharFormat after = link;
611
1/1
✓ Branch 64 → 65 taken 5 times.
5 after.setAnchor(false);
612
1/1
✓ Branch 65 → 66 taken 5 times.
5 after.clearProperty(QTextFormat::AnchorHref);
613
1/1
✓ Branch 66 → 67 taken 5 times.
5 m_pendingFormat = after;
614 5 m_pendingValid = true;
615
1/1
✓ Branch 67 → 68 taken 5 times.
5 m_pendingPosition = cursor.position();
616
1/1
✓ Branch 68 → 69 taken 5 times.
5 emitState();
617
2/2
✓ Branch 69 → 70 taken 5 times.
✓ Branch 70 → 71 taken 5 times.
5 emit announcement(tr("Link"));
618 5 return true;
619 6 }
620
621 1 void RichTextController::removeLink() {
622
2/3
✓ Branch 2 → 3 taken 1 time.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (refuseInPlainMode()) return;
623
1/1
✓ Branch 5 → 6 taken 1 time.
1 const QVariantMap existing = linkAtCursor();
624
4/7
✓ Branch 6 → 7 taken 1 time.
✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 11 not taken.
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 11 not taken.
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 35 not taken.
1 if (!existing.isEmpty() && !hasSelection()) {
625
5/5
✓ Branch 17 → 18 taken 1 time.
✓ Branch 18 → 19 taken 1 time.
✓ Branch 23 → 24 taken 1 time.
✓ Branch 24 → 25 taken 1 time.
✓ Branch 25 → 26 taken 1 time.
3 setSelection(existing.value(QStringLiteral("start")).toInt(), existing.value(QStringLiteral("end")).toInt(),
626 m_cursorPosition);
627 }
628
1/1
✓ Branch 36 → 37 taken 1 time.
1 changeCharFormat([](QTextCharFormat &format) {
629 1 format.setAnchor(false);
630 1 format.clearProperty(QTextFormat::AnchorHref);
631 1 });
632 1 }
633
634 12 void RichTextController::addImageResource(const QString &name, const QImage &image) {
635
4/5
✓ Branch 3 → 4 taken 12 times.
✗ Branch 3 → 11 not taken.
✓ Branch 5 → 6 taken 12 times.
✓ Branch 6 → 7 taken 12 times.
✓ Branch 7 → 8 taken 12 times.
12 if (m_document) m_document->addResource(QTextDocument::ImageResource, QUrl(name), image);
636 12 }
637
638 7 void RichTextController::insertImage(const QString &name, int width, const QString &alternativeText) {
639
2/3
✓ Branch 2 → 3 taken 7 times.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 7 times.
7 if (refuseInPlainMode()) return;
640
1/1
✓ Branch 5 → 6 taken 7 times.
7 QTextImageFormat image;
641
1/1
✓ Branch 6 → 7 taken 7 times.
7 image.setName(name);
642
3/3
✓ Branch 7 → 8 taken 6 times.
✓ Branch 7 → 9 taken 1 time.
✓ Branch 8 → 9 taken 6 times.
7 if (width > 0) image.setWidth(width);
643
2/3
✓ Branch 10 → 11 taken 7 times.
✗ Branch 10 → 15 not taken.
✓ Branch 12 → 13 taken 7 times.
7 if (!alternativeText.isEmpty()) image.setProperty(ImageAltProperty, alternativeText);
644
1/1
✓ Branch 15 → 16 taken 7 times.
7 QTextCursor cursor = selectionCursor();
645 7 m_applying = true;
646
1/1
✓ Branch 16 → 17 taken 7 times.
7 cursor.beginEditBlock();
647
1/1
✓ Branch 17 → 18 taken 7 times.
7 cursor.removeSelectedText();
648
1/1
✓ Branch 18 → 19 taken 7 times.
7 cursor.insertImage(image);
649
1/1
✓ Branch 19 → 20 taken 7 times.
7 cursor.endEditBlock();
650 7 m_applying = false;
651
1/1
✓ Branch 20 → 21 taken 7 times.
7 emitState();
652 7 }
653
654 1 void RichTextController::setImageWidth(int width) {
655
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (!m_document) return;
656 // The selected image, else the one right before or after the caret.
657
2/4
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 8 not taken.
✓ Branch 45 → 11 taken 1 time.
✗ Branch 45 → 46 not taken.
1 for (const int position : {hasSelection() ? m_selectionStart : m_cursorPosition - 1, m_cursorPosition}) {
658
1/2
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 1 time.
1 if (position < 0) continue;
659
1/1
✓ Branch 14 → 15 taken 1 time.
1 QTextCursor cursor(m_document);
660
1/1
✓ Branch 15 → 16 taken 1 time.
1 cursor.setPosition(position);
661
1/1
✓ Branch 16 → 17 taken 1 time.
1 cursor.setPosition(position + 1, QTextCursor::KeepAnchor);
662
1/1
✓ Branch 18 → 19 taken 1 time.
1 QTextCursor probe(m_document);
663
1/1
✓ Branch 19 → 20 taken 1 time.
1 probe.setPosition(position + 1);
664
3/4
✓ Branch 20 → 21 taken 1 time.
✓ Branch 21 → 22 taken 1 time.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 1 time.
1 if (!probe.charFormat().isImageFormat()) continue;
665
2/2
✓ Branch 25 → 26 taken 1 time.
✓ Branch 26 → 27 taken 1 time.
1 QTextImageFormat image = probe.charFormat().toImageFormat();
666
2/3
✓ Branch 28 → 29 taken 1 time.
✗ Branch 28 → 30 not taken.
✓ Branch 29 → 31 taken 1 time.
1 if (width > 0) image.setWidth(width);
667 ✗ else image.clearProperty(QTextFormat::ImageWidth);
668
1/1
✓ Branch 31 → 32 taken 1 time.
1 image.clearProperty(QTextFormat::ImageHeight);
669 1 m_applying = true;
670
1/1
✓ Branch 32 → 33 taken 1 time.
1 cursor.setCharFormat(image);
671 1 m_applying = false;
672
1/1
✓ Branch 33 → 34 taken 1 time.
1 emitState();
673 1 return;
674 3 }
675 }
676
677 23 QStringList RichTextController::imageNames() const {
678 23 QStringList names;
679
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 23 times.
23 if (!m_document) return names;
680
5/5
✓ Branch 6 → 7 taken 23 times.
✓ Branch 27 → 28 taken 88 times.
✓ Branch 30 → 31 taken 111 times.
✓ Branch 31 → 8 taken 88 times.
✓ Branch 31 → 32 taken 23 times.
111 for (QTextBlock block = m_document->begin(); block.isValid(); block = block.next())
681
4/4
✓ Branch 8 → 9 taken 88 times.
✓ Branch 24 → 25 taken 93 times.
✓ Branch 26 → 10 taken 93 times.
✓ Branch 26 → 27 taken 88 times.
181 for (auto it = block.begin(); !it.atEnd(); ++it)
682
5/5
✓ Branch 10 → 11 taken 93 times.
✓ Branch 11 → 12 taken 93 times.
✓ Branch 12 → 13 taken 93 times.
✓ Branch 14 → 15 taken 21 times.
✓ Branch 14 → 24 taken 72 times.
93 if (it.fragment().charFormat().isImageFormat())
683
5/5
✓ Branch 15 → 16 taken 21 times.
✓ Branch 16 → 17 taken 21 times.
✓ Branch 17 → 18 taken 21 times.
✓ Branch 18 → 19 taken 21 times.
✓ Branch 19 → 20 taken 21 times.
21 names.append(it.fragment().charFormat().toImageFormat().name());
684 23 return names;
685 ✗ }
686
687 1 void RichTextController::removeImage(const QString &name) {
688
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (!m_document) return;
689 1 QList<int> positions;
690
5/5
✓ Branch 6 → 7 taken 1 time.
✓ Branch 42 → 43 taken 1 time.
✓ Branch 45 → 46 taken 2 times.
✓ Branch 46 → 8 taken 1 time.
✓ Branch 46 → 47 taken 1 time.
2 for (QTextBlock block = m_document->begin(); block.isValid(); block = block.next())
691
4/4
✓ Branch 8 → 9 taken 1 time.
✓ Branch 39 → 40 taken 2 times.
✓ Branch 41 → 10 taken 2 times.
✓ Branch 41 → 42 taken 1 time.
3 for (auto it = block.begin(); !it.atEnd(); ++it)
692
19/29
✓ Branch 10 → 11 taken 2 times.
✓ Branch 11 → 12 taken 2 times.
✓ Branch 12 → 13 taken 2 times.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 21 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
✓ Branch 15 → 16 taken 1 time.
✓ Branch 16 → 17 taken 1 time.
✓ Branch 17 → 18 taken 1 time.
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 21 not taken.
✓ Branch 22 → 23 taken 1 time.
✓ Branch 22 → 24 taken 1 time.
✓ Branch 24 → 25 taken 1 time.
✓ Branch 24 → 26 taken 1 time.
✓ Branch 26 → 27 taken 1 time.
✓ Branch 26 → 28 taken 1 time.
✓ Branch 28 → 29 taken 2 times.
✗ Branch 28 → 30 not taken.
✓ Branch 30 → 31 taken 1 time.
✓ Branch 30 → 39 taken 1 time.
✗ Branch 75 → 76 not taken.
✗ Branch 75 → 77 not taken.
✗ Branch 79 → 80 not taken.
✗ Branch 79 → 81 not taken.
✗ Branch 83 → 84 not taken.
✗ Branch 83 → 85 not taken.
✗ Branch 88 → 89 not taken.
✗ Branch 88 → 90 not taken.
2 if (it.fragment().charFormat().isImageFormat() && it.fragment().charFormat().toImageFormat().name() == name)
693
7/7
✓ Branch 32 → 33 taken 1 time.
✓ Branch 33 → 34 taken 1 time.
✓ Branch 34 → 35 taken 1 time.
✓ Branch 36 → 37 taken 2 times.
✓ Branch 37 → 38 taken 2 times.
✓ Branch 38 → 32 taken 1 time.
✓ Branch 38 → 39 taken 1 time.
2 for (int i = 0; i < it.fragment().length(); ++i) positions.prepend(it.fragment().position() + i);
694
1/2
✗ Branch 48 → 49 not taken.
✓ Branch 48 → 50 taken 1 time.
1 if (positions.isEmpty()) return;
695 1 m_applying = true;
696
1/1
✓ Branch 51 → 52 taken 1 time.
1 QTextCursor edit(m_document);
697
1/1
✓ Branch 52 → 53 taken 1 time.
1 edit.beginEditBlock();
698
2/2
✓ Branch 64 → 56 taken 1 time.
✓ Branch 64 → 65 taken 1 time.
2 for (const int position : std::as_const(positions)) {
699
1/1
✓ Branch 58 → 59 taken 1 time.
1 QTextCursor cursor(m_document);
700
1/1
✓ Branch 59 → 60 taken 1 time.
1 cursor.setPosition(position);
701
1/1
✓ Branch 60 → 61 taken 1 time.
1 cursor.deleteChar();
702 1 }
703
1/1
✓ Branch 65 → 66 taken 1 time.
1 edit.endEditBlock();
704 1 m_applying = false;
705
1/1
✓ Branch 66 → 67 taken 1 time.
1 emitState();
706 1 }
707
708 2 void RichTextController::insertTable(int rows, int columns, bool headerRow) {
709
2/3
✓ Branch 2 → 3 taken 2 times.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 if (refuseInPlainMode()) return;
710
1/1
✓ Branch 5 → 6 taken 2 times.
2 rows = qBound(1, rows, 50);
711
1/1
✓ Branch 6 → 7 taken 2 times.
2 columns = qBound(1, columns, 12);
712
1/1
✓ Branch 7 → 8 taken 2 times.
2 QTextTableFormat format;
713
2/3
✓ Branch 8 → 9 taken 2 times.
✗ Branch 8 → 10 not taken.
✓ Branch 11 → 12 taken 2 times.
2 format.setHeaderRowCount(headerRow ? 1 : 0);
714
1/1
✓ Branch 12 → 13 taken 2 times.
2 format.setBorder(1);
715
1/1
✓ Branch 13 → 14 taken 2 times.
2 format.setBorderStyle(QTextFrameFormat::BorderStyle_Solid);
716
1/1
✓ Branch 14 → 15 taken 2 times.
2 format.setBorderCollapse(true);
717
1/1
✓ Branch 15 → 16 taken 2 times.
2 format.setCellPadding(4);
718
1/1
✓ Branch 16 → 17 taken 2 times.
2 format.setCellSpacing(0);
719 2 QList<QTextLength> widths;
720
3/3
✓ Branch 19 → 20 taken 5 times.
✓ Branch 21 → 18 taken 5 times.
✓ Branch 21 → 22 taken 2 times.
7 for (int i = 0; i < columns; ++i) widths.append(QTextLength(QTextLength::PercentageLength, 100.0 / columns));
721
1/1
✓ Branch 22 → 23 taken 2 times.
2 format.setColumnWidthConstraints(widths);
722
1/1
✓ Branch 23 → 24 taken 2 times.
2 QTextCursor cursor = selectionCursor();
723 2 m_applying = true;
724
1/1
✓ Branch 24 → 25 taken 2 times.
2 cursor.beginEditBlock();
725
1/1
✓ Branch 25 → 26 taken 2 times.
2 cursor.removeSelectedText();
726
1/1
✓ Branch 26 → 27 taken 2 times.
2 cursor.insertTable(rows, columns, format);
727
1/1
✓ Branch 27 → 28 taken 2 times.
2 cursor.endEditBlock();
728 2 m_applying = false;
729
1/1
✓ Branch 28 → 29 taken 2 times.
2 emitState();
730
4/4
✓ Branch 29 → 30 taken 2 times.
✓ Branch 31 → 32 taken 2 times.
✓ Branch 33 → 34 taken 2 times.
✓ Branch 34 → 35 taken 2 times.
6 emit announcement(tr("Table with %1 rows and %2 columns").arg(rows).arg(columns));
731 2 }
732
733 namespace {
734 6 QTextTable *tableAtCursor(const QTextCursor &cursor) { return cursor.currentTable(); }
735
736 2 void equalizeWidths(QTextTable *table) {
737
1/1
✓ Branch 2 → 3 taken 2 times.
2 QTextTableFormat format = table->format();
738 2 QList<QTextLength> widths;
739
3/3
✓ Branch 8 → 9 taken 9 times.
✓ Branch 9 → 4 taken 7 times.
✓ Branch 9 → 10 taken 2 times.
9 for (int i = 0; i < table->columns(); ++i)
740
2/2
✓ Branch 4 → 5 taken 7 times.
✓ Branch 6 → 7 taken 7 times.
7 widths.append(QTextLength(QTextLength::PercentageLength, 100.0 / table->columns()));
741
1/1
✓ Branch 10 → 11 taken 2 times.
2 format.setColumnWidthConstraints(widths);
742
1/1
✓ Branch 11 → 12 taken 2 times.
2 table->setFormat(format);
743 2 }
744 } // namespace
745
746 1 void RichTextController::tableInsertRow(bool below) {
747
2/3
✓ Branch 2 → 3 taken 1 time.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (refuseInPlainMode()) return;
748
1/1
✓ Branch 5 → 6 taken 1 time.
1 const QTextCursor cursor = selectionCursor();
749
1/1
✓ Branch 6 → 7 taken 1 time.
1 QTextTable *table = tableAtCursor(cursor);
750
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 1 time.
1 if (!table) return;
751
1/1
✓ Branch 9 → 10 taken 1 time.
1 const QTextTableCell cell = table->cellAt(cursor);
752
3/4
✓ Branch 10 → 11 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 13 not taken.
✓ Branch 14 → 15 taken 1 time.
1 table->insertRows(cell.row() + (below ? 1 : 0), 1);
753
1/1
✓ Branch 15 → 16 taken 1 time.
1 emitState();
754 1 }
755
756 1 void RichTextController::tableRemoveRow() {
757
2/3
✓ Branch 2 → 3 taken 1 time.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (refuseInPlainMode()) return;
758
1/1
✓ Branch 5 → 6 taken 1 time.
1 const QTextCursor cursor = selectionCursor();
759
1/1
✓ Branch 6 → 7 taken 1 time.
1 QTextTable *table = tableAtCursor(cursor);
760
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 1 time.
1 if (!table) return;
761
2/3
✓ Branch 9 → 10 taken 1 time.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 13 taken 1 time.
1 if (table->rows() <= 1) {
762 ✗ table->removeRows(0, 1);
763 ✗ return;
764 }
765
3/3
✓ Branch 13 → 14 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
✓ Branch 15 → 16 taken 1 time.
1 table->removeRows(table->cellAt(cursor).row(), 1);
766
1/1
✓ Branch 17 → 18 taken 1 time.
1 emitState();
767 1 }
768
769 1 void RichTextController::tableInsertColumn(bool after) {
770
2/3
✓ Branch 2 → 3 taken 1 time.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (refuseInPlainMode()) return;
771
1/1
✓ Branch 5 → 6 taken 1 time.
1 const QTextCursor cursor = selectionCursor();
772
1/1
✓ Branch 6 → 7 taken 1 time.
1 QTextTable *table = tableAtCursor(cursor);
773
4/7
✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 10 not taken.
✓ Branch 8 → 9 taken 1 time.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 1 time.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 1 time.
1 if (!table || table->columns() >= 12) return;
774
4/5
✓ Branch 14 → 15 taken 1 time.
✓ Branch 15 → 16 taken 1 time.
✓ Branch 16 → 17 taken 1 time.
✗ Branch 16 → 18 not taken.
✓ Branch 19 → 20 taken 1 time.
1 table->insertColumns(table->cellAt(cursor).column() + (after ? 1 : 0), 1);
775
1/1
✓ Branch 21 → 22 taken 1 time.
1 equalizeWidths(table);
776
1/1
✓ Branch 22 → 23 taken 1 time.
1 emitState();
777 1 }
778
779 1 void RichTextController::tableRemoveColumn() {
780
2/3
✓ Branch 2 → 3 taken 1 time.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (refuseInPlainMode()) return;
781
1/1
✓ Branch 5 → 6 taken 1 time.
1 const QTextCursor cursor = selectionCursor();
782
1/1
✓ Branch 6 → 7 taken 1 time.
1 QTextTable *table = tableAtCursor(cursor);
783
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 1 time.
1 if (!table) return;
784
2/3
✓ Branch 9 → 10 taken 1 time.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 13 taken 1 time.
1 if (table->columns() <= 1) {
785 ✗ table->removeColumns(0, 1);
786 ✗ return;
787 }
788
3/3
✓ Branch 13 → 14 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
✓ Branch 15 → 16 taken 1 time.
1 table->removeColumns(table->cellAt(cursor).column(), 1);
789
1/1
✓ Branch 17 → 18 taken 1 time.
1 equalizeWidths(table);
790
1/1
✓ Branch 18 → 19 taken 1 time.
1 emitState();
791 1 }
792
793 1 void RichTextController::tableToggleHeaderRow() {
794
2/3
✓ Branch 2 → 3 taken 1 time.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (refuseInPlainMode()) return;
795
2/2
✓ Branch 5 → 6 taken 1 time.
✓ Branch 6 → 7 taken 1 time.
1 QTextTable *table = tableAtCursor(selectionCursor());
796
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 1 time.
1 if (!table) return;
797
1/1
✓ Branch 10 → 11 taken 1 time.
1 QTextTableFormat format = table->format();
798
2/2
✓ Branch 11 → 12 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
1 format.setHeaderRowCount(format.headerRowCount() > 0 ? 0 : 1);
799
1/1
✓ Branch 13 → 14 taken 1 time.
1 table->setFormat(format);
800
1/1
✓ Branch 14 → 15 taken 1 time.
1 emitState();
801 1 }
802
803 1 void RichTextController::tableSetColumnWidths(const QVariantList &percentages) {
804
2/3
✓ Branch 2 → 3 taken 1 time.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (refuseInPlainMode()) return;
805
2/2
✓ Branch 5 → 6 taken 1 time.
✓ Branch 6 → 7 taken 1 time.
1 QTextTable *table = tableAtCursor(selectionCursor());
806
4/7
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 12 not taken.
✓ Branch 10 → 11 taken 1 time.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 1 time.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 1 time.
1 if (!table || percentages.size() != table->columns()) return;
807 1 QList<QTextLength> widths;
808
2/2
✓ Branch 25 → 18 taken 3 times.
✓ Branch 25 → 26 taken 1 time.
4 for (const QVariant &value : percentages)
809
2/2
✓ Branch 19 → 20 taken 3 times.
✓ Branch 22 → 23 taken 3 times.
3 widths.append(QTextLength(QTextLength::PercentageLength, qBound(5.0, value.toDouble(), 95.0)));
810
1/1
✓ Branch 26 → 27 taken 1 time.
1 QTextTableFormat format = table->format();
811
1/1
✓ Branch 27 → 28 taken 1 time.
1 format.setColumnWidthConstraints(widths);
812
1/1
✓ Branch 28 → 29 taken 1 time.
1 table->setFormat(format);
813 1 }
814
815 10 int RichTextController::handleTab(bool backwards) {
816
5/6
✓ Branch 3 → 4 taken 10 times.
✗ Branch 3 → 5 not taken.
✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 6 taken 6 times.
✓ Branch 7 → 8 taken 4 times.
✓ Branch 7 → 9 taken 6 times.
10 if (!m_document || !m_formatted) return -1;
817
1/1
✓ Branch 9 → 10 taken 6 times.
6 QTextCursor cursor = selectionCursor();
818
3/3
✓ Branch 10 → 11 taken 6 times.
✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 38 taken 4 times.
6 if (QTextTable *table = cursor.currentTable()) {
819
1/1
✓ Branch 12 → 13 taken 2 times.
2 const QTextTableCell cell = table->cellAt(cursor);
820
2/2
✓ Branch 13 → 14 taken 2 times.
✓ Branch 14 → 15 taken 2 times.
2 int row = cell.row(), column = cell.column();
821
1/2
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 24 taken 2 times.
2 if (backwards) {
822 ✗ if (--column < 0) {
823 ✗ column = table->columns() - 1;
824 ✗ if (--row < 0) return cell.firstCursorPosition().position();
825 }
826
3/3
✓ Branch 24 → 25 taken 2 times.
✓ Branch 25 → 26 taken 1 time.
✓ Branch 25 → 29 taken 1 time.
2 } else if (++column >= table->columns()) {
827 1 column = 0;
828
3/4
✓ Branch 26 → 27 taken 1 time.
✓ Branch 27 → 28 taken 1 time.
✗ Branch 27 → 29 not taken.
✓ Branch 28 → 29 taken 1 time.
1 if (++row >= table->rows()) table->appendRows(1);
829 }
830
3/3
✓ Branch 29 → 30 taken 2 times.
✓ Branch 30 → 31 taken 2 times.
✓ Branch 31 → 32 taken 2 times.
2 return table->cellAt(row, column).firstCursorPosition().position();
831 2 }
832
4/4
✓ Branch 38 → 39 taken 4 times.
✓ Branch 39 → 40 taken 4 times.
✓ Branch 40 → 41 taken 2 times.
✓ Branch 40 → 45 taken 2 times.
4 if (cursor.block().textList()) {
833
3/3
✓ Branch 41 → 42 taken 1 time.
✓ Branch 41 → 43 taken 1 time.
✓ Branch 42 → 44 taken 1 time.
2 if (backwards) outdent();
834
1/1
✓ Branch 43 → 44 taken 1 time.
1 else indent();
835 2 return m_cursorPosition;
836 }
837 2 return -1;
838 6 }
839
840 88 int RichTextController::insertPlainText(const QString &text) {
841
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 88 times.
88 if (!m_document) return m_cursorPosition;
842
1/1
✓ Branch 5 → 6 taken 88 times.
88 QTextCursor cursor = selectionCursor();
843
1/1
✓ Branch 6 → 7 taken 88 times.
88 QTextCharFormat format = formatAtCaret();
844
2/5
✓ Branch 7 → 8 taken 88 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 13 taken 88 times.
✗ Branch 9 → 10 not taken.
✗ Branch 10 → 11 not taken.
88 if (format.isImageFormat()) format = QTextCharFormat();
845
1/1
✓ Branch 13 → 14 taken 88 times.
88 format.setAnchor(false);
846
1/1
✓ Branch 14 → 15 taken 88 times.
88 format.clearProperty(QTextFormat::AnchorHref);
847 88 QString clean = text;
848
1/1
✓ Branch 22 → 23 taken 88 times.
176 clean.replace(QStringLiteral("\r\n"), QStringLiteral("\n"));
849 88 m_applying = true;
850
1/1
✓ Branch 27 → 28 taken 88 times.
88 cursor.beginEditBlock();
851
1/1
✓ Branch 28 → 29 taken 88 times.
88 cursor.removeSelectedText();
852
5/5
✓ Branch 29 → 30 taken 9 times.
✓ Branch 29 → 31 taken 79 times.
✓ Branch 30 → 32 taken 9 times.
✓ Branch 31 → 32 taken 79 times.
✓ Branch 32 → 33 taken 88 times.
88 cursor.insertText(clean, m_formatted ? format : QTextCharFormat());
853
1/1
✓ Branch 34 → 35 taken 88 times.
88 cursor.endEditBlock();
854 88 m_applying = false;
855
1/1
✓ Branch 35 → 36 taken 88 times.
88 emitState();
856
1/1
✓ Branch 36 → 37 taken 88 times.
88 return cursor.position();
857 88 }
858
859 22 int RichTextController::insertFragment(const QTextDocumentFragment &fragment) {
860
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 22 times.
22 if (!m_document) return m_cursorPosition;
861
1/1
✓ Branch 5 → 6 taken 22 times.
22 QTextCursor cursor = selectionCursor();
862 22 m_applying = true;
863
1/1
✓ Branch 6 → 7 taken 22 times.
22 cursor.beginEditBlock();
864
1/1
✓ Branch 7 → 8 taken 22 times.
22 cursor.removeSelectedText();
865
1/1
✓ Branch 8 → 9 taken 22 times.
22 cursor.insertFragment(fragment);
866
1/1
✓ Branch 9 → 10 taken 22 times.
22 cursor.endEditBlock();
867 22 m_applying = false;
868
1/1
✓ Branch 10 → 11 taken 22 times.
22 emitState();
869
1/1
✓ Branch 11 → 12 taken 22 times.
22 return cursor.position();
870 22 }
871
872 34 int RichTextController::insertFragmentPart(QTextCursor &at, const QTextDocumentFragment &part, bool first) {
873
6/10
✓ Branch 3 → 4 taken 34 times.
✗ Branch 3 → 9 not taken.
✓ Branch 4 → 5 taken 34 times.
✓ Branch 5 → 6 taken 34 times.
✗ Branch 5 → 9 not taken.
✓ Branch 6 → 7 taken 34 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 34 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 34 times.
34 if (!m_document || at.isNull() || at.document() != m_document) return -1;
874 34 m_applying = true;
875
2/2
✓ Branch 13 → 14 taken 2 times.
✓ Branch 13 → 16 taken 32 times.
34 if (first) {
876 2 at.beginEditBlock();
877 2 at.removeSelectedText();
878 } else {
879 32 at.joinPreviousEditBlock();
880 }
881 34 at.insertFragment(part);
882 34 at.endEditBlock();
883 34 m_applying = false;
884 34 emitState();
885 34 return at.position();
886 }
887
888 4 QStringList RichTextController::plainTextLosses() const {
889 4 QStringList losses;
890
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 4 times.
4 if (!m_document) return losses;
891 4 int images = 0;
892 4 bool colours = false, fonts = false, links = false, headings = false, tables = false, emphasis = false;
893
5/5
✓ Branch 6 → 7 taken 4 times.
✓ Branch 64 → 65 taken 5 times.
✓ Branch 67 → 68 taken 9 times.
✓ Branch 68 → 8 taken 5 times.
✓ Branch 68 → 69 taken 4 times.
9 for (QTextBlock block = m_document->begin(); block.isValid(); block = block.next()) {
894
2/2
✓ Branch 8 → 9 taken 5 times.
✓ Branch 9 → 10 taken 5 times.
5 headings |= block.blockFormat().headingLevel() > 0;
895
2/2
✓ Branch 11 → 12 taken 5 times.
✓ Branch 12 → 13 taken 5 times.
5 tables |= QTextCursor(block).currentTable() != nullptr;
896
4/4
✓ Branch 14 → 15 taken 5 times.
✓ Branch 61 → 62 taken 8 times.
✓ Branch 63 → 16 taken 8 times.
✓ Branch 63 → 64 taken 5 times.
13 for (auto it = block.begin(); !it.atEnd(); ++it) {
897
2/2
✓ Branch 16 → 17 taken 8 times.
✓ Branch 17 → 18 taken 8 times.
8 const QTextCharFormat format = it.fragment().charFormat();
898
3/3
✓ Branch 18 → 19 taken 8 times.
✓ Branch 19 → 20 taken 1 time.
✓ Branch 19 → 23 taken 7 times.
8 if (format.isImageFormat()) {
899
2/2
✓ Branch 20 → 21 taken 1 time.
✓ Branch 21 → 22 taken 1 time.
1 images += it.fragment().length();
900 1 continue;
901 }
902
5/6
✓ Branch 23 → 24 taken 7 times.
✓ Branch 24 → 25 taken 3 times.
✓ Branch 24 → 27 taken 4 times.
✓ Branch 25 → 26 taken 3 times.
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 29 taken 3 times.
11 colours |= (format.hasProperty(QTextFormat::ForegroundBrush) && !format.isAnchor()) ||
903
2/3
✓ Branch 27 → 28 taken 4 times.
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 4 times.
4 format.hasProperty(QTextFormat::BackgroundBrush);
904
8/18
✓ Branch 31 → 32 taken 7 times.
✓ Branch 32 → 33 taken 7 times.
✓ Branch 33 → 34 taken 7 times.
✓ Branch 34 → 35 taken 7 times.
✓ Branch 35 → 36 taken 7 times.
✗ Branch 35 → 38 not taken.
✓ Branch 40 → 41 taken 7 times.
✗ Branch 40 → 42 not taken.
✓ Branch 42 → 43 taken 7 times.
✗ Branch 42 → 44 not taken.
✓ Branch 44 → 45 taken 7 times.
✗ Branch 44 → 46 not taken.
✗ Branch 116 → 117 not taken.
✗ Branch 116 → 118 not taken.
✗ Branch 120 → 121 not taken.
✗ Branch 120 → 122 not taken.
✗ Branch 124 → 125 not taken.
✗ Branch 124 → 126 not taken.
14 fonts |= fontFamilyFor(format.fontFamilies().toStringList().value(0)) != nullptr ||
905
2/3
✓ Branch 36 → 37 taken 7 times.
✗ Branch 37 → 38 not taken.
✓ Branch 37 → 39 taken 7 times.
7 format.hasProperty(QTextFormat::FontPointSize);
906
1/1
✓ Branch 46 → 47 taken 7 times.
7 links |= format.isAnchor();
907
4/6
✓ Branch 47 → 48 taken 7 times.
✓ Branch 48 → 49 taken 7 times.
✗ Branch 48 → 53 not taken.
✓ Branch 49 → 50 taken 7 times.
✓ Branch 50 → 51 taken 7 times.
✗ Branch 50 → 53 not taken.
14 emphasis |= format.fontUnderline() || format.fontStrikeOut() ||
908
2/3
✓ Branch 51 → 52 taken 7 times.
✗ Branch 52 → 53 not taken.
✓ Branch 52 → 54 taken 7 times.
7 format.verticalAlignment() != QTextCharFormat::AlignNormal;
909 8 }
910 }
911
2/2
✓ Branch 69 → 70 taken 1 time.
✓ Branch 69 → 76 taken 3 times.
4 if (images > 0)
912
3/5
✓ Branch 70 → 71 taken 1 time.
✗ Branch 70 → 72 not taken.
✓ Branch 71 → 73 taken 1 time.
✗ Branch 72 → 73 not taken.
✓ Branch 73 → 74 taken 1 time.
1 losses.append(images == 1 ? tr("1 image becomes an attachment") : tr("%n images become attachments", nullptr, images));
913
4/4
✓ Branch 76 → 77 taken 3 times.
✓ Branch 76 → 81 taken 1 time.
✓ Branch 77 → 78 taken 3 times.
✓ Branch 78 → 79 taken 3 times.
4 if (colours) losses.append(tr("colours are removed"));
914
1/4
✗ Branch 81 → 82 not taken.
✓ Branch 81 → 86 taken 4 times.
✗ Branch 82 → 83 not taken.
✗ Branch 83 → 84 not taken.
4 if (fonts) losses.append(tr("fonts and sizes are removed"));
915
1/4
✗ Branch 86 → 87 not taken.
✓ Branch 86 → 91 taken 4 times.
✗ Branch 87 → 88 not taken.
✗ Branch 88 → 89 not taken.
4 if (links) losses.append(tr("links keep their address in brackets"));
916
1/4
✗ Branch 91 → 92 not taken.
✓ Branch 91 → 96 taken 4 times.
✗ Branch 92 → 93 not taken.
✗ Branch 93 → 94 not taken.
4 if (headings) losses.append(tr("headings become ordinary lines"));
917
1/4
✗ Branch 96 → 97 not taken.
✓ Branch 96 → 101 taken 4 times.
✗ Branch 97 → 98 not taken.
✗ Branch 98 → 99 not taken.
4 if (tables) losses.append(tr("tables become text rows"));
918
1/4
✗ Branch 101 → 102 not taken.
✓ Branch 101 → 106 taken 4 times.
✗ Branch 102 → 103 not taken.
✗ Branch 103 → 104 not taken.
4 if (emphasis) losses.append(tr("underline, strike-through and raised text are removed"));
919 4 return losses;
920 ✗ }
921
922 3 void RichTextController::convertToPlainText() {
923
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 3 times.
3 if (!m_document) return;
924
1/1
✓ Branch 6 → 7 taken 3 times.
3 const QString text = MailTextWriter::write(*m_document);
925 3 m_applying = true;
926
1/1
✓ Branch 8 → 9 taken 3 times.
3 QTextCursor cursor(m_document);
927
1/1
✓ Branch 9 → 10 taken 3 times.
3 cursor.beginEditBlock();
928
1/1
✓ Branch 10 → 11 taken 3 times.
3 cursor.select(QTextCursor::Document);
929
1/1
✓ Branch 11 → 12 taken 3 times.
3 cursor.removeSelectedText();
930
2/2
✓ Branch 12 → 13 taken 3 times.
✓ Branch 13 → 14 taken 3 times.
3 cursor.setBlockFormat(QTextBlockFormat());
931
2/2
✓ Branch 15 → 16 taken 3 times.
✓ Branch 16 → 17 taken 3 times.
3 cursor.setCharFormat(QTextCharFormat());
932
2/2
✓ Branch 18 → 19 taken 3 times.
✓ Branch 19 → 20 taken 3 times.
3 cursor.insertText(text, QTextCharFormat());
933 // The signature after its "-- " separator stays the signature.
934 3 bool signature = false;
935
5/5
✓ Branch 22 → 23 taken 3 times.
✓ Branch 41 → 42 taken 4 times.
✓ Branch 44 → 45 taken 7 times.
✓ Branch 45 → 24 taken 4 times.
✓ Branch 45 → 46 taken 3 times.
7 for (QTextBlock block = m_document->begin(); block.isValid(); block = block.next()) {
936
2/2
✓ Branch 24 → 25 taken 4 times.
✓ Branch 25 → 26 taken 4 times.
4 signature |= block.text() == MailTextWriter::signatureSeparator();
937
1/2
✓ Branch 29 → 30 taken 4 times.
✗ Branch 29 → 31 not taken.
4 if (!signature) continue;
938 ✗ QTextCursor blockCursor(block);
939 ✗ QTextBlockFormat format = block.blockFormat();
940 ✗ format.setProperty(RegionProperty, SignatureRegion);
941 ✗ blockCursor.setBlockFormat(format);
942 ✗ }
943
1/1
✓ Branch 46 → 47 taken 3 times.
3 cursor.endEditBlock();
944 3 m_applying = false;
945 3 m_pendingValid = false;
946
1/1
✓ Branch 47 → 48 taken 3 times.
3 emitState();
947 3 }
948
949 17 void RichTextController::decorateQuotes(QTextDocument &document) {
950
1/1
✓ Branch 2 → 3 taken 17 times.
17 QTextCursor edit(&document);
951
1/1
✓ Branch 3 → 4 taken 17 times.
17 edit.beginEditBlock();
952
5/5
✓ Branch 4 → 5 taken 17 times.
✓ Branch 26 → 27 taken 61 times.
✓ Branch 29 → 30 taken 78 times.
✓ Branch 30 → 6 taken 61 times.
✓ Branch 30 → 31 taken 17 times.
78 for (QTextBlock block = document.begin(); block.isValid(); block = block.next()) {
953
1/1
✓ Branch 6 → 7 taken 61 times.
61 const int level = quoteLevel(block);
954
9/12
✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 12 taken 59 times.
✓ Branch 8 → 9 taken 2 times.
✓ Branch 9 → 10 taken 2 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 2 times.
✓ Branch 14 → 15 taken 2 times.
✓ Branch 14 → 16 taken 59 times.
✓ Branch 16 → 17 taken 59 times.
✓ Branch 16 → 18 taken 2 times.
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 36 not taken.
61 if (level <= 0 || qFuzzyCompare(block.blockFormat().leftMargin(), level * QuoteDisplayMargin)) continue;
955
1/1
✓ Branch 18 → 19 taken 2 times.
2 QTextCursor cursor(block);
956
1/1
✓ Branch 19 → 20 taken 2 times.
2 QTextBlockFormat format = block.blockFormat();
957
1/1
✓ Branch 20 → 21 taken 2 times.
2 format.setLeftMargin(level * QuoteDisplayMargin);
958
1/1
✓ Branch 21 → 22 taken 2 times.
2 cursor.setBlockFormat(format);
959 2 }
960
1/1
✓ Branch 31 → 32 taken 17 times.
17 edit.endEditBlock();
961 17 }
962
963 // ─── Typing: pending caret format and Markdown conversions ───────────────
964
965 1519 void RichTextController::onContentsChange(int from, int removed, int added) {
966 // QTextDocument reports a typed character at the end of a paragraph as
967 // (from, 1, 2): the paragraph separator counts as removed and re-added.
968 // The net count is what was typed.
969 1519 const int inserted = added - removed;
970
7/8
✓ Branch 2 → 3 taken 1278 times.
✓ Branch 2 → 6 taken 241 times.
✓ Branch 4 → 5 taken 1278 times.
✗ Branch 4 → 6 not taken.
✓ Branch 5 → 6 taken 413 times.
✓ Branch 5 → 7 taken 865 times.
✓ Branch 8 → 9 taken 654 times.
✓ Branch 8 → 10 taken 865 times.
2178 if (m_applying || !m_document || inserted <= 0) return;
971 // The document is in the middle of the TextEdit's edit: changes follow on
972 // the next turn of the event loop, after a check that the text is still
973 // what was typed.
974
5/6
✓ Branch 10 → 11 taken 24 times.
✓ Branch 10 → 22 taken 841 times.
✓ Branch 11 → 12 taken 23 times.
✓ Branch 11 → 22 taken 1 time.
✓ Branch 12 → 13 taken 23 times.
✗ Branch 12 → 22 not taken.
865 if (m_pendingValid && from == m_pendingPosition && !m_composing) {
975
1/1
✓ Branch 13 → 14 taken 23 times.
23 const QTextCharFormat format = m_pendingFormat;
976 23 const QPointer<QTextDocument> document = m_document;
977 23 m_pendingPosition = from + inserted;
978
2/6
✓ Branch 16 → 17 taken 23 times.
✓ Branch 17 → 18 taken 23 times.
✗ Branch 37 → 38 not taken.
✗ Branch 37 → 39 not taken.
✗ Branch 40 → 41 not taken.
✗ Branch 40 → 42 not taken.
23 QTimer::singleShot(0, this, [this, document, format, from, inserted] {
979
5/9
✓ Branch 3 → 4 taken 23 times.
✗ Branch 3 → 9 not taken.
✓ Branch 5 → 6 taken 23 times.
✗ Branch 5 → 9 not taken.
✓ Branch 7 → 8 taken 23 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 23 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 23 times.
23 if (!document || document != m_document || from + inserted > document->characterCount() - 1) return;
980 23 m_applying = true;
981
1/1
✓ Branch 14 → 15 taken 23 times.
23 QTextCursor cursor(document);
982
1/1
✓ Branch 15 → 16 taken 23 times.
23 cursor.setPosition(from);
983
1/1
✓ Branch 16 → 17 taken 23 times.
23 cursor.setPosition(from + inserted, QTextCursor::KeepAnchor);
984
1/1
✓ Branch 17 → 18 taken 23 times.
23 cursor.setCharFormat(format);
985 23 m_applying = false;
986
1/1
✓ Branch 18 → 19 taken 23 times.
23 emitState();
987 23 });
988 23 }
989
8/8
✓ Branch 22 → 23 taken 261 times.
✓ Branch 22 → 26 taken 604 times.
✓ Branch 23 → 24 taken 258 times.
✓ Branch 23 → 26 taken 3 times.
✓ Branch 24 → 25 taken 253 times.
✓ Branch 24 → 26 taken 5 times.
✓ Branch 25 → 26 taken 47 times.
✓ Branch 25 → 27 taken 206 times.
865 if (!m_formatted || !m_markdownTyping || m_composing || inserted != 1) return;
990
1/1
✓ Branch 28 → 29 taken 206 times.
206 const QChar typed = m_document->characterAt(from);
991
1/3
✓ Branch 30 → 31 taken 206 times.
✗ Branch 51 → 52 not taken.
✗ Branch 51 → 53 not taken.
206 QTimer::singleShot(0, this, [this, document = QPointer<QTextDocument>(m_document), from, typed] {
992
5/6
✓ Branch 3 → 4 taken 195 times.
✗ Branch 3 → 7 not taken.
✓ Branch 5 → 6 taken 191 times.
✓ Branch 5 → 7 taken 4 times.
✓ Branch 8 → 9 taken 191 times.
✓ Branch 8 → 10 taken 4 times.
195 if (document && document == m_document) applyTypingConversion(from, typed);
993 195 });
994 }
995
996 191 void RichTextController::applyTypingConversion(int position, QChar typed) {
997
9/10
✓ Branch 3 → 4 taken 191 times.
✗ Branch 3 → 11 not taken.
✓ Branch 5 → 6 taken 191 times.
✓ Branch 6 → 7 taken 187 times.
✓ Branch 6 → 11 taken 4 times.
✓ Branch 8 → 9 taken 187 times.
✓ Branch 10 → 11 taken 52 times.
✓ Branch 10 → 12 taken 135 times.
✓ Branch 13 → 14 taken 56 times.
✓ Branch 13 → 15 taken 135 times.
191 if (!m_document || position >= m_document->characterCount() - 1 || m_document->characterAt(position) != typed)
998 57 return;
999 using namespace MarkdownTyping;
1000
2/2
✓ Branch 17 → 18 taken 1 time.
✓ Branch 17 → 33 taken 134 times.
135 if (typed == QChar::ParagraphSeparator) {
1001
1/1
✓ Branch 19 → 20 taken 1 time.
1 const QTextBlock block = m_document->findBlock(position);
1002 1 Context context;
1003
1/1
✓ Branch 20 → 21 taken 1 time.
1 context.inCodeBlock = isCodeBlock(block);
1004
1/1
✓ Branch 21 → 22 taken 1 time.
1 context.inQuote = quoteLevel(block) > 0;
1005
1/1
✓ Branch 22 → 23 taken 1 time.
1 context.inList = block.textList() != nullptr;
1006
2/2
✓ Branch 23 → 24 taken 1 time.
✓ Branch 24 → 25 taken 1 time.
1 context.inHeading = block.blockFormat().headingLevel() > 0;
1007
2/2
✓ Branch 26 → 27 taken 1 time.
✓ Branch 27 → 28 taken 1 time.
1 const Conversion conversion = afterEnter(block.text(), context);
1008
2/3
✓ Branch 30 → 31 taken 1 time.
✗ Branch 30 → 32 not taken.
✓ Branch 31 → 32 taken 1 time.
1 if (!conversion.isNone()) applyConversion(block, conversion);
1009 1 return;
1010 }
1011
1/1
✓ Branch 34 → 35 taken 134 times.
134 const QTextBlock block = m_document->findBlock(position);
1012 134 Context context;
1013
1/1
✓ Branch 35 → 36 taken 134 times.
134 context.inCodeBlock = isCodeBlock(block);
1014
4/6
✓ Branch 36 → 37 taken 134 times.
✓ Branch 37 → 38 taken 134 times.
✗ Branch 37 → 40 not taken.
✓ Branch 38 → 39 taken 134 times.
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 41 taken 134 times.
134 context.inQuote = quoteLevel(block) > 0 || region(block) == QuoteRegion;
1015
1/1
✓ Branch 42 → 43 taken 134 times.
134 context.inList = block.textList() != nullptr;
1016
2/2
✓ Branch 43 → 44 taken 134 times.
✓ Branch 44 → 45 taken 134 times.
134 context.inHeading = block.blockFormat().headingLevel() > 0;
1017
1/1
✓ Branch 47 → 48 taken 134 times.
134 QTextCursor probe(m_document);
1018
1/1
✓ Branch 48 → 49 taken 134 times.
134 probe.setPosition(position);
1019
2/2
✓ Branch 49 → 50 taken 134 times.
✓ Branch 50 → 51 taken 134 times.
134 context.inInlineCode = isInlineCode(probe.charFormat());
1020
3/3
✓ Branch 52 → 53 taken 134 times.
✓ Branch 53 → 54 taken 134 times.
✓ Branch 54 → 55 taken 134 times.
134 const Conversion conversion = afterTyping(block.text(), position - block.position() + 1, typed, context);
1021
3/3
✓ Branch 57 → 58 taken 5 times.
✓ Branch 57 → 59 taken 129 times.
✓ Branch 58 → 59 taken 5 times.
134 if (!conversion.isNone()) applyConversion(block, conversion);
1022 134 }
1023
1024 6 void RichTextController::applyConversion(const QTextBlock &block, const MarkdownTyping::Conversion &conversion) {
1025 using MarkdownTyping::Kind;
1026
1/1
✓ Branch 2 → 3 taken 6 times.
6 const int base = block.position();
1027 6 m_applying = true;
1028
1/1
✓ Branch 4 → 5 taken 6 times.
6 QTextCursor cursor(m_document);
1029
1/1
✓ Branch 5 → 6 taken 6 times.
6 cursor.beginEditBlock();
1030 6 int caret = -1;
1031
3/5
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 35 taken 1 time.
✓ Branch 6 → 47 taken 3 times.
✗ Branch 6 → 93 not taken.
✗ Branch 6 → 94 not taken.
6 switch (conversion.kind) {
1032 2 case Kind::BulletList:
1033 case Kind::NumberedList:
1034 case Kind::Quote:
1035 case Kind::Heading: {
1036
1/1
✓ Branch 7 → 8 taken 2 times.
2 cursor.setPosition(base);
1037
1/1
✓ Branch 8 → 9 taken 2 times.
2 cursor.setPosition(base + conversion.prefixLength, QTextCursor::KeepAnchor);
1038
1/1
✓ Branch 9 → 10 taken 2 times.
2 cursor.removeSelectedText();
1039
2/2
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 16 taken 1 time.
2 if (conversion.kind == Kind::Heading) {
1040
1/1
✓ Branch 11 → 12 taken 1 time.
1 QTextBlockFormat format = cursor.blockFormat();
1041
1/1
✓ Branch 12 → 13 taken 1 time.
1 format.setHeadingLevel(conversion.headingLevel);
1042
1/1
✓ Branch 13 → 14 taken 1 time.
1 cursor.setBlockFormat(format);
1043
1/2
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 25 taken 1 time.
2 } else if (conversion.kind == Kind::Quote) {
1044 ✗ QTextBlockFormat format = cursor.blockFormat();
1045 ✗ format.setProperty(QTextFormat::BlockQuoteLevel, 1);
1046 ✗ format.setLeftMargin(QuoteDisplayMargin);
1047 ✗ cursor.setBlockFormat(format);
1048 ✗ } else {
1049
1/1
✓ Branch 25 → 26 taken 1 time.
1 QTextListFormat format;
1050
2/3
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 28 taken 1 time.
✓ Branch 29 → 30 taken 1 time.
1 format.setStyle(conversion.kind == Kind::NumberedList ? QTextListFormat::ListDecimal
1051 : QTextListFormat::ListDisc);
1052
1/1
✓ Branch 30 → 31 taken 1 time.
1 format.setIndent(1);
1053
1/1
✓ Branch 31 → 32 taken 1 time.
1 cursor.createList(format);
1054 1 }
1055 2 caret = base;
1056 2 break;
1057 }
1058 1 case Kind::Rule: {
1059
1/1
✓ Branch 35 → 36 taken 1 time.
1 cursor.setPosition(base);
1060
1/1
✓ Branch 36 → 37 taken 1 time.
1 cursor.setPosition(base + conversion.prefixLength, QTextCursor::KeepAnchor);
1061
1/1
✓ Branch 37 → 38 taken 1 time.
1 cursor.removeSelectedText();
1062
1/1
✓ Branch 38 → 39 taken 1 time.
1 QTextBlockFormat rule;
1063
2/2
✓ Branch 40 → 41 taken 1 time.
✓ Branch 41 → 42 taken 1 time.
1 rule.setProperty(QTextFormat::BlockTrailingHorizontalRulerWidth, QTextLength(QTextLength::PercentageLength, 100));
1064
1/1
✓ Branch 43 → 44 taken 1 time.
1 cursor.setBlockFormat(rule);
1065 1 break;
1066 1 }
1067 3 case Kind::Bold:
1068 case Kind::Italic:
1069 case Kind::Code: {
1070 3 const int open = base + conversion.openStart, close = base + conversion.closeStart;
1071 3 const int length = conversion.markerLength;
1072
1/1
✓ Branch 47 → 48 taken 3 times.
3 cursor.setPosition(close);
1073
1/1
✓ Branch 48 → 49 taken 3 times.
3 cursor.setPosition(close + length, QTextCursor::KeepAnchor);
1074
1/1
✓ Branch 49 → 50 taken 3 times.
3 cursor.removeSelectedText();
1075
1/1
✓ Branch 50 → 51 taken 3 times.
3 cursor.setPosition(base + conversion.contentStart);
1076
1/1
✓ Branch 51 → 52 taken 3 times.
3 cursor.setPosition(base + conversion.contentStart + conversion.contentLength, QTextCursor::KeepAnchor);
1077
1/1
✓ Branch 52 → 53 taken 3 times.
3 QTextCharFormat format;
1078
3/3
✓ Branch 53 → 54 taken 1 time.
✓ Branch 53 → 55 taken 2 times.
✓ Branch 54 → 72 taken 1 time.
3 if (conversion.kind == Kind::Bold) format.setFontWeight(QFont::Bold);
1079
3/3
✓ Branch 55 → 56 taken 1 time.
✓ Branch 55 → 57 taken 1 time.
✓ Branch 56 → 72 taken 1 time.
2 else if (conversion.kind == Kind::Italic) format.setFontItalic(true);
1080 else {
1081
1/1
✓ Branch 58 → 59 taken 1 time.
1 format.setProperty(CodeProperty, true);
1082
1/1
✓ Branch 60 → 61 taken 1 time.
1 format.setFontFixedPitch(true);
1083
3/5
✓ Branch 65 → 66 taken 1 time.
✓ Branch 68 → 69 taken 1 time.
✓ Branch 68 → 70 taken 1 time.
✗ Branch 125 → 126 not taken.
✗ Branch 125 → 127 not taken.
2 format.setFontFamilies({QStringLiteral("monospace")});
1084 }
1085
1/1
✓ Branch 72 → 73 taken 3 times.
3 cursor.mergeCharFormat(format);
1086
1/1
✓ Branch 73 → 74 taken 3 times.
3 cursor.setPosition(open);
1087
1/1
✓ Branch 74 → 75 taken 3 times.
3 cursor.setPosition(open + length, QTextCursor::KeepAnchor);
1088
1/1
✓ Branch 75 → 76 taken 3 times.
3 cursor.removeSelectedText();
1089 3 caret = close - length;
1090 // Typing goes on without the new format.
1091
1/1
✓ Branch 77 → 78 taken 3 times.
3 QTextCursor after(m_document);
1092
1/1
✓ Branch 78 → 79 taken 3 times.
3 after.setPosition(caret);
1093
1/1
✓ Branch 79 → 80 taken 3 times.
3 QTextCharFormat next = after.charFormat();
1094
3/3
✓ Branch 80 → 81 taken 1 time.
✓ Branch 80 → 82 taken 2 times.
✓ Branch 81 → 87 taken 1 time.
3 if (conversion.kind == Kind::Bold) next.setFontWeight(QFont::Normal);
1095
3/3
✓ Branch 82 → 83 taken 1 time.
✓ Branch 82 → 84 taken 1 time.
✓ Branch 83 → 87 taken 1 time.
2 else if (conversion.kind == Kind::Italic) next.setFontItalic(false);
1096 else {
1097
1/1
✓ Branch 84 → 85 taken 1 time.
1 next.clearProperty(CodeProperty);
1098
1/1
✓ Branch 85 → 86 taken 1 time.
1 next.clearProperty(QTextFormat::FontFixedPitch);
1099
1/1
✓ Branch 86 → 87 taken 1 time.
1 next.clearProperty(QTextFormat::FontFamilies);
1100 }
1101
1/1
✓ Branch 87 → 88 taken 3 times.
3 m_pendingFormat = next;
1102 3 m_pendingValid = true;
1103 3 m_pendingPosition = caret;
1104 3 break;
1105 3 }
1106 ✗ case Kind::None:
1107 ✗ break;
1108 }
1109
1/1
✓ Branch 94 → 95 taken 6 times.
6 cursor.endEditBlock();
1110 6 m_applying = false;
1111
3/3
✓ Branch 95 → 96 taken 5 times.
✓ Branch 95 → 97 taken 1 time.
✓ Branch 96 → 97 taken 5 times.
6 if (caret >= 0) emit typingConverted(caret);
1112
1/1
✓ Branch 97 → 98 taken 6 times.
6 emitState();
1113 8 }
1114
1115 2272 void RichTextController::emitState() { emit stateChanged(); }
1116
1117 437 QVariantMap RichTextController::state() const {
1118 437 QVariantMap state;
1119
2/2
✓ Branch 3 → 4 taken 54 times.
✓ Branch 3 → 5 taken 383 times.
437 if (!m_document) return state;
1120
1/1
✓ Branch 5 → 6 taken 383 times.
383 const QTextCharFormat format = formatAtCaret();
1121
1/1
✓ Branch 6 → 7 taken 383 times.
383 const QTextCursor cursor = selectionCursor();
1122
3/3
✓ Branch 9 → 10 taken 58 times.
✓ Branch 9 → 11 taken 325 times.
✓ Branch 12 → 13 taken 383 times.
383 const QTextBlock block = m_document->findBlock(hasSelection() ? m_selectionStart : m_cursorPosition);
1123
2/2
✓ Branch 13 → 14 taken 383 times.
✓ Branch 18 → 19 taken 383 times.
766 state.insert(QStringLiteral("bold"), format.fontWeight() >= QFont::Bold);
1124
2/2
✓ Branch 22 → 23 taken 383 times.
✓ Branch 27 → 28 taken 383 times.
766 state.insert(QStringLiteral("italic"), format.fontItalic());
1125
3/7
✓ Branch 31 → 32 taken 383 times.
✗ Branch 32 → 33 not taken.
✓ Branch 32 → 36 taken 383 times.
✗ Branch 33 → 34 not taken.
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 36 not taken.
✓ Branch 41 → 42 taken 383 times.
766 state.insert(QStringLiteral("underline"), format.fontUnderline() && !format.isAnchor());
1126
2/2
✓ Branch 45 → 46 taken 383 times.
✓ Branch 50 → 51 taken 383 times.
766 state.insert(QStringLiteral("strikeOut"), format.fontStrikeOut());
1127
2/2
✓ Branch 54 → 55 taken 383 times.
✓ Branch 59 → 60 taken 383 times.
766 state.insert(QStringLiteral("superscript"), format.verticalAlignment() == QTextCharFormat::AlignSuperScript);
1128
2/2
✓ Branch 63 → 64 taken 383 times.
✓ Branch 68 → 69 taken 383 times.
766 state.insert(QStringLiteral("subscript"), format.verticalAlignment() == QTextCharFormat::AlignSubScript);
1129
2/2
✓ Branch 72 → 73 taken 383 times.
✓ Branch 77 → 78 taken 383 times.
766 state.insert(QStringLiteral("code"), isInlineCode(format));
1130
2/2
✓ Branch 81 → 82 taken 383 times.
✓ Branch 86 → 87 taken 383 times.
766 state.insert(QStringLiteral("link"), format.isAnchor());
1131
2/2
✓ Branch 90 → 91 taken 383 times.
✓ Branch 95 → 96 taken 383 times.
766 state.insert(QStringLiteral("linkHref"), format.anchorHref());
1132
4/4
✓ Branch 100 → 101 taken 383 times.
✓ Branch 101 → 102 taken 383 times.
✓ Branch 102 → 103 taken 383 times.
✓ Branch 103 → 104 taken 383 times.
383 const FontFamily *family = fontFamilyFor(format.fontFamilies().toStringList().value(0));
1133
5/7
✓ Branch 107 → 108 taken 6 times.
✓ Branch 107 → 109 taken 377 times.
✓ Branch 116 → 117 taken 383 times.
✓ Branch 121 → 122 taken 377 times.
✓ Branch 121 → 123 taken 6 times.
✗ Branch 493 → 494 not taken.
✗ Branch 493 → 495 not taken.
1143 state.insert(QStringLiteral("fontFamily"), family ? family->key : QStringLiteral("default"));
1134
1/1
✓ Branch 132 → 133 taken 383 times.
766 state.insert(QStringLiteral("fontPointSize"),
1135
4/4
✓ Branch 123 → 124 taken 383 times.
✓ Branch 124 → 125 taken 1 time.
✓ Branch 124 → 127 taken 382 times.
✓ Branch 125 → 126 taken 1 time.
383 format.hasProperty(QTextFormat::FontPointSize) ? qRound(format.fontPointSize()) : 0);
1136
3/4
✓ Branch 138 → 139 taken 1 time.
✓ Branch 139 → 140 taken 1 time.
✗ Branch 139 → 144 not taken.
✓ Branch 149 → 150 taken 383 times.
767 state.insert(QStringLiteral("textColor"), format.hasProperty(QTextFormat::ForegroundBrush) && !format.isAnchor()
1137
7/9
✓ Branch 136 → 137 taken 383 times.
✓ Branch 137 → 138 taken 1 time.
✓ Branch 137 → 144 taken 382 times.
✓ Branch 140 → 141 taken 1 time.
✓ Branch 142 → 143 taken 1 time.
✓ Branch 154 → 155 taken 1 time.
✓ Branch 154 → 156 taken 382 times.
✗ Branch 518 → 519 not taken.
✗ Branch 518 → 520 not taken.
767 ? cssColor(format.foreground().color()) : QString());
1138
2/2
✓ Branch 156 → 157 taken 383 times.
✓ Branch 166 → 167 taken 383 times.
1149 state.insert(QStringLiteral("highlightColor"), format.hasProperty(QTextFormat::BackgroundBrush)
1139
6/8
✓ Branch 157 → 158 taken 1 time.
✓ Branch 157 → 161 taken 382 times.
✓ Branch 158 → 159 taken 1 time.
✓ Branch 160 → 162 taken 1 time.
✓ Branch 171 → 172 taken 1 time.
✓ Branch 171 → 173 taken 382 times.
✗ Branch 534 → 535 not taken.
✗ Branch 534 → 536 not taken.
766 ? cssColor(format.background().color()) : QString());
1140
3/3
✓ Branch 173 → 174 taken 383 times.
✓ Branch 174 → 175 taken 383 times.
✓ Branch 179 → 180 taken 383 times.
766 state.insert(QStringLiteral("headingLevel"), block.blockFormat().headingLevel());
1141
2/2
✓ Branch 184 → 185 taken 383 times.
✓ Branch 189 → 190 taken 383 times.
766 state.insert(QStringLiteral("codeBlock"), isCodeBlock(block));
1142
2/2
✓ Branch 193 → 194 taken 383 times.
✓ Branch 198 → 199 taken 383 times.
766 state.insert(QStringLiteral("quote"), quoteLevel(block) > 0);
1143
1/1
✓ Branch 202 → 203 taken 383 times.
383 QTextList *list = block.textList();
1144
8/10
✓ Branch 203 → 204 taken 86 times.
✓ Branch 203 → 207 taken 297 times.
✓ Branch 204 → 205 taken 86 times.
✓ Branch 205 → 206 taken 86 times.
✓ Branch 206 → 208 taken 86 times.
✓ Branch 212 → 213 taken 383 times.
✓ Branch 217 → 218 taken 86 times.
✓ Branch 217 → 219 taken 297 times.
✗ Branch 580 → 581 not taken.
✗ Branch 580 → 582 not taken.
766 state.insert(QStringLiteral("listStyle"), list ? listStyleName(list->format().style()) : QString());
1145
7/9
✓ Branch 219 → 220 taken 86 times.
✓ Branch 219 → 223 taken 297 times.
✓ Branch 220 → 221 taken 86 times.
✓ Branch 221 → 222 taken 86 times.
✓ Branch 228 → 229 taken 383 times.
✓ Branch 232 → 233 taken 86 times.
✓ Branch 232 → 234 taken 297 times.
✗ Branch 594 → 595 not taken.
✗ Branch 594 → 596 not taken.
766 state.insert(QStringLiteral("listLevel"), list ? list->format().indent() : 0);
1146 383 QString alignment = QStringLiteral("start");
1147
4/4
✓ Branch 238 → 239 taken 383 times.
✓ Branch 239 → 240 taken 383 times.
✓ Branch 241 → 242 taken 1 time.
✓ Branch 241 → 276 taken 382 times.
383 if (block.blockFormat().hasProperty(QTextFormat::BlockAlignment)) {
1148
2/2
✓ Branch 242 → 243 taken 1 time.
✓ Branch 243 → 244 taken 1 time.
1 const Qt::Alignment value = block.blockFormat().alignment();
1149
1/2
✓ Branch 247 → 248 taken 1 time.
✗ Branch 247 → 255 not taken.
2 if (value & Qt::AlignHCenter) alignment = QStringLiteral("center");
1150 ✗ else if (value & Qt::AlignJustify) alignment = QStringLiteral("justify");
1151 ✗ else if (value & Qt::AlignRight) alignment = QStringLiteral("end");
1152 }
1153
1/1
✓ Branch 280 → 281 taken 383 times.
766 state.insert(QStringLiteral("alignment"), alignment);
1154
2/2
✓ Branch 284 → 285 taken 383 times.
✓ Branch 289 → 290 taken 383 times.
766 state.insert(QStringLiteral("rightToLeft"), block.textDirection() == Qt::RightToLeft);
1155
2/2
✓ Branch 293 → 294 taken 383 times.
✓ Branch 298 → 299 taken 383 times.
766 state.insert(QStringLiteral("inTable"), cursor.currentTable() != nullptr);
1156 383 bool imageSelected = false;
1157 383 QString imageName;
1158 383 int imageWidth = 0;
1159
2/2
✓ Branch 303 → 304 taken 58 times.
✓ Branch 303 → 305 taken 325 times.
383 const int imagePosition = hasSelection() ? m_selectionStart : m_cursorPosition;
1160
6/7
✓ Branch 306 → 307 taken 383 times.
✗ Branch 306 → 311 not taken.
✓ Branch 308 → 309 taken 383 times.
✓ Branch 309 → 310 taken 97 times.
✓ Branch 309 → 311 taken 286 times.
✓ Branch 312 → 313 taken 97 times.
✓ Branch 312 → 352 taken 286 times.
383 if (imagePosition >= 0 && imagePosition < m_document->characterCount() - 1) {
1161
1/1
✓ Branch 314 → 315 taken 97 times.
97 QTextCursor probe(m_document);
1162
1/1
✓ Branch 315 → 316 taken 97 times.
97 probe.setPosition(imagePosition + 1);
1163
8/15
✓ Branch 316 → 317 taken 97 times.
✓ Branch 317 → 318 taken 97 times.
✓ Branch 318 → 319 taken 1 time.
✓ Branch 318 → 327 taken 96 times.
✗ Branch 320 → 321 not taken.
✓ Branch 320 → 326 taken 1 time.
✗ Branch 323 → 324 not taken.
✗ Branch 325 → 326 not taken.
✗ Branch 325 → 327 not taken.
✓ Branch 328 → 329 taken 97 times.
✗ Branch 328 → 330 not taken.
✓ Branch 330 → 331 taken 1 time.
✓ Branch 330 → 350 taken 96 times.
✗ Branch 635 → 636 not taken.
✗ Branch 635 → 637 not taken.
97 if (probe.charFormat().isImageFormat() && (hasSelection() || m_document->characterAt(imagePosition) == QChar::ObjectReplacementCharacter)) {
1164
2/4
✓ Branch 332 → 333 taken 1 time.
✗ Branch 332 → 335 not taken.
✓ Branch 333 → 334 taken 1 time.
✗ Branch 333 → 335 not taken.
1 imageSelected = hasSelection() && m_selectionEnd - m_selectionStart == 1;
1165
3/3
✓ Branch 336 → 337 taken 1 time.
✓ Branch 337 → 338 taken 1 time.
✓ Branch 338 → 339 taken 1 time.
1 imageName = probe.charFormat().toImageFormat().name();
1166
3/3
✓ Branch 343 → 344 taken 1 time.
✓ Branch 344 → 345 taken 1 time.
✓ Branch 345 → 346 taken 1 time.
1 imageWidth = qRound(probe.charFormat().toImageFormat().width());
1167 }
1168 97 }
1169
1/1
✓ Branch 356 → 357 taken 383 times.
766 state.insert(QStringLiteral("imageSelected"), imageSelected);
1170
1/1
✓ Branch 364 → 365 taken 383 times.
766 state.insert(QStringLiteral("imageName"), imageName);
1171
1/1
✓ Branch 372 → 373 taken 383 times.
766 state.insert(QStringLiteral("imageWidth"), imageWidth);
1172
1/1
✓ Branch 380 → 381 taken 383 times.
766 state.insert(QStringLiteral("formatted"), m_formatted);
1173 383 return state;
1174 383 }
1175