diff options
| -rw-r--r-- | src/mainwindow.cpp | 23 | ||||
| -rw-r--r-- | src/mainwindow.h | 10 | ||||
| -rw-r--r-- | src/messagedetailsdialog.cpp | 15 | ||||
| -rw-r--r-- | src/messagedetailsdialog.h | 10 | ||||
| -rw-r--r-- | src/messageview.cpp | 14 | ||||
| -rw-r--r-- | src/messageview.h | 9 | ||||
| -rw-r--r-- | tests/test_mainwindow.cpp | 9 | ||||
| -rw-r--r-- | tests/test_messagedetailsdialog.cpp | 14 | ||||
| -rw-r--r-- | tests/test_messageview.cpp | 2 |
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(); |
