summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-14 13:36:11 +0200
committerDanilo M. <danix@danix.xyz>2026-08-14 13:36:11 +0200
commit5b18bc2123d8bf0830ebf8ab3f735c9496342e97 (patch)
treed8ed0118845b39d1c9a9e82199aa810078b04352 /src
parent2f124b22920fb290684206a2c905996f1c378fd7 (diff)
downloadqtmaildir-5b18bc2123d8bf0830ebf8ab3f735c9496342e97.tar.gz
qtmaildir-5b18bc2123d8bf0830ebf8ab3f735c9496342e97.zip
feat(search): run a search asked for from the message pane
The panes carry a finished query and know nothing of the query bar; the window sets the field and calls the existing runner, so the account scope and the generation counter keep working as they do for a typed query. Narrowing combines here rather than in a pane, because only the window can see what the bar currently holds. The tag strip's chips join the header, the body selection and the details dialog as a fourth surface. Also fixes the details dialog to actually close when a search is chosen: the comment above the connection already described this requirement, but nothing called accept() or reject(), so the dialog stayed open, the query ran behind it, and the modal exec() never returned. This hung the whole test suite on QT_QPA_PLATFORM=offscreen once a covering test was added.
Diffstat (limited to 'src')
-rw-r--r--src/mainwindow.cpp18
-rw-r--r--src/mainwindow.h7
-rw-r--r--src/messageview.cpp30
3 files changed, 53 insertions, 2 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index bb6aa43..115646e 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -55,6 +55,7 @@
#include "querycompleter.h"
#include "carddelegate.h"
#include "cardlayout.h"
+#include "searchterm.h"
#include "tagchip.h"
#include "tagdialog.h"
#include "savequerydialog.h"
@@ -672,6 +673,8 @@ void MainWindow::buildUi()
this, &MainWindow::onPlaceholderQueryRequested);
connect(m_messageView, &MessageView::staleThreadRecoveryRequested,
this, &MainWindow::recoverStaleThread);
+ connect(m_messageView, &MessageView::searchRequested,
+ this, &MainWindow::runSearchFromPane);
m_splitter = new QSplitter(Qt::Horizontal, central);
m_splitter->addWidget(m_threadView);
@@ -1648,6 +1651,21 @@ void MainWindow::onPlaceholderQueryRequested(const QString &query)
runCurrentQuery();
}
+void MainWindow::runSearchFromPane(const QString &query, bool extend)
+{
+ if (query.isEmpty())
+ return;
+
+ const QString next =
+ extend ? SearchTerm::extend(m_queryEdit->text(), query) : query;
+
+ // 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
+ // for a typed query. Nothing here builds a second query path.
+ m_queryEdit->setText(next);
+ runCurrentQuery();
+}
+
void MainWindow::showWarnings()
{
const QStringList warnings = m_config.warnings() + m_keyMap.warnings();
diff --git a/src/mainwindow.h b/src/mainwindow.h
index a68fcd9..f54a889 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -373,6 +373,13 @@ private slots:
/// Runs a query the user clicked on the placeholder pane.
void onPlaceholderQueryRequested(const QString &query);
+ /// 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);
+
/// 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
/// compare the rule against its results.
diff --git a/src/messageview.cpp b/src/messageview.cpp
index fbd43d6..1e6c256 100644
--- a/src/messageview.cpp
+++ b/src/messageview.cpp
@@ -252,6 +252,20 @@ MessageView::MessageView(QWidget *parent)
m_tagStrip = new TagStrip(this);
m_tagStrip->hide();
+ // Item 85: a tag chip is searchable. The strip reports which chip was hit
+ // and where; what a tag can do is decided here, beside the other menus, so
+ // all three surfaces offer the same pair of operations.
+ connect(m_tagStrip, &TagStrip::tagContextMenuRequested, this,
+ [this](const QString &tag, const QPoint &globalPos) {
+ const QString query = SearchTerm::tag(tag);
+ if (query.isEmpty())
+ return;
+
+ QMenu menu(this);
+ addSearchEntries(&menu, { { tr("tag %1").arg(tag), query } });
+ menu.exec(globalPos);
+ });
+
auto *layout = new QVBoxLayout(this);
layout->addLayout(headerRow);
layout->addLayout(blockedRow);
@@ -616,10 +630,22 @@ void MessageView::showDetailsDialog()
return;
MessageDetailsDialog dialog(m_items, this);
+
// The dialog's searches are the pane's searches: one signal reaches the
// window whichever surface the user used.
- connect(&dialog, &MessageDetailsDialog::searchRequested,
- this, &MessageView::searchRequested);
+ //
+ // It CLOSES on the way out, and that is not tidiness. The dialog is modal,
+ // so without this the query runs and the thread list repaints behind a
+ // window the user still has to dismiss, making the search look like it did
+ // nothing. The dialog is also built from m_items, which the new query is
+ // about to replace, so what it displays would describe a thread the pane
+ // has already stopped showing.
+ connect(&dialog, &MessageDetailsDialog::searchRequested, this,
+ [this, &dialog](const QString &query, bool extend) {
+ emit searchRequested(query, extend);
+ dialog.accept();
+ });
+
dialog.exec();
}