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
|
/*
* 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 <QDialog>
#include <QList>
#include <QString>
#include "htmlbuilder.h"
#include "searchterm.h"
/// One header of one message, as shown and as searched for.
///
/// The query is built when the row is, from the parsed value, so nothing has
/// to parse displayed text back into structure. That is the whole reason this
/// dialog stopped being a text box.
struct HeaderRow
{
/// notmuch's field name, or empty for a header with no searchable form.
/// Wire format, never translated.
QString field;
/// Translated label shown at the start of the row, e.g. "From:".
QString label;
/// The header's value, verbatim and untrusted.
QString value;
/// The finished query, empty when the header has no searchable form.
QString query;
/// Which message of the thread this row belongs to, zero-based.
int messageIndex = 0;
};
/// The full headers of every message in a thread, read-only.
///
/// Rows rather than one text box, so a value can carry its own context menu
/// without anything parsing rendered text back into structure. The user also
/// asked not to be shown a text box.
///
/// **Every value label is explicitly `Qt::PlainText`.** This replaced a
/// `QPlainTextEdit` whose plain-textness was a security property rather than a
/// style: header values come from strangers, and plain text cannot interpret
/// markup, so there is nothing to escape and nothing that can render. A QLabel
/// guesses under `Qt::AutoText`, so stating the format is what preserves that.
class MessageDetailsDialog : public QDialog
{
Q_OBJECT
public:
/// `hasQuery` says whether the query bar holds anything, which decides
/// whether "Exclude from search" is offered: there must be something to
/// exclude FROM. Taken at construction rather than set later, since the
/// dialog is built fresh per invocation and so cannot go stale.
explicit MessageDetailsDialog(const QList<ThreadRenderItem> &items,
bool hasQuery = false,
QWidget *parent = nullptr);
/// The rows on display, in order. Exposed for testing without rendering.
QList<HeaderRow> rows() const { return m_rows; }
/// Emits searchRequested for `row`, or nothing when the row carries no
/// searchable query. The menu entries call this; a test can too, without
/// popping a menu.
void requestSearch(const HeaderRow &row, SearchTerm::SearchMode mode);
signals:
/// The user chose a search from a row's menu. `mode` says whether to
/// replace the query, narrow it, or narrow it by everything that is not
/// this value.
void searchRequested(const QString &query, SearchTerm::SearchMode mode);
private:
/// Builds the rows from the thread, one group per message.
void buildRows(const QList<ThreadRenderItem> &items);
QList<HeaderRow> m_rows;
bool m_hasQuery = false;
};
|