summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/mainwindow.cpp23
-rw-r--r--src/mainwindow.h10
-rw-r--r--src/messagedetailsdialog.cpp15
-rw-r--r--src/messagedetailsdialog.h10
-rw-r--r--src/messageview.cpp14
-rw-r--r--src/messageview.h9
-rw-r--r--tests/test_mainwindow.cpp9
-rw-r--r--tests/test_messagedetailsdialog.cpp14
-rw-r--r--tests/test_messageview.cpp2
9 files changed, 71 insertions, 35 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 115646e..e82e61d 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1651,13 +1651,30 @@ void MainWindow::onPlaceholderQueryRequested(const QString &query)
runCurrentQuery();
}
-void MainWindow::runSearchFromPane(const QString &query, bool extend)
+void MainWindow::runSearchFromPane(const QString &query,
+ SearchTerm::SearchMode mode)
{
if (query.isEmpty())
return;
- const QString next =
- extend ? SearchTerm::extend(m_queryEdit->text(), query) : query;
+ QString next;
+ switch (mode) {
+ case SearchTerm::SearchMode::Replace:
+ next = query;
+ break;
+ case SearchTerm::SearchMode::Narrow:
+ next = SearchTerm::extend(m_queryEdit->text(), query);
+ break;
+ case SearchTerm::SearchMode::Exclude:
+ next = SearchTerm::exclude(m_queryEdit->text(), query);
+ break;
+ }
+
+ // exclude() returns empty when there is nothing to exclude from, which the
+ // greyed menu entry should already have prevented. Running it would clear
+ // the query bar and show the whole Maildir, so refuse instead.
+ if (next.isEmpty())
+ return;
// Through the query bar and the existing runner, so the account scope, the
// generation counter and the flat-mode reset all behave exactly as they do
diff --git a/src/mainwindow.h b/src/mainwindow.h
index f54a889..b41f75d 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -34,6 +34,7 @@
// Included rather than forward-declared: SyncPhaseTracker is held by value, so
// its size must be known here. MailSync itself stays a forward declaration.
#include "mailsync.h"
+#include "searchterm.h"
// Complete type, not a forward declaration: showTagRulesDialog() defaults its
// seed to TagRule().
#include "tagrules.h"
@@ -375,10 +376,11 @@ private slots:
/// Runs a search asked for from the message pane.
///
- /// `extend` narrows the current query rather than replacing it. The panes
- /// carry a finished query and no knowledge of the bar; the combining
- /// happens here, because only the window can see what the bar holds.
- void runSearchFromPane(const QString &query, bool extend);
+ /// `mode` says whether to replace the query bar, narrow it, or narrow it
+ /// by everything that is not this value. The panes carry a finished query
+ /// and no knowledge of the bar; the combining happens here, because only
+ /// the window can see what the bar holds.
+ void runSearchFromPane(const QString &query, SearchTerm::SearchMode mode);
/// Runs one tagging rule's query in the thread list, so the user can see
/// which mail it collects. The rules dialog stays open; the point is to
diff --git a/src/messagedetailsdialog.cpp b/src/messagedetailsdialog.cpp
index 058814b..e487270 100644
--- a/src/messagedetailsdialog.cpp
+++ b/src/messagedetailsdialog.cpp
@@ -88,10 +88,16 @@ MessageDetailsDialog::MessageDetailsDialog(const QList<ThreadRenderItem> &items,
QMenu menu(this);
auto *replace = menu.addAction(tr("Search for this"));
connect(replace, &QAction::triggered, this,
- [this, row]() { requestSearch(row, false); });
+ [this, row]() {
+ requestSearch(
+ row, SearchTerm::SearchMode::Replace);
+ });
auto *narrow = menu.addAction(tr("Add to search"));
connect(narrow, &QAction::triggered, this,
- [this, row]() { requestSearch(row, true); });
+ [this, row]() {
+ requestSearch(
+ row, SearchTerm::SearchMode::Narrow);
+ });
menu.exec(value->mapToGlobal(pos));
});
}
@@ -145,9 +151,10 @@ void MessageDetailsDialog::buildRows(const QList<ThreadRenderItem> &items)
}
}
-void MessageDetailsDialog::requestSearch(const HeaderRow &row, bool extend)
+void MessageDetailsDialog::requestSearch(const HeaderRow &row,
+ SearchTerm::SearchMode mode)
{
if (row.query.isEmpty())
return;
- emit searchRequested(row.query, extend);
+ emit searchRequested(row.query, mode);
}
diff --git a/src/messagedetailsdialog.h b/src/messagedetailsdialog.h
index f865913..b141c39 100644
--- a/src/messagedetailsdialog.h
+++ b/src/messagedetailsdialog.h
@@ -23,6 +23,7 @@
#include <QString>
#include "htmlbuilder.h"
+#include "searchterm.h"
/// One header of one message, as shown and as searched for.
///
@@ -72,12 +73,13 @@ public:
/// 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, bool extend);
+ void requestSearch(const HeaderRow &row, SearchTerm::SearchMode mode);
signals:
- /// The user chose a search from a row's menu. `extend` narrows the current
- /// query rather than replacing it.
- void searchRequested(const QString &query, bool extend);
+ /// 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.
diff --git a/src/messageview.cpp b/src/messageview.cpp
index 2e7e694..26ef7e3 100644
--- a/src/messageview.cpp
+++ b/src/messageview.cpp
@@ -569,12 +569,14 @@ void MessageView::addSearchEntries(QMenu *menu, const QList<SearchOffer> &offers
auto *sub = menu->addMenu(tr("Search for %1").arg(entry.label));
auto *replace = sub->addAction(tr("Search for this"));
- connect(replace, &QAction::triggered, this,
- [this, entry]() { emit searchRequested(entry.query, false); });
+ connect(replace, &QAction::triggered, this, [this, entry]() {
+ emit searchRequested(entry.query, SearchTerm::SearchMode::Replace);
+ });
auto *narrow = sub->addAction(tr("Add to search"));
- connect(narrow, &QAction::triggered, this,
- [this, entry]() { emit searchRequested(entry.query, true); });
+ connect(narrow, &QAction::triggered, this, [this, entry]() {
+ emit searchRequested(entry.query, SearchTerm::SearchMode::Narrow);
+ });
}
}
@@ -645,9 +647,9 @@ void MessageView::showDetailsDialog()
// from. Closing first leaves no window in which the dialog describes a
// thread the pane has already dropped.
connect(&dialog, &MessageDetailsDialog::searchRequested, this,
- [this, &dialog](const QString &query, bool extend) {
+ [this, &dialog](const QString &query, SearchTerm::SearchMode mode) {
dialog.accept();
- emit searchRequested(query, extend);
+ emit searchRequested(query, mode);
});
dialog.exec();
diff --git a/src/messageview.h b/src/messageview.h
index 3e600f0..877064e 100644
--- a/src/messageview.h
+++ b/src/messageview.h
@@ -182,15 +182,16 @@ signals:
/// The user chose a search from one of the pane's context menus.
///
- /// `extend` narrows the current query rather than replacing it. The view
- /// does not know what the query bar holds and must not: the window owns
- /// that field and does the combining.
+ /// `mode` says whether to replace the query bar, narrow it, or narrow it
+ /// by everything that is not this value. The view does not know what the
+ /// query bar holds and must not: the window owns that field and does the
+ /// combining.
///
/// Separate from queryRequested(), which carries a gate against a link in
/// a rendered document driving the thread list. These menus are chrome
/// built by our own code from values we extracted, so they need no gate,
/// and widening the existing signal would change what that gate protects.
- void searchRequested(const QString &query, bool extend);
+ void searchRequested(const QString &query, SearchTerm::SearchMode mode);
protected:
/// Turns Ctrl+wheel over the body into zoom, and Ctrl+middle-click into a
diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp
index 8b60e37..0bf8925 100644
--- a/tests/test_mainwindow.cpp
+++ b/tests/test_mainwindow.cpp
@@ -3741,7 +3741,8 @@ void TestMainWindow::aSearchFromThePaneReplacesTheQuery()
QVERIFY2(view, "no message view");
queryEdit->setText(QStringLiteral("tag:inbox"));
- emit view->searchRequested(QStringLiteral("from:\"foo@example.org\""), false);
+ emit view->searchRequested(QStringLiteral("from:\"foo@example.org\""),
+ SearchTerm::SearchMode::Replace);
QCOMPARE(queryEdit->text(), QStringLiteral("from:\"foo@example.org\""));
}
@@ -3763,7 +3764,8 @@ void TestMainWindow::aSearchFromThePaneCanNarrowTheQuery()
QVERIFY2(view, "no message view");
queryEdit->setText(QStringLiteral("tag:inbox or tag:flagged"));
- emit view->searchRequested(QStringLiteral("from:\"foo@example.org\""), true);
+ emit view->searchRequested(QStringLiteral("from:\"foo@example.org\""),
+ SearchTerm::SearchMode::Narrow);
QCOMPARE(queryEdit->text(),
QStringLiteral("(tag:inbox or tag:flagged) AND (from:\"foo@example.org\")"));
@@ -3783,7 +3785,8 @@ void TestMainWindow::narrowingAnEmptyQueryBarIsAPlainSearch()
QVERIFY2(view, "no message view");
queryEdit->clear();
- emit view->searchRequested(QStringLiteral("tag:inbox"), true);
+ emit view->searchRequested(QStringLiteral("tag:inbox"),
+ SearchTerm::SearchMode::Narrow);
QCOMPARE(queryEdit->text(), QStringLiteral("tag:inbox"));
}
diff --git a/tests/test_messagedetailsdialog.cpp b/tests/test_messagedetailsdialog.cpp
index 4e685ce..403750b 100644
--- a/tests/test_messagedetailsdialog.cpp
+++ b/tests/test_messagedetailsdialog.cpp
@@ -131,13 +131,15 @@ void TestMessageDetailsDialog::offersASearchForEachValue()
QVERIFY2(date != rows.cend(), "no Date row");
QCOMPARE(date->query, QStringLiteral("date:2026-08-14..2026-08-14"));
- // Replacing and narrowing are both offered, and the flag distinguishes them.
- dialog.requestSearch(*from, false);
- dialog.requestSearch(*from, true);
+ // Replacing and narrowing are both offered, and the mode distinguishes them.
+ dialog.requestSearch(*from, SearchTerm::SearchMode::Replace);
+ dialog.requestSearch(*from, SearchTerm::SearchMode::Narrow);
QCOMPARE(spy.count(), 2);
QCOMPARE(spy.at(0).at(0).toString(), from->query);
- QCOMPARE(spy.at(0).at(1).toBool(), false);
- QCOMPARE(spy.at(1).at(1).toBool(), true);
+ QCOMPARE(spy.at(0).at(1).value<SearchTerm::SearchMode>(),
+ SearchTerm::SearchMode::Replace);
+ QCOMPARE(spy.at(1).at(1).value<SearchTerm::SearchMode>(),
+ SearchTerm::SearchMode::Narrow);
}
void TestMessageDetailsDialog::omitsAnEmptyHeader()
@@ -174,7 +176,7 @@ void TestMessageDetailsDialog::messageIdIsShownButNotSearchable()
// And asking to search it emits nothing rather than an empty query.
QSignalSpy spy(&dialog, &MessageDetailsDialog::searchRequested);
- dialog.requestSearch(*id, false);
+ dialog.requestSearch(*id, SearchTerm::SearchMode::Replace);
QCOMPARE(spy.count(), 0);
}
diff --git a/tests/test_messageview.cpp b/tests/test_messageview.cpp
index edcba03..c97a216 100644
--- a/tests/test_messageview.cpp
+++ b/tests/test_messageview.cpp
@@ -734,7 +734,7 @@ void TestMessageView::aSearchFromTheDetailsDialogClosesIt()
return;
}
- dialog->requestSearch(*from, false);
+ dialog->requestSearch(*from, SearchTerm::SearchMode::Replace);
});
view.showDetailsDialog();