From 02d218358347e5d7a29164bc70a346c57fe098ab Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sat, 8 Aug 2026 10:20:14 +0200 Subject: refactor(model): convert ThreadListModel to QAbstractItemModel A table cannot indent or expand, so message rows need a tree. This task changes only the base class and the index plumbing: no children are produced yet, so the 30 pre-existing tests in test_threadlistmodel are the regression net proving a thread row still behaves exactly as it did, and QAbstractItemModelTester checks the index/parent round trip a hand-written assertion would miss. Two things the table version could leave wrong and a tree cannot. columnCount returned 0 for a valid parent, which would give message rows no columns and render them blank. And rowCount now answers only for column 0, since a tree takes one set of children per row and offering them under every column draws an expander in each. The model stays two levels deep even though replies carry a reply depth of their own. The visual nesting past the first level comes from that depth, not from further parent-child structure, so no index calculation has to recurse. --- src/threadlistmodel.h | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) (limited to 'src/threadlistmodel.h') diff --git a/src/threadlistmodel.h b/src/threadlistmodel.h index 2eaa88e..1aa0271 100644 --- a/src/threadlistmodel.h +++ b/src/threadlistmodel.h @@ -18,16 +18,23 @@ #pragma once -#include +#include #include #include #include "tagcolors.h" #include "types.h" -/// Table model over query results, filled in batches so a large query paints +/// Tree model over query results, filled in batches so a large query paints /// its first screenful immediately. -class ThreadListModel : public QAbstractTableModel +/// +/// A tree rather than a table since item 20: a thread's replies are child rows +/// under it. The tree is at most two levels deep in the MODEL (a thread, then +/// its messages) even though the messages carry a reply depth of their own; the +/// visual nesting beyond the first level comes from that depth, not from +/// further parent-child structure. A deeper model would buy nothing and make +/// every index calculation recursive. +class ThreadListModel : public QAbstractItemModel { Q_OBJECT public: @@ -109,6 +116,10 @@ public: /// Without one, chips fall back to a colour generated from the tag name. void setTagColors(const TagColors *colours) { m_tagColors = colours; } + QModelIndex index(int row, int column, + const QModelIndex &parent = {}) const override; + QModelIndex parent(const QModelIndex &child) const override; + int rowCount(const QModelIndex &parent = {}) const override; int columnCount(const QModelIndex &parent = {}) const override; QVariant data(const QModelIndex &index, int role) const override; @@ -137,6 +148,24 @@ public: const QStringList &removed); private: - QVector m_threads; + /// One thread root and the message rows expanded under it. + /// + /// Children live beside the summary rather than in a separate map keyed by + /// thread id, so a row and its expansion are appended, cleared and + /// destroyed together. The model is rebuilt wholesale on every query, so + /// nothing here has to survive a reset. + struct ThreadNode + { + ThreadSummary summary; + QVector children; ///< Empty until the thread is expanded. + + /// Distinguishes "this thread has no replies" from "its replies have + /// not been asked for yet". Without it an expander would be drawn over + /// every thread, including the ones that turn out to be single + /// messages. + bool loaded = false; + }; + + QVector m_threads; const TagColors *m_tagColors = nullptr; }; -- cgit v1.2.3