diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-26 19:16:37 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-26 19:16:37 +0200 |
| commit | 6ee94127510862139e89e8d653cd4994e27e5ffe (patch) | |
| tree | e8fefe65eac56d126b78240e7dc5a1af1eddaf6d /src/mainwindow.cpp | |
| parent | 06b0435830daaed49a2d5231dcb6f02ff0124d5d (diff) | |
| download | qtmaildir-6ee94127510862139e89e8d653cd4994e27e5ffe.tar.gz qtmaildir-6ee94127510862139e89e8d653cd4994e27e5ffe.zip | |
feat: snapshot the pending changes as rows
Item 119, first half: the data the list behind the unsynced-changes count
is built from, with no dialog and no worker, so the rules it has to follow
are testable on their own.
pendingChangeSnapshot() gathers the three queues the count sums into
PendingChange rows. Two properties are the whole point.
Scope follows the ACTION, not the storage. A held thread edit stays one
thread row, because a `*_thread` action made it and reporting its messages
instead would claim the user acted on each one; a netted tag edit and a held
move are message rows. The queues already encode that distinction, so
nothing is expanded and nothing is escalated.
The rows are grouped by id, so a message with several outstanding actions
appears once with its actions beneath it, which is the layout the user
asked for. The sort is stable, so those actions keep the order they were
made in; QHash has none of its own, and without it the list would reshuffle
between openings.
A snapshot, taken once and frozen. Subjects are empty here and filled by the
resolve step to come.
m_pendingTagEdits gains the action name beside the direction it already
kept. The direction alone was enough to count with; a list has to say what
each change was, and only the action that made it knows. It is carried from
TagChange::description rather than derived from the tag, so there is no
second table of tag names to labels to drift from the first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P88Q3MCSCSQxKDy7pmXh9F
Diffstat (limited to 'src/mainwindow.cpp')
| -rw-r--r-- | src/mainwindow.cpp | 58 |
1 files changed, 53 insertions, 5 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 59edf53..0c6092e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -18,6 +18,8 @@ #include "mainwindow.h" +#include <algorithm> + #include "maildirname.h" #include <QAction> @@ -4440,9 +4442,9 @@ void MainWindow::onTagsApplied(const TagChange &change) // message are two independent changes and must not cancel each other. for (const QString &messageId : change.messageIds) { for (const QString &tag : change.added) - recordPendingEdit(messageId, tag, true); + recordPendingEdit(messageId, tag, true, change.description); for (const QString &tag : change.removed) - recordPendingEdit(messageId, tag, false); + recordPendingEdit(messageId, tag, false, change.description); } updatePendingIndicator(); @@ -4989,7 +4991,7 @@ void MainWindow::runAutoSync() } void MainWindow::recordPendingEdit(const QString &messageId, const QString &tag, - bool added) + bool added, const QString &action) { const QString key = messageId + QLatin1Char('\n') + tag; @@ -4998,12 +5000,12 @@ void MainWindow::recordPendingEdit(const QString &messageId, const QString &tag, // long session of tagging and untagging. const auto existing = m_pendingTagEdits.constFind(key); if (existing != m_pendingTagEdits.constEnd()) { - if (*existing != added) + if (existing->added != added) m_pendingTagEdits.erase(m_pendingTagEdits.find(key)); return; } - m_pendingTagEdits.insert(key, added); + m_pendingTagEdits.insert(key, PendingEdit{ added, action }); } QStringList MainWindow::pendingSyncChannels() const @@ -5060,6 +5062,52 @@ int MainWindow::pendingEditCount() const return m_pendingTagEdits.size() + held + heldMoves; } +QVector<PendingChange> MainWindow::pendingChangeSnapshot() const +{ + QVector<PendingChange> rows; + + // The netted per-(message, tag) edits. The key is `messageId\ntag`, built + // by recordPendingEdit(), so the id is everything before the first + // newline: a TAG may contain almost anything, but a message id cannot + // contain a newline and neither separator can be confused for the other. + for (auto it = m_pendingTagEdits.cbegin(); it != m_pendingTagEdits.cend(); + ++it) { + const QString id = it.key().section(QLatin1Char('\n'), 0, 0); + rows.append(PendingChange{ id, false, it->action, QString(), -1 }); + } + + // Held THREAD edits, which stay thread-scoped: a `*_thread` action is what + // made them, and reporting the messages instead would claim the user acted + // on each one. One row per thread the edit named, since a single edit can + // cover a multi-row selection. + for (const HeldEdit &edit : m_heldEdits) { + for (const QString &threadId : edit.threadIds) { + rows.append(PendingChange{ threadId, true, edit.change.description, + QString(), -1 }); + } + } + + // Held MOVES, which are message-scoped. A move is not a tag change and is + // queued separately for that reason, but it is the same kind of row here: + // one message, one action the user took. + for (const HeldMove &move : m_heldMoves) { + for (const QString &messageId : move.messageIds) + rows.append(PendingChange{ messageId, false, move.description, + QString(), -1 }); + } + + // Grouped by id so a message with several outstanding actions appears + // ONCE with its actions beneath it, which is the layout the user asked + // for. A stable sort, so the actions under one message keep the order + // they were made in rather than an arbitrary one; QHash has no order of + // its own, so without this the list reshuffles between openings. + std::stable_sort(rows.begin(), rows.end(), + [](const PendingChange &a, const PendingChange &b) { + return a.id < b.id; + }); + return rows; +} + void MainWindow::updatePendingIndicator() { const int pending = pendingEditCount(); |
