aboutsummaryrefslogtreecommitdiffstats
path: root/src/threadlistmodel.h
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-31 11:26:52 +0100
committerDanilo M. <danix@danix.xyz>2026-10-31 11:26:52 +0100
commit467fa84fd851459d5d9c9e5297e2500e938f0a18 (patch)
tree392d0237cac273a6a07db11b120a8a453e8e8ff6 /src/threadlistmodel.h
parent9ae9473acdb7cb3310c3028765cc4b30fedfce27 (diff)
downloadqtmaildir-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.h20
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;