From e05405874587f40fd14ab74b095e827189daca16 Mon Sep 17 00:00:00 2001 From: blocksmith314 Date: Mon, 9 Nov 2026 21:53:58 +0800 Subject: [PATCH] feat(ui): add Help menu with shortcuts and links - Keyboard Shortcuts dialog generated from QKeySequence so labels use the platform-native modifiers (Cmd on macOS, Ctrl elsewhere) - Documentation and Report an Issue open the GitHub pages - About stays in the application menu on macOS (Qt AboutRole) - add a GUI test for the Help menu entries --- mainwindow.cpp | 44 ++++++++++++++++++++++++++++++++++++++++++++ mainwindow.h | 1 + test/test_gui.cpp | 19 +++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/mainwindow.cpp b/mainwindow.cpp index a1560f7..a110a7d 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -269,6 +270,19 @@ void MainWindow::SetupUi() { [this](int, int) { SyncSplitterWidths(false); }); QMenu* helpMenu = menuBar()->addMenu("Help"); + QAction* shortcutsAct = helpMenu->addAction("Keyboard Shortcuts"); + connect(shortcutsAct, &QAction::triggered, this, &MainWindow::ShowShortcuts); + QAction* docsAct = helpMenu->addAction("Documentation"); + connect(docsAct, &QAction::triggered, this, []() { + QDesktopServices::openUrl( + QUrl("https://github.com/blocksmith314/DigitalImageAnalyzer#readme")); + }); + QAction* issueAct = helpMenu->addAction("Report an Issue"); + connect(issueAct, &QAction::triggered, this, []() { + QDesktopServices::openUrl( + QUrl("https://github.com/blocksmith314/DigitalImageAnalyzer/issues")); + }); + helpMenu->addSeparator(); QAction* aboutAct = helpMenu->addAction("About Digital Image Analyzer"); connect(aboutAct, &QAction::triggered, this, &MainWindow::ShowAbout); @@ -299,6 +313,36 @@ void MainWindow::OpenFile() { } } +void MainWindow::ShowShortcuts() { + const auto key = [](QKeySequence::StandardKey standard) { + return QKeySequence(standard).toString(QKeySequence::NativeText); + }; + const auto keyOf = [](int key_value) { + return QKeySequence(key_value).toString(QKeySequence::NativeText); + }; + const auto keyCombo = [](Qt::Key key_value) { + return QKeySequence(QKeyCombination(Qt::CTRL, key_value)) + .toString(QKeySequence::NativeText); + }; + + const QString text = + QString( + "

Keyboard Shortcuts

" + "" + "" + "" + "" + "" + "" + "" + "
Open file%1
Recent files%2
Play / pause%3
Previous / next frame%4 / %5
Leave fullscreen%6
Quit%7
") + .arg(key(QKeySequence::Open), + keyCombo(Qt::Key_1) + " ... " + keyCombo(Qt::Key_9), + keyOf(Qt::Key_Space), keyOf(Qt::Key_Left), keyOf(Qt::Key_Right), + keyOf(Qt::Key_Escape), key(QKeySequence::Quit)); + QMessageBox::information(this, "Keyboard Shortcuts", text); +} + void MainWindow::ShowAbout() { QMessageBox::about(this, "About Digital Image Analyzer", QString("

Digital Image Analyzer %1

" diff --git a/mainwindow.h b/mainwindow.h index 365266e..7243059 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -53,6 +53,7 @@ class MainWindow : public QMainWindow { void OnPlayerTick(); void OnSpeedChanged(int index); void ShowAbout(); + void ShowShortcuts(); private: static constexpr int MAX_RECENT_FILES = 10; diff --git a/test/test_gui.cpp b/test/test_gui.cpp index f980a8d..5c537c4 100644 --- a/test/test_gui.cpp +++ b/test/test_gui.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -193,6 +194,24 @@ TEST(MainWindowTest, HasHelpMenu) { EXPECT_TRUE(has_help); } +TEST(MainWindowTest, HelpMenuHasEntries) { + MainWindow window; + QMenu* help = nullptr; + for (QAction* action : window.menuBar()->actions()) { + if (action->text().contains("Help")) { + help = action->menu(); + } + } + ASSERT_NE(help, nullptr); + QStringList entries; + for (QAction* action : help->actions()) { + entries << action->text(); + } + EXPECT_TRUE(entries.contains("Keyboard Shortcuts")); + EXPECT_TRUE(entries.contains("Documentation")); + EXPECT_TRUE(entries.contains("Report an Issue")); +} + TEST(MainWindowTest, OpensFileByDrop) { if (!exists(data_path("output.h264"))) { GTEST_SKIP() << "H.264 fixture missing";