Open the menu bar on F10, and add a test that can answer whether it works

F10 opens the menu bar in most applications and is the usual way to reach
the menus without a mouse. Qt provides this on Windows but not on X11, so on
Linux the key did nothing and the press fell through to whichever widget had
focus. It matters more here than it might elsewhere: "&Édition" takes É for
its own letter, which is not on a UK or US keyboard, so that menu has no
direct Alt route at all.

A window-context QShortcut rather than a key handler -- key presses go to
the focused child widget, so a keyPressEvent() on the window would never see
F10 while the canvas or a panel has focus.

tests/qttest/tst_menubarkeyboard.cpp covers three things: that Alt and a
letter opens a menu (the control), that plain F10 does nothing in Qt itself
(which is why the shortcut exists, and which will fail loudly if a future Qt
starts handling it), and that the shortcut mechanism opens the bar.

It uses QTest instead of driving a real X server for a specific reason.
xdotool on Xvfb delivers every function key with Alt held: a Qt key logger
shows Key_F10 arriving with modifiers == Qt::AltModifier. --clearmodifiers,
keydown/keyup pairs, --window targeting and flattening the keycode with
xmodmap all made no difference. Two rounds of GUI automation therefore gave
confident, wrong answers about F10 -- first that it was broken, then that
this very fix did not work. QTest posts the event straight to the widget, so
the key arrives as written.

What the test does not cover, since initCommonActions() calls
QETApp::instance() and constructing that pulls in the whole application: it
repeats the wiring rather than driving QETMainWindow. Confirming the real
window responds still needs someone to press F10 in a running QElectroTech.

