diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-31 11:26:52 +0100 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-31 11:26:52 +0100 |
| commit | 467fa84fd851459d5d9c9e5297e2500e938f0a18 (patch) | |
| tree | 392d0237cac273a6a07db11b120a8a453e8e8ff6 /src/threadlistmodel.h | |
| parent | 9ae9473acdb7cb3310c3028765cc4b30fedfce27 (diff) | |
| download | qtmaildir-467fa84fd851459d5d9c9e5297e2500e938f0a18.tar.gz qtmaildir-467fa84fd851459d5d9c9e5297e2500e938f0a18.zip | |
fix: keep bulk tag actions linear, and say when they land
Select all on a 10k-row search, then Archive, froze the window with one
CPU pinned. gdb on the live process showed the UI thread cubic before the
write was ever sent: each optimistic row write emitted dataChanged, whose
handler refreshed the action labels by walking the whole selection, and
each step of that walk looked the message up with a linear scan of every
thread.
ThreadListModel now answers id lookups from a lazily rebuilt hash,
invalidated by its own structural signals and by hand at the two in-place
id changes. MainWindow::batchModelWrites() holds the label refresh while a
loop of optimistic writes runs, then refreshes once. Two list membership
tests became sets.
The announcement also used to fade on a timer whether or not the write
had landed. It now reads "applying" until a round trip through the
worker's queue says the write has run, then "done". An error takes the
label back from a late "done".
Item 211.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'src/threadlistmodel.h')
| -rw-r--r-- | src/threadlistmodel.h | 20 |
1 files changed, 20 insertions, 0 deletions
diff --git a/src/threadlistmodel.h b/src/threadlistmodel.h index bed099d..fc7f65a 100644 --- a/src/threadlistmodel.h +++ b/src/threadlistmodel.h @@ -20,6 +20,7 @@ #include <QAbstractItemModel> #include <QColor> +#include <QHash> #include <QVector> #include "tagcolors.h" @@ -450,7 +451,26 @@ private: /// than claiming the union as one message's. static ThreadNode nodeFor(const ThreadSummary &summary); + /// The top-level row holding a thread or a message, or -1. + /// + /// Answered from a hash rather than a walk, because a bulk action asks + /// once per message and a walk per message made archiving ten thousand + /// rows quadratic in the lookups alone (item 211). The FIRST row in list + /// order wins, which is the row every linear walk here used to stop at, + /// so the two are interchangeable; a flat view can hold one thread on + /// several rows and still answers with the first. + int rowForThread(const QString &threadId) const; + int rowForMessage(const QString &messageId) const; + void rebuildIndex() const; + QVector<ThreadNode> m_threads; + // Rebuilt lazily on the next lookup after anything renumbers rows or + // changes which ids a node carries. Every structural change signals, and + // the two in-place id changes (reconcile, setThreadMessages) mark it by + // hand. + mutable QHash<QString, int> m_rowByThread; + mutable QHash<QString, int> m_rowByMessage; + mutable bool m_indexStale = true; const TagColors *m_tagColors = nullptr; QString m_dateFormat; QStringList m_forwardPrefixes; |
