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: 99.0% 101 / 0 / 102
Functions: 100.0% 10 / 0 / 10
Branches: 80.3% 114 / 0 / 142

presentation/TextMarksItem.cpp
Line Branch Exec Source
1 #include "TextMarksItem.h"
2
3 #include <QAbstractTextDocumentLayout>
4 #include <QPainter>
5 #include <QPainterPath>
6 #include <QQuickTextDocument>
7 #include <QTextBlock>
8 #include <QTextDocument>
9 #include <QTextLayout>
10 #include <QtQml>
11
12 #include <mutex>
13
14 150 TextMarksItem::TextMarksItem(QQuickItem *parent) : QQuickPaintedItem(parent) {
15
1/1
✓ Branch 8 → 9 taken 150 times.
150 setAntialiasing(true);
16 // The marks follow the text's size and place; a new size repaints.
17
1/1
✓ Branch 9 → 10 taken 150 times.
150 connect(this, &QQuickItem::widthChanged, this, &TextMarksItem::schedule);
18
1/1
✓ Branch 11 → 12 taken 150 times.
150 connect(this, &QQuickItem::heightChanged, this, &TextMarksItem::schedule);
19 150 }
20
21 874 void TextMarksItem::registerTypes() {
22 static std::once_flag once;
23
1/1
✓ Branch 2 → 3 taken 874 times.
900 std::call_once(once, [] { qmlRegisterType<TextMarksItem>("MailJD.Text", 1, 0, "TextMarks"); });
24 874 }
25
26 150 void TextMarksItem::setTextItem(QQuickItem *item) {
27
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 150 times.
150 if (m_textItem == item) return;
28
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 9 taken 150 times.
150 if (m_textItem) disconnect(m_textItem, nullptr, this, nullptr);
29 150 m_textItem = item;
30
1/2
✓ Branch 11 → 12 taken 150 times.
✗ Branch 11 → 29 not taken.
150 if (m_textItem) {
31 // A rewrap or a padding change moves the marks; the document may be
32 // replaced (a view reused for another message).
33
2/2
✓ Branch 18 → 14 taken 600 times.
✓ Branch 18 → 19 taken 150 times.
750 for (auto signal : {&QQuickItem::widthChanged, &QQuickItem::heightChanged, &QQuickItem::xChanged, &QQuickItem::yChanged})
34
1/1
✓ Branch 15 → 16 taken 600 times.
600 connect(m_textItem, signal, this, &TextMarksItem::schedule);
35
1/2
✓ Branch 22 → 23 taken 150 times.
✗ Branch 22 → 29 not taken.
150 if (m_textItem->metaObject()->indexOfSignal("textChanged()") >= 0)
36
3/3
✓ Branch 23 → 24 taken 150 times.
✓ Branch 24 → 25 taken 150 times.
✓ Branch 26 → 27 taken 150 times.
150 connect(m_textItem, SIGNAL(textChanged()), this, SLOT(schedule()));
37 }
38 150 emit textItemChanged();
39 150 schedule();
40 }
41
42 155 void TextMarksItem::setScrollOffset(QPointF offset) {
43
2/2
✓ Branch 3 → 4 taken 150 times.
✓ Branch 3 → 5 taken 5 times.
155 if (m_scrollOffset == offset) return;
44 5 m_scrollOffset = offset;
45 5 emit scrollOffsetChanged();
46 5 schedule();
47 }
48
49 1072 QTextDocument *TextMarksItem::document() const {
50
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1072 times.
1072 if (!m_textItem) return nullptr;
51
2/2
✓ Branch 6 → 7 taken 1072 times.
✓ Branch 7 → 8 taken 1072 times.
1072 auto *quick = m_textItem->property("textDocument").value<QQuickTextDocument *>();
52
1/2
✓ Branch 9 → 10 taken 1072 times.
✗ Branch 9 → 12 not taken.
1072 return quick ? quick->textDocument() : nullptr;
53 }
54
55 536 void TextMarksItem::watchDocument() {
56
1/1
✓ Branch 2 → 3 taken 536 times.
536 QTextDocument *current = document();
57
2/2
✓ Branch 4 → 5 taken 408 times.
✓ Branch 4 → 6 taken 128 times.
536 if (current == m_document) return;
58
1/3
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 10 taken 128 times.
✗ Branch 9 → 10 not taken.
128 if (m_document) disconnect(m_document, nullptr, this, nullptr);
59
1/1
✓ Branch 10 → 11 taken 128 times.
128 m_document = current;
60
1/2
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 128 times.
128 if (!m_document) return;
61 // A highlighter's new formats re-lay the block out: the layout reports
62 // it, and every edit does too.
63
1/1
✓ Branch 15 → 16 taken 128 times.
128 connect(m_document, &QTextDocument::contentsChanged, this, &TextMarksItem::schedule);
64
2/3
✓ Branch 18 → 19 taken 128 times.
✓ Branch 19 → 20 taken 128 times.
✗ Branch 19 → 23 not taken.
128 if (auto *layout = m_document->documentLayout())
65
1/1
✓ Branch 20 → 21 taken 128 times.
128 connect(layout, &QAbstractTextDocumentLayout::update, this, &TextMarksItem::schedule);
66 }
67
68 536 QList<TextMarksItem::Mark> TextMarksItem::collect() const {
69 536 QList<Mark> marks;
70
1/1
✓ Branch 2 → 3 taken 536 times.
536 QTextDocument *doc = document();
71
7/12
✓ Branch 3 → 4 taken 536 times.
✗ Branch 3 → 10 not taken.
✓ Branch 5 → 6 taken 536 times.
✗ Branch 5 → 10 not taken.
✓ Branch 6 → 7 taken 536 times.
✓ Branch 7 → 8 taken 536 times.
✗ Branch 7 → 10 not taken.
✓ Branch 8 → 9 taken 536 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 536 times.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 536 times.
536 if (!doc || !m_textItem || width() <= 0 || height() <= 0) return marks;
72
1/1
✓ Branch 14 → 15 taken 536 times.
536 QAbstractTextDocumentLayout *layout = doc->documentLayout();
73
1/2
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 536 times.
536 if (!layout) return marks;
74 // QQuickTextEdit draws the document at its padding.
75
1/1
✓ Branch 25 → 26 taken 536 times.
1072 const QPointF origin = m_textItem->mapToItem(
76
4/4
✓ Branch 19 → 20 taken 536 times.
✓ Branch 20 → 21 taken 536 times.
✓ Branch 22 → 23 taken 536 times.
✓ Branch 23 → 24 taken 536 times.
1072 this, QPointF(m_textItem->property("leftPadding").toReal(), m_textItem->property("topPadding").toReal()));
77 536 const qreal top = -origin.y();
78
1/1
✓ Branch 29 → 30 taken 536 times.
536 const qreal bottom = top + height();
79
1/1
✓ Branch 32 → 33 taken 536 times.
536 const int first = layout->hitTest(QPointF(0, std::max<qreal>(0, top)), Qt::FuzzyHit);
80
5/5
✓ Branch 34 → 35 taken 536 times.
✓ Branch 109 → 110 taken 1891 times.
✓ Branch 112 → 113 taken 2427 times.
✓ Branch 113 → 36 taken 1935 times.
✓ Branch 113 → 114 taken 492 times.
2427 for (QTextBlock block = doc->findBlock(std::max(0, first)); block.isValid(); block = block.next()) {
81
1/1
✓ Branch 36 → 37 taken 1935 times.
1935 const QRectF blockRect = layout->blockBoundingRect(block);
82
2/2
✓ Branch 38 → 39 taken 44 times.
✓ Branch 38 → 40 taken 1891 times.
1935 if (blockRect.top() > bottom) break;
83
4/7
✓ Branch 41 → 42 taken 1891 times.
✗ Branch 41 → 44 not taken.
✓ Branch 42 → 43 taken 1891 times.
✗ Branch 43 → 44 not taken.
✓ Branch 43 → 45 taken 1891 times.
✗ Branch 46 → 47 not taken.
✓ Branch 46 → 48 taken 1891 times.
1891 if (blockRect.bottom() < top || !block.isVisible()) continue;
84
1/1
✓ Branch 48 → 49 taken 1891 times.
1891 const QTextLayout *text = block.layout();
85
1/2
✗ Branch 49 → 50 not taken.
✓ Branch 49 → 51 taken 1891 times.
1891 if (!text) continue;
86
5/5
✓ Branch 51 → 52 taken 1891 times.
✓ Branch 52 → 53 taken 1891 times.
✓ Branch 53 → 54 taken 1891 times.
✓ Branch 101 → 55 taken 2223 times.
✓ Branch 101 → 102 taken 1891 times.
4114 for (const QTextLayout::FormatRange &range : text->formats()) {
87
6/7
✓ Branch 56 → 57 taken 2223 times.
✓ Branch 57 → 58 taken 201 times.
✓ Branch 57 → 59 taken 2022 times.
✗ Branch 58 → 59 not taken.
✓ Branch 58 → 60 taken 201 times.
✓ Branch 61 → 62 taken 2022 times.
✓ Branch 61 → 63 taken 201 times.
2223 if (range.format.underlineStyle() != QTextCharFormat::WaveUnderline || range.length <= 0) continue;
88
3/4
✓ Branch 63 → 64 taken 201 times.
✓ Branch 65 → 66 taken 201 times.
✗ Branch 65 → 67 not taken.
✓ Branch 66 → 68 taken 201 times.
201 const QColor color = range.format.underlineColor().isValid() ? range.format.underlineColor() : QColor(Qt::red);
89 // A word may wrap: one mark per line it covers.
90 201 int position = range.start;
91 201 const int end = range.start + range.length;
92
2/2
✓ Branch 96 → 69 taken 201 times.
✓ Branch 96 → 97 taken 201 times.
402 while (position < end) {
93
1/1
✓ Branch 69 → 70 taken 201 times.
201 const QTextLine line = text->lineForTextPosition(position);
94
1/2
✗ Branch 71 → 72 not taken.
✓ Branch 71 → 73 taken 201 times.
201 if (!line.isValid()) break;
95
2/2
✓ Branch 73 → 74 taken 201 times.
✓ Branch 74 → 75 taken 201 times.
201 const int lineEnd = std::min(end, line.textStart() + line.textLength());
96
1/1
✓ Branch 76 → 77 taken 201 times.
201 const qreal x1 = line.cursorToX(position);
97
1/1
✓ Branch 77 → 78 taken 201 times.
201 const qreal x2 = line.cursorToX(lineEnd);
98 201 Mark mark;
99 201 mark.x = origin.x() + blockRect.left() + std::min(x1, x2);
100
2/2
✓ Branch 83 → 84 taken 201 times.
✓ Branch 84 → 85 taken 201 times.
201 mark.y = origin.y() + blockRect.top() + line.y() + line.ascent() + 2;
101 201 mark.width = std::abs(x2 - x1);
102 201 mark.color = color;
103
2/3
✓ Branch 86 → 87 taken 201 times.
✗ Branch 86 → 88 not taken.
✓ Branch 87 → 88 taken 201 times.
201 if (mark.width > 0) marks.append(mark);
104
1/2
✗ Branch 89 → 90 not taken.
✓ Branch 89 → 91 taken 201 times.
201 if (marks.size() >= MaxMarks) return marks;
105
1/2
✗ Branch 91 → 92 not taken.
✓ Branch 91 → 93 taken 201 times.
201 if (lineEnd <= position) break;
106 201 position = lineEnd;
107 }
108 1891 }
109 }
110 536 return marks;
111 ✗ }
112
113 536 void TextMarksItem::updatePolish() {
114 // The document a reused TextEdit shows may have changed since.
115
1/1
✓ Branch 2 → 3 taken 536 times.
536 watchDocument();
116
1/1
✓ Branch 3 → 4 taken 536 times.
536 m_marks = collect();
117 536 QVariantList drawnList;
118
1/1
✓ Branch 7 → 8 taken 536 times.
536 drawnList.reserve(m_marks.size());
119
2/2
✓ Branch 40 → 11 taken 201 times.
✓ Branch 40 → 41 taken 536 times.
737 for (const Mark &mark : std::as_const(m_marks))
120
3/5
✓ Branch 26 → 27 taken 201 times.
✓ Branch 30 → 31 taken 603 times.
✓ Branch 30 → 32 taken 201 times.
✗ Branch 55 → 56 not taken.
✗ Branch 55 → 57 not taken.
1407 drawnList.append(QVariantMap{{QStringLiteral("x"), mark.x}, {QStringLiteral("y"), mark.y}, {QStringLiteral("width"), mark.width}});
121
3/3
✓ Branch 41 → 42 taken 536 times.
✓ Branch 42 → 43 taken 57 times.
✓ Branch 42 → 45 taken 479 times.
536 if (drawnList != m_drawnList) {
122 57 m_drawnList = drawnList;
123
1/1
✓ Branch 44 → 45 taken 57 times.
57 emit drawnChanged();
124 }
125
1/1
✓ Branch 46 → 47 taken 536 times.
536 update();
126 1340 }
127
128 365 void TextMarksItem::paint(QPainter *painter) {
129 // A wave of 4 px per period, 1.5 px high: the usual spelling mark.
130 365 constexpr qreal period = 4.0;
131 365 constexpr qreal amplitude = 1.5;
132
2/2
✓ Branch 25 → 5 taken 178 times.
✓ Branch 25 → 26 taken 365 times.
543 for (const Mark &mark : std::as_const(m_marks)) {
133
1/1
✓ Branch 7 → 8 taken 178 times.
178 QPainterPath path(QPointF(mark.x, mark.y));
134
2/2
✓ Branch 15 → 9 taken 3188 times.
✓ Branch 15 → 16 taken 178 times.
3366 for (qreal x = 0; x < mark.width; x += period / 2) {
135 3188 const qreal next = std::min(mark.width, x + period / 2);
136 3188 const bool up = int(x / (period / 2)) % 2 == 0;
137
3/3
✓ Branch 10 → 11 taken 1641 times.
✓ Branch 10 → 12 taken 1547 times.
✓ Branch 13 → 14 taken 3188 times.
3188 path.lineTo(mark.x + next, mark.y + (up ? -amplitude : 0));
138 }
139
3/3
✓ Branch 16 → 17 taken 178 times.
✓ Branch 17 → 18 taken 178 times.
✓ Branch 18 → 19 taken 178 times.
178 painter->setPen(QPen(mark.color, 1.0));
140
1/1
✓ Branch 21 → 22 taken 178 times.
178 painter->drawPath(path);
141 178 }
142 365 }
143