MailJD nbsp;·nbsp; Test Dashboard nbsp;·nbsp; Coverage
LCOV - code coverage report
Current view: top level - ui - MailListModel.cpp (source / functions) Coverage Total Hit
Test: MailJD Coverage (Unit + E2E) Lines: 98.5 % 275 271
Test Date: 2026-06-21 21:10:19 Functions: 96.6 % 29 28
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 68.8 % 459 316

             Branch data     Line data    Source code
       1                 :             : #include "MailListModel.h"
       2                 :             : 
       3                 :             : #include <QColor>
       4                 :             : #include <QDataStream>
       5                 :             : #include <QFont>
       6                 :             : #include <QIODevice>
       7                 :             : #include <QLocale>
       8                 :             : #include <QLoggingCategory>
       9                 :             : 
      10                 :             : #include "service/ImapResponseParser.h"
      11                 :             : #include "ui/ThemeManager.h"
      12                 :             : 
      13   [ #  #  #  #  :           0 : Q_LOGGING_CATEGORY(lcMailModel, "mailjd.mailmodel")
             #  #  #  # ]
      14                 :             : 
      15                 :         203 : MailListModel::MailListModel(QObject *parent) : QAbstractTableModel(parent) {}
      16                 :             : 
      17                 :      279408 : int MailListModel::rowCount(const QModelIndex &parent) const {
      18         [ +  + ]:      279408 :   return parent.isValid() ? 0 : m_headers.size();
      19                 :             : }
      20                 :             : 
      21                 :      277277 : int MailListModel::columnCount(const QModelIndex &parent) const {
      22         [ +  + ]:      277277 :   return parent.isValid() ? 0 : ColumnCount;
      23                 :             : }
      24                 :             : 
      25                 :      198624 : QVariant MailListModel::data(const QModelIndex &index, int role) const {
      26   [ +  +  +  +  :      198624 :   if (!index.isValid() || index.row() >= m_headers.size())
                   +  + ]
      27                 :           2 :     return {};
      28                 :             : 
      29                 :      198622 :   const auto &h = m_headers.at(index.row());
      30                 :             : 
      31         [ +  + ]:      198622 :   if (role == Qt::DisplayRole) {
      32   [ +  +  +  +  :       28547 :     switch (index.column()) {
             +  +  +  - ]
      33                 :        4048 :     case Star:
      34   [ +  +  +  +  :        8096 :       return h.isFlagged() ? QStringLiteral("★") : QStringLiteral("☆");
                   +  + ]
      35                 :        4040 :     case Attachment:
      36                 :        4040 :       return {};  // painted by dedicated delegate
      37                 :        6597 :     case Subject: {
      38   [ +  +  +  - ]:        6597 :       QString subj = h.subject.isEmpty() ? tr("(No Subject)") : h.subject;
      39   [ +  +  +  - ]:        6599 :       if (h.isAnswered()) subj.prepend(QStringLiteral("↩ "));
      40                 :        6597 :       return subj;
      41                 :        6597 :     }
      42                 :        1629 :     case Suggestion: {
      43                 :        1629 :       auto it = m_suggestionCache.constFind(MailKey{h.folderId, h.uid});
      44         [ +  + ]:        1629 :       return it != m_suggestionCache.constEnd() ? it->text : QString();
      45                 :             :     }
      46                 :        4151 :     case From:
      47                 :        4151 :       return h.from;
      48                 :        4042 :     case Date:
      49         [ +  - ]:        4042 :       return formatDate(h.date);
      50                 :        4040 :     case Size:
      51         [ +  - ]:        4040 :       return formatSize(h.size);
      52                 :             :     }
      53                 :             :   }
      54                 :             : 
      55                 :             :   // Star column: gold for flagged, light gray for unflagged
      56   [ +  +  +  +  :      170075 :   if (role == Qt::ForegroundRole && index.column() == Star) {
                   +  + ]
      57                 :        4045 :     auto &tm = ThemeManager::instance();
      58   [ +  +  +  -  :       12135 :     return h.isFlagged() ? QColor(tm.color(QStringLiteral("@star_active")))
          +  +  +  +  -  
             -  -  -  -  
                      - ]
      59   [ +  -  +  -  :        8069 :                          : QColor(tm.color(QStringLiteral("@star_inactive")));
          +  +  +  +  +  
          +  +  +  -  -  
             -  -  -  - ]
      60                 :             :   }
      61                 :             : 
      62                 :             :   // T-232: Suggestion column color based on confidence (shared palette)
      63   [ +  +  +  +  :      166030 :   if (role == Qt::ForegroundRole && index.column() == Suggestion) {
                   +  + ]
      64                 :        1628 :     auto it = m_suggestionCache.constFind(MailKey{h.folderId, h.uid});
      65   [ +  +  +  - ]:        1633 :     return ThemeManager::confidenceColor(
      66         [ +  - ]:        4889 :         it != m_suggestionCache.constEnd() ? it->confidence : 0.0);
      67                 :             :   }
      68                 :             : 
      69                 :             :   // Tooltip for Star column
      70   [ +  +  +  +  :      164402 :   if (role == Qt::ToolTipRole && index.column() == Star) {
                   +  + ]
      71   [ +  +  +  -  :           2 :     return h.isFlagged() ? tr("Remove Flag") : tr("Flag");
                   +  - ]
      72                 :             :   }
      73                 :             : 
      74                 :             :   // Tooltip for Subject column: show full subject on hover
      75   [ +  +  +  -  :      164400 :   if (role == Qt::ToolTipRole && index.column() == Subject) {
                   +  + ]
      76                 :           1 :     return h.subject;
      77                 :             :   }
      78                 :             : 
      79                 :             :   // Tooltip for Attachment column
      80   [ -  +  -  -  :      164399 :   if (role == Qt::ToolTipRole && index.column() == Attachment) {
                   -  + ]
      81   [ #  #  #  # ]:           0 :     return h.hasAttachments ? tr("Has attachment") : QString();
      82                 :             :   }
      83                 :             : 
      84                 :             :   // 67.B4: unread = medium weight (paired with the accent unread dot;
      85                 :             :   // replaces the former bold-only signal) — applied to all columns
      86   [ +  +  +  +  :      164399 :   if (role == Qt::FontRole && !h.isSeen()) {
                   +  + ]
      87         [ +  - ]:       12511 :     QFont font;
      88         [ +  - ]:       12511 :     font.setWeight(QFont::DemiBold);
      89         [ +  - ]:       12511 :     return font;
      90                 :       12511 :   }
      91                 :             : 
      92                 :             :   // Text alignment: Star column centered
      93   [ +  +  +  +  :      151888 :   if (role == Qt::TextAlignmentRole && index.column() == Star) {
                   +  + ]
      94                 :        4042 :     return static_cast<int>(Qt::AlignCenter);
      95                 :             :   }
      96                 :             : 
      97                 :             :   // Sort role: return raw values for proper sorting
      98         [ +  + ]:      147846 :   if (role == SortRole) {
      99   [ +  +  +  +  :        3349 :     switch (index.column()) {
                      + ]
     100                 :           8 :     case Star:
     101         [ +  + ]:           8 :       return h.isFlagged() ? 1 : 0;
     102                 :        3327 :     case Date:
     103                 :        3327 :       return h.date;
     104                 :           3 :     case Size:
     105                 :           3 :       return h.size;
     106                 :           4 :     case Suggestion: {
     107                 :           4 :       auto it = m_suggestionCache.constFind(MailKey{h.folderId, h.uid});
     108         [ +  + ]:           4 :       return it != m_suggestionCache.constEnd() ? it->confidence : 0.0;
     109                 :             :     }
     110                 :           7 :     default:
     111                 :           7 :       return data(index, Qt::DisplayRole);
     112                 :             :     }
     113                 :             :   }
     114                 :             : 
     115                 :             :   // Custom FlagsRole: return the raw flags bitmask (for filtering)
     116         [ +  + ]:      144497 :   if (role == FlagsRole) {
     117                 :        3896 :     return h.flags;
     118                 :             :   }
     119                 :             : 
     120                 :             :   // HasAttachmentsRole: from MailHeader (populated by cache/body parser)
     121         [ +  + ]:      140601 :   if (role == HasAttachmentsRole) {
     122                 :        2288 :     return h.hasAttachments;
     123                 :             :   }
     124                 :             : 
     125                 :             :   // LabelsRole: IMAP keywords parsed from FLAGS
     126                 :             :   // Filter internal keywords (e.g. NonJunk) at display time for cached data
     127         [ +  + ]:      138313 :   if (role == LabelsRole) {
     128                 :        2295 :     QStringList filtered;
     129         [ +  + ]:        2333 :     for (const auto &label : h.labels) {
     130   [ +  -  +  + ]:          38 :       if (!ImapResponseParser::isInternalKeyword(label)) {
     131         [ +  - ]:          35 :         filtered.append(label);
     132                 :             :       }
     133                 :             :     }
     134                 :        2295 :     return filtered;
     135                 :        2295 :   }
     136                 :             : 
     137                 :      136018 :   return {};
     138                 :             : }
     139                 :             : 
     140                 :       38549 : QVariant MailListModel::headerData(int section, Qt::Orientation orientation,
     141                 :             :                                    int role) const {
     142   [ +  +  +  + ]:       38549 :   if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
     143                 :       30560 :     return {};
     144                 :             : 
     145   [ +  +  +  +  :        7989 :   switch (section) {
             +  +  +  + ]
     146                 :        1330 :   case Star:
     147                 :        1330 :     return QStringLiteral(""); // narrow column, no text header
     148                 :        1328 :   case Attachment:
     149                 :        1328 :     return QStringLiteral(""); // icon-only column
     150                 :        1329 :   case Subject:
     151         [ +  - ]:        1329 :     return tr("Subject");
     152                 :          27 :   case Suggestion:
     153                 :          27 :     return QStringLiteral("→"); // arrow symbol
     154                 :        1325 :   case From:
     155         [ +  - ]:        1325 :     return tr("From");
     156                 :        1325 :   case Date:
     157         [ +  - ]:        1325 :     return tr("Date");
     158                 :        1324 :   case Size:
     159         [ +  - ]:        1324 :     return tr("Size");
     160                 :             :   }
     161                 :           1 :   return {};
     162                 :             : }
     163                 :             : 
     164                 :             : // Sprint 76 (T-76.B3): models do not receive LanguageChange events, so the
     165                 :             : // language-switch path (MainWindow) calls this to make views re-read the
     166                 :             : // translated headers via headerData().
     167                 :           3 : void MailListModel::retranslateUi() {
     168   [ +  -  +  - ]:           3 :   if (columnCount() > 0)
     169   [ +  -  +  - ]:           3 :     emit headerDataChanged(Qt::Horizontal, 0, columnCount() - 1);
     170                 :           3 : }
     171                 :             : 
     172                 :             : // ═══════════════════════════════════════════════════════
     173                 :             : // Drag & Drop (T-102)
     174                 :             : // ═══════════════════════════════════════════════════════
     175                 :             : 
     176                 :      235591 : Qt::ItemFlags MailListModel::flags(const QModelIndex &index) const {
     177         [ +  - ]:      235591 :   auto defaultFlags = QAbstractTableModel::flags(index);
     178         [ +  + ]:      235591 :   if (index.isValid())
     179                 :      235590 :     return defaultFlags | Qt::ItemIsDragEnabled;
     180                 :           1 :   return defaultFlags;
     181                 :             : }
     182                 :             : 
     183                 :           3 : QStringList MailListModel::mimeTypes() const {
     184                 :           0 :   return {QStringLiteral("application/x-mailjd-mail-ids"),
     185   [ +  +  -  - ]:           9 :           QStringLiteral("application/x-mailjd-uids")};
     186   [ +  -  -  -  :           9 : }
                   -  - ]
     187                 :             : 
     188                 :           5 : QMimeData *MailListModel::mimeData(const QModelIndexList &indexes) const {
     189                 :             :   // Collect unique mail identities from the selected rows. UIDs are only
     190                 :             :   // unique within a folder, so drag payloads must carry the source folderId.
     191                 :           5 :   QList<MailIdentity> mails;
     192                 :           5 :   QSet<qint64> uidSet;
     193                 :           5 :   QSet<QString> identitySet;
     194         [ +  + ]:          14 :   for (const auto &idx : indexes) {
     195   [ +  +  +  +  :           9 :     if (!idx.isValid() || idx.row() >= m_headers.size())
                   +  + ]
     196                 :           3 :       continue;
     197                 :           7 :     const auto &header = m_headers.at(idx.row());
     198                 :             :     const QString key =
     199   [ +  -  +  - ]:          21 :         QStringLiteral("%1:%2").arg(header.folderId).arg(header.uid);
     200         [ +  + ]:           7 :     if (identitySet.contains(key))
     201                 :           1 :       continue;
     202         [ +  - ]:           6 :     identitySet.insert(key);
     203         [ +  - ]:           6 :     mails.append({QString(), header.folderId, header.uid});
     204         [ +  - ]:           6 :     uidSet.insert(header.uid);
     205         [ +  + ]:           7 :   }
     206                 :             : 
     207                 :           5 :   QByteArray identityEncoded;
     208         [ +  - ]:           5 :   QDataStream identityStream(&identityEncoded, QIODevice::WriteOnly);
     209   [ +  -  +  -  :          11 :   for (const auto &mail : mails)
                   +  + ]
     210   [ +  -  +  -  :           6 :     identityStream << mail.account << mail.folderId << mail.uid;
                   +  - ]
     211                 :             : 
     212                 :           5 :   QByteArray encoded;
     213         [ +  - ]:           5 :   QDataStream stream(&encoded, QIODevice::WriteOnly);
     214   [ +  -  +  -  :          10 :   for (qint64 uid : uidSet)
                   +  + ]
     215         [ +  - ]:           5 :     stream << uid;
     216                 :             : 
     217   [ +  -  +  -  :           5 :   auto *mimeData = new QMimeData;
             -  +  -  - ]
     218         [ +  - ]:          10 :   mimeData->setData(QStringLiteral("application/x-mailjd-mail-ids"),
     219                 :             :                     identityEncoded);
     220         [ +  - ]:          10 :   mimeData->setData(QStringLiteral("application/x-mailjd-uids"), encoded);
     221                 :             : 
     222                 :             :   // Set human-readable text for drag tooltip
     223         [ +  - ]:           5 :   mimeData->setText(
     224   [ +  -  +  - ]:          15 :       QString("%1 Mail(s)").arg(mails.size()));
     225                 :             : 
     226                 :           5 :   return mimeData;
     227                 :           5 : }
     228                 :             : 
     229                 :           3 : Qt::DropActions MailListModel::supportedDragActions() const {
     230                 :           3 :   return Qt::MoveAction;
     231                 :             : }
     232                 :             : 
     233                 :         241 : void MailListModel::setHeaders(const QList<MailHeader> &headers) {
     234                 :         241 :   beginResetModel();
     235                 :         241 :   m_headers = headers;
     236                 :         241 :   m_suggestionCache.clear(); // T-232
     237                 :         241 :   rebuildUidIndex();
     238                 :         241 :   endResetModel();
     239                 :         241 : }
     240                 :             : 
     241                 :          28 : void MailListModel::appendHeaders(const QList<MailHeader> &headers) {
     242         [ +  + ]:          28 :   if (headers.isEmpty())
     243                 :           1 :     return;
     244                 :             : 
     245         [ +  - ]:          27 :   beginInsertRows({}, m_headers.size(), m_headers.size() + headers.size() - 1);
     246                 :          27 :   int baseRow = m_headers.size();
     247                 :          27 :   m_headers.append(headers);
     248                 :             :   // T-514: extend composite UID index for new rows
     249         [ +  + ]:          81 :   for (int i = 0; i < headers.size(); ++i) {
     250                 :          54 :     const auto &hdr = headers.at(i);
     251         [ +  - ]:          54 :     m_uidIndex.insert(MailKey{hdr.folderId, hdr.uid}, baseRow + i);
     252                 :             :   }
     253                 :             :   // T-116: update unread count for new headers
     254         [ +  + ]:          81 :   for (const auto &h : headers) {
     255         [ +  + ]:          54 :     if (!h.isSeen())
     256                 :          46 :       ++m_unreadCount;
     257                 :             :   }
     258                 :          27 :   endInsertRows();
     259                 :             : }
     260                 :             : 
     261                 :           6 : void MailListModel::clear() {
     262                 :           6 :   beginResetModel();
     263                 :           6 :   m_headers.clear();
     264                 :           6 :   m_uidIndex.clear();
     265                 :           6 :   m_unreadCount = 0;
     266                 :           6 :   m_suggestionCache.clear(); // T-232
     267                 :           6 :   endResetModel();
     268                 :           6 : }
     269                 :             : 
     270                 :         150 : void MailListModel::updateFlags(qint64 uid, quint32 newFlags, qint64 folderId) {
     271                 :         150 :   int row = rowForUid(uid, folderId);
     272         [ +  + ]:         150 :   if (row < 0)
     273                 :          10 :     return;
     274                 :             : 
     275                 :             :   // T-116: adjust unread count based on Seen flag transition
     276                 :         140 :   bool wasSeen = m_headers.at(row).isSeen();
     277                 :             :   // T-261: Preserve labels (IMAP keywords) — they live outside the bitmask
     278                 :             :   // and must not be lost when the flag bitmask is updated during sync.
     279                 :         140 :   QStringList savedLabels = m_headers.at(row).labels;
     280         [ +  - ]:         140 :   m_headers[row].flags = newFlags;
     281         [ +  - ]:         140 :   m_headers[row].labels = savedLabels;
     282                 :         140 :   bool nowSeen = m_headers.at(row).isSeen();
     283   [ +  +  +  + ]:         140 :   if (wasSeen && !nowSeen)
     284                 :          12 :     ++m_unreadCount;
     285   [ +  +  +  + ]:         128 :   else if (!wasSeen && nowSeen)
     286                 :          48 :     --m_unreadCount;
     287                 :             : 
     288         [ +  - ]:         140 :   auto topLeft = index(row, 0);
     289         [ +  - ]:         140 :   auto bottomRight = index(row, ColumnCount - 1);
     290   [ +  -  +  - ]:         140 :   emit dataChanged(topLeft, bottomRight,
     291                 :             :                    {Qt::FontRole, Qt::DisplayRole, Qt::DecorationRole,
     292                 :             :                     FlagsRole});
     293                 :         140 : }
     294                 :             : 
     295                 :           4 : void MailListModel::setHasAttachments(qint64 uid, qint64 folderId, bool has) {
     296                 :           4 :   int row = rowForUid(uid, folderId);
     297         [ +  + ]:           4 :   if (row < 0)
     298                 :           1 :     return;
     299         [ -  + ]:           3 :   if (m_headers.at(row).hasAttachments == has)
     300                 :           0 :     return;
     301                 :           3 :   m_headers[row].hasAttachments = has;
     302   [ +  -  +  -  :           3 :   emit dataChanged(index(row, 0), index(row, ColumnCount - 1),
             +  -  +  - ]
     303                 :             :                    {HasAttachmentsRole});
     304                 :             : }
     305                 :             : 
     306                 :          50 : void MailListModel::removeByUid(qint64 uid, qint64 folderId) {
     307                 :          50 :   int row = rowForUid(uid, folderId);
     308         [ +  + ]:          50 :   if (row < 0)
     309                 :          12 :     return;
     310                 :             : 
     311                 :             :   // T-116: adjust unread count if removed mail was unread
     312         [ +  + ]:          38 :   if (!m_headers.at(row).isSeen())
     313                 :          25 :     --m_unreadCount;
     314                 :             : 
     315         [ +  - ]:          38 :   beginRemoveRows({}, row, row);
     316                 :          38 :   m_headers.removeAt(row);
     317                 :             : 
     318                 :             :   // T-115: rebuild index from scratch (rows shift after remove)
     319                 :             :   // Cost: O(n), but removeByUid is rare compared to rowForUid
     320                 :          38 :   rebuildUidIndex();
     321                 :             : 
     322                 :          38 :   endRemoveRows();
     323                 :             : }
     324                 :             : 
     325                 :             : // T-525/Cx8: Batch remove — single reset instead of per-UID rebuild
     326                 :           5 : void MailListModel::removeByUids(const QList<qint64> &uids, qint64 folderId) {
     327         [ +  + ]:           5 :   if (uids.isEmpty())
     328                 :           2 :     return;
     329                 :             : 
     330         [ +  - ]:           3 :   QSet<qint64> removeSet(uids.begin(), uids.end());
     331         [ +  - ]:           3 :   beginResetModel();
     332   [ +  -  +  -  :           3 :   m_headers.erase(
          +  -  +  -  +  
                      - ]
     333                 :             :       std::remove_if(m_headers.begin(), m_headers.end(),
     334                 :          11 :                      [&removeSet, folderId](const MailHeader &h) {
     335   [ +  +  +  + ]:          21 :                        return h.folderId == folderId &&
     336                 :          21 :                               removeSet.contains(h.uid);
     337                 :             :                      }),
     338                 :             :       m_headers.end());
     339         [ +  - ]:           3 :   rebuildUidIndex();
     340         [ +  - ]:           3 :   endResetModel();
     341                 :           3 : }
     342                 :             : 
     343                 :        1690 : const MailHeader *MailListModel::headerAt(int row) const {
     344   [ +  +  +  +  :        1690 :   if (row < 0 || row >= m_headers.size())
                   +  + ]
     345                 :           3 :     return nullptr;
     346                 :        1687 :   return &m_headers.at(row);
     347                 :             : }
     348                 :             : 
     349                 :          41 : MailHeader *MailListModel::mutableHeaderAt(int row) {
     350   [ +  +  +  +  :          41 :   if (row < 0 || row >= m_headers.size())
                   +  + ]
     351                 :           2 :     return nullptr;
     352                 :          39 :   return &m_headers[row];
     353                 :             : }
     354                 :             : 
     355                 :         123 : qint64 MailListModel::uidAt(int row) const {
     356                 :         123 :   auto *h = headerAt(row);
     357         [ +  + ]:         123 :   return h ? h->uid : -1;
     358                 :             : }
     359                 :             : 
     360                 :        5988 : int MailListModel::rowForUid(qint64 uid, qint64 folderId) const {
     361                 :             :   // T-514: O(1) composite hash lookup
     362                 :        5988 :   auto it = m_uidIndex.constFind(MailKey{folderId, uid});
     363         [ +  + ]:        5988 :   return it != m_uidIndex.constEnd() ? it.value() : -1;
     364                 :             : }
     365                 :             : 
     366                 :         184 : int MailListModel::unreadCount() const {
     367                 :             :   // T-116: O(1) cached count
     368                 :         184 :   return m_unreadCount;
     369                 :             : }
     370                 :             : 
     371                 :         282 : void MailListModel::rebuildUidIndex() {
     372                 :         282 :   m_uidIndex.clear();
     373                 :         282 :   m_uidIndex.reserve(m_headers.size());
     374                 :         282 :   m_unreadCount = 0;
     375         [ +  + ]:      156316 :   for (int i = 0; i < m_headers.size(); ++i) {
     376                 :      156034 :     const auto &h = m_headers.at(i);
     377         [ +  - ]:      156034 :     m_uidIndex.insert(MailKey{h.folderId, h.uid}, i);
     378         [ +  + ]:      156034 :     if (!h.isSeen())
     379                 :       78207 :       ++m_unreadCount;
     380                 :             :   }
     381                 :         282 : }
     382                 :             : 
     383                 :             : // 67.B4: humanized date column (DESIGN.md "MailList date format"):
     384                 :             : // today "14:32" · yesterday "Yesterday" · this week "Mon" ·
     385                 :             : // this year "12 May" · older "12.05.2024"
     386                 :        4211 : QString MailListModel::formatDate(const QDateTime &dt) {
     387   [ +  -  +  + ]:        4211 :   if (!dt.isValid())
     388                 :           2 :     return {};
     389                 :             : 
     390         [ +  - ]:        4209 :   const QDate today = QDate::currentDate();
     391         [ +  - ]:        4209 :   const QDate date = dt.date();
     392                 :             : 
     393         [ +  + ]:        4209 :   if (date == today)
     394   [ +  -  +  - ]:        3822 :     return dt.time().toString(QStringLiteral("HH:mm"));
     395   [ +  -  +  + ]:        2298 :   if (date == today.addDays(-1))
     396         [ +  - ]:           4 :     return tr("Yesterday");
     397   [ +  -  +  +  :        2294 :   if (date > today.addDays(-7) && date < today)
             +  +  +  + ]
     398   [ +  -  +  -  :           3 :     return QLocale().dayName(date.dayOfWeek(), QLocale::ShortFormat);
                   +  - ]
     399   [ +  -  +  -  :        2291 :   if (date.year() == today.year())
                   +  + ]
     400   [ +  -  +  - ]:        4570 :     return QLocale().toString(date, QStringLiteral("d. MMM"));
     401         [ +  - ]:           6 :   return date.toString(QStringLiteral("dd.MM.yyyy"));
     402                 :             : }
     403                 :             : 
     404                 :        4040 : QString MailListModel::formatSize(qint64 bytes) {
     405         [ +  + ]:        4040 :   if (bytes < 1024)
     406   [ +  -  +  - ]:        4032 :     return QString::number(bytes) + " B";
     407         [ +  + ]:           8 :   if (bytes < 1024 * 1024)
     408   [ +  -  +  - ]:           5 :     return QString::number(bytes / 1024.0, 'f', 1) + " KB";
     409   [ +  -  +  - ]:           3 :   return QString::number(bytes / (1024.0 * 1024.0), 'f', 1) + " MB";
     410                 :             : }
     411                 :             : 
     412                 :             : // T-232: Set suggestion text + confidence for a specific UID
     413                 :          11 : void MailListModel::setSuggestion(qint64 uid, qint64 folderId,
     414                 :             :                                    const QString &text,
     415                 :             :                                    double confidence) {
     416         [ +  - ]:          11 :   m_suggestionCache[MailKey{folderId, uid}] = {text, confidence};
     417                 :          11 :   int row = rowForUid(uid, folderId);
     418         [ +  + ]:          11 :   if (row >= 0) {
     419         [ +  - ]:           9 :     auto idx = index(row, Suggestion);
     420   [ +  -  +  - ]:           9 :     emit dataChanged(idx, idx, {Qt::DisplayRole, Qt::ForegroundRole, SortRole});
     421                 :             :   }
     422         [ -  - ]:          22 : }
     423                 :             : 
     424                 :             : // T-232: Clear all cached suggestions
     425                 :           7 : void MailListModel::clearSuggestions() {
     426         [ +  + ]:           7 :   if (m_suggestionCache.isEmpty())
     427                 :           3 :     return;
     428                 :           4 :   m_suggestionCache.clear();
     429         [ +  + ]:           4 :   if (!m_headers.isEmpty()) {
     430   [ +  -  +  -  :           9 :     emit dataChanged(index(0, Suggestion),
                   +  - ]
     431         [ +  - ]:           6 :                      index(m_headers.size() - 1, Suggestion),
     432                 :             :                      {Qt::DisplayRole, Qt::ForegroundRole, SortRole});
     433                 :             :   }
     434                 :             : }
        

Generated by: LCOV version 2.0-1