aboutsummaryrefslogtreecommitdiffstats
path: root/src/threadlistmodel.h
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-26 13:28:51 +0200
committerDanilo M. <danix@danix.xyz>2026-08-26 13:30:22 +0200
commit0ca4624195cdd8c78ff614e3912af5b914458497 (patch)
tree6fc739e77fed6b5a4e178cacf76ac29fd708ab0b /src/threadlistmodel.h
parentd835cf3c554e9657fffdb91971b66e3a74aee323 (diff)
downloadqtmaildir-0ca4624195cdd8c78ff614e3912af5b914458497.tar.gz
qtmaildir-0ca4624195cdd8c78ff614e3912af5b914458497.zip
feat: flag what you answered, mark what was forwarded to you
Item 68, which turned out to be three things once its premise was measured. The note asked to extend a "passed" subject rule to "Fw:"; there was no subject rule, and the correlation it rested on did not exist. What did exist was a gap nobody had reported. Reply and forward now flag their source. The Maildir R and P flags, which every other client sets and notmuch reads back as "replied" and "passed", had never been written here: measured on the developer's index, all 317 "replied" and all 6 "passed" came from other clients. ComposeWindow emits sourceMessageAnswered after a successful send and MainWindow routes it through sendMessageTagChange, message-scoped and off the undo stack, for the reason auto mark-read is: the flag records that the mail went, and the send cannot be undone. ComposeContext carries sourceMessageId rather than reusing inReplyTo, which is deliberately empty on a forward so the recipient's client does not file it under the thread it left. Keying on it made the "passed" half dead code that compiled and never fired. A resumed draft is excluded: its kind records how the file was opened, not what the user is doing, so flagging on it would set R from a guess. A received forward gets its own mark. Derived from the subject at paint time, storing nothing and reaching no server, because "passed" means "I forwarded this" and setting it from a guess would assert something false on 222 existing messages. subjectIsForwarded() shares forwardSubject()'s prefix table so the two cannot disagree, strips a Re: chain first, and takes extra locale spellings from [general] forward_prefixes, which extends the built-in table rather than replacing it. A mutation survived the first round and corrected a claim in the code: QRegularExpression::escape already makes a punctuation prefix inert, so the word guard is not about pattern validity. It stops a configured "-" matching "-: x". The comment and test say that now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXCZFLXbAii5n5wtovpdhh
Diffstat (limited to 'src/threadlistmodel.h')
-rw-r--r--src/threadlistmodel.h41
1 files changed, 41 insertions, 0 deletions
diff --git a/src/threadlistmodel.h b/src/threadlistmodel.h
index 717537c..2e56328 100644
--- a/src/threadlistmodel.h
+++ b/src/threadlistmodel.h
@@ -135,6 +135,12 @@ public:
/// Item 69 draws this as a mark where it used to read as the word
/// "passed" in the tag strip.
IsPassedRole,
+ /// True when the SUBJECT reads as a forward someone sent the user.
+ ///
+ /// Item 68. Derived from the subject at query time, not from a tag or
+ /// a Maildir flag: `passed` means "I forwarded this", which is a
+ /// different fact. Nothing is stored and nothing reaches the server.
+ IsReceivedForwardRole,
/// bool; the message was replied to, from the Maildir "R" flag.
IsRepliedRole,
@@ -187,6 +193,16 @@ public:
/// The pattern DateFormatRole answers with. Empty means the system format.
void setDateFormat(const QString &format) { m_dateFormat = format; }
+ /// Extra subject prefixes counting as a received forward (item 68).
+ ///
+ /// Pushed in from the config exactly as setDateFormat() is, rather than
+ /// giving the model a Config: both are display values the window already
+ /// holds, and the model draws rather than resolves.
+ void setForwardPrefixes(const QStringList &prefixes)
+ {
+ m_forwardPrefixes = prefixes;
+ }
+
/// One row per thread, with no expander and no reply count.
///
/// For the Sent view, where a thread is the wrong unit: the user's model of
@@ -203,6 +219,29 @@ public:
/// The children are not discarded, only hidden. Leaving flat mode restores
/// the tree without reloading anything.
void setFlatMode(bool flat);
+
+ /// Whether the list is showing the trash view.
+ ///
+ /// The doomed fill exists to tell the user a message is on its way out of
+ /// a view it is still sitting in. In the trash that is redundant: every
+ /// row is deleted, and a list painted entirely crimson says nothing while
+ /// costing legibility. Set on EVERY query run, like flat mode, so it
+ /// cannot leak into the next view.
+ void setTrashView(bool trash);
+
+ /// Drops any top-level row whose message no longer carries \p tag.
+ ///
+ /// The optimistic counterpart to a row simply vanishing at the next query.
+ /// Delete strips `inbox`, and in the Inbox view the row it stripped it
+ /// from stops belonging there; leaving it until the next sync is what made
+ /// a deleted message sit in the inbox looking undeleted.
+ ///
+ /// Top-level rows ONLY, and deliberately: a reply that no longer matches
+ /// still belongs to the conversation the user has open, and removing it
+ /// would collapse a thread under the reader's hands. \p tag is the tag the
+ /// CURRENT VIEW requires, so a caller passes what the query filters on and
+ /// nothing else.
+ void removeThreadsWithoutTag(const QString &tag);
bool flatMode() const { return m_flatMode; }
QModelIndex index(int row, int column,
@@ -414,5 +453,7 @@ private:
QVector<ThreadNode> m_threads;
const TagColors *m_tagColors = nullptr;
QString m_dateFormat;
+ QStringList m_forwardPrefixes;
bool m_flatMode = false;
+ bool m_trashView = false;
};