diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/carddelegate.cpp | 208 | ||||
| -rw-r--r-- | src/carddelegate.h | 68 | ||||
| -rw-r--r-- | src/tagchip.cpp | 230 | ||||
| -rw-r--r-- | src/tagchip.h | 56 |
5 files changed, 282 insertions, 281 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5478fab..fdac2c1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,6 +6,7 @@ add_library(qtmaildir_lib STATIC htmlbuilder.cpp cidschemehandler.cpp cardlayout.cpp + carddelegate.cpp notmuchworker.cpp tagchip.cpp tagcolors.cpp diff --git a/src/carddelegate.cpp b/src/carddelegate.cpp new file mode 100644 index 0000000..bc03b5a --- /dev/null +++ b/src/carddelegate.cpp @@ -0,0 +1,208 @@ +/* + * qtmaildir - a Qt6 mail client for notmuch-indexed Maildirs + * Copyright (C) 2026 Danilo M. <danix@danix.xyz> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "carddelegate.h" + +#include "cardlayout.h" +#include "threadlistmodel.h" + +#include <QApplication> +#include <QDateTime> +#include <QGuiApplication> +#include <QPainter> +#include <QRegularExpression> +#include <QStyle> + +namespace { + +CardLayout::Input inputFor(const QModelIndex &index) +{ + CardLayout::Input in; + in.isMessage = index.data(ThreadListModel::IsMessageRole).toBool(); + in.depth = index.data(ThreadListModel::MessageDepthRole).toInt(); + in.replyCount = index.data(ThreadListModel::ReplyCountRole).toInt(); + return in; +} + +} // namespace + +QRect CardDelegate::expanderRectFor(const QStyleOptionViewItem &option, + const QModelIndex &index) +{ + return CardLayout::compute(inputFor(index), option.rect, option.font) + .expanderRect; +} + +QColor CardDelegate::accentLineColour(const QColor &accountColour) +{ + if (!accountColour.isValid()) + return ThreadListModel::threadLineColour(); + + // The same 0.35 weight threadLineColour() uses, toward Base rather than + // toward Text, so the two kinds of line sit at the same visual strength. + const QColor base = QGuiApplication::palette().color(QPalette::Base); + constexpr qreal kWeight = 0.35; + const qreal inverse = 1.0 - kWeight; + return QColor::fromRgbF( + accountColour.redF() * kWeight + base.redF() * inverse, + accountColour.greenF() * kWeight + base.greenF() * inverse, + accountColour.blueF() * kWeight + base.blueF() * inverse); +} + +QSize CardDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + Q_UNUSED(index); + // One height for every row, thread and reply alike. Asserted directly in + // test_cardlayout rather than left to two cards happening to agree. + return QSize(option.rect.width(), CardLayout::heightFor(option.font)); +} + +void CardDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + // Background, selection and any model fill first, through the style, so a + // selected or doomed card looks right before anything is drawn on top. + QStyleOptionViewItem chrome = option; + initStyleOption(&chrome, index); + chrome.text.clear(); + const QWidget *widget = option.widget; + QStyle *style = widget ? widget->style() : QApplication::style(); + style->drawControl(QStyle::CE_ItemViewItem, &chrome, painter, widget); + + const CardLayout card = + CardLayout::compute(inputFor(index), option.rect, option.font); + + painter->save(); + + // The account's colour, for both the accent bar and the spines. + // + // A reply must resolve its THREAD's colour, not its own: AccountColourRole + // is empty on a message row, and a spine that fell back to the neutral + // line under an accented root would break the one continuous edge this + // design is built on. index.parent() is the thread for a depth-1 reply and + // the containing subtree for a deeper one, so walk to the root. + QModelIndex root = index; + while (root.parent().isValid()) + root = root.parent(); + const QColor accountColour = + root.data(ThreadListModel::AccountColourRole).value<QColor>(); + const QColor lineColour = accentLineColour(accountColour); + + // The accent bar, thread cards only. Drawn after the chrome so the + // selection highlight cannot cover it: which account a card belongs to + // must stay readable on the row the user is looking at. + if (!card.accentRect.isEmpty()) + painter->fillRect(card.accentRect, lineColour); + + // Spines, under everything else, in the same accent so an expanded thread + // is bounded by one colour from its root to its last reply. + for (const QRect &spine : card.spines) + painter->fillRect(spine, lineColour); + + // 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. + const QVariant foreground = index.data(Qt::ForegroundRole); + if (option.state & QStyle::State_Selected) + painter->setPen(option.palette.highlightedText().color()); + else if (foreground.isValid()) + painter->setPen(foreground.value<QBrush>().color()); + else + painter->setPen(option.palette.text().color()); + + // The model's font carries bold for unread and strike-out for deleted; + // initStyleOption resolved it into chrome.font. + painter->setFont(chrome.font); + const QFontMetrics metrics(chrome.font); + + // Line 1: sender, then the date flush right. + painter->drawText(card.senderRect, Qt::AlignVCenter | Qt::AlignLeft, + metrics.elidedText( + index.data(ThreadListModel::SendersRole).toString(), + Qt::ElideRight, card.senderRect.width())); + const QDateTime date = + index.data(ThreadListModel::DateRole).toDateTime(); + painter->drawText(card.dateRect, Qt::AlignVCenter | Qt::AlignRight, + date.toString(QStringLiteral("yyyy-MM-dd hh:mm"))); + + // Line 2: the flag mark, the subject, the attachment mark. + QString subject = index.data(ThreadListModel::SubjectRole).toString(); + if (index.data(ThreadListModel::IsMessageRole).toBool()) { + // Every reply repeating "Re: <the thread's subject>" is the visual + // signature of a table of records, which is what item 53 is about. + static const QRegularExpression re( + QStringLiteral("^\\s*(?:[Rr][Ee]\\s*:\\s*)+")); + subject.remove(re); + } + QString line2; + if (index.data(ThreadListModel::IsFlaggedRole).toBool()) + line2 += ThreadListModel::flagGlyph() + QLatin1Char(' '); + line2 += subject; + if (index.data(ThreadListModel::HasAttachmentRole).toBool()) + line2 += QLatin1Char(' ') + ThreadListModel::attachmentGlyph(); + painter->drawText(card.subjectRect, Qt::AlignVCenter | Qt::AlignLeft, + metrics.elidedText(line2, Qt::ElideRight, + card.subjectRect.width())); + + // The reply count, which is also the expander. + if (!card.expanderRect.isEmpty()) { + painter->setFont(CardLayout::smallFont(chrome.font)); + const int count = index.data(ThreadListModel::ReplyCountRole).toInt(); + const QString glyph = (option.state & QStyle::State_Open) + ? QStringLiteral("▾") + : QStringLiteral("▸"); + painter->drawText(card.expanderRect, Qt::AlignVCenter | Qt::AlignRight, + QStringLiteral("%1 %2").arg(glyph).arg(count)); + painter->setFont(chrome.font); + } + + painter->restore(); + + // Line 3: the chips. A thread card draws its own tags; a reply draws only + // the tags its thread does not already carry, so the thread's chips are + // not repeated down the whole expansion. + const bool isMessage = + index.data(ThreadListModel::IsMessageRole).toBool(); + const QStringList tags = + index.data(isMessage ? ThreadListModel::MessageOwnTagsRole + : ThreadListModel::PillTagsRole) + .toStringList(); + const QVariantList colours = + index.data(isMessage ? ThreadListModel::MessageOwnColoursRole + : ThreadListModel::PillColoursRole) + .toList(); + + const QFont chipFont = CardLayout::smallFont(chrome.font); + const QFontMetrics chipMetrics(chipFont); + painter->save(); + painter->setFont(chipFont); + int x = card.tagRect.left(); + for (int i = 0; i < tags.size(); ++i) { + const QSize size = TagChip::sizeFor(chipMetrics, tags.at(i)); + if (x + size.width() > card.tagRect.right()) + break; // Out of room; a clipped chip reads as a rendering fault. + const QColor colour = i < colours.size() + ? colours.at(i).value<QColor>() + : QColor(0x55, 0x55, 0x5f); + TagChip::paint(painter, QRect(QPoint(x, card.tagRect.top()), size), + tags.at(i), colour); + x += size.width() + TagChip::kSpacing; + } + painter->restore(); +} diff --git a/src/carddelegate.h b/src/carddelegate.h new file mode 100644 index 0000000..317c78a --- /dev/null +++ b/src/carddelegate.h @@ -0,0 +1,68 @@ +/* + * qtmaildir - a Qt6 mail client for notmuch-indexed Maildirs + * Copyright (C) 2026 Danilo M. <danix@danix.xyz> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include "tagchip.h" + +/// Paints a whole card: three lines, all of it, including the tag chips. +/// +/// It replaces both SubjectDelegate and ThreadListView::paintEvent. The view +/// used to paint the tag strip because a delegate cannot paint outside its +/// column and the strip spanned all five; with one column there is nothing to +/// span, so the strip comes home to the delegate and the view stops painting +/// entirely. That removes the two failure modes CLAUDE.md records for the +/// strip, a deleted row cut in half and every other row showing a bare stripe, +/// both of which existed because the view had to re-honour alternating +/// colours, the selection and BackgroundRole across cells it did not own. +/// +/// Inherits RowStyleDelegate for its one job, which still matters: Qt resolves +/// Qt::ForegroundRole into the palette's Text roles and then prefers those over +/// HighlightedText, so the read/unread dimming would otherwise win on a +/// selected row and land as grey on the highlight colour. +class CardDelegate : public RowStyleDelegate +{ + Q_OBJECT +public: + using RowStyleDelegate::RowStyleDelegate; + + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + + /// The expander's rect for a row, so the VIEW can hit-test a click without + /// duplicating the layout. The delegate draws it and the view owns the + /// click, because a delegate gets no click of its own without an editor. + static QRect expanderRectFor(const QStyleOptionViewItem &option, + const QModelIndex &index); + + /// An account's colour as a thin LINE rather than as a chip's fill. + /// + /// Never use the raw account colour for the accent bar or the spine. That + /// colour is chosen to be a background with legible text drawn on top + /// (TagColors::textColourOn picks black or white against it). The same + /// colour as a few pixels of line on the pane's own background is a + /// different problem: it has to be followable down a long expansion + /// WITHOUT competing with the senders beside it, which is the constraint + /// threadLineColour() states and meets by blending 0.35 toward the + /// palette's text. This blends the account colour toward the palette's + /// Base by the same weight, keeping the hue that identifies the account + /// and dropping the saturation that would shout. + static QColor accentLineColour(const QColor &accountColour); +}; diff --git a/src/tagchip.cpp b/src/tagchip.cpp index b3e743e..d770a56 100644 --- a/src/tagchip.cpp +++ b/src/tagchip.cpp @@ -55,42 +55,6 @@ void paint(QPainter *painter, const QRect &rect, const QString &text, } // namespace TagChip -int SubjectDelegate::subjectBandHeight(const QStyleOptionViewItem &option) -{ - return QFontMetrics(option.font).height(); -} - -QFont SubjectDelegate::pillFont(const QFont &rowFont) -{ - QFont font = rowFont; - - // Two points down, floored. One point was measured to change nothing at a - // 12pt desktop font: 12 and 11 both render 17px tall, so the pills came - // out the same size as the subject and read as competing content rather - // than as annotation. - // - // pointSize() is -1 when the font was specified in pixels, which - // subtracting from would be nonsense, hence the two branches. - if (rowFont.pointSize() > 0) - font.setPointSize(qMax(6, rowFont.pointSize() - 2)); - else if (rowFont.pixelSize() > 0) - font.setPixelSize(qMax(8, rowFont.pixelSize() - 3)); - - return font; -} - -int SubjectDelegate::rowHeightFor(const QFont &rowFont) -{ - // The text band uses the ROW's font and the strip its own smaller one. - // Measuring both with one font is what put the pills over the date text. - const QFontMetrics rowMetrics(rowFont); - const QFontMetrics pillMetrics(pillFont(rowFont)); - - return rowMetrics.height() - + TagChip::sizeFor(pillMetrics, QStringLiteral("x")).height() - + kRowPadding * 2 + TagChip::kSpacing; -} - void RowStyleDelegate::initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const { @@ -115,197 +79,9 @@ void RowStyleDelegate::initStyleOption(QStyleOptionViewItem *option, option->palette.setColor(QPalette::WindowText, highlighted); } - // Top-aligned and on one line, matching the subject beside them. - // - // The row is tall enough for a pill strip under the text, and Qt centres a - // cell's text in the whole rectangle by default: date and sender floated - // into the middle while the subject sat at the top, so the three did not - // share a baseline. Confining the rectangle to the text band puts them all - // on one. - // - // Wrapping matters more than it looks. A long sender ran to a second line, - // which reached down into the strip's band and collided with the pills; a - // cell cannot know they are there, since the view paints them afterwards. - // Eliding keeps every row's text inside its own band whatever it holds. - // Top of the row rather than centre of it, so the alignment is expressed - // without shrinking the rectangle: the rect is also what the background - // and selection fill are drawn into, and clipping it to the text band - // would leave the highlight covering only the upper part of the row. + // One line, elided. A card draws its own text through CardDelegate, but + // this still governs whatever Qt draws for the item itself, and a wrapped + // string would run past the card's own three lines. option->features &= ~QStyleOptionViewItem::WrapText; option->textElideMode = Qt::ElideRight; - - // The marker columns keep their centring. Their glyphs are the row's - // symbols rather than its text, so aligning them with the subject's - // baseline would strand them at the top of a tall row with the pill strip - // empty beneath; centred, they read as marking the whole row. - const bool marker = index.column() == ThreadListModel::AttachmentColumn - || index.column() == ThreadListModel::FlagColumn; - option->displayAlignment = marker - ? Qt::AlignCenter - : (Qt::AlignLeft | Qt::AlignTop); -} - -void SubjectDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const -{ - // AccountLabelRole is a property of the ROW, not of a cell, so this - // delegate must only ever be installed on the subject column. Installed - // view-wide it draws the account chip into every column, which is exactly - // what happened when that was tried. - Q_ASSERT(index.column() == ThreadListModel::SubjectColumn); - - 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 - // ThreadListView paints after every cell. - 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; - } - - // Draw the row's own background and selection first, then the chip and the - // subject on top, so a selected or struck-through row still looks right. - QStyleOptionViewItem chrome = option; - initStyleOption(&chrome, index); - chrome.text.clear(); - const QWidget *widget = option.widget; - QStyle *style = widget ? widget->style() : QApplication::style(); - style->drawControl(QStyle::CE_ItemViewItem, &chrome, painter, widget); - - const QFontMetrics metrics(option.font); - const QSize chipSize = TagChip::sizeFor(metrics, account); - - // The subject and its chip occupy the upper band; ThreadListView paints - // the pill strip across the lower one. Centring the chip in the whole row - // would leave it floating beside that gap rather than beside its text. - const int textBandHeight = subjectBandHeight(option); - const int textTop = option.rect.top() + kRowPadding; - - const QRect chipRect(option.rect.left() + expanderWidth + TagChip::kSpacing, - textTop + (textBandHeight - chipSize.height()) / 2, - chipSize.width(), chipSize.height()); - - const QColor colour = - index.data(ThreadListModel::AccountColourRole).value<QColor>(); - TagChip::paint(painter, chipRect, account, - colour.isValid() ? colour : QColor(0x55, 0x55, 0x5f)); - - // The subject follows the chip, elided so a long one cannot overflow. - QRect textRect = option.rect; - textRect.setLeft(chipRect.right() + TagChip::kSpacing * 2); - textRect.setTop(textTop); - textRect.setHeight(textBandHeight); - if (textRect.width() <= 0) - return; - - painter->save(); - // Selection outranks the model's colour, and that order matters. A read - // thread carries a dimmed foreground blended against the UNSELECTED - // background, so painting it over the highlight leaves grey-on-purple, - // which is close to unreadable. The highlight already carries the "this - // row" signal, so the read/unread distinction can yield to it for as long - // as the row is selected. - // - // A doomed thread is the exception that proves the rule: its white is not - // a dimming but a contrast requirement against its own fill, and the fill - // is drawn under the selection too. - const QVariant foreground = index.data(Qt::ForegroundRole); - if (option.state & QStyle::State_Selected) - painter->setPen(option.palette.highlightedText().color()); - else if (foreground.isValid()) - painter->setPen(foreground.value<QBrush>().color()); - else - painter->setPen(option.palette.text().color()); - - // The model's font carries bold for unread and strike-out for deleted. - // initStyleOption() already resolved it into chrome.font; using it rather - // than option.font is what keeps those cues on a delegate-drawn subject. - const QVariant fontData = index.data(Qt::FontRole); - const QFont rowFont = fontData.isValid() ? fontData.value<QFont>() - : chrome.font; - painter->setFont(rowFont); - const QFontMetrics rowMetrics(rowFont); - painter->drawText(textRect, Qt::AlignVCenter | Qt::AlignLeft, - 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, - const QModelIndex &index) const -{ - QSize size = QStyledItemDelegate::sizeHint(option, index); - const QString account = - index.data(ThreadListModel::AccountLabelRole).toString(); - if (!account.isEmpty()) { - const QFontMetrics metrics(option.font); - size.setWidth(size.width() + TagChip::sizeFor(metrics, account).width() - + TagChip::kSpacing * 3); - } - - // Height comes from rowHeightFor(), applied by the view to every row at - // once. A QTableView takes ONE height per row, so a hint returned here - // would only win if the view happened to ask this column, and this - // delegate is on the subject column alone. - size.setHeight(rowHeightFor(option.font)); - return size; } diff --git a/src/tagchip.h b/src/tagchip.h index 3145358..5514cae 100644 --- a/src/tagchip.h +++ b/src/tagchip.h @@ -59,8 +59,8 @@ void paint(QPainter *painter, const QRect &rect, const QString &text, /// the selection highlight it lands as grey on the highlight colour, close to /// unreadable. /// -/// Applied to the columns that have no delegate of their own; SubjectDelegate -/// inherits it for the subject column. +/// Inherited by CardDelegate, which is the only delegate the thread list +/// installs. class RowStyleDelegate : public QStyledItemDelegate { Q_OBJECT @@ -72,55 +72,3 @@ protected: const QModelIndex &index) const override; }; -/// Item delegate for the subject column: draws the account chip in front of -/// the subject text, so which mailbox a thread came from reads at a glance -/// without a tags column spelling it out. -/// **Install on the subject column only.** It reads AccountLabelRole, which is -/// a property of the row rather than of a cell, so as a view-wide delegate it -/// draws the account chip into every column. -class SubjectDelegate : public RowStyleDelegate -{ - Q_OBJECT -public: - using RowStyleDelegate::RowStyleDelegate; - - void paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const override; - QSize sizeHint(const QStyleOptionViewItem &option, - const QModelIndex &index) const override; - - /// Vertical breathing room above the subject and below the pill row. - static constexpr int kRowPadding = 4; - - /// How far a reply row is indented under its thread. - /// - /// Deliberately far wider than Qt's 20px default. A thread row carries an - /// account chip in front of its subject and a reply row does not, so a - /// reply's text starts about a chip's width to the LEFT of its thread's - /// before any indent is applied. 20px does not cover that, and the replies - /// come out looking flush or outdented; this has to beat a chip's width to - /// read as nesting at all. - static constexpr int kReplyIndent = 72; - - /// Horizontal room reserved in front of a thread's subject for the - /// expander glyph the delegate draws. - static constexpr int kExpanderWidth = 18; - - /// The font the pill strip is drawn in: a size down from the row's own. - /// - /// At the same size the pills read as a second row of content competing - /// with the subject, rather than as annotation beneath it. Derived from - /// the row font rather than fixed, so it follows the desktop's font size. - static QFont pillFont(const QFont &rowFont); - - /// The height every row gets, tall enough for the subject and a pill strip - /// beneath it. The view applies this itself: a QTableView takes one height - /// for the whole row, so leaving it to a single column's sizeHint would - /// let whichever column the view happens to ask decide. - static int rowHeightFor(const QFont &rowFont); - -protected: - /// The height of the band the subject text occupies. Everything below it - /// belongs to ThreadListView's row-wide pill strip. - static int subjectBandHeight(const QStyleOptionViewItem &option); -}; |
