diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-08 10:35:06 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-08 10:35:06 +0200 |
| commit | 78733fbc5ab8c1d4707444b5bce3990dbc0a9fcb (patch) | |
| tree | 4eacd2849ba501ce1c7c28ffc700bee06d63c9c7 /src/threadlistview.cpp | |
| parent | 39c3863b8c7fc6130890ab06fa4303eeddad3162 (diff) | |
| download | qtmaildir-78733fbc5ab8c1d4707444b5bce3990dbc0a9fcb.tar.gz qtmaildir-78733fbc5ab8c1d4707444b5bce3990dbc0a9fcb.zip | |
refactor(view): make ThreadListView a QTreeView for message rows
The strip survived the port because every geometry call it needs exists on both
classes. What did not survive is anything keyed on a row NUMBER: a tree numbers
rows per parent, so row 0 exists once per expanded thread and the old flat 0..N
walk would paint the first thread's strip over every one of them. The walk now
goes by index, and the alternating colour follows visual position rather than
index.row() for the same reason.
QTableView::isRowSelected(int) has no QTreeView equivalent; isSelected on the
index replaces it. MainWindow loses verticalHeader and selectRow, so row height
comes from uniformRowHeights and three helpers replace the row arithmetic.
next_thread and prev_thread now resolve the containing thread first: in a tree
current.row() + 1 is the next SIBLING, which under an expanded thread is the
next reply, not the next thread.
Two test defects found by mutation and worth recording, since both produced a
green suite over a broken assertion:
The indent test asserted on column 0. A QTreeView indents only the column
holding the expander, verified against Qt 6.11: with setTreePosition(4), column
0 reports the same left edge for a thread and its reply while column 4 reports
420 against 440. It was failing against a correctly indented tree.
The strip test passed with the view's skip deleted, because the real model
already returns no pills for a child row, so the view's guard was never the
thing under test. It now runs against a stub model that hands pills to every
row, which leaves the view's skip as the only thing that can keep replies clean.
That rewrite then failed for a third reason: without the delegates MainWindow
installs, rows take the default height, the band is measured against
SubjectDelegate::rowHeightFor and overflows into the row below, and the thread's
own strip paints across the reply. Reads exactly like a missing skip and is not
one.
Diffstat (limited to 'src/threadlistview.cpp')
| -rw-r--r-- | src/threadlistview.cpp | 45 |
1 files changed, 32 insertions, 13 deletions
diff --git a/src/threadlistview.cpp b/src/threadlistview.cpp index ef80e09..ebca4cc 100644 --- a/src/threadlistview.cpp +++ b/src/threadlistview.cpp @@ -27,7 +27,7 @@ void ThreadListView::paintEvent(QPaintEvent *event) { - QTableView::paintEvent(event); + QTreeView::paintEvent(event); if (!model()) return; @@ -43,18 +43,34 @@ void ThreadListView::paintEvent(QPaintEvent *event) const QFontMetrics metrics(pillFont); painter.setFont(pillFont); - // Only the rows actually on screen. Walking the whole model would paint - // thousands of strips outside the viewport on a large query. - const int first = rowAt(0); - const int last = rowAt(viewport()->height() - 1); - const int lastRow = last >= 0 ? last : model()->rowCount() - 1; + // Only the rows actually on screen, walked by INDEX rather than by row + // number. A tree numbers rows per parent, so row 0 exists once per expanded + // thread and the old flat 0..N walk would paint the first thread's strip + // over every one of them. + QModelIndex walk = indexAt(QPoint(0, 0)); + + // Counts the rows actually painted, for the alternating colour. In a tree + // that has to follow VISUAL position: row 0 under three different threads + // is three different stripes, and using index.row() would give all three + // the same one. + int visualRow = 0; + + for (; walk.isValid(); walk = indexBelow(walk), ++visualRow) { + const QRect rowRect = visualRect(walk); + if (rowRect.top() > viewport()->height()) + break; + + // No strip under a message row. The strip carries the THREAD's tags, so + // one under each reply would stripe the list and repeat identical tags + // down the whole expansion. + if (walk.parent().isValid()) + continue; - for (int row = qMax(0, first); row <= lastRow; ++row) { - const QModelIndex index = - model()->index(row, ThreadListModel::SubjectColumn); + const QModelIndex index = walk.siblingAtColumn( + ThreadListModel::SubjectColumn); - const int rowTop = rowViewportPosition(row); - const int height = rowHeight(row); + const int rowTop = rowRect.top(); + const int height = rowRect.height(); if (height <= 0) continue; @@ -86,9 +102,12 @@ void ThreadListView::paintEvent(QPaintEvent *event) if (background.isValid()) painter.fillRect(band, background.value<QBrush>()); - else if (selectionModel() && selectionModel()->isRowSelected(row)) + // isSelected on the index, not isRowSelected(int): a QTreeView has no + // such overload, and a row number alone cannot name a row in a tree + // anyway since it is only unique under one parent. + else if (selectionModel() && selectionModel()->isSelected(index)) painter.fillRect(band, palette().brush(QPalette::Highlight)); - else if (alternatingRowColors() && (row % 2)) + else if (alternatingRowColors() && (visualRow % 2)) painter.fillRect(band, palette().brush(QPalette::AlternateBase)); else painter.fillRect(band, palette().brush(QPalette::Base)); |
