diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-26 16:03:49 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-26 16:03:49 +0200 |
| commit | bd90331cba3633754482fc13b99ae39a3b5f79a2 (patch) | |
| tree | d850c76f382aa94db5139edceb72c18bc08851ec | |
| parent | ef4d48c56c39f19efc751488cc2df6ee5af24a7f (diff) | |
| download | qtmaildir-bd90331cba3633754482fc13b99ae39a3b5f79a2.tar.gz qtmaildir-bd90331cba3633754482fc13b99ae39a3b5f79a2.zip | |
feat: draw a sender's avatar on every card
| -rw-r--r-- | src/carddelegate.cpp | 33 | ||||
| -rw-r--r-- | src/carddelegate.h | 28 | ||||
| -rw-r--r-- | tests/test_carddelegate.cpp | 41 |
3 files changed, 102 insertions, 0 deletions
diff --git a/src/carddelegate.cpp b/src/carddelegate.cpp index 6541932..89c3ed0 100644 --- a/src/carddelegate.cpp +++ b/src/carddelegate.cpp @@ -18,6 +18,8 @@ #include "carddelegate.h" +#include "avatar.h" +#include "businesssenders.h" #include "cardlayout.h" #include "marks.h" #include "tagchip.h" @@ -160,6 +162,21 @@ QSize CardDelegate::sizeHint(const QStyleOptionViewItem &option, return QSize(option.rect.width(), CardLayout::heightFor(option.font)); } +QPixmap CardDelegate::avatarFor(const QString &senderAddress, + const QString &senderName, + const QString &accountAddress, + const QString &accountLabel, + bool isBusinessSender, int side, + const QFont &font) +{ + const bool haveSender = !senderAddress.trimmed().isEmpty(); + const QString seed = haveSender ? senderAddress : accountAddress; + const QString initials = + Avatar::initialsFor(senderName, senderAddress, accountLabel); + const Avatar::Fill fill = Avatar::fillFor(senderName, isBusinessSender); + return Avatar::pixmapFor(seed, initials, fill, side, font); +} + void CardDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { @@ -232,6 +249,22 @@ void CardDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, painter->fillRect(spine, spineColour); } + // The sender's squircle, in the layout's reserved gutter. Drawn before the + // text so a wide avatar can never overprint the sender line. + if (!card.avatarRect.isEmpty()) { + const QString senderAddress = + index.data(ThreadListModel::SenderAddressRole).toString(); + const QString senderName = + index.data(ThreadListModel::SenderNameRole).toString(); + painter->drawPixmap( + card.avatarRect, + avatarFor(senderAddress, senderName, m_accountAddress, + m_accountLabel, + BusinessSenders::contains(m_businessSenders, + senderAddress), + card.avatarRect.width(), option.font)); + } + // Selection outranks the model's foreground, and the order matters: a read // card carries a dimmed colour blended against the UNSELECTED background, // so over the highlight it lands grey-on-highlight and close to unreadable. diff --git a/src/carddelegate.h b/src/carddelegate.h index 5a0830c..18c735f 100644 --- a/src/carddelegate.h +++ b/src/carddelegate.h @@ -18,6 +18,7 @@ #pragma once +#include "businesssenders.h" #include "tagchip.h" /// Paints a whole card: three lines, all of it, including the tag chips. @@ -109,4 +110,31 @@ public: /// survived exactly that kind of test. static QSize chipSize(const QFontMetrics &metrics, const QString &text, bool own); + + /// The squircle for one row, resolved from what the model supplies. + /// + /// Falls back to the ACCOUNT when the row has no sender address, so every + /// card carries an avatar rather than a hole: the seed becomes the + /// account's own address and the letters come from its label. + static QPixmap avatarFor(const QString &senderAddress, + const QString &senderName, + const QString &accountAddress, + const QString &accountLabel, + bool isBusinessSender, int side, + const QFont &font); + + void setAccountAddress(const QString &address) + { + m_accountAddress = address; + } + void setAccountLabel(const QString &label) { m_accountLabel = label; } + void setBusinessSenders(const BusinessSenders::List &list) + { + m_businessSenders = list; + } + +private: + QString m_accountAddress; + QString m_accountLabel; + BusinessSenders::List m_businessSenders; }; diff --git a/tests/test_carddelegate.cpp b/tests/test_carddelegate.cpp index 6ec43b8..8b53309 100644 --- a/tests/test_carddelegate.cpp +++ b/tests/test_carddelegate.cpp @@ -38,6 +38,8 @@ private slots: void aSiblingChipsPaddingShrinksWithItsFont(); void theFadeEndsAtSixtyPercentOfTheCard(); void aReplyFadeStartsAtItsOwnSpine(); + void theDelegateAsksForAScaledSquircle(); + void aRowWithNoSenderFallsBackToTheAccount(); }; namespace { @@ -284,5 +286,44 @@ void TestCardDelegate::aReplyFadeStartsAtItsOwnSpine() QVERIFY(reply.width() < CardDelegate::fadeRectFor(card, QRect()).width()); } +void TestCardDelegate::theDelegateAsksForAScaledSquircle() +{ + // Asserted through the function the PRODUCTION path calls, not through + // Avatar::pixmapFor() directly: a test pointed at the function being + // called into proves what that function does and nothing about whether the + // delegate asks it for the right thing. CLAUDE.md records a mutation that + // survived exactly that mistake. + const QRect card(0, 0, 500, 60); + const QFont font; + const CardLayout layout = + CardLayout::compute(CardLayout::Input(), card, font); + + const QPixmap pixmap = CardDelegate::avatarFor( + QStringLiteral("john@example.org"), QStringLiteral("John Doe"), + QStringLiteral("me@example.org"), QStringLiteral("Work"), false, + layout.avatarRect.width(), font); + + QCOMPARE(pixmap.size(), + QSize(layout.avatarRect.width(), layout.avatarRect.width())); +} + +void TestCardDelegate::aRowWithNoSenderFallsBackToTheAccount() +{ + const QFont font; + // No sender address at all: the squircle is still drawn, seeded from the + // account, so a card never shows a hole. + const QPixmap fallback = CardDelegate::avatarFor( + QString(), QString(), QStringLiteral("me@example.org"), + QStringLiteral("Work"), false, 44, font); + QVERIFY(!fallback.isNull()); + + // And it is the ACCOUNT's identity, not an arbitrary one: seeding from the + // same account twice agrees. + const QPixmap again = CardDelegate::avatarFor( + QString(), QString(), QStringLiteral("me@example.org"), + QStringLiteral("Work"), false, 44, font); + QCOMPARE(fallback.toImage(), again.toImage()); +} + QTEST_MAIN(TestCardDelegate) #include "test_carddelegate.moc" |
