diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-08 10:13:30 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-08 10:13:30 +0200 |
| commit | e0595d421c2327ae44ec7af3940ec6ef3d966eae (patch) | |
| tree | 2cbcdf1585f9aa17273cacb4d6b015e308ce3c13 | |
| parent | 37c78f4839d6879144940ab3578e304f9714f839 (diff) | |
| download | qtmaildir-e0595d421c2327ae44ec7af3940ec6ef3d966eae.tar.gz qtmaildir-e0595d421c2327ae44ec7af3940ec6ef3d966eae.zip | |
feat(types): add MessageNode for message rows in the thread list
A message row has to be drawn without opening the message, so it needs sender,
subject and date. MessageRef carries none of them: it exists for rendering a
thread into the pane and holds only id, path, tags and matched.
depth defaults to 0, the thread's first message, which the root row stands for
rather than a child row. threadId is carried so a batch of nodes names the
thread it belongs to without the caller tracking it alongside.
| -rw-r--r-- | src/types.h | 31 | ||||
| -rw-r--r-- | tests/test_threadlistmodel.cpp | 26 |
2 files changed, 57 insertions, 0 deletions
diff --git a/src/types.h b/src/types.h index 999d3f3..6b2ca4c 100644 --- a/src/types.h +++ b/src/types.h @@ -63,6 +63,37 @@ struct MessageRef bool matched = true; }; +/// One message as a row in the thread list. +/// +/// Separate from MessageRef, which exists for RENDERING a thread and carries +/// only what the message pane needs. A row has to be drawn without opening the +/// message at all, so the display facts live here. +struct MessageNode +{ + QString messageId; + QString threadId; ///< The thread this message belongs to. + QString from; + QString subject; + QDateTime date; + QStringList tags; + QString filePath; + + /// Reply depth within the thread. 0 is the thread's first message, which + /// occupies the ROOT row rather than a child row: the user's model is + /// "N replies", so a thread of 7 shows 1 root and 6 descendants. + int depth = 0; + + bool isUnread() const { return tags.contains(QStringLiteral("unread")); } + bool isFlagged() const { return tags.contains(QStringLiteral("flagged")); } + + /// notmuch applies "attachment" while indexing, so this needs no MIME + /// parsing, exactly as on ThreadSummary. + bool hasAttachment() const + { + return tags.contains(QStringLiteral("attachment")); + } +}; + /// One tag mutation, kept so it can be inverted for undo. struct TagChange { diff --git a/tests/test_threadlistmodel.cpp b/tests/test_threadlistmodel.cpp index f84bbab..e2ca09f 100644 --- a/tests/test_threadlistmodel.cpp +++ b/tests/test_threadlistmodel.cpp @@ -27,6 +27,7 @@ class TestThreadListModel : public QObject { Q_OBJECT private slots: + void messageNodeHoldsDisplayFacts(); void startsEmpty(); void accountKeysComeFromTheAccountTags(); void accountKeysCoverAThreadSpanningTwoAccounts(); @@ -116,6 +117,31 @@ void TestThreadListModel::accountKeysAreEmptyForAnUnknownThread() QVERIFY(model.accountKeysForThread(QStringLiteral("nope")).isEmpty()); } +void TestThreadListModel::messageNodeHoldsDisplayFacts() +{ + // A message ROW has to be drawn without opening the message, so the display + // facts live on the node itself. MessageRef, which exists for rendering a + // thread into the pane, carries none of them. + MessageNode node; + node.messageId = QStringLiteral("id@example.org"); + node.from = QStringLiteral("A Sender <sender@example.org>"); + node.subject = QStringLiteral("Re: a subject"); + node.date = QDateTime::fromSecsSinceEpoch(1000); + node.depth = 2; + node.tags = QStringList{ QStringLiteral("unread") }; + + QCOMPARE(node.depth, 2); + QVERIFY(node.isUnread()); + QCOMPARE(node.from, QStringLiteral("A Sender <sender@example.org>")); + QCOMPARE(node.subject, QStringLiteral("Re: a subject")); + + // Depth 0 is the thread's first message, which the ROOT row stands for. + // Defaulting to 0 rather than 1 keeps "is this the root" a plain check. + const MessageNode fresh; + QCOMPARE(fresh.depth, 0); + QVERIFY(!fresh.isUnread()); +} + void TestThreadListModel::startsEmpty() { ThreadListModel model; |
