aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-26 15:55:13 +0200
committerDanilo M. <danix@danix.xyz>2026-08-26 15:55:13 +0200
commitca5b6ca711e73bc22fdf108e06d82241e059c6ee (patch)
tree26fc84fb4f5de11fbf71179fe2f3dc7eb9c01f2e
parent5e769b073062a8913d2c4250d079aeb2d7d7b0e8 (diff)
downloadqtmaildir-ca5b6ca711e73bc22fdf108e06d82241e059c6ee.tar.gz
qtmaildir-ca5b6ca711e73bc22fdf108e06d82241e059c6ee.zip
feat: expose a row's sender to the delegate
-rw-r--r--src/notmuchworker.cpp1
-rw-r--r--src/threadlistmodel.cpp17
-rw-r--r--src/threadlistmodel.h9
-rw-r--r--src/types.h7
-rw-r--r--tests/test_threadlistmodel.cpp23
5 files changed, 57 insertions, 0 deletions
diff --git a/src/notmuchworker.cpp b/src/notmuchworker.cpp
index 43f4758..28834df 100644
--- a/src/notmuchworker.cpp
+++ b/src/notmuchworker.cpp
@@ -222,6 +222,7 @@ void walkReplies(notmuch_messages_t *messages, int depth,
QString::fromUtf8(notmuch_message_get_filename(message));
node.from =
QString::fromUtf8(notmuch_message_get_header(message, "from"));
+ node.senderAddress = senderAddressOf(message);
node.subject =
QString::fromUtf8(notmuch_message_get_header(message, "subject"));
node.date =
diff --git a/src/threadlistmodel.cpp b/src/threadlistmodel.cpp
index fcd8e4f..a6fada3 100644
--- a/src/threadlistmodel.cpp
+++ b/src/threadlistmodel.cpp
@@ -383,6 +383,13 @@ QVariant ThreadListModel::data(const QModelIndex &index, int role) const
}
case AccountLabelRole:
return QString();
+ case SenderAddressRole:
+ // The REPLY's own sender, exactly as SendersRole serves the node's
+ // `from` and not the thread's summary. The bare address is what
+ // the avatar hashes and the business-senders list matches.
+ return node.senderAddress;
+ case SenderNameRole:
+ return node.from;
case Qt::DisplayRole:
case SubjectRole:
return node.subject;
@@ -678,6 +685,16 @@ QVariant ThreadListModel::data(const QModelIndex &index, int role) const
if (!thread.recipients.isEmpty())
return thread.recipients;
return thread.authors;
+ case SenderAddressRole:
+ // The bare address of the message this card stands for, carried from
+ // the query. This is what the avatar's hash and the business-senders
+ // lookup consume; the display name below is what the card draws.
+ return thread.firstMessageSender;
+ case SenderNameRole:
+ // The same string the card's first line shows: recipients in a flat
+ // view, where `authors` is the user on every row and says nothing.
+ return !thread.recipients.isEmpty() ? thread.recipients
+ : thread.authors;
case DateRole:
// The QDateTime itself. Formatting belongs to the delegate now: the
// card decides how much of a date it has room for, and a pre-formatted
diff --git a/src/threadlistmodel.h b/src/threadlistmodel.h
index 2e56328..4af09d8 100644
--- a/src/threadlistmodel.h
+++ b/src/threadlistmodel.h
@@ -52,6 +52,15 @@ public:
/// Fill colour for that chip.
AccountColourRole,
+ /// The bare address of the message this row stands for, for the
+ /// avatar's hash and for the business-senders lookup. Empty when the
+ /// query did not resolve one, which the delegate handles by falling
+ /// back to the account.
+ SenderAddressRole,
+ /// The display name to take initials from. `authors` for a thread row,
+ /// `recipients` in a flat view, matching what the card already shows.
+ SenderNameRole,
+
/// Every tag on the thread, for the strip under the message pane.
TagsRole,
diff --git a/src/types.h b/src/types.h
index 47ddb78..a1689ab 100644
--- a/src/types.h
+++ b/src/types.h
@@ -166,6 +166,13 @@ struct MessageNode
QString messageId;
QString threadId; ///< The thread this message belongs to.
QString from;
+
+ /// The BARE address of the message's From, with any display name
+ /// discarded, like `ThreadSummary::firstMessageSender` is for a thread
+ /// row. `from` above is the raw header and usually carries a display
+ /// name, which is what the avatar's hash and the business-senders
+ /// lookup cannot take initials or match against.
+ QString senderAddress;
QString subject;
QDateTime date;
QStringList tags;
diff --git a/tests/test_threadlistmodel.cpp b/tests/test_threadlistmodel.cpp
index 593a777..25c89b0 100644
--- a/tests/test_threadlistmodel.cpp
+++ b/tests/test_threadlistmodel.cpp
@@ -102,6 +102,7 @@ private slots:
void flatModeOffersNoExpanderAndNoReplyCount();
void flatModeIsOffByDefaultAndReversible();
void recipientsReplaceTheSenderWhenPresent();
+ void aRowCarriesItsSenderAndAccountAddress();
};
static ThreadSummary makeThread(const QString &id, const QString &subject)
@@ -510,6 +511,28 @@ void TestThreadListModel::reportsSubjectAndAuthors()
QStringList({ QStringLiteral("inbox"), QStringLiteral("unread") }));
}
+void TestThreadListModel::aRowCarriesItsSenderAndAccountAddress()
+{
+ // Task 8: the avatar needs the row's bare sender address to hash and to
+ // match against the business-senders list, and the display name to take
+ // initials from. Both come from the row itself, not from the load.
+ ThreadListModel model;
+ ThreadSummary summary;
+ summary.threadId = QStringLiteral("t1");
+ summary.subject = QStringLiteral("Subject");
+ summary.authors = QStringLiteral("John Doe");
+ summary.firstMessageId = QStringLiteral("m1");
+ summary.firstMessageSender = QStringLiteral("john@example.org");
+ model.appendBatch({ summary });
+
+ const QModelIndex index = model.index(0, 0);
+ QCOMPARE(index.data(ThreadListModel::SenderAddressRole).toString(),
+ QStringLiteral("john@example.org"));
+ // The display name comes from `authors`, which is all notmuch gives.
+ QCOMPARE(index.data(ThreadListModel::SenderNameRole).toString(),
+ QStringLiteral("John Doe"));
+}
+
void TestThreadListModel::theReplyCountExcludesTheRootMessage()
{
ThreadListModel model;