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: 95.2% 157 / 0 / 165
Functions: 93.8% 15 / 0 / 16
Branches: 82.4% 131 / 0 / 159

presentation/GraphAreasItem.cpp
Line Branch Exec Source
1 #include "GraphAreasItem.h"
2
3 #include <QElapsedTimer>
4 #include <QLoggingCategory>
5 #include <QSGGeometryNode>
6
7 #include <cmath>
8 #include <functional>
9
10
4/7
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 8 taken 30 times.
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 2 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
32 Q_LOGGING_CATEGORY(lcGraphAreas, "mailjd.graph.areas")
11
12
13 namespace {
14
15 // The outline through its edges' midpoints with quadratic corners, as the
16 // Canvas rounded it (world px, ±0.3 px).
17 32 QList<QPointF> smoothLoop(const QList<QPointF> &loop) {
18 32 const int n = int(loop.size());
19
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 32 times.
32 if (n < 3)
20 ✗ return loop;
21 32 QList<QPointF> out;
22
2/2
✓ Branch 22 → 6 taken 1198 times.
✓ Branch 22 → 23 taken 32 times.
1230 for (int k = 0; k < n; ++k) {
23 1198 const QPointF before = (loop.at((k - 1 + n) % n) + loop.at(k)) / 2;
24 1198 const QPointF after = (loop.at(k) + loop.at((k + 1) % n)) / 2;
25
1/1
✓ Branch 15 → 16 taken 1198 times.
1198 const QList<QPointF> corner = GraphGeometry::quadratic(before, loop.at(k), after, 0.3);
26
2/2
✓ Branch 17 → 18 taken 1198 times.
✓ Branch 18 → 19 taken 1198 times.
1198 out.append(corner.mid(0, corner.size() - 1)); // the next corner starts with this end
27 1198 }
28 32 return out;
29 32 }
30
31 7849 QPointF leftNormal(QPointF a, QPointF b) {
32 7849 const QPointF d = b - a;
33 7849 const double l = std::hypot(d.x(), d.y());
34
1/2
✓ Branch 5 → 6 taken 7849 times.
✗ Branch 5 → 9 not taken.
7849 return l > 1e-9 ? QPointF(-d.y() / l, d.x() / l) : QPointF(0, 0);
35 }
36
37 } // namespace
38
39
1/1
✓ Branch 9 → 10 taken 14 times.
14 GraphAreasItem::GraphAreasItem(QQuickItem *parent) : QQuickItem(parent) {
40
1/1
✓ Branch 10 → 11 taken 14 times.
14 setFlag(ItemHasContents, true);
41
1/1
✓ Branch 11 → 12 taken 14 times.
14 connect(this, &GraphAreasItem::changed, this, [this] {
42 68 m_contentChanged = true;
43 68 update();
44 68 });
45
1/1
✓ Branch 13 → 14 taken 14 times.
14 m_refresh.setSingleShot(true);
46
1/1
✓ Branch 14 → 15 taken 14 times.
14 m_refresh.setInterval(120);
47
1/1
✓ Branch 15 → 16 taken 14 times.
14 connect(&m_refresh, &QTimer::timeout, this, [this] {
48 2 m_contentChanged = true;
49 2 update();
50 2 });
51 14 }
52
53 ✗ QObject *GraphAreasItem::graph() const { return m_graph.data(); }
54
55 14 void GraphAreasItem::setGraph(QObject *graph) {
56
1/1
✓ Branch 2 → 3 taken 14 times.
14 auto *typed = qobject_cast<QuickContactGraph *>(graph);
57
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 14 times.
14 if (typed == m_graph)
58 ✗ return;
59
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 10 taken 14 times.
14 if (m_graph)
60 ✗ disconnect(m_graph, nullptr, this, nullptr);
61
1/1
✓ Branch 10 → 11 taken 14 times.
14 m_graph = typed;
62 14 m_prepared = false;
63
1/2
✓ Branch 12 → 13 taken 14 times.
✗ Branch 12 → 20 not taken.
14 if (m_graph) {
64
1/1
✓ Branch 14 → 15 taken 14 times.
14 connect(m_graph, &QuickContactGraph::viewChanged, this, &QQuickItem::update);
65
1/1
✓ Branch 17 → 18 taken 14 times.
14 connect(m_graph, &QuickContactGraph::territoriesChanged, this, [this] {
66 33 m_prepared = false;
67 33 m_contentChanged = true;
68 33 update();
69 33 });
70 }
71
1/1
✓ Branch 20 → 21 taken 14 times.
14 emit changed();
72 }
73
74 48 void GraphAreasItem::setColours(const QVariantList &values) {
75 48 m_coloursIn = values;
76 48 m_colours.clear();
77
2/2
✓ Branch 11 → 6 taken 35 times.
✓ Branch 11 → 12 taken 48 times.
83 for (const QVariant &value : values)
78
2/2
✓ Branch 7 → 8 taken 35 times.
✓ Branch 8 → 9 taken 35 times.
35 m_colours.append(value.value<QColor>());
79 48 emit changed();
80 48 }
81
82 29 void GraphAreasItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) {
83 29 QQuickItem::geometryChange(newGeometry, oldGeometry);
84 29 update();
85 29 }
86
87 // Once per set of territories: simplified, rounded outlines, which side of
88 // each is inside (asked of the territory's own field), and the fill.
89 32 void GraphAreasItem::prepare() {
90
1/1
✓ Branch 2 → 3 taken 32 times.
32 m_shapes.clear();
91 32 m_prepared = true;
92
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 32 times.
32 if (!m_graph)
93 ✗ return;
94 32 QElapsedTimer timer;
95 32 timer.start();
96
2/2
✓ Branch 119 → 11 taken 31 times.
✓ Branch 119 → 120 taken 32 times.
63 for (const RelationGraphTerritories::Territory &territory : m_graph->territoryShapes()) {
97 31 Shape shape;
98
2/2
✓ Branch 111 → 14 taken 32 times.
✓ Branch 111 → 112 taken 31 times.
63 for (const QList<QPointF> &loop : territory.loops) {
99
2/2
✓ Branch 15 → 16 taken 32 times.
✓ Branch 16 → 17 taken 32 times.
32 const QList<QPointF> smooth = smoothLoop(GraphGeometry::simplified(loop, 0.6));
100
1/2
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 21 taken 32 times.
32 if (smooth.size() < 3)
101 ✗ continue;
102 // Most points decide (a narrow neck may sample the other side).
103 32 int inside = 0, samples = 0;
104 32 const double probe = std::max(1.0, territory.field.step);
105
2/2
✓ Branch 39 → 23 taken 893 times.
✓ Branch 39 → 40 taken 32 times.
925 for (int i = 0; i < smooth.size(); i += std::max(1, int(smooth.size()) / 24)) {
106 893 const QPointF a = smooth.at(i), b = smooth.at((i + 1) % smooth.size());
107 893 const QPointF at = (a + b) / 2 + leftNormal(a, b) * probe;
108
2/3
✓ Branch 31 → 32 taken 893 times.
✗ Branch 32 → 33 not taken.
✓ Branch 32 → 34 taken 893 times.
893 inside += territory.field.at(at) > 0 ? 1 : 0;
109 893 ++samples;
110 }
111
1/2
✗ Branch 40 → 41 not taken.
✓ Branch 40 → 42 taken 32 times.
32 const double sign = 2 * inside >= samples ? 1 : -1;
112 // The glow along the inside never leaves the territory (the Canvas
113 // clipped it): per point the depth the field stays positive along the
114 // inward normal, up to the glow's 22 px at the strongest zoom-in.
115 32 QList<QPointF> normals;
116 32 QList<double> depth;
117 32 const int n = int(smooth.size());
118
2/2
✓ Branch 73 → 45 taken 3478 times.
✓ Branch 73 → 74 taken 32 times.
3510 for (int i = 0; i < n; ++i) {
119 3478 QPointF normal = leftNormal(smooth.at((i - 1 + n) % n), smooth.at(i)) + leftNormal(smooth.at(i), smooth.at((i + 1) % n));
120 3478 const double length = std::hypot(normal.x(), normal.y());
121
1/2
✓ Branch 54 → 55 taken 3478 times.
✗ Branch 54 → 57 not taken.
3478 normal = length > 1e-9 ? normal / length * sign : leftNormal(smooth.at(i), smooth.at((i + 1) % n)) * sign;
122 3478 double reach = 0;
123
2/2
✓ Branch 69 → 62 taken 202194 times.
✓ Branch 69 → 70 taken 2945 times.
205139 for (double t = probe / 2; t <= 120; t += probe / 2) {
124
3/3
✓ Branch 65 → 66 taken 202194 times.
✓ Branch 66 → 67 taken 533 times.
✓ Branch 66 → 68 taken 201661 times.
202194 if (territory.field.at(smooth.at(i) + normal * t) <= 0)
125 533 break;
126 201661 reach = t;
127 }
128
1/1
✓ Branch 70 → 71 taken 3478 times.
3478 normals.append(normal);
129
1/1
✓ Branch 71 → 72 taken 3478 times.
3478 depth.append(reach);
130 }
131 // Neighbouring depths may differ a lot at a corner: the band's quads
132 // would fold over each other (a darker wedge). The smallest depth
133 // nearby, then the mean nearby, keeps them in order.
134
2/2
✓ Branch 74 → 75 taken 32 times.
✓ Branch 75 → 76 taken 32 times.
32 QList<double> eroded(n), even(n);
135
2/2
✓ Branch 85 → 77 taken 3478 times.
✓ Branch 85 → 86 taken 32 times.
3510 for (int i = 0; i < n; ++i) {
136 3478 double least = depth.at(i);
137
2/2
✓ Branch 82 → 79 taken 31302 times.
✓ Branch 82 → 83 taken 3478 times.
34780 for (int d = -4; d <= 4; ++d)
138 31302 least = std::min(least, depth.at((i + d + n) % n));
139
1/1
✓ Branch 83 → 84 taken 3478 times.
3478 eroded[i] = least;
140 }
141
2/2
✓ Branch 93 → 87 taken 3478 times.
✓ Branch 93 → 94 taken 32 times.
3510 for (int i = 0; i < n; ++i) {
142 3478 double sum = 0;
143
2/2
✓ Branch 90 → 88 taken 17390 times.
✓ Branch 90 → 91 taken 3478 times.
20868 for (int d = -2; d <= 2; ++d)
144 17390 sum += eroded.at((i + d + n) % n);
145
1/1
✓ Branch 91 → 92 taken 3478 times.
3478 even[i] = sum / 5;
146 }
147 32 depth = even;
148
1/1
✓ Branch 95 → 96 taken 32 times.
32 shape.loops.append(smooth);
149
1/1
✓ Branch 96 → 97 taken 32 times.
32 shape.inward.append(sign);
150
1/1
✓ Branch 97 → 98 taken 32 times.
32 shape.normals.append(normals);
151
1/1
✓ Branch 98 → 99 taken 32 times.
32 shape.depth.append(depth);
152 32 }
153
1/1
✓ Branch 112 → 113 taken 31 times.
31 shape.fill = GraphGeometry::triangulate(shape.loops);
154
1/1
✓ Branch 115 → 116 taken 31 times.
31 m_shapes.append(shape);
155 31 }
156
8/8
✓ Branch 120 → 121 taken 32 times.
✓ Branch 124 → 125 taken 32 times.
✓ Branch 126 → 127 taken 32 times.
✓ Branch 127 → 128 taken 32 times.
✓ Branch 129 → 130 taken 32 times.
✓ Branch 130 → 131 taken 32 times.
✓ Branch 134 → 122 taken 32 times.
✓ Branch 134 → 135 taken 32 times.
64 qCDebug(lcGraphAreas) << m_shapes.size() << "territories prepared in" << timer.elapsed() << "ms";
157 }
158
159 // A faint tint, a glow along the inside of the border and a crisp border.
160 // `emphasis` 0 normal, 1 pointed at, 2 its name pointed at; `faded` while
161 // another family's name is pointed at.
162 49 void GraphAreasItem::paintTerritory(GraphGeometry::Sink *mesh, int index, int emphasis, bool faded) const {
163 49 const Shape &shape = m_shapes.at(index);
164
2/4
✓ Branch 4 → 5 taken 49 times.
✗ Branch 4 → 10 not taken.
✓ Branch 7 → 8 taken 49 times.
✗ Branch 7 → 10 not taken.
49 const QColor colour = index < m_colours.size() && m_colours.at(index).isValid() ? m_colours.at(index) : QColor(Qt::gray);
165
1/2
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 49 times.
49 const double dim = faded ? 0.35 : 1;
166 49 QList<QPointF> fill;
167
1/1
✓ Branch 15 → 16 taken 49 times.
49 fill.reserve(shape.fill.size());
168
2/2
✓ Branch 24 → 18 taken 17034 times.
✓ Branch 24 → 25 taken 49 times.
17083 for (const QPointF &p : shape.fill)
169
1/1
✓ Branch 21 → 22 taken 17034 times.
17034 fill.append(p * m_k + m_t);
170
4/5
✓ Branch 25 → 26 taken 49 times.
✗ Branch 25 → 30 not taken.
✓ Branch 26 → 27 taken 2 times.
✓ Branch 26 → 28 taken 47 times.
✓ Branch 31 → 32 taken 49 times.
49 mesh->fill(fill, colour, (emphasis == 2 ? 0.16 : emphasis == 1 ? 0.12 : 0.075) * dim);
171 // The Canvas glow: six clipped strokes of 0.03 each, 44 → 4 px wide, so
172 // 0.18 at the border fading to nothing 22 px inside.
173
2/2
✓ Branch 32 → 33 taken 2 times.
✓ Branch 32 → 34 taken 47 times.
49 const double glow = 0.18 * (emphasis > 0 ? 1.5 : 1) * dim;
174
2/2
✓ Branch 69 → 36 taken 50 times.
✓ Branch 69 → 70 taken 49 times.
99 for (int l = 0; l < shape.loops.size(); ++l) {
175 50 const QList<QPointF> &loop = shape.loops.at(l);
176 50 QList<QPointF> outer, inner;
177 50 const int n = int(loop.size());
178
1/1
✓ Branch 38 → 39 taken 50 times.
50 outer.reserve(n);
179
1/1
✓ Branch 39 → 40 taken 50 times.
50 inner.reserve(n);
180
2/2
✓ Branch 54 → 41 taken 5778 times.
✓ Branch 54 → 55 taken 50 times.
5828 for (int i = 0; i < n; ++i) {
181 5778 const QPointF at = loop.at(i) * m_k + m_t;
182
1/1
✓ Branch 44 → 45 taken 5778 times.
5778 outer.append(at);
183
1/1
✓ Branch 52 → 53 taken 5778 times.
5778 inner.append(at + shape.normals.at(l).at(i) * std::min(22.0, shape.depth.at(l).at(i) * m_k));
184 }
185
1/1
✓ Branch 55 → 56 taken 50 times.
50 mesh->band(outer, inner, colour, glow, 0, true);
186
5/5
✓ Branch 56 → 57 taken 2 times.
✓ Branch 56 → 58 taken 48 times.
✓ Branch 60 → 61 taken 2 times.
✓ Branch 60 → 62 taken 48 times.
✓ Branch 63 → 64 taken 50 times.
50 mesh->stroke(outer, emphasis > 0 ? 2.5 : 1.5, {}, colour, (emphasis > 0 ? 1 : 0.7) * dim, nullptr, true);
187 50 }
188 49 }
189
190 285 QSGNode *GraphAreasItem::updatePaintNode(QSGNode *old, UpdatePaintNodeData *) {
191
2/2
✓ Branch 2 → 3 taken 32 times.
✓ Branch 2 → 4 taken 253 times.
285 if (!m_prepared)
192
1/1
✓ Branch 3 → 4 taken 32 times.
32 prepare();
193
1/2
✓ Branch 5 → 6 taken 285 times.
✗ Branch 5 → 8 not taken.
285 m_k = m_graph ? m_graph->viewScale() : 1;
194
3/4
✓ Branch 10 → 11 taken 285 times.
✗ Branch 10 → 16 not taken.
✓ Branch 12 → 13 taken 285 times.
✓ Branch 14 → 15 taken 285 times.
285 m_t = m_graph ? QPointF(m_graph->viewX(), m_graph->viewY()) : QPointF();
195 285 bool refresh = false;
196 285 int vertices = m_vertexCount;
197
1/1
✓ Branch 18 → 19 taken 285 times.
285 QSGNode *node = m_cache.update(this, old, m_k, m_t, m_contentChanged, [this](GraphGeometry::Sink *sink) {
198
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 41 times.
41 if (!m_graph)
199 ✗ return;
200
2/2
✓ Branch 11 → 6 taken 49 times.
✓ Branch 11 → 12 taken 41 times.
90 for (int a = 0; a < m_shapes.size(); ++a)
201
3/4
✓ Branch 6 → 7 taken 47 times.
✓ Branch 6 → 9 taken 2 times.
✓ Branch 7 → 8 taken 47 times.
✗ Branch 7 → 9 not taken.
49 if (a != m_pointed && a != m_hovered)
202 47 paintTerritory(sink, a, 0, m_hovered >= 0);
203 // The pointed territory last, so its border lies on top of a shared one.
204
6/8
✓ Branch 12 → 13 taken 2 times.
✓ Branch 12 → 17 taken 39 times.
✓ Branch 13 → 14 taken 2 times.
✗ Branch 13 → 17 not taken.
✓ Branch 15 → 16 taken 2 times.
✗ Branch 15 → 17 not taken.
✓ Branch 18 → 19 taken 2 times.
✓ Branch 18 → 20 taken 39 times.
41 if (m_pointed >= 0 && m_pointed != m_hovered && m_pointed < m_shapes.size())
205 2 paintTerritory(sink, m_pointed, 1, m_hovered >= 0);
206
2/6
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 24 taken 41 times.
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 24 not taken.
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 27 taken 41 times.
41 if (m_hovered >= 0 && m_hovered < m_shapes.size())
207 ✗ paintTerritory(sink, m_hovered, 2, false);
208 }, &vertices, &refresh);
209 285 m_contentChanged = false;
210 285 m_refreshPending = refresh;
211 285 m_vertexCount = vertices;
212
1/1
✓ Branch 20 → 21 taken 285 times.
285 QMetaObject::invokeMethod(this, [this, refresh] {
213
2/2
✓ Branch 2 → 3 taken 242 times.
✓ Branch 2 → 4 taken 42 times.
284 if (refresh)
214 242 m_refresh.start();
215 284 emit built();
216 284 }, Qt::QueuedConnection);
217 285 return node;
218 }
219