MailJD nbsp;·nbsp; Test Dashboard nbsp;·nbsp; Coverage
LCOV - code coverage report
Current view: top level - ui - MailThreadModel.cpp (source / functions) Coverage Total Hit
Test: MailJD Coverage (Unit + E2E) Lines: 91.2 % 295 269
Test Date: 2026-06-21 21:10:19 Functions: 100.0 % 30 30
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 63.7 % 399 254

             Branch data     Line data    Source code
       1                 :             : #include "MailThreadModel.h"
       2                 :             : 
       3                 :             : #include <QColor>
       4                 :             : #include <QDataStream>
       5                 :             : #include <QFont>
       6                 :             : #include <QIODevice>
       7                 :             : #include <QRegularExpression>
       8                 :             : 
       9                 :             : #include "service/ImapResponseParser.h"
      10                 :             : #include "ui/MailListModel.h"
      11                 :             : #include "ui/ThemeManager.h"
      12                 :             : 
      13                 :         135 : MailThreadModel::MailThreadModel(QObject *parent)
      14                 :         135 :     : QAbstractItemModel(parent) {}
      15                 :             : 
      16                 :         139 : MailThreadModel::~MailThreadModel() = default;
      17                 :             : 
      18                 :          93 : void MailThreadModel::setHeaders(const QList<MailHeader> &headers) {
      19                 :          93 :   beginResetModel();
      20                 :          93 :   m_roots.clear();
      21                 :          93 :   m_uidIndex.clear();
      22                 :          93 :   m_suggestionCache.clear();
      23                 :          93 :   m_headers = headers;
      24         [ +  - ]:          93 :   m_roots = ThreadBuilder::buildThreads(m_headers);
      25                 :          93 :   buildUidIndex();
      26                 :          93 :   endResetModel();
      27                 :          93 : }
      28                 :             : 
      29                 :           2 : void MailThreadModel::appendHeaders(const QList<MailHeader> &headers) {
      30                 :           2 :   beginResetModel();
      31                 :           2 :   m_roots.clear();
      32                 :           2 :   m_uidIndex.clear();
      33                 :           2 :   m_headers.append(headers);
      34         [ +  - ]:           2 :   m_roots = ThreadBuilder::buildThreads(m_headers);
      35                 :           2 :   buildUidIndex();
      36                 :           2 :   endResetModel();
      37                 :           2 : }
      38                 :             : 
      39                 :           2 : void MailThreadModel::clear() {
      40                 :           2 :   beginResetModel();
      41                 :           2 :   m_roots.clear();
      42                 :           2 :   m_uidIndex.clear();
      43                 :           2 :   m_headers.clear();
      44                 :           2 :   m_suggestionCache.clear();
      45                 :           2 :   endResetModel();
      46                 :           2 : }
      47                 :             : 
      48                 :         163 : const MailHeader *MailThreadModel::headerAt(const QModelIndex &index) const {
      49                 :         163 :   auto *node = nodeFromIndex(index);
      50         [ +  + ]:         163 :   return node ? node->header : nullptr;
      51                 :             : }
      52                 :             : 
      53                 :           2 : qint64 MailThreadModel::uidAt(const QModelIndex &index) const {
      54                 :           2 :   auto *node = nodeFromIndex(index);
      55   [ +  +  +  - ]:           2 :   return (node && node->header) ? node->header->uid : -1;
      56                 :             : }
      57                 :             : 
      58                 :          12 : QModelIndex MailThreadModel::indexForUid(qint64 uid, qint64 folderId) const {
      59                 :          12 :   auto *node = m_uidIndex.value(MailKey{folderId, uid}, nullptr);
      60         [ +  + ]:          12 :   return node ? indexFromNode(node) : QModelIndex();
      61                 :             : }
      62                 :             : 
      63                 :         126 : void MailThreadModel::updateFlags(qint64 uid, quint32 flags, qint64 folderId) {
      64                 :             :   // Find the header in our flat list and update it
      65         [ +  + ]:         148 :   for (int i = 0; i < m_headers.size(); ++i) {
      66   [ +  +  +  -  :          32 :     if (m_headers[i].uid == uid && m_headers[i].folderId == folderId) {
                   +  + ]
      67                 :          10 :       m_headers[i].flags = flags;
      68                 :             :       // Find the node and emit dataChanged
      69                 :          10 :       auto *node = m_uidIndex.value(MailKey{folderId, uid}, nullptr);
      70         [ +  - ]:          10 :       if (node) {
      71         [ +  - ]:          10 :         auto topLeft = indexFromNode(node, 0);
      72         [ +  - ]:          10 :         auto bottomRight = indexFromNode(node, ColumnCount - 1);
      73         [ +  - ]:          10 :         emit dataChanged(topLeft, bottomRight);
      74                 :             :       }
      75                 :          10 :       return;
      76                 :             :     }
      77                 :             :   }
      78                 :             : }
      79                 :             : 
      80                 :             : // T-261: Update labels for a specific UID
      81                 :          25 : void MailThreadModel::updateLabels(qint64 uid, const QStringList &labels, qint64 folderId) {
      82         [ +  + ]:          27 :   for (int i = 0; i < m_headers.size(); ++i) {
      83   [ +  +  +  -  :           9 :     if (m_headers[i].uid == uid && m_headers[i].folderId == folderId) {
                   +  + ]
      84                 :           7 :       m_headers[i].labels = labels;
      85                 :           7 :       auto *node = m_uidIndex.value(MailKey{folderId, uid}, nullptr);
      86         [ +  - ]:           7 :       if (node) {
      87         [ +  - ]:           7 :         auto topLeft = indexFromNode(node, 0);
      88         [ +  - ]:           7 :         auto bottomRight = indexFromNode(node, ColumnCount - 1);
      89         [ +  - ]:           7 :         emit dataChanged(topLeft, bottomRight);
      90                 :             :       }
      91                 :           7 :       return;
      92                 :             :     }
      93                 :             :   }
      94                 :             : }
      95                 :             : 
      96                 :          20 : void MailThreadModel::removeByUid(qint64 uid, qint64 folderId) {
      97                 :             :   // Remove the header from our flat list
      98         [ +  + ]:          23 :   for (int i = 0; i < m_headers.size(); ++i) {
      99   [ +  +  +  -  :          13 :     if (m_headers[i].uid == uid && m_headers[i].folderId == folderId) {
                   +  + ]
     100                 :             :       // Rebuild the entire thread tree (simple & correct)
     101                 :          10 :       beginResetModel();
     102                 :          10 :       m_headers.removeAt(i);
     103                 :          10 :       m_roots.clear();
     104                 :          10 :       m_uidIndex.clear();
     105         [ +  - ]:          10 :       m_roots = ThreadBuilder::buildThreads(m_headers);
     106                 :          10 :       buildUidIndex();
     107                 :          10 :       endResetModel();
     108                 :          10 :       return;
     109                 :             :     }
     110                 :             :   }
     111                 :             : }
     112                 :             : 
     113                 :             : // --- QAbstractItemModel interface ---
     114                 :             : 
     115                 :       11090 : QModelIndex MailThreadModel::index(int row, int column,
     116                 :             :                                     const QModelIndex &parent) const {
     117   [ +  -  +  -  :       11090 :   if (row < 0 || column < 0 || column >= ColumnCount)
                   -  + ]
     118                 :           0 :     return {};
     119                 :             : 
     120         [ +  + ]:       11090 :   if (!parent.isValid()) {
     121                 :             :     // Root level
     122         [ -  + ]:       10034 :     if (row >= static_cast<int>(m_roots.size()))
     123                 :           0 :       return {};
     124                 :       10034 :     return createIndex(row, column, m_roots[row].get());
     125                 :             :   }
     126                 :             : 
     127                 :             :   // Child level
     128                 :        1056 :   auto *parentNode = nodeFromIndex(parent);
     129   [ +  -  -  +  :        1056 :   if (!parentNode || row >= static_cast<int>(parentNode->children.size()))
                   -  + ]
     130                 :           0 :     return {};
     131                 :        1056 :   return createIndex(row, column, parentNode->children[row].get());
     132                 :             : }
     133                 :             : 
     134                 :         199 : QModelIndex MailThreadModel::parent(const QModelIndex &child) const {
     135         [ -  + ]:         199 :   if (!child.isValid())
     136                 :           0 :     return {};
     137                 :             : 
     138                 :         199 :   auto *node = nodeFromIndex(child);
     139   [ +  -  +  + ]:         199 :   if (!node || !node->parent)
     140                 :         151 :     return {};
     141                 :             : 
     142                 :          48 :   auto *parentNode = node->parent;
     143                 :             :   // Find the row of parentNode in its parent's children (or roots)
     144         [ +  - ]:          48 :   if (!parentNode->parent) {
     145                 :             :     // parentNode is a root
     146                 :          48 :     int row = findRootIndex(parentNode);
     147                 :          48 :     return createIndex(row, 0, parentNode);
     148                 :             :   }
     149                 :             : 
     150                 :           0 :   int row = findChildIndex(parentNode);
     151                 :           0 :   return createIndex(row, 0, parentNode);
     152                 :             : }
     153                 :             : 
     154                 :         346 : int MailThreadModel::rowCount(const QModelIndex &parent) const {
     155         [ +  + ]:         346 :   if (!parent.isValid())
     156                 :         232 :     return static_cast<int>(m_roots.size());
     157                 :             : 
     158                 :         114 :   auto *node = nodeFromIndex(parent);
     159                 :             :   // Only column 0 items have children
     160         [ -  + ]:         114 :   if (parent.column() != 0)
     161                 :           0 :     return 0;
     162         [ +  - ]:         114 :   return node ? static_cast<int>(node->children.size()) : 0;
     163                 :             : }
     164                 :             : 
     165                 :         278 : int MailThreadModel::columnCount(const QModelIndex &parent) const {
     166                 :             :   Q_UNUSED(parent)
     167                 :         278 :   return ColumnCount;
     168                 :             : }
     169                 :             : 
     170                 :        7488 : QVariant MailThreadModel::data(const QModelIndex &index, int role) const {
     171                 :        7488 :   auto *node = nodeFromIndex(index);
     172   [ +  -  -  + ]:        7488 :   if (!node || !node->header)
     173                 :           0 :     return {};
     174                 :             : 
     175                 :        7488 :   const auto &h = *node->header;
     176                 :        7488 :   int col = index.column();
     177                 :             : 
     178         [ +  + ]:        7488 :   if (role == Qt::DisplayRole) {
     179   [ +  +  +  +  :        1049 :     switch (col) {
             +  +  +  - ]
     180                 :         155 :     case Star:
     181   [ +  +  +  +  :         310 :       return h.isFlagged() ? QStringLiteral("★") : QStringLiteral("☆");
                   +  + ]
     182                 :         154 :     case Attachment:
     183                 :         154 :       return {};  // painted by dedicated delegate
     184                 :         270 :     case Subject: {
     185   [ -  +  -  - ]:         270 :       QString subject = h.subject.isEmpty() ? tr("(No subject)") : h.subject;
     186                 :             :       // T-547: Show unseen count for root nodes with children
     187   [ +  +  +  +  :         270 :       if (node->depth == 0 && !node->children.empty()) {
                   +  + ]
     188         [ +  - ]:          28 :         int total = node->descendantCount();
     189         [ +  - ]:          28 :         int unseen = node->unseenDescendantCount();
     190         [ +  + ]:          28 :         if (unseen > 0)
     191   [ +  -  +  -  :          81 :           subject += QStringLiteral(" (%1/%2)").arg(unseen).arg(total);
                   +  - ]
     192                 :             :         else
     193   [ +  -  +  - ]:           2 :           subject += QStringLiteral(" (%1)").arg(total);
     194                 :             :       }
     195                 :             :       // T-128/T-136: Strip Re:/Fwd: for child messages
     196         [ +  + ]:         270 :       if (node->depth > 0) {
     197                 :             :         static const QRegularExpression rePrefix(
     198                 :           6 :             QStringLiteral("^(Re|Fwd|Aw|Wg):\\s*"),
     199   [ +  +  +  -  :          45 :             QRegularExpression::CaseInsensitiveOption);
             +  -  -  - ]
     200         [ +  - ]:          39 :         subject = subject.replace(rePrefix, QString());
     201                 :             :       }
     202                 :             :       // Show ↩ prefix for answered mails
     203   [ +  +  +  - ]:         271 :       if (h.isAnswered()) subject.prepend(QStringLiteral("↩ "));
     204                 :             :       // Bug 2: Visual indentation for threaded replies
     205         [ +  + ]:         270 :       if (node->depth > 0)
     206   [ +  -  +  - ]:          39 :         subject.prepend(QString(node->depth * 4, QChar(' ')));
     207                 :         270 :       return subject;
     208                 :         270 :     }
     209                 :         158 :     case From:
     210                 :         158 :       return h.from;
     211                 :         155 :     case Date:
     212                 :             :       // 67.B4: same humanized format as the flat list
     213         [ +  - ]:         155 :       return MailListModel::formatDate(h.date);
     214                 :         154 :     case Size:
     215         [ +  + ]:         154 :       if (h.size < 1024)
     216         [ +  - ]:         306 :         return QStringLiteral("%1 B").arg(h.size);
     217         [ +  - ]:           1 :       if (h.size < 1024 * 1024)
     218         [ +  - ]:           2 :         return QStringLiteral("%1 KB").arg(h.size / 1024.0, 0, 'f', 1);
     219         [ #  # ]:           0 :       return QStringLiteral("%1 MB").arg(h.size / (1024.0 * 1024.0), 0, 'f', 1);
     220                 :           3 :     case Suggestion: {
     221                 :           3 :       auto it = m_suggestionCache.constFind(MailKey{h.folderId, h.uid});
     222         [ +  + ]:           3 :       return it != m_suggestionCache.constEnd() ? it->text : QString();
     223                 :             :     }
     224                 :             :     }
     225                 :             :   }
     226                 :             : 
     227                 :             :   // T-508/T-547: Bold font for unread messages.
     228                 :             :   // For root nodes with children, also check if any descendant is unread
     229                 :             :   // so collapsed threads show as bold when they contain unread mail.
     230         [ +  + ]:        6439 :   if (role == Qt::FontRole) {
     231                 :        1027 :     bool unread = !h.isSeen();
     232   [ +  +  +  -  :        1027 :     if (!unread && node->depth == 0 && !node->children.empty())
             -  +  -  + ]
     233                 :           0 :       unread = node->hasUnseenDescendant();
     234         [ +  + ]:        1027 :     if (unread) {
     235                 :             :       // 67.B4: medium weight (paired with the accent unread dot)
     236         [ +  - ]:         611 :       QFont font;
     237         [ +  - ]:         611 :       font.setWeight(QFont::DemiBold);
     238         [ +  - ]:         611 :       return font;
     239                 :         611 :     }
     240                 :         416 :     return {};  // Use view's default font for read messages
     241                 :             :   }
     242                 :             : 
     243   [ +  +  +  + ]:        5412 :   if (role == Qt::ForegroundRole && col == Star) {
     244                 :         153 :     auto &tm = ThemeManager::instance();
     245   [ +  +  +  -  :         459 :     return h.isFlagged() ? QColor(tm.color(QStringLiteral("@star_active")))
          +  +  +  +  -  
             -  -  -  -  
                      - ]
     246   [ +  -  +  -  :         305 :                          : QColor(tm.color(QStringLiteral("@star_inactive")));
          +  +  +  +  +  
          +  +  +  -  -  
             -  -  -  - ]
     247                 :             :   }
     248                 :             : 
     249                 :             :   // T-232: Suggestion column color by confidence (shared palette)
     250   [ +  +  +  + ]:        5259 :   if (role == Qt::ForegroundRole && col == Suggestion) {
     251                 :           5 :     auto it = m_suggestionCache.constFind(MailKey{h.folderId, h.uid});
     252   [ +  +  +  - ]:           9 :     return ThemeManager::confidenceColor(
     253         [ +  - ]:          19 :         it != m_suggestionCache.constEnd() ? it->confidence : 0.0);
     254                 :             :   }
     255                 :             : 
     256                 :             :   // Text alignment for Star column
     257   [ +  +  +  + ]:        5254 :   if (role == Qt::TextAlignmentRole && col == Star) {
     258                 :         154 :     return static_cast<int>(Qt::AlignCenter);
     259                 :             :   }
     260                 :             : 
     261         [ +  + ]:        5100 :   if (role == SortRole) {
     262   [ -  +  -  +  :         179 :     switch (col) {
                -  -  - ]
     263                 :           0 :     case Star:
     264         [ #  # ]:           0 :       return h.isFlagged() ? 1 : 0;
     265                 :           1 :     case Subject:
     266                 :           1 :       return h.subject;
     267                 :           0 :     case From:
     268                 :           0 :       return h.from;
     269                 :         178 :     case Date:
     270                 :         178 :       return h.date;
     271                 :           0 :     case Size:
     272                 :           0 :       return h.size;
     273                 :           0 :     case Suggestion:
     274                 :           0 :       return 0.0;
     275                 :             :     }
     276                 :             :   }
     277                 :             : 
     278         [ +  + ]:        4921 :   if (role == FlagsRole)
     279                 :         126 :     return h.flags;
     280         [ +  + ]:        4795 :   if (role == HasAttachmentsRole)
     281                 :         110 :     return h.hasAttachments;
     282         [ +  + ]:        4685 :   if (role == LabelsRole) {
     283                 :         110 :     QStringList filtered;
     284         [ +  + ]:         118 :     for (const auto &label : h.labels) {
     285   [ +  -  +  - ]:           8 :       if (!ImapResponseParser::isInternalKeyword(label))
     286         [ +  - ]:           8 :         filtered.append(label);
     287                 :             :     }
     288                 :         110 :     return filtered;
     289                 :         110 :   }
     290         [ +  + ]:        4575 :   if (role == ThreadCountRole)
     291                 :           1 :     return node->descendantCount();
     292         [ +  + ]:        4574 :   if (role == DepthRole)  // T-128
     293                 :           3 :     return node->depth;
     294                 :             : 
     295                 :        4571 :   return {};
     296                 :             : }
     297                 :             : 
     298                 :        2225 : QVariant MailThreadModel::headerData(int section, Qt::Orientation orientation,
     299                 :             :                                       int role) const {
     300   [ +  +  +  + ]:        2225 :   if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
     301                 :        1729 :     return {};
     302                 :             : 
     303   [ +  +  +  +  :         496 :   switch (section) {
             +  +  +  - ]
     304                 :          82 :   case Star:
     305                 :          82 :     return QStringLiteral("★");
     306                 :          82 :   case Attachment:
     307                 :          82 :     return QStringLiteral("");
     308                 :          83 :   case Subject:
     309         [ +  - ]:          83 :     return tr("Subject");
     310                 :           1 :   case Suggestion:
     311                 :           1 :     return QStringLiteral("→");
     312                 :          83 :   case From:
     313         [ +  - ]:          83 :     return tr("From");
     314                 :          83 :   case Date:
     315         [ +  - ]:          83 :     return tr("Date");
     316                 :          82 :   case Size:
     317         [ +  - ]:          82 :     return tr("Size");
     318                 :             :   }
     319                 :           0 :   return {};
     320                 :             : }
     321                 :             : 
     322                 :             : // Sprint 76 (T-76.B3): refresh translated headers on a live language switch.
     323                 :           2 : void MailThreadModel::retranslateUi() {
     324   [ +  -  +  - ]:           2 :   if (columnCount() > 0)
     325   [ +  -  +  - ]:           2 :     emit headerDataChanged(Qt::Horizontal, 0, columnCount() - 1);
     326                 :           2 : }
     327                 :             : 
     328                 :             : // ═══════════════════════════════════════════════════════
     329                 :             : // Drag & Drop (T-102)
     330                 :             : // ═══════════════════════════════════════════════════════
     331                 :             : 
     332                 :        1074 : Qt::ItemFlags MailThreadModel::flags(const QModelIndex &index) const {
     333         [ +  - ]:        1074 :   auto defaultFlags = QAbstractItemModel::flags(index);
     334         [ +  - ]:        1074 :   if (index.isValid())
     335                 :        1074 :     return defaultFlags | Qt::ItemIsDragEnabled;
     336                 :           0 :   return defaultFlags;
     337                 :             : }
     338                 :             : 
     339                 :           2 : QStringList MailThreadModel::mimeTypes() const {
     340   [ +  +  -  - ]:           4 :   return {QStringLiteral("application/x-mailjd-uids")};
     341   [ +  -  -  -  :           4 : }
                   -  - ]
     342                 :             : 
     343                 :           1 : QMimeData *MailThreadModel::mimeData(const QModelIndexList &indexes) const {
     344                 :           1 :   QSet<qint64> uidSet;
     345         [ +  + ]:           2 :   for (const auto &idx : indexes) {
     346                 :           1 :     auto *node = nodeFromIndex(idx);
     347   [ +  -  +  - ]:           1 :     if (node && node->header)
     348         [ +  - ]:           1 :       uidSet.insert(node->header->uid);
     349                 :             :   }
     350                 :             : 
     351                 :           1 :   QByteArray encoded;
     352         [ +  - ]:           1 :   QDataStream stream(&encoded, QIODevice::WriteOnly);
     353   [ +  -  +  -  :           2 :   for (qint64 uid : uidSet)
                   +  + ]
     354         [ +  - ]:           1 :     stream << uid;
     355                 :             : 
     356   [ +  -  +  -  :           1 :   auto *mimeData = new QMimeData;
             -  +  -  - ]
     357         [ +  - ]:           2 :   mimeData->setData(QStringLiteral("application/x-mailjd-uids"), encoded);
     358   [ +  -  +  -  :           2 :   mimeData->setText(QString("%1 Mail(s)").arg(uidSet.size()));
                   +  - ]
     359                 :           1 :   return mimeData;
     360                 :           1 : }
     361                 :             : 
     362                 :           2 : Qt::DropActions MailThreadModel::supportedDragActions() const {
     363                 :           2 :   return Qt::MoveAction;
     364                 :             : }
     365                 :             : 
     366                 :             : // --- Private helpers ---
     367                 :             : 
     368                 :        9023 : ThreadNode *MailThreadModel::nodeFromIndex(const QModelIndex &index) const {
     369         [ +  + ]:        9023 :   if (!index.isValid())
     370                 :           2 :     return nullptr;
     371                 :        9021 :   return static_cast<ThreadNode *>(index.internalPointer());
     372                 :             : }
     373                 :             : 
     374                 :          50 : QModelIndex MailThreadModel::indexFromNode(ThreadNode *node, int column) const {
     375         [ -  + ]:          50 :   if (!node)
     376                 :           0 :     return {};
     377                 :             : 
     378         [ +  + ]:          50 :   if (!node->parent) {
     379                 :          44 :     int row = findRootIndex(node);
     380         [ -  + ]:          44 :     if (row < 0)
     381                 :           0 :       return {};
     382                 :          44 :     return createIndex(row, column, node);
     383                 :             :   }
     384                 :             : 
     385                 :           6 :   int row = findChildIndex(node);
     386         [ -  + ]:           6 :   if (row < 0)
     387                 :           0 :     return {};
     388                 :           6 :   return createIndex(row, column, node);
     389                 :             : }
     390                 :             : 
     391                 :          92 : int MailThreadModel::findRootIndex(ThreadNode *node) const {
     392         [ +  - ]:         107 :   for (size_t i = 0; i < m_roots.size(); ++i) {
     393         [ +  + ]:         107 :     if (m_roots[i].get() == node)
     394                 :          92 :       return i;
     395                 :             :   }
     396                 :           0 :   return -1;
     397                 :             : }
     398                 :             : 
     399                 :           6 : int MailThreadModel::findChildIndex(ThreadNode *node) const {
     400   [ +  -  -  + ]:           6 :   if (!node || !node->parent)
     401                 :           0 :     return -1;
     402         [ +  - ]:           9 :   for (size_t i = 0; i < node->parent->children.size(); ++i) {
     403         [ +  + ]:           9 :     if (node->parent->children[i].get() == node)
     404                 :           6 :       return i;
     405                 :             :   }
     406                 :           0 :   return -1;
     407                 :             : }
     408                 :             : 
     409                 :         105 : void MailThreadModel::buildUidIndex() {
     410                 :         105 :   m_uidIndex.clear();
     411                 :         407 :   std::function<void(ThreadNode *)> walk = [&](ThreadNode *node) {
     412         [ +  - ]:         197 :     if (node->header)
     413         [ +  - ]:         197 :       m_uidIndex.insert(MailKey{node->header->folderId, node->header->uid}, node);
     414         [ +  + ]:         209 :     for (auto &child : node->children)
     415         [ +  - ]:          12 :       walk(child.get());
     416                 :         302 :   };
     417         [ +  + ]:         290 :   for (auto &root : m_roots)
     418         [ +  - ]:         185 :     walk(root.get());
     419                 :         105 : }
     420                 :             : 
     421                 :             : // T-232: Set suggestion for a specific UID
     422                 :           7 : void MailThreadModel::setSuggestion(qint64 uid, qint64 folderId,
     423                 :             :                                      const QString &text,
     424                 :             :                                      double confidence) {
     425         [ +  - ]:           7 :   m_suggestionCache[MailKey{folderId, uid}] = {text, confidence};
     426                 :           7 :   auto *node = m_uidIndex.value(MailKey{folderId, uid}, nullptr);
     427         [ +  - ]:           7 :   if (node) {
     428         [ +  - ]:           7 :     auto idx = indexFromNode(node, Suggestion);
     429   [ +  -  +  - ]:           7 :     emit dataChanged(idx, idx, {Qt::DisplayRole, Qt::ForegroundRole});
     430                 :             :   }
     431         [ -  - ]:          14 : }
     432                 :             : 
     433                 :             : // T-232: Clear all suggestion data
     434                 :           4 : void MailThreadModel::clearSuggestions() {
     435         [ +  + ]:           4 :   if (m_suggestionCache.isEmpty())
     436                 :           2 :     return;
     437                 :           2 :   m_suggestionCache.clear();
     438                 :             :   // Use dataChanged for the suggestion column range instead of dangerous
     439                 :             :   // bare layoutChanged() (which causes crash without layoutAboutToBeChanged).
     440         [ +  - ]:           2 :   if (!m_roots.empty()) {
     441   [ +  -  +  -  :           6 :     emit dataChanged(index(0, Suggestion),
                   +  - ]
     442   [ +  -  +  - ]:           4 :                      index(rowCount() - 1, Suggestion),
     443                 :             :                      {Qt::DisplayRole, Qt::ForegroundRole});
     444                 :             :   }
     445                 :             : }
        

Generated by: LCOV version 2.0-1