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: 94.7% 180 / 0 / 190
Functions: 93.8% 15 / 0 / 16
Branches: 89.3% 226 / 0 / 253

presentation/KeyOverlayLayout.cpp
Line Branch Exec Source
1 #include "KeyOverlayLayout.h"
2
3 #include <QHash>
4 #include <QLineF>
5 #include <algorithm>
6 #include <cmath>
7
8 namespace {
9 constexpr qreal kGap = 2.0; // between two badges
10 constexpr qreal kCalloutGap = 12.0; // between a badge and its callout
11 constexpr qreal kSpotPadding = 6.0;
12 constexpr qreal kSpotMerge = 8.0;
13 constexpr int kStaggerStepMs = 6;
14 constexpr int kStaggerMaxMs = 80;
15 constexpr int kCalloutLineMs = 180;
16 constexpr int kCalloutStepMs = 30;
17
18
1/1
✓ Branch 3 → 4 taken 48921 times.
48921 qreal distance(const QPointF &a, const QPointF &b) { return QLineF(a, b).length(); }
19
20 26278 bool overlapsAny(const QRectF &rect, const QList<QRectF> &others, qreal gap) {
21 26278 const QRectF grown = rect.adjusted(-gap, -gap, gap, gap);
22
1/1
✓ Branch 5 → 6 taken 26278 times.
323900 return std::any_of(others.cbegin(), others.cend(), [&](const QRectF &other) { return grown.intersects(other); });
23 }
24
25 // Corner anchors in preference order per element kind (left/right as in a
26 // left-to-right layout; mirrored for right-to-left).
27 enum class Corner { TopLeft, TopRight, BottomLeft, BottomRight, RightCentre, LeftCentre };
28
29 5765 QList<Corner> corners(KeyOverlayLayout::Kind kind) {
30
4/6
✓ Branch 2 → 3 taken 5340 times.
✓ Branch 2 → 4 taken 104 times.
✗ Branch 2 → 5 not taken.
✓ Branch 2 → 6 taken 172 times.
✓ Branch 2 → 8 taken 149 times.
✗ Branch 2 → 9 not taken.
5765 switch (kind) {
31 5340 case KeyOverlayLayout::Kind::Button: return {Corner::BottomRight, Corner::TopRight, Corner::BottomLeft, Corner::TopLeft};
32 104 case KeyOverlayLayout::Kind::Chip: return {Corner::TopRight, Corner::BottomRight, Corner::TopLeft, Corner::BottomLeft};
33 ✗ case KeyOverlayLayout::Kind::Field: return {Corner::RightCentre, Corner::BottomRight, Corner::TopRight};
34
1/1
✓ Branch 6 → 7 taken 172 times.
172 case KeyOverlayLayout::Kind::Pane: return {Corner::TopLeft, Corner::TopRight};
35 149 case KeyOverlayLayout::Kind::Row: return {Corner::LeftCentre, Corner::RightCentre};
36 }
37 ✗ return {Corner::BottomRight};
38 }
39
40 4 Corner mirrored(Corner corner) {
41
4/7
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 1 time.
✓ Branch 2 → 5 taken 1 time.
✓ Branch 2 → 6 taken 1 time.
✗ Branch 2 → 7 not taken.
✗ Branch 2 → 8 not taken.
✗ Branch 2 → 9 not taken.
4 switch (corner) {
42 1 case Corner::TopLeft: return Corner::TopRight;
43 1 case Corner::TopRight: return Corner::TopLeft;
44 1 case Corner::BottomLeft: return Corner::BottomRight;
45 1 case Corner::BottomRight: return Corner::BottomLeft;
46 ✗ case Corner::RightCentre: return Corner::LeftCentre;
47 ✗ case Corner::LeftCentre: return Corner::RightCentre;
48 }
49 ✗ return corner;
50 }
51
52 // Outside kinds (buttons, chips) overlap their element by 30 % of the
53 // badge at a corner; inside kinds (panes, rows, fields) keep the badge 4 px
54 // inside the element.
55 22418 QRectF placeAt(const QRectF &element, const QSizeF &size, Corner corner, KeyOverlayLayout::Kind kind) {
56 22418 const qreal w = size.width(), h = size.height();
57
2/2
✓ Branch 5 → 6 taken 21776 times.
✓ Branch 5 → 7 taken 298 times.
22074 const bool inside = kind == KeyOverlayLayout::Kind::Pane || kind == KeyOverlayLayout::Kind::Row
58
3/4
✓ Branch 4 → 5 taken 22074 times.
✓ Branch 4 → 7 taken 344 times.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 21776 times.
44492 || kind == KeyOverlayLayout::Kind::Field;
59
2/2
✓ Branch 9 → 10 taken 642 times.
✓ Branch 9 → 12 taken 21776 times.
22418 const qreal left = inside ? element.left() + 4.0 : element.left() - 0.7 * w;
60
2/2
✓ Branch 14 → 15 taken 642 times.
✓ Branch 14 → 17 taken 21776 times.
22418 const qreal right = inside ? element.right() - w - 4.0 : element.right() - 0.3 * w;
61
2/2
✓ Branch 19 → 20 taken 642 times.
✓ Branch 19 → 22 taken 21776 times.
22418 const qreal top = inside ? element.top() + 4.0 : element.top() - 0.7 * h;
62
2/2
✓ Branch 24 → 25 taken 642 times.
✓ Branch 24 → 27 taken 21776 times.
22418 const qreal bottom = inside ? element.bottom() - h - 4.0 : element.bottom() - 0.3 * h;
63 22418 const qreal middle = element.center().y() - h / 2;
64
6/7
✓ Branch 31 → 32 taken 5616 times.
✓ Branch 31 → 33 taken 5616 times.
✓ Branch 31 → 34 taken 5444 times.
✓ Branch 31 → 35 taken 5444 times.
✓ Branch 31 → 36 taken 149 times.
✓ Branch 31 → 38 taken 149 times.
✗ Branch 31 → 40 not taken.
22418 switch (corner) {
65 5616 case Corner::TopLeft: return {left, top, w, h};
66 5616 case Corner::TopRight: return {right, top, w, h};
67 5444 case Corner::BottomLeft: return {left, bottom, w, h};
68 5444 case Corner::BottomRight: return {right, bottom, w, h};
69 149 case Corner::RightCentre: return {element.right() - w - 4.0, middle, w, h};
70 149 case Corner::LeftCentre: return {element.left() + 4.0, middle, w, h};
71 }
72 ✗ return {element.topLeft(), size};
73 }
74
75 90956 QRectF clampInto(QRectF rect, const QRectF &bounds) {
76
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 90956 times.
90956 if (bounds.isEmpty()) return rect;
77
1/2
✓ Branch 7 → 8 taken 90956 times.
✗ Branch 7 → 18 not taken.
90956 if (rect.width() <= bounds.width()) {
78
2/2
✓ Branch 10 → 11 taken 1030 times.
✓ Branch 10 → 13 taken 89926 times.
90956 if (rect.left() < bounds.left()) rect.moveLeft(bounds.left());
79
2/2
✓ Branch 15 → 16 taken 1491 times.
✓ Branch 15 → 18 taken 89465 times.
90956 if (rect.right() > bounds.right()) rect.moveRight(bounds.right());
80 }
81
2/2
✓ Branch 20 → 21 taken 90868 times.
✓ Branch 20 → 31 taken 88 times.
90956 if (rect.height() <= bounds.height()) {
82
2/2
✓ Branch 23 → 24 taken 1535 times.
✓ Branch 23 → 26 taken 89333 times.
90868 if (rect.top() < bounds.top()) rect.moveTop(bounds.top());
83
2/2
✓ Branch 28 → 29 taken 2137 times.
✓ Branch 28 → 31 taken 88731 times.
90868 if (rect.bottom() > bounds.bottom()) rect.moveBottom(bounds.bottom());
84 }
85 90956 return rect;
86 }
87
88 10795 bool segmentsCross(const QLineF &a, const QLineF &b) {
89 10795 QPointF point;
90
2/3
✓ Branch 2 → 3 taken 10795 times.
✓ Branch 3 → 4 taken 10795 times.
✗ Branch 3 → 5 not taken.
10795 if (a.intersects(b, &point) != QLineF::BoundedIntersection) return false;
91 // Lines that only touch at their ends (two callouts of one badge) do not cross.
92 ✗ const auto near = [&](const QPointF &p) { return distance(point, p) < 0.5; };
93 ✗ return !(near(a.p1()) || near(a.p2()));
94 }
95
96 10058 QPointF nearestPoint(const QRectF &rect, const QPointF &point) {
97
2/2
✓ Branch 5 → 6 taken 10058 times.
✓ Branch 9 → 10 taken 10058 times.
10058 return {std::clamp(point.x(), rect.left(), rect.right()), std::clamp(point.y(), rect.top(), rect.bottom())};
98 }
99 } // namespace
100
101 282 int KeyOverlayLayout::badgeBudget(const QSizeF &window) {
102 282 const qreal extra = window.width() * window.height() - 1280.0 * 800.0;
103
2/2
✓ Branch 4 → 5 taken 104 times.
✓ Branch 4 → 6 taken 178 times.
282 return 24 + (extra > 0 ? int(extra / 40000.0) : 0);
104 }
105
106 281 QList<QRectF> KeyOverlayLayout::spotlights(const QList<QRectF> &elements) {
107 281 QList<QRectF> rects;
108
2/2
✓ Branch 9 → 4 taken 5676 times.
✓ Branch 9 → 10 taken 281 times.
5957 for (const QRectF &element : elements)
109
1/1
✓ Branch 6 → 7 taken 5676 times.
5676 rects.append(element.adjusted(-kSpotPadding, -kSpotPadding, kSpotPadding, kSpotPadding));
110 281 bool merged = true;
111
2/2
✓ Branch 40 → 11 taken 2821 times.
✓ Branch 40 → 41 taken 281 times.
3102 while (merged) {
112 2821 merged = false;
113
6/6
✓ Branch 35 → 36 taken 14421 times.
✓ Branch 35 → 38 taken 376 times.
✓ Branch 36 → 37 taken 11976 times.
✓ Branch 36 → 38 taken 2445 times.
✓ Branch 39 → 12 taken 11976 times.
✓ Branch 39 → 40 taken 2821 times.
14797 for (int i = 0; i < rects.size() && !merged; ++i)
114
6/6
✓ Branch 28 → 29 taken 150511 times.
✓ Branch 28 → 31 taken 9802 times.
✓ Branch 29 → 30 taken 148337 times.
✓ Branch 29 → 31 taken 2174 times.
✓ Branch 32 → 13 taken 148337 times.
✓ Branch 32 → 33 taken 11976 times.
160313 for (int j = i + 1; j < rects.size() && !merged; ++j) {
115
1/1
✓ Branch 13 → 14 taken 148337 times.
148337 const QRectF grown = rects[i].adjusted(-kSpotMerge, -kSpotMerge, kSpotMerge, kSpotMerge);
116
3/3
✓ Branch 15 → 16 taken 148337 times.
✓ Branch 17 → 18 taken 145797 times.
✓ Branch 17 → 19 taken 2540 times.
148337 if (!grown.intersects(rects[j])) continue;
117
3/3
✓ Branch 19 → 20 taken 2540 times.
✓ Branch 20 → 21 taken 2540 times.
✓ Branch 22 → 23 taken 2540 times.
2540 rects[i] = rects[i].united(rects[j]);
118
1/1
✓ Branch 23 → 24 taken 2540 times.
2540 rects.removeAt(j);
119 2540 merged = true;
120 }
121 }
122 281 return rects;
123 ✗ }
124
125 280 KeyOverlayLayout::Result KeyOverlayLayout::compute(const QList<Entry> &entries, const Input &input) {
126 280 Result result;
127 280 const QRectF windowRect(QPointF(0, 0), input.window);
128 280 const QRectF inner = windowRect.adjusted(2, 2, -2, -2);
129
2/2
✓ Branch 6 → 7 taken 6 times.
✓ Branch 6 → 8 taken 274 times.
280 const QPointF focusCentre = input.focus.isEmpty() ? windowRect.center() : input.focus.center();
130
131 struct Ranked {
132 const Entry *entry;
133 QRectF visible;
134 qreal score;
135 };
136 280 QList<Ranked> ranked;
137
2/2
✓ Branch 32 → 11 taken 7843 times.
✓ Branch 32 → 33 taken 280 times.
8123 for (const Entry &entry : entries) {
138
2/2
✓ Branch 13 → 14 taken 7251 times.
✓ Branch 13 → 15 taken 592 times.
7843 const QRectF clip = entry.clip.isEmpty() ? windowRect : entry.clip & windowRect;
139 7843 const QRectF visible = entry.rect & clip;
140
5/6
✓ Branch 18 → 19 taken 7841 times.
✓ Branch 18 → 21 taken 2 times.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 7841 times.
✓ Branch 23 → 24 taken 2 times.
✓ Branch 23 → 25 taken 7841 times.
7843 if (visible.isEmpty() || entry.badgeSize.isEmpty()) continue;
141
1/1
✓ Branch 26 → 27 taken 7841 times.
7841 const qreal nearness = 1.0 / (1.0 + distance(visible.center(), focusCentre) / 400.0);
142
1/1
✓ Branch 27 → 28 taken 7841 times.
7841 ranked.append({&entry, visible, entry.priority * nearness});
143 }
144 // The way around (areas, rules, settings, menu) is never crowded out by
145 // the many daily keys near the focus (user review 2026-10-12).
146
3/3
✓ Branch 33 → 34 taken 280 times.
✓ Branch 34 → 35 taken 280 times.
✓ Branch 35 → 36 taken 280 times.
280 std::stable_sort(ranked.begin(), ranked.end(), [](const Ranked &a, const Ranked &b) {
147 35391 const bool pinnedA = a.entry->priority >= kPinned, pinnedB = b.entry->priority >= kPinned;
148
2/2
✓ Branch 2 → 3 taken 1741 times.
✓ Branch 2 → 4 taken 33650 times.
35391 if (pinnedA != pinnedB) return pinnedA;
149
1/2
✓ Branch 4 → 5 taken 33650 times.
✗ Branch 4 → 6 not taken.
33650 if (a.score != b.score) return a.score > b.score;
150 ✗ return a.entry->id < b.entry->id;
151 });
152 // Then each pane's best kPerPane, then everything else (each by score).
153 280 QHash<QString, int> perPane;
154 280 QList<Ranked> pinned, reserved, rest;
155
2/2
✓ Branch 57 → 39 taken 7841 times.
✓ Branch 57 → 58 taken 280 times.
8121 for (const Ranked &rank : std::as_const(ranked)) {
156
2/2
✓ Branch 40 → 41 taken 421 times.
✓ Branch 40 → 42 taken 7420 times.
7841 if (rank.entry->priority >= kPinned) {
157
1/1
✓ Branch 41 → 55 taken 421 times.
421 pinned.append(rank);
158
8/8
✓ Branch 43 → 44 taken 675 times.
✓ Branch 43 → 48 taken 6745 times.
✓ Branch 44 → 45 taken 503 times.
✓ Branch 44 → 47 taken 172 times.
✓ Branch 46 → 47 taken 260 times.
✓ Branch 46 → 48 taken 243 times.
✓ Branch 49 → 50 taken 432 times.
✓ Branch 49 → 54 taken 6988 times.
7420 } else if (!rank.entry->pane.isEmpty() && (rank.entry->kind == Kind::Pane || perPane.value(rank.entry->pane) < kPerPane)) {
159
3/3
✓ Branch 50 → 51 taken 260 times.
✓ Branch 50 → 53 taken 172 times.
✓ Branch 51 → 52 taken 260 times.
432 if (rank.entry->kind != Kind::Pane) ++perPane[rank.entry->pane]; // the pane's own label is extra
160
1/1
✓ Branch 53 → 55 taken 432 times.
432 reserved.append(rank);
161 } else {
162
1/1
✓ Branch 54 → 55 taken 6988 times.
6988 rest.append(rank);
163 }
164 }
165
2/2
✓ Branch 58 → 59 taken 280 times.
✓ Branch 59 → 60 taken 280 times.
280 ranked = pinned + reserved + rest;
166
167
2/2
✓ Branch 63 → 64 taken 1 time.
✓ Branch 63 → 65 taken 279 times.
280 const int budget = input.maxBadges >= 0 ? input.maxBadges : badgeBudget(input.window);
168 280 QList<QRectF> placed;
169 280 QList<QRectF> placedElements;
170 280 QList<const Ranked *> placedRanks;
171 280 QList<QRectF> obstacles;
172
3/3
✓ Branch 67 → 68 taken 73 times.
✓ Branch 67 → 69 taken 207 times.
✓ Branch 68 → 69 taken 73 times.
280 if (!input.legend.isEmpty()) obstacles.append(input.legend);
173
1/3
✗ Branch 72 → 73 not taken.
✗ Branch 75 → 71 not taken.
✓ Branch 75 → 76 taken 280 times.
280 for (const QRectF &card : input.paneLegends) obstacles.append(card);
174
4/4
✓ Branch 76 → 77 taken 280 times.
✓ Branch 77 → 78 taken 280 times.
✓ Branch 161 → 79 taken 7841 times.
✓ Branch 161 → 162 taken 280 times.
8121 for (const Ranked &rank : ranked) {
175
2/2
✓ Branch 81 → 82 taken 2076 times.
✓ Branch 81 → 83 taken 5765 times.
7841 if (result.badges.size() >= budget) { ++result.droppedBadges; continue; }
176 5765 const Entry &entry = *rank.entry;
177 5765 const QSizeF size = entry.badgeSize;
178
2/2
✓ Branch 84 → 85 taken 5220 times.
✓ Branch 84 → 86 taken 545 times.
5765 const QRectF clip = (entry.clip.isEmpty() ? windowRect : entry.clip & windowRect) & inner;
179 // The focused element is an obstacle only for elements outside it.
180 5765 QList<QRectF> blocked = obstacles;
181
7/7
✓ Branch 90 → 91 taken 5752 times.
✓ Branch 90 → 94 taken 13 times.
✓ Branch 92 → 93 taken 5336 times.
✓ Branch 92 → 94 taken 416 times.
✓ Branch 95 → 96 taken 5336 times.
✓ Branch 95 → 97 taken 429 times.
✓ Branch 96 → 97 taken 5336 times.
5765 if (!input.focus.isEmpty() && !input.focus.intersects(rank.visible)) blocked.append(input.focus);
182 5765 QList<QRectF> candidates;
183
5/5
✓ Branch 97 → 98 taken 5765 times.
✓ Branch 98 → 99 taken 5765 times.
✓ Branch 99 → 100 taken 5765 times.
✓ Branch 111 → 101 taken 22418 times.
✓ Branch 111 → 112 taken 5765 times.
28183 for (Corner corner : corners(entry.kind)) {
184
2/2
✓ Branch 102 → 103 taken 4 times.
✓ Branch 102 → 104 taken 22414 times.
22418 const Corner side = input.rtl ? mirrored(corner) : corner;
185
1/1
✓ Branch 108 → 109 taken 22418 times.
22418 candidates.append(clampInto(clampInto(placeAt(rank.visible, size, side, entry.kind), clip), inner));
186 5765 }
187 // Nudges of the first corner along x, then y (overlay.md §4.3).
188
1/1
✓ Branch 113 → 114 taken 5765 times.
5765 const QRectF first = candidates.value(0);
189
2/2
✓ Branch 130 → 116 taken 11530 times.
✓ Branch 130 → 131 taken 5765 times.
17295 for (const qreal step : {0.5, 1.0}) {
190 11530 const qreal dx = size.height() * step, dy = size.height() * step;
191
2/2
✓ Branch 128 → 124 taken 46120 times.
✓ Branch 128 → 129 taken 11530 times.
57650 for (const QPointF offset : {QPointF(-dx, 0), QPointF(dx, 0), QPointF(0, -dy), QPointF(0, dy)})
192
1/1
✓ Branch 126 → 127 taken 46120 times.
46120 candidates.append(clampInto(first.translated(offset), inner));
193 }
194 5765 bool done = false;
195
2/2
✓ Branch 152 → 134 taken 7596 times.
✓ Branch 152 → 153 taken 92 times.
7688 for (const QRectF &candidate : std::as_const(candidates)) {
196
8/8
✓ Branch 135 → 136 taken 7596 times.
✓ Branch 136 → 137 taken 6496 times.
✓ Branch 136 → 139 taken 1100 times.
✓ Branch 137 → 138 taken 6496 times.
✓ Branch 138 → 139 taken 823 times.
✓ Branch 138 → 140 taken 5673 times.
✓ Branch 141 → 142 taken 1923 times.
✓ Branch 141 → 143 taken 5673 times.
7596 if (overlapsAny(candidate, placed, kGap) || overlapsAny(candidate, blocked, 0.0)) continue;
197
1/1
✓ Branch 143 → 144 taken 5673 times.
5673 placed.append(candidate);
198
1/1
✓ Branch 144 → 145 taken 5673 times.
5673 placedElements.append(rank.visible);
199
1/1
✓ Branch 145 → 146 taken 5673 times.
5673 placedRanks.append(&rank);
200 5673 result.badges.append({entry.id, candidate, 0});
201 5673 done = true;
202 5673 break;
203 }
204
2/2
✓ Branch 153 → 154 taken 92 times.
✓ Branch 153 → 155 taken 5673 times.
5765 if (!done) ++result.droppedBadges;
205 5765 }
206
207 // Stagger from the focus outwards.
208
1/1
✓ Branch 163 → 164 taken 280 times.
280 QList<int> order(result.badges.size());
209
3/3
✓ Branch 165 → 166 taken 5673 times.
✓ Branch 168 → 165 taken 5673 times.
✓ Branch 168 → 169 taken 280 times.
5953 for (int i = 0; i < order.size(); ++i) order[i] = i;
210
3/3
✓ Branch 169 → 170 taken 280 times.
✓ Branch 170 → 171 taken 280 times.
✓ Branch 171 → 172 taken 280 times.
280 std::stable_sort(order.begin(), order.end(), [&](int a, int b) {
211
4/4
✓ Branch 2 → 3 taken 20540 times.
✓ Branch 4 → 5 taken 20540 times.
✓ Branch 5 → 6 taken 20540 times.
✓ Branch 7 → 8 taken 20540 times.
20540 return distance(result.badges[a].rect.center(), focusCentre) < distance(result.badges[b].rect.center(), focusCentre);
212 });
213
2/2
✓ Branch 178 → 173 taken 5673 times.
✓ Branch 178 → 179 taken 280 times.
5953 for (int rank = 0; rank < order.size(); ++rank)
214
2/2
✓ Branch 174 → 175 taken 5673 times.
✓ Branch 175 → 176 taken 5673 times.
5673 result.badges[order[rank]].delayMs = std::min(kStaggerMaxMs, rank * kStaggerStepMs);
215
1/1
✓ Branch 179 → 180 taken 280 times.
280 result.spotlights = spotlights(placedElements);
216
217
3/4
✓ Branch 182 → 183 taken 264 times.
✓ Branch 182 → 184 taken 16 times.
✗ Branch 183 → 184 not taken.
✓ Branch 183 → 185 taken 264 times.
280 if (!input.explanations || input.maxCallouts <= 0) return result;
218 // Callouts: the placed badges with an explanation, by score.
219 264 QList<QRectF> calloutRects;
220 264 QList<QLineF> lines;
221
1/1
✓ Branch 185 → 186 taken 264 times.
264 QList<QRectF> calloutBlocked = placed + obstacles;
222
2/3
✓ Branch 187 → 188 taken 264 times.
✗ Branch 187 → 189 not taken.
✓ Branch 188 → 189 taken 264 times.
264 if (!input.focus.isEmpty()) calloutBlocked.append(input.focus);
223
2/2
✓ Branch 341 → 190 taken 5470 times.
✓ Branch 341 → 342 taken 264 times.
5734 for (int index = 0; index < result.badges.size(); ++index) {
224
1/1
✓ Branch 190 → 191 taken 5470 times.
5470 const Badge &badge = result.badges[index];
225
1/1
✓ Branch 191 → 192 taken 5470 times.
5470 const Entry &entry = *placedRanks[index]->entry;
226
2/2
✓ Branch 193 → 194 taken 3932 times.
✓ Branch 193 → 195 taken 1538 times.
5964 if (entry.calloutSize.isEmpty()) continue;
227
3/3
✓ Branch 196 → 197 taken 147 times.
✓ Branch 196 → 199 taken 1391 times.
✓ Branch 197 → 198 taken 147 times.
1538 if (result.callouts.size() >= input.maxCallouts) { result.droppedCallouts.append(entry.id); continue; }
228 1391 const QSizeF size = entry.calloutSize;
229 1391 const QRectF b = badge.rect;
230 1391 const qreal g = kCalloutGap;
231 const QList<QRectF> options = {
232 2782 {b.right() + g, b.center().y() - size.height() / 2, size.width(), size.height()},
233 2782 {b.left() - g - size.width(), b.center().y() - size.height() / 2, size.width(), size.height()},
234 1391 {b.center().x() - size.width() / 2, b.bottom() + g, size.width(), size.height()},
235 2782 {b.center().x() - size.width() / 2, b.top() - g - size.height(), size.width(), size.height()},
236 1391 {b.right() + g, b.bottom() + g, size.width(), size.height()},
237 1391 {b.left() - g - size.width(), b.bottom() + g, size.width(), size.height()},
238 2782 {b.right() + g, b.top() - g - size.height(), size.width(), size.height()},
239 2782 {b.left() - g - size.width(), b.top() - g - size.height(), size.width(), size.height()},
240
1/1
✓ Branch 253 → 254 taken 1391 times.
18083 };
241 1391 qreal bestScore = 0;
242 1391 int best = -1;
243
2/2
✓ Branch 294 → 255 taken 11128 times.
✓ Branch 294 → 295 taken 1391 times.
12519 for (int option = 0; option < options.size(); ++option) {
244
1/2
✗ Branch 255 → 256 not taken.
✓ Branch 255 → 265 taken 11128 times.
11128 const QRectF rect = input.rtl ? QRectF(b.center().x() * 2 - options[option].right(), options[option].top(),
245 size.width(), size.height())
246 11128 : options[option];
247
2/2
✓ Branch 268 → 269 taken 3396 times.
✓ Branch 268 → 270 taken 7732 times.
14875 if (!inner.contains(rect)) continue;
248
8/8
✓ Branch 270 → 271 taken 7732 times.
✓ Branch 271 → 272 taken 4454 times.
✓ Branch 271 → 274 taken 3278 times.
✓ Branch 272 → 273 taken 4454 times.
✓ Branch 273 → 274 taken 469 times.
✓ Branch 273 → 275 taken 3985 times.
✓ Branch 276 → 277 taken 3747 times.
✓ Branch 276 → 278 taken 3985 times.
7732 if (overlapsAny(rect, calloutBlocked, 0.0) || overlapsAny(rect, calloutRects, 4.0)) continue;
249
2/2
✓ Branch 279 → 280 taken 3985 times.
✓ Branch 281 → 282 taken 3985 times.
3985 const QLineF line(nearestPoint(b, rect.center()), nearestPoint(rect, b.center()));
250
1/1
✓ Branch 285 → 286 taken 3985 times.
3985 const int crossings = int(std::count_if(lines.cbegin(), lines.cend(),
251 10795 [&](const QLineF &other) { return segmentsCross(line, other); }));
252
1/1
✓ Branch 286 → 287 taken 3985 times.
3985 const qreal score = line.length() + 500.0 * crossings + option; // option: a stable tie-break
253
3/4
✓ Branch 287 → 288 taken 2941 times.
✓ Branch 287 → 289 taken 1044 times.
✗ Branch 288 → 289 not taken.
✓ Branch 288 → 290 taken 2941 times.
3985 if (best < 0 || score < bestScore) { best = option; bestScore = score; }
254 }
255
3/3
✓ Branch 295 → 296 taken 347 times.
✓ Branch 295 → 298 taken 1044 times.
✓ Branch 296 → 297 taken 347 times.
1391 if (best < 0) { result.droppedCallouts.append(entry.id); continue; }
256
1/2
✗ Branch 298 → 299 not taken.
✓ Branch 298 → 308 taken 1044 times.
1044 const QRectF rect = input.rtl ? QRectF(b.center().x() * 2 - options[best].right(), options[best].top(),
257 size.width(), size.height())
258 1044 : options[best];
259
1/1
✓ Branch 311 → 312 taken 1044 times.
1044 const QPointF from = nearestPoint(b, rect.center());
260
1/1
✓ Branch 313 → 314 taken 1044 times.
1044 const QPointF to = nearestPoint(rect, b.center());
261 1044 const QLineF line(from, to);
262 // A gentle curve: the control point sits 20 % of the length off the middle.
263
3/4
✓ Branch 315 → 316 taken 1044 times.
✓ Branch 316 → 317 taken 1044 times.
✗ Branch 316 → 322 not taken.
✓ Branch 317 → 318 taken 1044 times.
1044 const QPointF normal = line.length() > 0 ? QPointF(-line.dy(), line.dx()) / line.length() : QPointF();
264
1/1
✓ Branch 323 → 324 taken 1044 times.
1044 const QPointF control = line.center() + normal * (0.2 * line.length());
265
1/1
✓ Branch 327 → 328 taken 1044 times.
1044 calloutRects.append(rect);
266
1/1
✓ Branch 328 → 329 taken 1044 times.
1044 lines.append(line);
267 1044 result.callouts.append({entry.id, rect, from, control, to, kCalloutLineMs + int(result.callouts.size()) * kCalloutStepMs});
268 1391 }
269 264 return result;
270 6997 }
271