MailJD nbsp;·nbsp; Test Dashboard nbsp;·nbsp; Coverage
LCOV - code coverage report
Current view: top level - ui - LabelDelegate.cpp (source / functions) Coverage Total Hit
Test: MailJD Coverage (Unit + E2E) Lines: 92.0 % 112 103
Test Date: 2026-06-21 21:10:19 Functions: 100.0 % 5 5
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 56.9 % 174 99

             Branch data     Line data    Source code
       1                 :             : #include "LabelDelegate.h"
       2                 :             : 
       3                 :             : #include <QPainter>
       4                 :             : 
       5                 :             : #include "ui/MailListModel.h"
       6                 :             : #include "ui/ThemeManager.h"
       7                 :             : 
       8                 :          64 : LabelDelegate::LabelDelegate(QObject *parent)
       9                 :          64 :     : QStyledItemDelegate(parent) {}
      10                 :             : 
      11                 :        2386 : 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         [ +  - ]:        2386 :   QStyleOptionViewItem opt = option;
      16         [ +  - ]:        2386 :   initStyleOption(&opt, index);
      17                 :             : 
      18         [ +  - ]:        2386 :   auto &tm = ThemeManager::instance();
      19                 :             : 
      20                 :             :   // Draw selection background
      21         [ +  - ]:        2386 :   painter->save();
      22         [ +  + ]:        2386 :   if (opt.state & QStyle::State_Selected) {
      23   [ +  -  +  -  :         154 :     painter->fillRect(opt.rect, QColor(tm.color("@bg_selected")));
                   +  - ]
      24         [ -  + ]:        2232 :   } else if (opt.state & QStyle::State_MouseOver) {
      25   [ #  #  #  #  :           0 :     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   [ +  -  +  - ]:        2386 :   QString subject = index.data(Qt::DisplayRole).toString();
      33   [ +  -  +  - ]:        2386 :   QStringList labels = index.data(MailListModel::LabelsRole).toStringList();
      34                 :             : 
      35                 :        2386 :   QRect textRect = opt.rect.adjusted(8, 0, 0, 0);
      36                 :             : 
      37                 :             :   // Set font (bold for unread)
      38         [ +  - ]:        2386 :   QFont font = opt.font;
      39         [ +  - ]:        2386 :   QVariant fontVar = index.data(Qt::FontRole);
      40   [ +  -  +  + ]:        2386 :   if (fontVar.isValid()) {
      41         [ +  - ]:         949 :     font = fontVar.value<QFont>();
      42                 :             :   }
      43         [ +  - ]:        2386 :   painter->setFont(font);
      44                 :             : 
      45                 :             :   // Text color
      46   [ +  -  +  -  :        2386 :   painter->setPen(QColor(tm.color("@text_primary")));
                   +  - ]
      47                 :             : 
      48                 :             :   // Draw subject text, measure how much space it used
      49         [ +  - ]:        2386 :   QFontMetrics fm(font);
      50                 :             :   QString elidedSubject = fm.elidedText(
      51                 :             :       subject, Qt::ElideRight,
      52   [ +  +  +  - ]:        2386 :       labels.isEmpty() ? textRect.width() : textRect.width() - 120);
      53         [ +  - ]:        2386 :   painter->drawText(textRect, Qt::AlignVCenter | Qt::AlignLeft, elidedSubject);
      54                 :             : 
      55                 :             :   // Draw label chips after subject text
      56         [ +  + ]:        2386 :   if (!labels.isEmpty()) {
      57         [ +  - ]:           9 :     int chipX = textRect.left() + fm.horizontalAdvance(elidedSubject) + 8;
      58                 :           9 :     int chipY = textRect.center().y() - 7;
      59                 :           9 :     int chipHeight = 14;
      60                 :             : 
      61   [ +  -  +  - ]:           9 :     QFont chipFont = painter->font();
      62         [ +  - ]:           9 :     qreal basePt = chipFont.pointSizeF();
      63         [ +  + ]:           9 :     if (basePt > 0) {
      64         [ +  - ]:           2 :       chipFont.setPointSizeF(basePt * 0.8);
      65                 :             :     } else {
      66                 :             :       // Pixel-sized font: scale via pixel size
      67         [ +  - ]:           7 :       int px = chipFont.pixelSize();
      68         [ +  - ]:           7 :       if (px > 0)
      69         [ +  - ]:           7 :         chipFont.setPixelSize(qMax(1, static_cast<int>(px * 0.8)));
      70                 :             :     }
      71         [ +  - ]:           9 :     chipFont.setBold(false);
      72         [ +  - ]:           9 :     painter->setFont(chipFont);
      73         [ +  - ]:           9 :     QFontMetrics chipFm(chipFont);
      74                 :             : 
      75         [ +  + ]:          16 :     for (int i = 0; i < labels.size(); ++i) {
      76         [ +  - ]:           9 :       const auto &label = labels[i];
      77         [ +  - ]:           9 :       QString name = displayName(label);
      78         [ +  - ]:           9 :       QColor color = colorForLabel(label);
      79                 :             : 
      80         [ +  - ]:           9 :       int textWidth = chipFm.horizontalAdvance(name);
      81                 :           9 :       int chipWidth = textWidth + 10;
      82                 :             : 
      83                 :             :       // Check if this chip fits; reserve space for "+N" if more follow
      84                 :           9 :       int remaining = labels.size() - i - 1;
      85                 :           9 :       int reservedWidth = 0;
      86         [ +  + ]:           9 :       if (remaining > 0) {
      87         [ +  - ]:           4 :         QString overflowText = QStringLiteral("+%1").arg(remaining);
      88         [ +  - ]:           2 :         reservedWidth = chipFm.horizontalAdvance(overflowText) + 14;
      89                 :           2 :       }
      90                 :             : 
      91         [ +  + ]:           9 :       if (chipX + chipWidth > opt.rect.right() - 4 - reservedWidth) {
      92                 :             :         // Not enough room — render "+N" overflow indicator
      93                 :           2 :         int overflowCount = labels.size() - i;
      94         [ +  - ]:           4 :         QString overflowText = QStringLiteral("+%1").arg(overflowCount);
      95         [ +  - ]:           2 :         int owWidth = chipFm.horizontalAdvance(overflowText) + 10;
      96                 :           2 :         QRect owRect(chipX, chipY, owWidth, chipHeight);
      97                 :             : 
      98         [ +  - ]:           2 :         painter->setRenderHint(QPainter::Antialiasing, true);
      99         [ +  - ]:           2 :         painter->setPen(Qt::NoPen);
     100   [ +  -  +  -  :           2 :         painter->setBrush(ThemeManager::labelPalette().at(6)); // gray
                   +  - ]
     101         [ +  - ]:           2 :         painter->drawRoundedRect(owRect, 3, 3);
     102         [ +  - ]:           2 :         painter->setPen(Qt::white);
     103         [ +  - ]:           2 :         painter->drawText(owRect, Qt::AlignCenter, overflowText);
     104                 :           2 :         break;
     105                 :           2 :       }
     106                 :             : 
     107                 :           7 :       QRect chipRect(chipX, chipY, chipWidth, chipHeight);
     108                 :             : 
     109                 :             :       // Draw chip background (rounded)
     110         [ +  - ]:           7 :       painter->setRenderHint(QPainter::Antialiasing, true);
     111         [ +  - ]:           7 :       painter->setPen(Qt::NoPen);
     112   [ +  -  +  - ]:           7 :       painter->setBrush(color);
     113         [ +  - ]:           7 :       painter->drawRoundedRect(chipRect, 3, 3);
     114                 :             : 
     115                 :             :       // Draw chip text (white on colored background)
     116         [ +  - ]:           7 :       painter->setPen(Qt::white);
     117         [ +  - ]:           7 :       painter->drawText(chipRect, Qt::AlignCenter, name);
     118                 :             : 
     119                 :           7 :       chipX += chipWidth + 4;
     120         [ +  + ]:           9 :     }
     121                 :           9 :   }
     122                 :             : 
     123         [ +  - ]:        2386 :   painter->restore();
     124                 :        2386 : }
     125                 :             : 
     126                 :         202 : 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                 :         202 :   return QStyledItemDelegate::sizeHint(option, index);
     135                 :             : }
     136                 :             : 
     137                 :          24 : 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         [ +  - ]:          24 :   const QList<QColor> palette = ThemeManager::labelPalette();
     141         [ +  + ]:          24 :   if (label == QLatin1String("$label1"))
     142                 :           1 :     return palette.at(0); // Red
     143         [ +  + ]:          23 :   if (label == QLatin1String("$label2"))
     144                 :           2 :     return palette.at(1); // Orange
     145         [ -  + ]:          21 :   if (label == QLatin1String("$label3"))
     146                 :           0 :     return palette.at(2); // Green
     147         [ -  + ]:          21 :   if (label == QLatin1String("$label4"))
     148                 :           0 :     return palette.at(3); // Blue
     149         [ -  + ]:          21 :   if (label == QLatin1String("$label5"))
     150                 :           0 :     return palette.at(4); // Purple
     151         [ +  + ]:          21 :   if (label.compare(QLatin1String("$Important"), Qt::CaseInsensitive) == 0)
     152                 :           5 :     return palette.at(5); // Gold
     153         [ -  + ]:          16 :   if (label.compare(QLatin1String("Junk"), Qt::CaseInsensitive) == 0)
     154                 :           0 :     return palette.at(6); // Gray
     155                 :             : 
     156                 :             :   // Hash-based color for unknown labels
     157                 :          16 :   uint hash = qHash(label);
     158                 :          16 :   int hue = hash % 360;
     159         [ +  - ]:          16 :   return QColor::fromHsl(hue, 140, 100);
     160                 :          24 : }
     161                 :             : 
     162                 :          19 : QString LabelDelegate::displayName(const QString &label) {
     163                 :             :   // Map internal names to friendly display names
     164         [ +  + ]:          19 :   if (label == QLatin1String("$label1"))
     165                 :           1 :     return tr("Important");
     166         [ +  + ]:          18 :   if (label == QLatin1String("$label2"))
     167                 :           2 :     return tr("Work");
     168         [ -  + ]:          16 :   if (label == QLatin1String("$label3"))
     169                 :           0 :     return tr("Personal");
     170         [ -  + ]:          16 :   if (label == QLatin1String("$label4"))
     171                 :           0 :     return tr("To Do");
     172         [ -  + ]:          16 :   if (label == QLatin1String("$label5"))
     173                 :           0 :     return tr("Later");
     174         [ +  + ]:          16 :   if (label.compare(QLatin1String("$Important"), Qt::CaseInsensitive) == 0)
     175                 :           5 :     return QStringLiteral("Important");
     176                 :             : 
     177                 :             :   // Strip leading $ for custom labels
     178   [ +  -  -  + ]:          11 :   if (label.startsWith('$'))
     179                 :           0 :     return label.mid(1);
     180                 :             : 
     181                 :          11 :   return label;
     182                 :             : }
        

Generated by: LCOV version 2.0-1