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: 28.6% 32 / 0 / 112
Functions: 40.0% 2 / 0 / 5
Branches: 24.5% 27 / 0 / 110

ui/LabelDelegate.cpp
Line Branch Exec Source
1 #include "LabelDelegate.h"
2
3 #include <QPainter>
4
5 #include "ui/MailListModel.h"
6 #include "ui/ThemeManager.h"
7
8 ✗ LabelDelegate::LabelDelegate(QObject *parent)
9 ✗ : QStyledItemDelegate(parent) {}
10
11 ✗ void LabelDelegate::paint(QPainter *painter,
12 const QStyleOptionViewItem &option,
13 const QModelIndex &index) const {
14 // Let the base class handle selection highlighting, fonts, etc.
15 ✗ QStyleOptionViewItem opt = option;
16 ✗ initStyleOption(&opt, index);
17
18 ✗ auto &tm = ThemeManager::instance();
19
20 // Draw selection background
21 ✗ painter->save();
22 ✗ if (opt.state & QStyle::State_Selected) {
23 ✗ painter->fillRect(opt.rect, QColor(tm.color("@bg_selected")));
24 ✗ } else if (opt.state & QStyle::State_MouseOver) {
25 ✗ painter->fillRect(opt.rect, QColor(tm.color("@bg_hover")));
26 }
27
28 // Sprint 70: row separator removed — selection/hover backgrounds
29 // carry the separation alone (see SPRINT-70.md T-70.1).
30
31 // Draw subject text
32 ✗ QString subject = index.data(Qt::DisplayRole).toString();
33 ✗ QStringList labels = index.data(MailListModel::LabelsRole).toStringList();
34
35 ✗ QRect textRect = opt.rect.adjusted(8, 0, 0, 0);
36
37 // Set font (bold for unread)
38 ✗ QFont font = opt.font;
39 ✗ QVariant fontVar = index.data(Qt::FontRole);
40 ✗ if (fontVar.isValid()) {
41 ✗ font = fontVar.value<QFont>();
42 }
43 ✗ painter->setFont(font);
44
45 // Text color
46 ✗ painter->setPen(QColor(tm.color("@text_primary")));
47
48 // Draw subject text, measure how much space it used
49 ✗ QFontMetrics fm(font);
50 QString elidedSubject = fm.elidedText(
51 subject, Qt::ElideRight,
52 ✗ labels.isEmpty() ? textRect.width() : textRect.width() - 120);
53 ✗ painter->drawText(textRect, Qt::AlignVCenter | Qt::AlignLeft, elidedSubject);
54
55 // Draw label chips after subject text
56 ✗ if (!labels.isEmpty()) {
57 ✗ int chipX = textRect.left() + fm.horizontalAdvance(elidedSubject) + 8;
58 ✗ int chipY = textRect.center().y() - 7;
59 ✗ int chipHeight = 14;
60
61 ✗ QFont chipFont = painter->font();
62 ✗ qreal basePt = chipFont.pointSizeF();
63 ✗ if (basePt > 0) {
64 ✗ chipFont.setPointSizeF(basePt * 0.8);
65 } else {
66 // Pixel-sized font: scale via pixel size
67 ✗ int px = chipFont.pixelSize();
68 ✗ if (px > 0)
69 ✗ chipFont.setPixelSize(qMax(1, static_cast<int>(px * 0.8)));
70 }
71 ✗ chipFont.setBold(false);
72 ✗ painter->setFont(chipFont);
73 ✗ QFontMetrics chipFm(chipFont);
74
75 ✗ for (int i = 0; i < labels.size(); ++i) {
76 ✗ const auto &label = labels[i];
77 ✗ QString name = displayName(label);
78 ✗ QColor color = colorForLabel(label);
79
80 ✗ int textWidth = chipFm.horizontalAdvance(name);
81 ✗ int chipWidth = textWidth + 10;
82
83 // Check if this chip fits; reserve space for "+N" if more follow
84 ✗ int remaining = labels.size() - i - 1;
85 ✗ int reservedWidth = 0;
86 ✗ if (remaining > 0) {
87 ✗ QString overflowText = QStringLiteral("+%1").arg(remaining);
88 ✗ reservedWidth = chipFm.horizontalAdvance(overflowText) + 14;
89 ✗ }
90
91 ✗ if (chipX + chipWidth > opt.rect.right() - 4 - reservedWidth) {
92 // Not enough room — render "+N" overflow indicator
93 ✗ int overflowCount = labels.size() - i;
94 ✗ QString overflowText = QStringLiteral("+%1").arg(overflowCount);
95 ✗ int owWidth = chipFm.horizontalAdvance(overflowText) + 10;
96 ✗ QRect owRect(chipX, chipY, owWidth, chipHeight);
97
98 ✗ painter->setRenderHint(QPainter::Antialiasing, true);
99 ✗ painter->setPen(Qt::NoPen);
100 ✗ painter->setBrush(ThemeManager::labelPalette().at(6)); // gray
101 ✗ painter->drawRoundedRect(owRect, 3, 3);
102 ✗ painter->setPen(Qt::white);
103 ✗ painter->drawText(owRect, Qt::AlignCenter, overflowText);
104 ✗ break;
105 ✗ }
106
107 ✗ QRect chipRect(chipX, chipY, chipWidth, chipHeight);
108
109 // Draw chip background (rounded)
110 ✗ painter->setRenderHint(QPainter::Antialiasing, true);
111 ✗ painter->setPen(Qt::NoPen);
112 ✗ painter->setBrush(color);
113 ✗ painter->drawRoundedRect(chipRect, 3, 3);
114
115 // Draw chip text (white on colored background)
116 ✗ painter->setPen(Qt::white);
117 ✗ painter->drawText(chipRect, Qt::AlignCenter, name);
118
119 ✗ chipX += chipWidth + 4;
120 ✗ }
121 ✗ }
122
123 ✗ painter->restore();
124 ✗ }
125
126 ✗ QSize LabelDelegate::sizeHint(const QStyleOptionViewItem &option,
127 const QModelIndex &index) const {
128 // Sprint 70: removed the qMax(base.height(), 22) floor that forced
129 // Subject-column rows to a 22px minimum. Row height is now driven
130 // purely by font metrics + QSS padding (1px vertical in main.qss),
131 // giving ~17-18px rows at 13px font. The Subject delegate no longer
132 // dictates the tallest column; setUniformRowHeights(true) on the
133 // QTreeView ensures all columns agree on the same compact height.
134 ✗ return QStyledItemDelegate::sizeHint(option, index);
135 }
136
137 2946 QColor LabelDelegate::colorForLabel(const QString &label) {
138 // Standard Thunderbird/IMAP label colors (shared palette, 67.B3):
139 // red, orange, green, blue, purple, gold, gray
140
1/1
✓ Branch 2 → 3 taken 2946 times.
2946 const QList<QColor> palette = ThemeManager::labelPalette();
141
2/2
✓ Branch 5 → 6 taken 118 times.
✓ Branch 5 → 8 taken 2828 times.
2946 if (label == QLatin1String("$label1"))
142 118 return palette.at(0); // Red
143
2/2
✓ Branch 10 → 11 taken 224 times.
✓ Branch 10 → 13 taken 2604 times.
2828 if (label == QLatin1String("$label2"))
144 224 return palette.at(1); // Orange
145
2/2
✓ Branch 15 → 16 taken 83 times.
✓ Branch 15 → 18 taken 2521 times.
2604 if (label == QLatin1String("$label3"))
146 83 return palette.at(2); // Green
147
2/2
✓ Branch 20 → 21 taken 117 times.
✓ Branch 20 → 23 taken 2404 times.
2521 if (label == QLatin1String("$label4"))
148 117 return palette.at(3); // Blue
149
2/2
✓ Branch 25 → 26 taken 32 times.
✓ Branch 25 → 28 taken 2372 times.
2404 if (label == QLatin1String("$label5"))
150 32 return palette.at(4); // Purple
151
1/2
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 33 taken 2372 times.
2372 if (label.compare(QLatin1String("$Important"), Qt::CaseInsensitive) == 0)
152 ✗ return palette.at(5); // Gold
153
1/2
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 38 taken 2372 times.
2372 if (label.compare(QLatin1String("Junk"), Qt::CaseInsensitive) == 0)
154 ✗ return palette.at(6); // Gray
155
156 // Hash-based color for unknown labels
157 2372 uint hash = qHash(label);
158 2372 int hue = hash % 360;
159
1/1
✓ Branch 38 → 39 taken 2372 times.
2372 return QColor::fromHsl(hue, 140, 100);
160 2946 }
161
162 3316 QString LabelDelegate::displayName(const QString &label) {
163 // Map internal names to friendly display names
164
2/2
✓ Branch 4 → 5 taken 119 times.
✓ Branch 4 → 6 taken 3197 times.
3316 if (label == QLatin1String("$label1"))
165 119 return tr("Important");
166
2/2
✓ Branch 8 → 9 taken 226 times.
✓ Branch 8 → 10 taken 2971 times.
3197 if (label == QLatin1String("$label2"))
167 226 return tr("Work");
168
2/2
✓ Branch 12 → 13 taken 83 times.
✓ Branch 12 → 14 taken 2888 times.
2971 if (label == QLatin1String("$label3"))
169 83 return tr("Personal");
170
2/2
✓ Branch 16 → 17 taken 117 times.
✓ Branch 16 → 18 taken 2771 times.
2888 if (label == QLatin1String("$label4"))
171 117 return tr("To Do");
172
2/2
✓ Branch 20 → 21 taken 32 times.
✓ Branch 20 → 22 taken 2739 times.
2771 if (label == QLatin1String("$label5"))
173 32 return tr("Later");
174
1/2
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 30 taken 2739 times.
2739 if (label.compare(QLatin1String("$Important"), Qt::CaseInsensitive) == 0)
175 ✗ return QStringLiteral("Important");
176
177 // Strip leading $ for custom labels
178
2/3
✓ Branch 31 → 32 taken 2739 times.
✗ Branch 32 → 33 not taken.
✓ Branch 32 → 34 taken 2739 times.
2739 if (label.startsWith('$'))
179 ✗ return label.mid(1);
180
181 2739 return label;
182 }
183