aboutsummaryrefslogtreecommitdiffstats
path: root/src/mainwindow.cpp
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-08 10:37:48 +0200
committerDanilo M. <danix@danix.xyz>2026-08-10 08:23:24 +0200
commit98250d51021aee8929d9f5084a440647c43132b0 (patch)
treef5ee9a37fdde65a8f8c00415f1165147aa0b1489 /src/mainwindow.cpp
parent5487d581069333a64e0e0480f53f06a7b64e486d (diff)
downloadqtmaildir-98250d51021aee8929d9f5084a440647c43132b0.tar.gz
qtmaildir-98250d51021aee8929d9f5084a440647c43132b0.zip
feat(ui): load a thread's replies when its row is expanded
Replies are fetched on expansion rather than with the query: walking the reply tree of every thread in a 10k-thread result would cost more than the query and almost none of it would be looked at. hasChildren is what makes that lazy loading work, and its absence would have shipped the feature unreachable. rowCount is 0 until the worker has walked the thread, so a view left to infer the expander from rowCount alone draws none, the user can never expand, and the replies are never requested. It answers from the summary's totalCount before loading and from the children afterwards, so a thread whose count included duplicates stops offering an expander that opens onto nothing. onThreadTreeLoaded reads the thread id from the reply rather than remembering it from the request. Two expansions can be in flight at once, and pairing them by order would attach one thread's replies to the other.
Diffstat (limited to 'src/mainwindow.cpp')
-rw-r--r--src/mainwindow.cpp40
1 files changed, 40 insertions, 0 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 740edbe..720ec60 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -606,6 +606,12 @@ void MainWindow::buildUi()
m_threadView->setColumnWidth(ThreadListModel::AuthorsColumn, 180);
m_threadView->setColumnWidth(ThreadListModel::SubjectColumn, 520);
+ // Replies are loaded when a thread is expanded, not with the query.
+ // Walking the reply tree of every thread in a 10k-thread result would cost
+ // far more than the query itself and almost none of it would be looked at.
+ connect(m_threadView, &QTreeView::expanded,
+ this, &MainWindow::onThreadExpanded);
+
connect(m_threadView->selectionModel(),
&QItemSelectionModel::currentRowChanged,
this, &MainWindow::onThreadSelected);
@@ -1298,6 +1304,8 @@ void MainWindow::wireWorker()
this, &MainWindow::onThreadsReady);
connect(m_worker, &NotmuchWorker::queryFinished,
this, &MainWindow::onQueryFinished);
+ connect(m_worker, &NotmuchWorker::threadTreeLoaded,
+ this, &MainWindow::onThreadTreeLoaded);
connect(m_worker, &NotmuchWorker::threadLoaded,
this, &MainWindow::onThreadLoaded);
connect(m_worker, &NotmuchWorker::errorOccurred,
@@ -1667,6 +1675,38 @@ void MainWindow::onThreadSelected(const QModelIndex &current,
Q_ARG(quint64, m_generation));
}
+void MainWindow::onThreadExpanded(const QModelIndex &index)
+{
+ if (!index.isValid() || m_model->isMessageRow(index))
+ return;
+
+ const QString threadId =
+ m_model->data(index, ThreadListModel::ThreadIdRole).toString();
+ if (threadId.isEmpty())
+ return;
+
+ QMetaObject::invokeMethod(m_worker, "loadThreadTree", Qt::QueuedConnection,
+ Q_ARG(QString, threadId),
+ Q_ARG(QString, m_lastQuery),
+ Q_ARG(quint64, m_generation));
+}
+
+void MainWindow::onThreadTreeLoaded(const QVector<MessageNode> &nodes,
+ quint64 generation)
+{
+ // The same generation guard every other worker reply carries: an expansion
+ // whose query has since been replaced must not insert rows into the new
+ // result, where that thread may not even appear.
+ if (generation != m_generation || nodes.isEmpty())
+ return;
+
+ // Every node in one reply belongs to one thread, so the first one names it.
+ // Read from the node rather than remembered from the request: two
+ // expansions can be in flight at once, and pairing them by order would
+ // attach one thread's replies to the other.
+ m_model->setThreadMessages(nodes.first().threadId, nodes);
+}
+
void MainWindow::onThreadLoaded(const QVector<MessageRef> &messages,
quint64 generation)
{