diff options
Diffstat (limited to 'src/tagchip.cpp')
| -rw-r--r-- | src/tagchip.cpp | 230 |
1 files changed, 3 insertions, 227 deletions
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; } |
