aboutsummaryrefslogtreecommitdiffstats
path: root/src/tagchip.cpp
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-08 10:56:58 +0200
committerDanilo M. <danix@danix.xyz>2026-08-10 08:23:25 +0200
commit10ff78629b3d60810b85110a2f194e0d1b87752a (patch)
treeb6850828478daa42efee7e66a544955e776ee28d /src/tagchip.cpp
parent98250d51021aee8929d9f5084a440647c43132b0 (diff)
downloadqtmaildir-10ff78629b3d60810b85110a2f194e0d1b87752a.tar.gz
qtmaildir-10ff78629b3d60810b85110a2f194e0d1b87752a.zip
fix(ui): make the expander visible and the reply indent readable
Both were reported from the running application after the previous commit claimed them working, and the tests that passed could not see either fault. The expander took four attempts, each of which looked right in code: - QTreeView::drawBranches is the documented hook and does not work here. It runs BEFORE the row's cells, so with the expander on a content column the delegate's own background paints over it. A 60-pixel triangle survived as 8, indistinguishable from the theme's near-invisible dot. - Sizing the glyph from the row rather than the branch rect put most of it outside that rect. - Moving it into SubjectDelegate but calling it from only the no-chip branch left every real row without one, since every real row has an account chip and takes the other branch. It is now drawn by the delegate, which owns the cell and paints after the background, from both branches, with setRootIsDecorated(false) so the style does not draw its dot underneath. The indent was 20px and invisible for a reason the geometry could not show: a thread row draws an account chip before its subject and a reply row does not, so a reply's text already starts about a chip's width LEFT of its thread's. The indent has to beat that before any nesting reads at all, hence 72px. The indent test asserted on visualRect, which was correctly indented the whole time, and so passed against a build with no visible nesting. It now measures where the TEXT lands, accounting for the chip, and fails at 20px. The new expander test counts painted pixels of the glyph colour against a control row with no replies, and fails when the call is dropped from either branch.
Diffstat (limited to 'src/tagchip.cpp')
-rw-r--r--src/tagchip.cpp56
1 files changed, 55 insertions, 1 deletions
diff --git a/src/tagchip.cpp b/src/tagchip.cpp
index 1f4ad79..b3e743e 100644
--- a/src/tagchip.cpp
+++ b/src/tagchip.cpp
@@ -156,6 +156,51 @@ void SubjectDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
const QString account =
index.data(ThreadListModel::AccountLabelRole).toString();
+
+ // The expander is drawn HERE and not in QTreeView::drawBranches, which is
+ // the obvious place and does not work. drawBranches runs before the row's
+ // cells, so with the expander column set to the subject the delegate's own
+ // background fills straight over it: measured at 8 surviving pixels of a
+ // 60-pixel triangle, which is exactly the near-invisible dot that made this
+ // override necessary in the first place. The delegate owns this cell and
+ // paints after the background, so it is the only place the glyph survives.
+ const auto drawExpander = [&](const QRect &cell) {
+ if (!index.data(ThreadListModel::HasRepliesRole).toBool())
+ return;
+
+ const int size = qMax(7, qMin(cell.height() / 3, 10));
+ const QPoint centre(cell.left() + size,
+ cell.top() + subjectBandHeight(option) / 2
+ + kRowPadding);
+
+ QPolygon triangle;
+ if (option.state & QStyle::State_Open) {
+ triangle << QPoint(centre.x() - size / 2, centre.y() - size / 4)
+ << QPoint(centre.x() + size / 2, centre.y() - size / 4)
+ << QPoint(centre.x(), centre.y() + size / 2);
+ } else {
+ triangle << QPoint(centre.x() - size / 4, centre.y() - size / 2)
+ << QPoint(centre.x() + size / 2, centre.y())
+ << QPoint(centre.x() - size / 4, centre.y() + size / 2);
+ }
+
+ painter->save();
+ painter->setRenderHint(QPainter::Antialiasing, true);
+ painter->setPen(Qt::NoPen);
+ // From the palette, so it survives a theme change, and undimmed: this
+ // is the only cue that a thread can be opened at all.
+ painter->setBrush(option.palette.color(QPalette::Text));
+ painter->drawPolygon(triangle);
+ painter->restore();
+ };
+ // Room for the expander in front of whatever follows, on a thread row that
+ // has one. Reserved before either branch draws, so the chip and the bare
+ // subject are indented identically and a thread with replies does not sit
+ // a few pixels left of one without.
+ const bool hasReplies =
+ index.data(ThreadListModel::HasRepliesRole).toBool();
+ const int expanderWidth = hasReplies ? kExpanderWidth : 0;
+
if (account.isEmpty()) {
// No chip to draw, so the base class renders the text, confined to the
// upper band: the lower one belongs to the row-wide pill strip that
@@ -163,8 +208,10 @@ void SubjectDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
QStyleOptionViewItem chrome = option;
initStyleOption(&chrome, index);
chrome.rect.setHeight(subjectBandHeight(option));
+ chrome.rect.setLeft(chrome.rect.left() + expanderWidth);
QStyledItemDelegate::paint(painter, chrome, index);
+ drawExpander(option.rect);
return;
}
@@ -186,7 +233,7 @@ void SubjectDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
const int textBandHeight = subjectBandHeight(option);
const int textTop = option.rect.top() + kRowPadding;
- const QRect chipRect(option.rect.left() + TagChip::kSpacing,
+ const QRect chipRect(option.rect.left() + expanderWidth + TagChip::kSpacing,
textTop + (textBandHeight - chipSize.height()) / 2,
chipSize.width(), chipSize.height());
@@ -234,6 +281,13 @@ void SubjectDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
rowMetrics.elidedText(index.data(Qt::DisplayRole).toString(),
Qt::ElideRight, textRect.width()));
painter->restore();
+
+ // Last, so the chrome fill above cannot cover it. BOTH branches of this
+ // function have to call it: a thread row with an account chip takes this
+ // one, and that is every row in the real application, so calling it only
+ // from the no-chip branch leaves the feature invisible in practice while
+ // still passing any test built on an untagged thread.
+ drawExpander(option.rect);
}
QSize SubjectDelegate::sizeHint(const QStyleOptionViewItem &option,