Verified by breaking it: bound to F11 instead, the test fails. Qt 5 and Qt 6
both build clean, 6/6 tests on each.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-09-15 13:44:59 +12:00
parent 199444b6db
commit bd6bed8d61
4 changed files with 177 additions and 0 deletions
+31
View File
@@ -19,6 +19,7 @@
#include <QWhatsThis>
#include <QMenu>
#include <QMenuBar>
#include <QShortcut>
#include <QDragEnterEvent>
#include <QDesktopServices>
@@ -41,6 +42,14 @@ QETMainWindow::QETMainWindow(QWidget *widget, Qt::WindowFlags flags) :
initCommonMenus();
setAcceptDrops(true);
//A shortcut rather than a key handler: a key press goes to the
//focused child widget, so a keyPressEvent() here would never see F10
//while the canvas or a panel holds focus.
QShortcut *menu_bar_shortcut = new QShortcut(QKeySequence(Qt::Key_F10), this);
menu_bar_shortcut -> setContext(Qt::WindowShortcut);
connect(menu_bar_shortcut, &QShortcut::activated,
this, &QETMainWindow::activateMenuBar);
}
/**
@@ -251,6 +260,28 @@ void QETMainWindow::checkToolbarsmenu()
/**
Handle the \a e event.
*/
/**
@brief QETMainWindow::activateMenuBar
Open the first usable menu, as pressing Alt and a menu's letter would.
Qt implements F10 for this on Windows but not on X11, so on Linux the key
did nothing and the press fell through to whichever widget had focus. F10
is the usual way to reach the menus without a mouse, and it matters here
because one menu cannot be reached by its own letter at all: "&Édition"
takes É, which is not on a UK or US keyboard.
*/
void QETMainWindow::activateMenuBar() {
QMenuBar *bar = menuBar();
if (!bar) return;
for (QAction *action : bar -> actions()) {
if (action -> isVisible() && action -> isEnabled() && action -> menu()) {
bar -> setActiveAction(action);
return;
}
}
}
bool QETMainWindow::event(QEvent *e) {
if (e -> type() == QEvent::WindowStateChange) {
updateFullScreenAction();
+1
View File
@@ -39,6 +39,7 @@ class QETMainWindow : public QMainWindow {
QAction *actionForMenu(QMenu *);
protected:
void activateMenuBar();
bool event(QEvent *) override;
void dragEnterEvent(QDragEnterEvent *e) override;
void dropEvent(QDropEvent *e) override;
+7
View File
@@ -113,3 +113,10 @@ add_executable(
add_test(NAME tst_qetstrings COMMAND tst_qetstrings)
target_include_directories(tst_qetstrings PRIVATE ${QET_DIR}/sources)
target_link_libraries(tst_qetstrings PRIVATE Qt::Test Qt::Widgets Qt::Xml)
add_executable(
tst_menubarkeyboard
tst_menubarkeyboard.cpp)
add_test(NAME tst_menubarkeyboard COMMAND tst_menubarkeyboard)
target_include_directories(tst_menubarkeyboard PRIVATE ${QET_DIR}/sources)
target_link_libraries(tst_menubarkeyboard PRIVATE Qt::Test Qt::Widgets)
+138
View File
@@ -0,0 +1,138 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QtTest>
#include <QMainWindow>
#include <QMenu>
#include <QMenuBar>
#include <QShortcut>
/*
F10 should open the menu bar, as it does in most applications and as
someone working without a mouse will expect. Qt provides this on Windows
but not on X11, so QElectroTech adds it (QETMainWindow::activateMenuBar).
These tests use QTest rather than driving a real X server. That is not a
convenience: xdotool on Xvfb delivers every function key with Alt held, so
the application receives Alt+F10 and never the plain key. Two rounds of
GUI automation gave confident, wrong answers about F10 before that was
understood. QTest posts the event directly to the widget, so the key
arrives exactly as written.
*/
class TstMenuBarKeyboard : public QObject
{
Q_OBJECT
private slots:
void altLetterOpensMenu(); // control -- must pass, or nothing below means anything
void plainF10DoesNothingInQt(); // the gap being filled
void shortcutOpensMenuBar(); // the mechanism QETMainWindow uses
};
namespace {
QMainWindow *makeWindow(QMenu **file_menu)
{
auto *w = new QMainWindow;
*file_menu = w->menuBar()->addMenu(QStringLiteral("&File"));
(*file_menu)->addAction(QStringLiteral("Quit"));
w->menuBar()->addMenu(QStringLiteral("&Edit"))->addAction(QStringLiteral("Copy"));
w->resize(600, 400);
w->show();
w->activateWindow();
w->raise();
return w;
}
}
/*
The control. Alt and a menu's letter is known to work, so if this fails
the environment cannot open menus at all and the other two tests are
measuring nothing.
*/
void TstMenuBarKeyboard::altLetterOpensMenu()
{
QMenu *file = nullptr;
QScopedPointer<QMainWindow> w(makeWindow(&file));
QVERIFY(QTest::qWaitForWindowExposed(w.data()));
QTest::keyClick(w.data(), Qt::Key_F, Qt::AltModifier);
QTest::qWait(300);
QVERIFY2(file->isVisible(),
"control failed: Alt+F did not open a menu, so this environment "
"cannot judge any of the keyboard tests below");
}
/*
Records why the shortcut in QETMainWindow exists. If a future Qt starts
handling F10 on this platform, this test fails and the shortcut can go.
*/
void TstMenuBarKeyboard::plainF10DoesNothingInQt()
{
QMenu *file = nullptr;
QScopedPointer<QMainWindow> w(makeWindow(&file));
QVERIFY(QTest::qWaitForWindowExposed(w.data()));
QTest::keyClick(w.data(), Qt::Key_F10, Qt::NoModifier);
QTest::qWait(300);
QVERIFY2(!file->isVisible() && w->menuBar()->activeAction() == nullptr,
"Qt now handles F10 by itself -- QETMainWindow's shortcut is "
"redundant and can be removed");
}
/*
The mechanism QETMainWindow uses, exercised on a plain QMainWindow.
Worth being clear about what this does not cover: it repeats the shortcut
wiring rather than driving QETMainWindow itself, because
initCommonActions() calls QETApp::instance() and constructing that pulls
in the whole application -- element collections and all -- which does not
belong in a unit test. So this proves the approach works and would catch
it breaking in a future Qt; it does not prove QETMainWindow is wired up.
That last step needs someone to press F10 in a running QElectroTech.
*/
void TstMenuBarKeyboard::shortcutOpensMenuBar()
{
QMenu *file = nullptr;
QScopedPointer<QMainWindow> holder(makeWindow(&file));
QMainWindow *w = holder.data();
QVERIFY(QTest::qWaitForWindowExposed(w));
auto *shortcut = new QShortcut(QKeySequence(Qt::Key_F10), w);
shortcut->setContext(Qt::WindowShortcut);
QObject::connect(shortcut, &QShortcut::activated, w, [w]() {
for (QAction *action : w->menuBar()->actions()) {
if (action->isVisible() && action->isEnabled() && action->menu()) {
w->menuBar()->setActiveAction(action);
return;
}
}
});
QTest::keyClick(w, Qt::Key_F10, Qt::NoModifier);
QTest::qWait(300);
QVERIFY2(w->menuBar()->activeAction() != nullptr,
"F10 did not activate the menu bar");
QCOMPARE(w->menuBar()->activeAction()->text(), QStringLiteral("&File"));
}
QTEST_MAIN(TstMenuBarKeyboard)
#include "tst_menubarkeyboard.moc"