summaryrefslogtreecommitdiffstats
path: root/src/threadlistmodel.cpp
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-07 12:48:30 +0200
committerDanilo M. <danix@danix.xyz>2026-08-07 12:48:30 +0200
commitde884b036689b253d10ff48daa3a05cca20ba61d (patch)
tree1a5a5cf5020996d9f751b107e9fac9b9c466e40c /src/threadlistmodel.cpp
parent3826759d5167fb7a6f9449f9a39814c75771f449 (diff)
downloadqtmaildir-de884b036689b253d10ff48daa3a05cca20ba61d.tar.gz
qtmaildir-de884b036689b253d10ff48daa3a05cca20ba61d.zip
fix(ui): tell read threads from unread without relying on bold
Bold was unread's only cue, and it renders identically to regular on the user's system: confirmed by eye against a bare QTableView holding a plain QStandardItemModel, with no code from this project involved. The fault is in Qt or fontconfig, below this application, and nothing in the model could ever have reached it. Read and unread mail looked exactly alike. The emphasis is inverted instead. Unread rows keep the palette's own text colour and read rows are dimmed toward the background, so the cue rides on Qt::ForegroundRole, which the delegate already honours, and costs no column. It also suits the real ratio, measured at 99 unread against 4220 read: dimming the bulk is calmer than highlighting it. The dim colour is derived from the palette, never hardcoded, per the rule item 12 established. Bold is kept for systems where it works, but nothing depends on it now. That exposed a second defect, visible the moment it shipped. Qt resolves ForegroundRole into the palette and then prefers it over HighlightedText, so a model-supplied colour wins on a SELECTED row too. The dim is blended against the unselected background, so a selected read row painted grey on the selection colour, near unreadable. SubjectDelegate::initStyleOption now reverses that, and the delegate is installed view-wide rather than on the subject column alone, so every column gets the same handling instead of three of them keeping Qt's ordering. The guarding tests state the property rather than the mechanism: strip the font from the model's answer and the two states must still differ. A test asserting only that bold is set passes on a system where bold paints like regular, which is exactly how this survived. The selection test renders two rows identical but for the unread tag, selects both, and requires zero differing pixels. Part of item 5; the density work and the star column remain. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src/threadlistmodel.cpp')
-rw-r--r--src/threadlistmodel.cpp46
1 files changed, 46 insertions, 0 deletions
diff --git a/src/threadlistmodel.cpp b/src/threadlistmodel.cpp
index 2289e6c..7e0477d 100644
--- a/src/threadlistmodel.cpp
+++ b/src/threadlistmodel.cpp
@@ -20,6 +20,8 @@
#include <QBrush>
#include <QFont>
+#include <QGuiApplication>
+#include <QPalette>
#include <QFontDatabase>
#include <QFontMetrics>
@@ -57,6 +59,29 @@ QColor ThreadListModel::spamColour()
return QColor(0xa8, 0x5c, 0x18);
}
+QColor ThreadListModel::readColour()
+{
+ // Derived from the palette, never hardcoded: a fixed grey that reads as
+ // "quiet" on a light theme is nearly invisible on a dark one, which is the
+ // rule item 12 established for the message pane.
+ //
+ // Mixed toward the background rather than simply made transparent, so it
+ // composites the same over a selected row as over an unselected one.
+ const QPalette palette = QGuiApplication::palette();
+ const QColor text = palette.color(QPalette::Text);
+ const QColor background = palette.color(QPalette::Base);
+
+ // 0.55 of the text colour: clearly recessive beside an undimmed row, and
+ // still comfortably readable on its own. A read thread is not disabled,
+ // it is simply not the thing being pointed at.
+ constexpr qreal kWeight = 0.55;
+ const qreal inverse = 1.0 - kWeight;
+ return QColor::fromRgbF(
+ text.redF() * kWeight + background.redF() * inverse,
+ text.greenF() * kWeight + background.greenF() * inverse,
+ text.blueF() * kWeight + background.blueF() * inverse);
+}
+
ThreadListModel::ThreadListModel(QObject *parent)
: QAbstractTableModel(parent)
{
@@ -145,6 +170,27 @@ QVariant ThreadListModel::data(const QModelIndex &index, int role) const
return QBrush(QColor(Qt::white));
}
+ // Unread's cue, and it deliberately does NOT rely on the bold below.
+ //
+ // Bold was the only cue until 2026-08-07, when it turned out to render
+ // identically to regular on the user's system: confirmed with a bare
+ // QTableView and a plain QStandardItemModel, so the fault is in Qt or
+ // fontconfig, below this application, and nothing here can reach it.
+ //
+ // So the emphasis is inverted instead. Unread rows are left at the
+ // palette's own text colour, and READ rows are dimmed toward the
+ // background. That way the cue rides on ForegroundRole, which the delegate
+ // already honours, and it costs no column. It also suits the real ratio:
+ // with a few dozen unread among thousands read, dimming the bulk is calmer
+ // than highlighting it.
+ //
+ // BELOW the doomed branch on purpose, and that ordering is the whole
+ // protection: a deleted or spam thread has already returned white text for
+ // this role above, and dimming it because it is also read would drop that
+ // to unreadable against the crimson. Do not hoist this.
+ if (role == Qt::ForegroundRole && !thread.isUnread())
+ return QBrush(readColour());
+
if (role == Qt::FontRole) {
QFont font;
bool styled = false;