aboutsummaryrefslogtreecommitdiffstats
path: root/src/threadlistmodel.cpp
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-28 11:37:21 +0200
committerDanilo M. <danix@danix.xyz>2026-08-28 11:37:21 +0200
commit20eb3877c4502a0b8ebe770a563066d8c85387ba (patch)
tree08c5ad3490094289b00137f66e35055b7d6b0d64 /src/threadlistmodel.cpp
parent9bec59c98a9f00e6e7f2c0181659a7639bc04e72 (diff)
downloadqtmaildir-20eb3877c4502a0b8ebe770a563066d8c85387ba.tar.gz
qtmaildir-20eb3877c4502a0b8ebe770a563066d8c85387ba.zip
feat: draw a conversation row's own tags, in one tier
Items 110 and 111 reconciled a card that showed one message with a row that was a thread. The row is the conversation now, so the union is simply what it means: the first-message substitution, PillOwnCountRole and the seeded first node all go.
Diffstat (limited to 'src/threadlistmodel.cpp')
-rw-r--r--src/threadlistmodel.cpp123
1 files changed, 16 insertions, 107 deletions
diff --git a/src/threadlistmodel.cpp b/src/threadlistmodel.cpp
index 1bda728..4a75d47 100644
--- a/src/threadlistmodel.cpp
+++ b/src/threadlistmodel.cpp
@@ -503,24 +503,13 @@ QVariant ThreadListModel::data(const QModelIndex &index, int role) const
const ThreadNode &rowNode = m_threads.at(index.row());
- // A card stands for ONE message since item 108, so it must draw that
- // message's tags and not the thread's. `ThreadSummary::tags` is notmuch's
- // UNION over the conversation: a four-message thread whose third message
- // is signed reads as signed, and the card said so about a message that was
- // not (item 110).
- //
- // Only the tags are substituted. Everything else on the card, the subject,
- // the authors, the date and the reply count, describes the THREAD and is
- // correct as it stands; only the tags were ever the union that lied.
- //
- // `first.tags` is populated when the message is loaded, which is when the
- // user selects the row. Before that the union is the only answer available
- // and is what the card shows, which is why an unopened row can still
- // display a sibling's mark. Narrowing that further needs per-message state
- // in the query itself.
- ThreadSummary thread = rowNode.summary;
- if (!rowNode.first.messageId.isEmpty())
- thread.tags = rowNode.first.tags;
+ // The row IS the conversation since item 177, so `ThreadSummary::tags`,
+ // notmuch's UNION over it, is simply what the row means. Items 110 and 111
+ // substituted the displayed message's tags here and drew the rest in a
+ // muted second tier, to reconcile "this card shows one message" with "this
+ // row is a thread"; the first half of that is gone, so the whole apparatus
+ // is.
+ const ThreadSummary &thread = rowNode.summary;
if (role == ThreadIdRole)
return thread.threadId;
@@ -566,8 +555,7 @@ QVariant ThreadListModel::data(const QModelIndex &index, int role) const
if (role == MessageOwnColoursRole)
return QVariantList();
- if (role == PillTagsRole || role == PillColoursRole
- || role == PillOwnCountRole) {
+ if (role == PillTagsRole || role == PillColoursRole) {
// Everything the row already says another way is dropped: the account
// is the chip in the subject cell, flagged is the star column,
// attachment is the paperclip, unread is the row not being dimmed, and
@@ -601,28 +589,8 @@ QVariant ThreadListModel::data(const QModelIndex &index, int role) const
return pills;
};
- // `thread.tags` is the displayed message's own tags once the row has
- // been opened, and the thread's union before that (see the
- // substitution above). The union is always the full set, so the
- // difference is what belongs only to siblings.
- QStringList pills = pillsFrom(thread.tags);
- const int ownCount = pills.size();
-
- // The sibling tier, appended after the message's own. Shown rather
- // than dropped at the user's request: a card sits above a
- // conversation, so what the rest of it carries is worth seeing, just
- // not at the same weight. The delegate draws these smaller and muted.
- //
- // Empty until the row has been opened, because before that
- // `thread.tags` IS the union and the difference is nothing. That is
- // what makes a chip shrink rather than appear.
- for (const QString &tag : pillsFrom(rowNode.summary.tags)) {
- if (!pills.contains(tag))
- pills.append(tag);
- }
-
- if (role == PillOwnCountRole)
- return ownCount;
+ // One tier: the thread's own tags, which is what the row stands for.
+ const QStringList pills = pillsFrom(thread.tags);
if (role == PillTagsRole)
return pills;
@@ -799,22 +767,11 @@ QVariant ThreadListModel::data(const QModelIndex &index, int role) const
ThreadListModel::ThreadNode
ThreadListModel::nodeFor(const ThreadSummary &summary)
{
- ThreadNode node{ summary, {}, {}, false };
-
- // Only when the query actually supplied them. An empty list here would be
- // indistinguishable from "this message carries nothing", which would put
- // every chip in the sibling tier and mute the whole card.
- if (!summary.firstMessageId.isEmpty()
- && !summary.firstMessageTags.isEmpty()) {
- node.first.messageId = summary.firstMessageId;
- node.first.threadId = summary.threadId;
- node.first.tags = summary.firstMessageTags;
- // Carried alongside the tags, for the same reason messageById()
- // carries it onto a synthesised root: an unexpanded row has to know
- // which account it belongs to before Delete can name a folder.
- node.first.filePath = summary.firstMessagePath;
- }
- return node;
+ // No `first` node. A conversation row stands for the thread and draws the
+ // union; a one-message row's message arrives with its children like any
+ // other. Seeding it here is what made a row an ambiguous half-message
+ // (item 177).
+ return ThreadNode{ summary, {}, {}, false };
}
void ThreadListModel::appendBatch(const QVector<ThreadSummary> &batch)
@@ -926,26 +883,8 @@ void ThreadListModel::reconcile(const QVector<ThreadSummary> &threads)
|| m_threads.at(row).summary.authors != summary.authors
|| m_threads.at(row).summary.date != summary.date
|| m_threads.at(row).summary.totalCount != summary.totalCount
- || m_threads.at(row).summary.matchedCount != summary.matchedCount
- // The card's OWN message, which can move while the thread's union
- // does not: a root read elsewhere leaves the thread unread as long
- // as any reply is. Without this the card kept the tags it was
- // first given, and the sibling tier with them.
- || m_threads.at(row).summary.firstMessageTags
- != summary.firstMessageTags) {
+ || m_threads.at(row).summary.matchedCount != summary.matchedCount) {
m_threads[row].summary = summary;
-
- // The node too, since the card draws its tags from there. Only the
- // tags: the node's children and loaded flag are the expansion
- // state this whole method exists to preserve, and `first` carries
- // no children.
- if (!summary.firstMessageId.isEmpty()
- && !summary.firstMessageTags.isEmpty()) {
- m_threads[row].first.messageId = summary.firstMessageId;
- m_threads[row].first.threadId = summary.threadId;
- m_threads[row].first.tags = summary.firstMessageTags;
- }
-
emit dataChanged(index(row, 0), index(row, 0));
}
}
@@ -1064,36 +1003,6 @@ QString ThreadListModel::threadIdForMessage(const QString &messageId) const
return {};
}
-void ThreadListModel::setRootMessageTags(const QString &messageId,
- const QStringList &tags)
-{
- if (messageId.isEmpty())
- return;
-
- for (int row = 0; row < m_threads.size(); ++row) {
- ThreadNode &node = m_threads[row];
- if (node.summary.firstMessageId != messageId
- && node.first.messageId != messageId) {
- continue;
- }
-
- if (node.first.tags == tags && !node.first.messageId.isEmpty())
- return; // Nothing changed; do not churn the view.
-
- // Enough of a node for the card to draw from. The rest of the display
- // still comes from the summary, which is correct for it: the subject,
- // the authors and the date describe the thread, and only the TAGS were
- // ever the union that lied about this message.
- node.first.messageId = messageId;
- node.first.threadId = node.summary.threadId;
- node.first.tags = tags;
-
- const QModelIndex threadIndex = index(row, 0, QModelIndex());
- emit dataChanged(threadIndex, threadIndex);
- return;
- }
-}
-
MessageNode ThreadListModel::messageById(const QString &messageId) const
{
if (messageId.isEmpty())