aboutsummaryrefslogtreecommitdiffstats
path: root/src/tagrulesdialog.cpp
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-17 13:49:23 +0200
committerDanilo M. <danix@danix.xyz>2026-08-17 13:49:23 +0200
commit46cdd10e9407dfc605ddc29425c9dac99e47d011 (patch)
treeebf7f397e12c4c8f360b2506db3697556993eeb3 /src/tagrulesdialog.cpp
parent435a41c8476a4e94f28ad7730351498465194454 (diff)
downloadqtmaildir-46cdd10e9407dfc605ddc29425c9dac99e47d011.tar.gz
qtmaildir-46cdd10e9407dfc605ddc29425c9dac99e47d011.zip
feat(rules): show each rule's note in the rule list
The `note` field explains why a rule is shaped the way it is, and it was reachable only by selecting the rule and reading the editor form, which is the wrong way round for the one field that says what a rule is for. Note is the LAST column, after Matches, at the user's request: a note is prose and the widest thing in the table, so it belongs where it can run on without pushing the narrow columns off screen. That is fiddlier than it looks, because "Matches" is not in the Column enum at all: it is appended past the end at index ColumnCount. Note therefore has to be declared before ColumnCount and still draw after it, and setColumnCount takes a new ColumnTotal rather than ColumnCount + 1. Both columns hold text, so a mix-up puts the counts under Note and looks entirely plausible; the test asserts the counts land under Matches as well as asserting the header order, since the header assertion alone passes with the two swapped. The cell is simplified(), because a note is free text and a newline truncates a tree row at it. The full text is the cell's tooltip and is untouched in the editor. Also fixes a defect found on the way, which is not in the backlog entry. QHeaderView::restoreState REFUSES a state saved with a different column count, returning false and leaving the header untouched, which is what every existing uistate.conf now does. The restore path set m_columnsSized and m_countColumnSized regardless, spending the one auto-size each column gets on a restore that did nothing: the new Note column would have opened at its default width, once, permanently. Now guarded on the return value. Upgrading costs one reset of this dialog's column widths, which is unavoidable, since the saved state genuinely describes a table that no longer exists. Backlog item 102.
Diffstat (limited to 'src/tagrulesdialog.cpp')
-rw-r--r--src/tagrulesdialog.cpp59
1 files changed, 54 insertions, 5 deletions
diff --git a/src/tagrulesdialog.cpp b/src/tagrulesdialog.cpp
index f208527..e037e37 100644
--- a/src/tagrulesdialog.cpp
+++ b/src/tagrulesdialog.cpp
@@ -45,8 +45,21 @@
namespace {
/// Columns of the rule list.
+///
+/// Item 102 added ColumnNote, and the user asked for it LAST, after Matches:
+/// a note is prose and the widest thing here, so it belongs at the end where
+/// it can run on without pushing the narrow columns off screen.
+///
+/// "Matches" is the counts column and is the one that is NOT in this enum: it
+/// sits at index ColumnCount, appended past the end. That is why Note is
+/// declared before ColumnCount and still draws after Matches, and why
+/// ColumnNote must be given its index explicitly rather than left to follow
+/// ColumnTags. Get this wrong and the counts land in the Note column, which
+/// no test would call an error since both hold text.
enum Column { ColumnEnabled = 0, ColumnStage, ColumnId, ColumnTags,
- ColumnCount };
+ ColumnCount, ///< "Matches", filled by the preview
+ ColumnNote, ///< last, per the user
+ ColumnTotal };
QStringList splitTags(const QString &text)
{
@@ -112,9 +125,11 @@ TagRulesDialog::TagRulesDialog(const TagRule &seed, QWidget *parent)
layout->addWidget(intro);
m_list = new QTreeWidget(this);
- m_list->setColumnCount(ColumnCount + 1);
+ // ColumnTotal, not ColumnCount + 1: the counts column used to be the one
+ // past the end, and since item 102 put Note after it there are two.
+ m_list->setColumnCount(ColumnTotal);
m_list->setHeaderLabels({ tr("On"), tr("Stage"), tr("Rule"), tr("Tags"),
- tr("Matches") });
+ tr("Matches"), tr("Note") });
m_list->setRootIsDecorated(false);
m_list->setUniformRowHeights(true);
@@ -390,8 +405,14 @@ void TagRulesDialog::restoreUiState()
const QByteArray header =
state.value(QStringLiteral("tagrules/header")).toByteArray();
- if (!header.isEmpty()) {
- m_list->header()->restoreState(header);
+ // Only when the restore actually took. QHeaderView::restoreState REFUSES a
+ // state saved with a different column count, returning false and leaving
+ // the header untouched, which is exactly what every existing state file
+ // does now that item 102 added a column. Setting the sized flags anyway
+ // would spend the one auto-size each column gets on a restore that did
+ // nothing, and the new Note column would open at whatever width it
+ // defaulted to, once, for good.
+ if (!header.isEmpty() && m_list->header()->restoreState(header)) {
// Counts as the one auto-size each column gets, so the restore is not
// immediately overwritten. reloadList() runs before this in the
// constructor and has already sized the first two; the count column
@@ -482,6 +503,26 @@ void TagRulesDialog::setColumnWidthForTest(int column, int width)
m_list->setColumnWidth(column, width);
}
+QString TagRulesDialog::listCellTextForTest(int row, int column) const
+{
+ const QTreeWidgetItem *item = m_list->topLevelItem(row);
+ return item ? item->text(column) : QString();
+}
+
+QString TagRulesDialog::listCellToolTipForTest(int row, int column) const
+{
+ const QTreeWidgetItem *item = m_list->topLevelItem(row);
+ return item ? item->toolTip(column) : QString();
+}
+
+QStringList TagRulesDialog::listHeaderLabelsForTest() const
+{
+ QStringList labels;
+ for (int column = 0; column < m_list->columnCount(); ++column)
+ labels.append(m_list->headerItem()->text(column));
+ return labels;
+}
+
void TagRulesDialog::reloadListForTest()
{
reloadList();
@@ -531,6 +572,14 @@ void TagRulesDialog::fillItem(QTreeWidgetItem *item, const TagRule &rule) const
for (const QString &tag : rule.remove)
tags.append(QStringLiteral("-") + tag);
item->setText(ColumnTags, tags.join(QStringLiteral(" ")));
+
+ // Item 102. The note explains why a rule is shaped the way it is, and was
+ // reachable only by selecting the rule and reading the form. simplified()
+ // because a note is free text and a newline in a tree cell truncates the
+ // row at it; the full text stays in the tooltip and in the editor.
+ const QString note = rule.note.simplified();
+ item->setText(ColumnNote, note);
+ item->setToolTip(ColumnNote, rule.note);
}
void TagRulesDialog::reloadList()