aboutsummaryrefslogtreecommitdiffstats
path: root/src/mainwindow.h
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-25 18:11:25 +0200
committerDanilo M. <danix@danix.xyz>2026-08-25 18:11:25 +0200
commit3955ff80e3b0d558cd854c4517d1835514010b90 (patch)
tree0e3fd9186f7db039646729f3139ef15b3cb43468 /src/mainwindow.h
parent39d0371f284a5fb20eb132a60c795d6814b41b0a (diff)
downloadqtmaildir-3955ff80e3b0d558cd854c4517d1835514010b90.tar.gz
qtmaildir-3955ff80e3b0d558cd854c4517d1835514010b90.zip
feat: say which way the unread action will go, and hide it when it cannot
Item 112, and 99 and 147 with it: the user's note is one design across all three. A union is not a state. ThreadSummary::tags is notmuch's union over the conversation, so a thread holding even one unread message answered "unread" and the thread toggle always chose "mark read". There was no input that reached "mark thread unread" on a mixed thread, which is the thread a user wants it for. The thread toggle becomes two absolute actions, mark_thread_read and mark_thread_unread. Neither takes a default chord, at the user's choice: Ctrl+Alt+U meant whichever direction the union picked, and since item 132 a shortcut is a chosen subset rather than a requirement. It is now unbound. The message-scoped toggle stays a toggle, because one message has a real two-valued state, and its label now names the direction it will go. On a selection with no single state the entry is hidden rather than labelled wrongly, chosen over disabling it; the thread submenu is the route then, and its entries are absolute. selectionTagPresence() is the three-valued predicate that needed to exist. everySelectedRowHasTag() delegates to it and keeps its two-valued answer, which is all a direction needs; a label needs the third value. The refresh is keyed on the model's dataChanged as well as on the selection, so a write moves the label without reselecting and none of the six optimistic-update call sites has to remember. Three mutations fail: restoring the union predicate reports the user's original symptom, showing the action on a mixed selection, and dropping the dataChanged refresh. The suite is 37 of 38, the failure being item 136 on an unrelated path. Four new strings translated, lrelease reports 0 unfinished. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HFuRPtzFrSxCQjFk6tq7gD
Diffstat (limited to 'src/mainwindow.h')
-rw-r--r--src/mainwindow.h17
1 files changed, 17 insertions, 0 deletions
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 951eaa4..d3c5d15 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -887,6 +887,23 @@ private:
bool everySelectedRowHasTag(const QString &tag,
TagScope scope = TagScope::Message) const;
+ /// The three-valued version of the question above, which is what a LABEL
+ /// needs and a toggle's direction does not.
+ ///
+ /// `everySelectedRowHasTag` answers yes or no over a reality with three
+ /// states: every row has the tag, none does, or they disagree. That is
+ /// enough to choose a direction, since a mixed selection has to go one way
+ /// or the other, but it cannot name the direction honestly, and item 112
+ /// is what happens when a two-valued predicate is asked a three-valued
+ /// question.
+ enum class TagPresence { None, Every, Mixed };
+ TagPresence selectionTagPresence(
+ const QString &tag, TagScope scope = TagScope::Message) const;
+
+ /// Relabels the unread action, and hides it when the selection has no
+ /// single state. Called whenever the selection changes.
+ void refreshUnreadAction();
+
void editTagsOnSelection();
/// Set once the user has answered the exit prompt, or once a sync started