diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-28 18:14:58 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-28 18:14:58 +0200 |
| commit | 2c51c9653e4e045373120d50ce026f0a752a608f (patch) | |
| tree | 664f7c758d219b70346abebc83b1925936c35562 /src/threaddashboard.h | |
| parent | a346bef6c08e9725f3ab41f8237f01e9eb69eea7 (diff) | |
| download | qtmaildir-2c51c9653e4e045373120d50ce026f0a752a608f.tar.gz qtmaildir-2c51c9653e4e045373120d50ce026f0a752a608f.zip | |
feat: add the thread dashboard
A widget over a ThreadDigest: header, tags, counts, the unread list capped
with a link to the rest, and an activity sparkline, scrolling under a pinned
action strip. It invents no colours.
ThreadDigest::unread is QVector<MessageNode> rather than QVector<MessageRef>.
The dashboard draws rows for messages it never opens, which is what
MessageNode exists for; MessageRef carries only what the pane needs once a
message is already open and has no subject, sender or date. The struct's
no-file-opening contract still holds, since all four of those fields are
served from the notmuch index.
Diffstat (limited to 'src/threaddashboard.h')
| -rw-r--r-- | src/threaddashboard.h | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/src/threaddashboard.h b/src/threaddashboard.h new file mode 100644 index 0000000..0fe9773 --- /dev/null +++ b/src/threaddashboard.h @@ -0,0 +1,150 @@ +/* + * 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 <QVector> +#include <QWidget> + +#include "threaddigest.h" +#include "types.h" + +class TagColors; +class TagStrip; +class QLabel; +class QProgressBar; +class QPushButton; +class QVBoxLayout; + +/// The activity histogram, as its own widget. +/// +/// Separate because it is the one part of the dashboard that must paint, and +/// keeping it apart is what leaves the rest of the pane assertable without a +/// painter. It draws ThreadDigest::buckets at a fixed height, one colour for +/// the bars with the palette's highlight on the busiest. +class ActivitySparkline : public QWidget +{ + Q_OBJECT +public: + explicit ActivitySparkline(QWidget *parent = nullptr); + + void setBuckets(const QVector<int> &buckets, int busiest); + + QSize sizeHint() const override; + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + QVector<int> m_buckets; + int m_busiest = -1; +}; + +/// The right pane for a THREAD row: what a conversation is, rather than one of +/// its messages. +/// +/// A thread row stands for the conversation, so there is no single message to +/// render. This shows the conversation itself: who is in it, what it is +/// tagged, how much of it is unread, which messages are still waiting, and +/// when it was busy. +/// +/// Built once in the constructor and repopulated by setDigest(), so switching +/// rows rebuilds no widgets. +/// +/// **It invents no colours.** Participants reuse Avatar::pixmapFor(), tags use +/// TagColors::colourFor() through a TagStrip, and the progress bar and the +/// sparkline use the palette's highlight. Every colour here already means the +/// same thing somewhere else in the application. +class ThreadDashboard : public QWidget +{ + Q_OBJECT +public: + explicit ThreadDashboard(QWidget *parent = nullptr); + + /// Not owned; must outlive the dashboard. Forwarded to the tag strip. + void setTagColors(const TagColors *colours); + + /// The thread's subject and account, which the digest does not carry: it + /// is built from the index by the worker, while these are already on the + /// summary the list holds. Call before or after setDigest(), either way. + void setThreadHeading(const QString &subject, const QString &accountLabel); + + /// The thread's tags, from the summary. The digest carries none: a + /// thread's tags are notmuch's union and the model already has them. + void setTags(const QStringList &tags); + + void setDigest(const ThreadDigest &digest); + + /// How many unread entries the Waiting-for-you block actually lists. + /// + /// This and the two below exist so the pane's content is assertable + /// without rendering it. A probe that renders and counts pixels proves + /// nothing here, for every reason under "Rendering probes lie". + int unreadCountShown() const { return m_unreadShown.size(); } + + /// How many unread messages the block could not list. Zero when the list + /// is complete, which is also when no "+N more" link is shown. + /// + /// Taken from unreadTotal rather than from the list's own size, because + /// the list is capped and a count derived from it would under-report on + /// exactly the threads where the number matters. + int hiddenUnreadCount() const; + + /// True when the thread has nothing unread, so the block is replaced by a + /// single "All caught up" line. + bool showingAllCaughtUp() const { return m_digest.unreadTotal == 0; } + +signals: + /// An unread entry was clicked. The pane is a dead end without this: it + /// has just told the user what they have not read. + void messageActivated(const QString &messageId); + + /// The "+N more" link was clicked. Expands the thread in the LEFT pane, + /// where the full list already lives, and leaves the selection alone. + void expandRequested(); + + void markAllReadRequested(); + void archiveRequested(); + void deleteRequested(); + +private: + /// Rebuilds the Waiting-for-you block from m_digest. + void rebuildUnread(); + + /// Rebuilds the participants row from m_digest.senders. + void rebuildParticipants(); + + ThreadDigest m_digest; + QVector<MessageNode> m_unreadShown; + + QLabel *m_subject = nullptr; + QLabel *m_summaryLine = nullptr; ///< "N people, N days". + QLabel *m_account = nullptr; + QWidget *m_participants = nullptr; + QVBoxLayout *m_participantsLayout = nullptr; + TagStrip *m_tags = nullptr; + QLabel *m_counts = nullptr; + QProgressBar *m_progress = nullptr; + QLabel *m_waitingHeading = nullptr; + QWidget *m_unreadBlock = nullptr; + QVBoxLayout *m_unreadLayout = nullptr; + QLabel *m_allCaughtUp = nullptr; + QPushButton *m_moreLink = nullptr; + ActivitySparkline *m_sparkline = nullptr; + QLabel *m_activityLine = nullptr; +}; |
