summaryrefslogtreecommitdiffstats
path: root/src/mainwindow.cpp
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-13 17:07:16 +0200
committerDanilo M. <danix@danix.xyz>2026-08-13 17:07:16 +0200
commit4d84062e48ec35598b7c2ecdf8446939be6064c4 (patch)
treeab280755094321666d29d50694c48722809d953c /src/mainwindow.cpp
parentdf6461fa9136f2110c8a24567a3e4a32432a9476 (diff)
parentb8de7ce746718c7193e40b0f70cd3e2178f4ed8a (diff)
downloadqtmaildir-4d84062e48ec35598b7c2ecdf8446939be6064c4.tar.gz
qtmaildir-4d84062e48ec35598b7c2ecdf8446939be6064c4.zip
Merge branch 'rule-builder': a row builder for the tagging rules
Item 76 replaces the four free-text fields with a row builder: field and operator dropdowns per condition, +/- to add and remove them, a match all/any choice, and a separate "but not" block. The stored format does not change, so mailctl needs no edit. The query string stays authoritative and remains visible, and a rule the builder cannot represent opens in a text mode every rule carries. Along the way, items 75, 77 and 80, and a data-loss defect released in 0.16.0 (item 79): opening the dialog and pressing Save destroyed the first rule with nothing edited. That one damaged a real rule in the user's own file, which was repaired by hand. Four defects in this work were found by hand rather than by the suite, and each is recorded where it was missed: a lost note, a one-way text mode toggle, a geometry save on a path neither button takes, and a rule list squeezed to one row by a long rule. Two Qt traps and one about the user's compositor went into CLAUDE.md.
Diffstat (limited to 'src/mainwindow.cpp')
-rw-r--r--src/mainwindow.cpp61
1 files changed, 61 insertions, 0 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 331354f..e2df6de 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1313,8 +1313,20 @@ void MainWindow::showTagRulesDialog()
auto *dialog = new TagRulesDialog(this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
+
m_tagRulesDialog = dialog;
+ // The Folder row's dropdown, filled from the Maildir tree on disk rather
+ // than from config. Config names one subtree per account and nothing
+ // below it, so the dropdown offered five entries and no way to say Drafts
+ // or Sent, which is a folder a rule wants to target as often as a whole
+ // account. The answer comes back queued, after the dialog is already up;
+ // setFolders refills the rows that exist by then.
+ QMetaObject::invokeMethod(m_worker, "requestFolders", Qt::QueuedConnection);
+
+ connect(dialog, &TagRulesDialog::previewRequested,
+ this, &MainWindow::onRulePreviewRequested);
+
connect(dialog, &TagRulesDialog::countsRequested, this, [this, dialog]() {
QMetaObject::invokeMethod(
m_worker, "requestMessageCounts", Qt::QueuedConnection,
@@ -1414,6 +1426,15 @@ void MainWindow::wireWorker()
connect(m_worker, &NotmuchWorker::messageCountsReady,
this, &MainWindow::onRuleCountsReady);
+ // The rules dialog is the only consumer, and it may have been closed while
+ // the scan was in flight. No generation counter: the tree on disk does not
+ // change under a query, so a late answer is still the right one.
+ connect(m_worker, &NotmuchWorker::foldersReady, this,
+ [this](const QStringList &folders) {
+ if (m_tagRulesDialog)
+ m_tagRulesDialog->setFolders(folders);
+ });
+
// A confirmed write clears the pending revert: without this, a later
// unrelated error would roll back a change that actually succeeded.
connect(m_worker, &NotmuchWorker::tagsApplied,
@@ -1552,6 +1573,46 @@ void MainWindow::onCountsReady(const QVector<int> &counts, quint64 generation)
m_messageView->showPlaceholder(placeholderHelpers());
}
+QString MainWindow::queryTextForTesting() const
+{
+ return m_queryEdit->text();
+}
+
+QString MainWindow::selectedAccountForTesting() const
+{
+ return m_accountBox->currentData().toString();
+}
+
+void MainWindow::selectAccountForTesting(const QString &key)
+{
+ const int index = m_accountBox->findData(key);
+ if (index >= 0)
+ m_accountBox->setCurrentIndex(index);
+}
+
+void MainWindow::onRulePreviewRequested(const QString &query)
+{
+ // Unscoped, deliberately. runQuery() wraps the bar's text in the selected
+ // account's scope, and a rule query usually names its own path already
+ // (path:"work/**" is what every account rule looks like), so previewing
+ // one with an account selected would scope it twice and match nothing.
+ // That reads as "this rule collects no mail", which is the opposite of
+ // what the preview is for.
+ m_accountBox->setCurrentIndex(0);
+
+ // Through the query bar, like onPlaceholderQueryRequested: the bar then
+ // shows what is on screen and the user can edit the rule's query there
+ // before deciding to change the rule itself.
+ m_queryEdit->setText(query);
+ runCurrentQuery();
+
+ // The dialog is a separate window and may be covering this one or sitting
+ // beside it. Raising makes the result visible either way, and the dialog
+ // stays open so the two can be compared.
+ raise();
+ activateWindow();
+}
+
void MainWindow::onPlaceholderQueryRequested(const QString &query)
{
// Through the query bar rather than straight to the worker, so the bar