1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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;
};
|