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.6 % 289 285
Test Date: 2026-07-27 17:53:44 Functions: 96.8 % 31 30
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 68.6 % 481 330

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

Generated by: LCOV version 2.0-1