aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-17 13:48:54 +0200
committerDanilo M. <danix@danix.xyz>2026-08-17 13:48:54 +0200
commitabf56abd167109f50e0ddb06e1baf92192174cd7 (patch)
tree20495d1bedd7949d2d0809ae6573fc5e3f23dd0b /src
parent019117aa8e52ce39cab58f77b57a9a67f510696f (diff)
downloadqtmaildir-abf56abd167109f50e0ddb06e1baf92192174cd7.tar.gz
qtmaildir-abf56abd167109f50e0ddb06e1baf92192174cd7.zip
fix(ui): make Important a toggle, like Delete and Toggle unread
The `flag` action only ever added the `flagged` tag, so pressing Ctrl+I on a thread or message that was already important re-applied a tag it already had. Re-applying a tag changes nothing and repaints nothing, so the key read as dead, and removing `flagged` meant opening the tag dialog. It now reads the current state and picks a direction, exactly as `delete` and `toggle_unread` beside it do. One direction is chosen for the whole selection: it unmarks only when every selected row is already important, so a single keystroke cannot leave a selection in two states. The direction comes from everySelectedRowHasTag(), never a hand-rolled loop. Two separate bugs went into that helper on 2026-08-16 (items 88 and 105), and a copy of the then-current `delete` loop would have inherited both: resolving a reply's row number against the top-level list, and asking a reply's THREAD where the write is message-scoped, which makes a toggle one-way. The reply test needs THREE different states to mean anything: the first thread in the list unflagged, the reply's own thread flagged, and the reply itself unflagged. With the reply left in its thread's state, the mutation putting item 105's bug back stayed green, measured. The fixture helper defaults replyTags to the thread's, so a test that does not pass them explicitly asserts nothing about scope. Backlog item 98.
Diffstat (limited to 'src')
-rw-r--r--src/mainwindow.cpp19
1 files changed, 17 insertions, 2 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index ee883b0..594535b 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -860,8 +860,23 @@ void MainWindow::registerActions()
// &I rather than &S: the Message menu already has "Mark &spam", so
// "Starred" would have needed an accelerator from inside the word.
addAction(QStringLiteral("flag"), tr("&Important"),
- tr("Mark the selected threads as important"), [this]() {
- tagSelected({ QStringLiteral("flagged") }, {}, tr("Mark important"));
+ tr("Add or remove the important tag"), [this]() {
+ // Item 98. A toggle, like Delete and Toggle unread beside it: adding a
+ // tag that is already there is a no-op the user cannot see, so a
+ // one-way add read as a dead key on anything already important.
+ //
+ // everySelectedRowHasTag() rather than a loop of its own. Two separate
+ // bugs went into that logic on 2026-08-16 (items 88 and 105), and a
+ // copy of the then-current Delete loop would have inherited both:
+ // resolving a reply's row number to the wrong thread, and asking a
+ // reply's THREAD where the write is message-scoped, which makes a
+ // toggle one-way.
+ const bool allFlagged = everySelectedRowHasTag(QStringLiteral("flagged"));
+
+ if (allFlagged)
+ tagSelected({}, { QStringLiteral("flagged") }, tr("Unmark important"));
+ else
+ tagSelected({ QStringLiteral("flagged") }, {}, tr("Mark important"));
});
addAction(QStringLiteral("toggle_unread"), tr("Toggle &unread"),
tr("Toggle the unread tag"), [this]() {