diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-03 15:41:16 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-04 12:53:28 +0200 |
| commit | 0dacb5c4c61818088efb5bf513f15d2b715491e3 (patch) | |
| tree | 2f9994867f07b62d80e01a8256d3ee2acb5d62a9 /src/tagstrip.cpp | |
| parent | 702568b65a9e8322b646ce7ae03303f85076d47c (diff) | |
| download | qtmaildir-0dacb5c4c61818088efb5bf513f15d2b715491e3.tar.gz qtmaildir-0dacb5c4c61818088efb5bf513f15d2b715491e3.zip | |
feat: render tags as chips instead of a text column
Spelled out per row, tags ran to 500 pixels of largely repeated text and
took most of the thread list's width. The column is gone; tags render as
coloured chips split by what they actually mean.
An account tag says which mailbox a thread arrived in, and draws as a
chip in front of the subject. A functional tag says what state a thread
is in, and those fill one row under the message pane. One row keeps the
message area from shifting between threads with different tag counts, so
whatever does not fit collapses into a +N chip that names the rest in its
tooltip.
TagColors resolves a colour by exact tag first, then by top-level prefix,
so a single "shopping" entry covers shopping/amazon and shopping/nike
while shopping/amazon can still override its own. That matters at 96
tags. Built-in defaults cover the usual state tags, and anything left
unconfigured falls back to a hash of the name, stable so a chip does not
change colour as the list scrolls.
Diffstat (limited to 'src/tagstrip.cpp')
| -rw-r--r-- | src/tagstrip.cpp | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/src/tagstrip.cpp b/src/tagstrip.cpp new file mode 100644 index 0000000..bad116a --- /dev/null +++ b/src/tagstrip.cpp @@ -0,0 +1,136 @@ +/* + * 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 "tagstrip.h" + +#include <QFontMetrics> +#include <QPainter> + +#include "tagchip.h" +#include "tagcolors.h" + +namespace { + +/// Text of the chip standing in for tags that did not fit. +QString overflowText(int count) +{ + return QStringLiteral("+%1").arg(count); +} + +} // namespace + +TagStrip::TagStrip(QWidget *parent) + : QWidget(parent) +{ + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); +} + +void TagStrip::setTagColors(const TagColors *colours) +{ + m_tagColors = colours; + update(); +} + +void TagStrip::setTags(const QStringList &tags) +{ + m_tags.clear(); + for (const QString &tag : tags) { + // The account tag is shown as a chip in the thread list instead: it + // says which mailbox the thread came from, not what state it is in. + if (!TagColors::isAccountTag(tag)) + m_tags.append(tag); + } + m_tags.sort(); + + relayout(); + setVisible(!m_tags.isEmpty()); + update(); +} + +void TagStrip::relayout() +{ + m_visible.clear(); + m_hidden.clear(); + if (m_tags.isEmpty()) + return; + + const QFontMetrics metrics(font()); + // Reserve room for the overflow chip up front. Sizing it for the worst + // case avoids the loop having to back out a chip it already placed. + const int overflowWidth = + TagChip::sizeFor(metrics, overflowText(m_tags.size())).width() + + TagChip::kSpacing; + + int used = 0; + for (int i = 0; i < m_tags.size(); ++i) { + const int chipWidth = + TagChip::sizeFor(metrics, m_tags.at(i)).width() + TagChip::kSpacing; + const bool isLast = (i == m_tags.size() - 1); + // Every chip but the last must also leave room for the overflow chip, + // since anything after it will be hidden. + const int needed = used + chipWidth + (isLast ? 0 : overflowWidth); + if (needed > width() && !m_visible.isEmpty()) { + m_hidden = m_tags.mid(i); + break; + } + m_visible.append(m_tags.at(i)); + used += chipWidth; + } + + setToolTip(m_hidden.isEmpty() ? QString() + : m_hidden.join(QStringLiteral(", "))); +} + +QSize TagStrip::sizeHint() const +{ + const QFontMetrics metrics(font()); + return QSize(0, metrics.height() + TagChip::kPaddingY * 2 + + TagChip::kSpacing * 2); +} + +void TagStrip::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + relayout(); +} + +void TagStrip::paintEvent(QPaintEvent *) +{ + if (m_visible.isEmpty()) + return; + + QPainter painter(this); + const QFontMetrics metrics(font()); + const int top = (height() - (metrics.height() + TagChip::kPaddingY * 2)) / 2; + + int x = 0; + for (const QString &tag : m_visible) { + const QSize size = TagChip::sizeFor(metrics, tag); + const QColor colour = m_tagColors ? m_tagColors->colourFor(tag) + : TagColors().colourFor(tag); + TagChip::paint(&painter, QRect(QPoint(x, top), size), tag, colour); + x += size.width() + TagChip::kSpacing; + } + + if (!m_hidden.isEmpty()) { + const QString text = overflowText(m_hidden.size()); + const QSize size = TagChip::sizeFor(metrics, text); + TagChip::paint(&painter, QRect(QPoint(x, top), size), text, + QColor(0x44, 0x44, 0x4c)); + } +} |
