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
|
/*
* 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 "pendingchangesdialog.h"
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QLabel>
#include <QScrollArea>
#include <QVBoxLayout>
QVector<PendingChangeRow> PendingChangesDialog::rowsFor(
const QVector<PendingChange> &changes)
{
QVector<PendingChangeRow> rows;
rows.reserve(changes.size());
// A run is a stretch of changes sharing one id, which the snapshot has
// already grouped. Only the first row of a run carries a subject, so the
// actions read as belonging to the message above them.
//
// Compared against the PREVIOUS id rather than collected into a map: the
// snapshot's order is deliberate (the actions under one message keep the
// order they were made in), and a map would discard it.
QString previousId;
bool first = true;
for (const PendingChange &change : changes) {
const bool startsMessage = first || change.id != previousId;
rows.append(PendingChangeRow{
startsMessage ? change.subject : QString(),
change.action,
startsMessage,
startsMessage ? change.messageCount : -1 });
previousId = change.id;
first = false;
}
return rows;
}
PendingChangesDialog::PendingChangesDialog(
const QVector<PendingChange> &changes, QWidget *parent)
: QDialog(parent), m_rows(rowsFor(changes))
{
setWindowTitle(tr("Unsynced changes"));
auto *layout = new QVBoxLayout(this);
auto *intro = new QLabel(
tr("Changes made here that a sync has not yet carried to the mail "
"store. This list is a snapshot taken when it was opened."),
this);
intro->setWordWrap(true);
layout->addWidget(intro);
auto *content = new QWidget;
auto *grid = new QGridLayout(content);
grid->setColumnStretch(0, 1);
int line = 0;
for (const PendingChangeRow &row : m_rows) {
if (row.startsMessage) {
// PlainText stated rather than left to Qt, for the reason
// MessageDetailsDialog states it on every value: a subject comes
// from a stranger, and a QLabel guesses under Qt::AutoText. Plain
// text cannot interpret markup, so there is nothing to escape.
QString text = row.subject;
if (text.isEmpty()) {
// The id no longer resolves. The row stays, because the count
// the user clicked has to equal the list they are shown.
text = tr("(no longer in the index)");
}
if (row.messageCount >= 0) {
text = tr("%1 (whole thread, %n message(s))", "",
row.messageCount).arg(text);
}
auto *subject = new QLabel(text, content);
subject->setTextFormat(Qt::PlainText);
subject->setWordWrap(true);
grid->addWidget(subject, line, 0);
}
auto *action = new QLabel(row.action, content);
action->setTextFormat(Qt::PlainText);
grid->addWidget(action, line, 1, Qt::AlignTop | Qt::AlignRight);
++line;
}
if (m_rows.isEmpty()) {
grid->addWidget(new QLabel(tr("Nothing is waiting to be synced."),
content),
0, 0);
}
grid->setRowStretch(line, 1);
auto *scroll = new QScrollArea(this);
scroll->setWidget(content);
scroll->setWidgetResizable(true);
layout->addWidget(scroll);
auto *buttons = new QDialogButtonBox(QDialogButtonBox::Close, this);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
layout->addWidget(buttons);
// Sized to the content rather than to a fixed guess: a handful of pending
// changes is the common case and a 380px box left most of itself empty.
// The cap is what keeps a long list scrollable instead of taller than the
// screen; the floor keeps the dialog from collapsing around one row.
//
// sizeHint() on the content is the whole grid's, so this asks the layout
// what it needs rather than multiplying a row height by a count.
const int wanted = content->sizeHint().height()
+ intro->sizeHint().height()
+ buttons->sizeHint().height() + 60;
resize(620, qBound(180, wanted, 560));
}
|