aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-04 10:03:24 +0200
committerDanilo M. <danix@danix.xyz>2026-08-04 12:54:32 +0200
commit0e149dfb514de8eada2c8c8430352ac984d3c5ba (patch)
treef8dac7d1c10d2a3ecf2d0358fc295153429328be /src
parent9016063582c75f6d9a5fbd770b1f0b2f363b3054 (diff)
downloadqtmaildir-0e149dfb514de8eada2c8c8430352ac984d3c5ba.tar.gz
qtmaildir-0e149dfb514de8eada2c8c8430352ac984d3c5ba.zip
fix(query): let the query bar keep Return instead of open_thread
Pressing Return in the query bar moved focus to the thread list and left the query unrun, so a query typed at the keyboard could not be executed at all. Return is bound to open_thread as a Qt::WindowShortcut, and a shortcut is dispatched before the focused widget ever sees the key. Qt withholds a plain-LETTER shortcut from an editable widget, which is why every other binding in the map was safe here, but Return is not a letter and gets no such protection: the action fired from inside the bar, its handler called setFocus() on the thread list, and QLineEdit::returnPressed was never emitted. Accept the ShortcutOverride for Return and Enter on the query bar, which tells Qt the focused widget wants the key as ordinary input and stops the shortcut being dispatched. Narrow by design, one widget and one key, so open_thread keeps working everywhere else in the window. Three earlier hypotheses were tested and disproven before this one, and two synthetic probes wrongly reported the binding as harmless: real input sends ShortcutOverride first and only dispatches the shortcut if nothing claims it, while QTest::keyClick skips that round trip entirely. The new test drives the override exchange rather than the keystroke and fails against the old code. The comment claiming no filter was needed said the letter rule covered this case; it did not, and it now says so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src')
-rw-r--r--src/mainwindow.cpp55
-rw-r--r--src/mainwindow.h5
2 files changed, 55 insertions, 5 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 8ca6aa9..0f169d9 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -19,7 +19,9 @@
#include "mainwindow.h"
#include <QAction>
+#include <QApplication>
#include <QCloseEvent>
+#include <QKeyEvent>
#include <QComboBox>
#include <QDialog>
#include <QDialogButtonBox>
@@ -146,6 +148,30 @@ void MainWindow::closeEvent(QCloseEvent *event)
QMainWindow::closeEvent(event);
}
+bool MainWindow::eventFilter(QObject *watched, QEvent *event)
+{
+ // Return is bound to open_thread as a WindowShortcut. A shortcut is
+ // dispatched before the focused widget sees the key, and Qt's protection
+ // for editable widgets covers plain LETTERS only, so from inside the query
+ // bar Return triggered the action, focus jumped to the thread list, and the
+ // query was never run.
+ //
+ // Accepting the ShortcutOverride tells Qt the focused widget wants this key
+ // as ordinary input, which stops the shortcut from being dispatched at all;
+ // QLineEdit then emits returnPressed as usual. Narrow on purpose: one
+ // widget, one key, so open_thread keeps working everywhere else.
+ if (watched == m_queryEdit && event->type() == QEvent::ShortcutOverride) {
+ auto *keyEvent = static_cast<QKeyEvent *>(event);
+ if (keyEvent->key() == Qt::Key_Return
+ || keyEvent->key() == Qt::Key_Enter) {
+ keyEvent->accept();
+ return true;
+ }
+ }
+
+ return QMainWindow::eventFilter(watched, event);
+}
+
MainWindow::MainWindow(const Config &config, QWidget *parent)
: QMainWindow(parent), m_config(config)
{
@@ -178,12 +204,17 @@ MainWindow::MainWindow(const Config &config, QWidget *parent)
wireWorker();
showWarnings();
- // No event filter: QAction shortcuts are dispatched before the focused
- // widget sees the key, so they beat QAbstractItemView's type-to-search
- // without one. Qt also suppresses a plain-letter shortcut while an
- // editable widget has focus, so typing in the query bar stays typing;
- // modifier shortcuts such as Ctrl+Q still work there, which the old
+ // No window-wide event filter: QAction shortcuts are dispatched before the
+ // focused widget sees the key, so they beat QAbstractItemView's
+ // type-to-search without one. Qt also suppresses a plain-letter shortcut
+ // while an editable widget has focus, so typing in the query bar stays
+ // typing; modifier shortcuts such as Ctrl+Q still work there, which the old
// filter blocked.
+ //
+ // That letter rule does NOT cover Return, which is bound to open_thread:
+ // it reached the action from inside the query bar and stole the key. The
+ // narrow filter buildUi() installs on the query bar claims it back. See
+ // eventFilter().
// Not savedQueries().first(): [queries] is read through childKeys(), which
// sorts alphabetically, so "first" means whatever happens to sort first
@@ -222,6 +253,16 @@ void MainWindow::buildUi()
m_queryEdit->setPlaceholderText(tr("notmuch query, e.g. tag:inbox"));
connect(m_queryEdit, &QLineEdit::returnPressed,
this, &MainWindow::runCurrentQuery);
+
+ // Return is bound to open_thread as a WindowShortcut, and a shortcut is
+ // dispatched before the focused widget sees the key. Qt withholds a plain
+ // LETTER shortcut from an editable widget, which is why every other binding
+ // here is safe, but Return is not a letter and gets no such protection: it
+ // reached the action, focus jumped to the thread list, and the query never
+ // ran. Accepting the ShortcutOverride is what claims the key back, and it
+ // is scoped to the one widget and the one key, so open_thread still works
+ // everywhere else in the window.
+ m_queryEdit->installEventFilter(this);
m_queryCompleter = new QueryCompleter(m_queryEdit, m_config, this);
m_syncLog = new QPlainTextEdit(central);
@@ -372,6 +413,10 @@ void MainWindow::registerActions()
});
addAction(QStringLiteral("open_thread"), tr("&Open thread"),
tr("Focus the thread list"), [this]() {
+ qDebug("[MW] open_thread action TRIGGERED (focus=%s)",
+ QApplication::focusWidget()
+ ? QApplication::focusWidget()->metaObject()->className()
+ : "none");
m_threadView->setFocus();
});
addAction(QStringLiteral("archive"), tr("&Archive"),
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 3623b04..65f9636 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -74,6 +74,11 @@ public:
protected:
void closeEvent(QCloseEvent *event) override;
+ /// Claims Return back for the query bar. Return is bound to open_thread as
+ /// a WindowShortcut, and a shortcut outranks the focused widget, so without
+ /// this the action fires from inside the bar and the query never runs.
+ bool eventFilter(QObject *watched, QEvent *event) override;
+
private slots:
void runCurrentQuery();
void onThreadsReady(const QVector<ThreadSummary> &threads, quint64 generation);