diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-28 17:56:25 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-28 17:56:25 +0200 |
| commit | a346bef6c08e9725f3ab41f8237f01e9eb69eea7 (patch) | |
| tree | ca3ea16c23da8971d6b9f8f7d4867845e5c25e0c /src/threadlistmodel.cpp | |
| parent | f934029d3333396bde6ec306ab300641929c7cdc (diff) | |
| download | qtmaildir-a346bef6c08e9725f3ab41f8237f01e9eb69eea7.tar.gz qtmaildir-a346bef6c08e9725f3ab41f8237f01e9eb69eea7.zip | |
feat: judge a row's membership on the thread's union
Closes item 170 under item 177. A conversation belongs to a view while any
of its messages match, so reading one message of a thread no longer takes
the conversation out of the Unread view. The current row is never evicted,
and an automatic write defers its eviction until the selection moves.
Diffstat (limited to 'src/threadlistmodel.cpp')
| -rw-r--r-- | src/threadlistmodel.cpp | 54 |
1 files changed, 54 insertions, 0 deletions
diff --git a/src/threadlistmodel.cpp b/src/threadlistmodel.cpp index fd4899b..0559694 100644 --- a/src/threadlistmodel.cpp +++ b/src/threadlistmodel.cpp @@ -206,6 +206,34 @@ void ThreadListModel::setTrashView(bool trash) } } +void ThreadListModel::removeThreadsWithoutTag(const QStringList &threadIds, + const QString &tag) +{ + if (tag.isEmpty() || threadIds.isEmpty()) + return; + + // Backwards for the same reason the sweeping form below is: each removal + // renumbers everything after it. + for (int row = m_threads.size() - 1; row >= 0; --row) { + const ThreadNode &node = m_threads.at(row); + if (!threadIds.contains(node.summary.threadId)) + continue; + + // The SUMMARY, which is notmuch's union over the conversation, and + // never `first.tags`. A thread belongs to a view while ANY of its + // messages match it (item 177), so reading the message a 44-message + // card happens to draw must not evict the conversation while two of + // its replies are still unread. `first.tags` is right for what the + // card PAINTS and wrong for whether the row belongs here at all. + if (node.summary.tags.contains(tag)) + continue; + + beginRemoveRows({}, row, row); + m_threads.remove(row); + endRemoveRows(); + } +} + void ThreadListModel::removeThreadsWithoutTag(const QString &tag) { if (tag.isEmpty() || m_threads.isEmpty()) @@ -995,6 +1023,15 @@ MessageNode ThreadListModel::messageAt(const QModelIndex &index) const QString ThreadListModel::threadIdForMessage(const QString &messageId) const { for (const ThreadNode &node : m_threads) { + // The ROOT first. A thread's first message is not among its children + // (item 109: setThreadMessages drops depth 0 because the root row + // stands for it), so a search over children alone answers "no thread" + // for every thread row's own message, which is the id an ordinary tag + // action resolves to since item 108. + if (node.first.messageId == messageId + || node.summary.firstMessageId == messageId) { + return node.summary.threadId; + } for (const MessageNode &child : node.children) { if (child.messageId == messageId) return node.summary.threadId; @@ -1003,6 +1040,23 @@ QString ThreadListModel::threadIdForMessage(const QString &messageId) const return {}; } +bool ThreadListModel::hasThread(const QString &threadId) const +{ + return std::any_of(m_threads.cbegin(), m_threads.cend(), + [&threadId](const ThreadNode &node) { + return node.summary.threadId == threadId; + }); +} + +int ThreadListModel::threadCountFor(const QString &threadId) const +{ + for (const ThreadNode &node : m_threads) { + if (node.summary.threadId == threadId) + return node.summary.totalCount; + } + return 0; +} + MessageNode ThreadListModel::messageById(const QString &messageId) const { if (messageId.isEmpty()) |
