Rework keyboard shortcuts with app-level event filter

- Replace QShortcut with QApplication eventFilter to intercept
  Ctrl+C/S/N/W/D/F/R before widgets consume them
- New bindings: Ctrl+N (new tab), Ctrl+C (clear log), Ctrl+S (find),
  Ctrl+F (flash), Ctrl+D (connect/disconnect), Ctrl+R (normal mode)
- Single bindings tuple as source of truth for key map and help dialog
- Apply saved font settings via one-shot showEvent (after stylesheet)
- Add Shortcuts entry under Help menu
- Remove unused imports (QShortcut, QKeySequence, QMenuBar)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
wongyiekheng 2026-03-31 10:00:00 +08:00
parent eee479b02a
commit 5b873aefc4

View File

@ -4,20 +4,19 @@ import logging
import logging.handlers import logging.handlers
import sys import sys
from PySide6.QtCore import Qt from PySide6.QtCore import QEvent, Qt
from PySide6.QtGui import QFont, QKeySequence, QShortcut from PySide6.QtGui import QFont
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QApplication, QApplication,
QInputDialog, QInputDialog,
QMainWindow, QMainWindow,
QMenuBar,
QMessageBox, QMessageBox,
QTabWidget, QTabWidget,
QToolButton, QToolButton,
) )
from ameba_control_panel import config from ameba_control_panel import config
from ameba_control_panel.config import DeviceProfile from ameba_control_panel.config import DeviceProfile, Mode
from ameba_control_panel import theme from ameba_control_panel import theme
from ameba_control_panel.controllers.device_tab_controller import DeviceTabController from ameba_control_panel.controllers.device_tab_controller import DeviceTabController
from ameba_control_panel.services.session_store import SessionStore from ameba_control_panel.services.session_store import SessionStore
@ -39,10 +38,11 @@ class MainWindow(QMainWindow):
self._settings = Settings() self._settings = Settings()
self._dut_controllers: list[DeviceTabController] = [] self._dut_controllers: list[DeviceTabController] = []
self._next_dut_num = 1 self._next_dut_num = 1
self._settings_applied = False
add_btn = QToolButton() add_btn = QToolButton()
add_btn.setText("+") add_btn.setText("+")
add_btn.setToolTip("Add DUT tab (Ctrl+T)") add_btn.setToolTip("Add DUT tab (Ctrl+N)")
add_btn.clicked.connect(self._add_dut_tab_auto) add_btn.clicked.connect(self._add_dut_tab_auto)
self._tabs.setCornerWidget(add_btn, Qt.TopLeftCorner) self._tabs.setCornerWidget(add_btn, Qt.TopLeftCorner)
@ -61,21 +61,28 @@ class MainWindow(QMainWindow):
else: else:
self._add_dut_tab("dut_1", "DUT 1") self._add_dut_tab("dut_1", "DUT 1")
def showEvent(self, event) -> None: # noqa: N802
super().showEvent(event)
if not self._settings_applied:
self._settings_applied = True
self._apply_settings()
def _build_menu_bar(self) -> None: def _build_menu_bar(self) -> None:
mb = self.menuBar() mb = self.menuBar()
view_menu = mb.addMenu("&View") view_menu = mb.addMenu("&View")
view_menu.addAction("New Tab", self._add_dut_tab_auto, QKeySequence("Ctrl+T")) view_menu.addAction("New Tab\tCtrl+N", self._add_dut_tab_auto)
view_menu.addAction("Close Tab", self._close_current_tab, QKeySequence("Ctrl+W")) view_menu.addAction("Close Tab\tCtrl+W", self._close_current_tab)
view_menu.addSeparator() view_menu.addSeparator()
view_menu.addAction("Clear Log", self._clear_current_log, QKeySequence("Ctrl+L")) view_menu.addAction("Clear Log\tCtrl+C", self._clear_current_log)
view_menu.addAction("Find", self._focus_find, QKeySequence("Ctrl+F")) view_menu.addAction("Find\tCtrl+S", self._focus_find)
settings_menu = mb.addMenu("&Settings") settings_menu = mb.addMenu("&Settings")
settings_menu.addAction("Preferences...", self._open_settings) settings_menu.addAction("Preferences...", self._open_settings)
help_menu = mb.addMenu("&Help") help_menu = mb.addMenu("&Help")
help_menu.addAction("About", self._show_about) help_menu.addAction("About", self._show_about)
help_menu.addAction("Shortcuts", self._show_shortcuts)
def _toggle_theme(self) -> None: def _toggle_theme(self) -> None:
if self._current_palette.name == "light": if self._current_palette.name == "light":
@ -87,6 +94,15 @@ class MainWindow(QMainWindow):
for ctrl in self._dut_controllers: for ctrl in self._dut_controllers:
ctrl.view.log_view.set_colors(p.log_rx, p.log_tx, p.log_info) ctrl.view.log_view.set_colors(p.log_rx, p.log_tx, p.log_info)
def _toggle_connect(self) -> None:
ctrl = self._current_controller()
if ctrl:
ctrl.view.connect_button.click()
def _show_shortcuts(self) -> None:
text = "\n".join(f"Ctrl+{lbl}\t{desc}" for lbl, desc in self._shortcut_labels)
QMessageBox.information(self, "Shortcuts", text)
def _show_about(self) -> None: def _show_about(self) -> None:
QMessageBox.about(self, config.APP_NAME, QMessageBox.about(self, config.APP_NAME,
f"{config.APP_NAME}\n" f"{config.APP_NAME}\n"
@ -96,14 +112,34 @@ class MainWindow(QMainWindow):
f"Licensed under the Apache License, Version 2.0\n" f"Licensed under the Apache License, Version 2.0\n"
f"All rights reserved. For internal use only.") f"All rights reserved. For internal use only.")
_KEY_EVENTS = frozenset({QEvent.KeyPress, QEvent.ShortcutOverride})
def _setup_shortcuts(self) -> None: def _setup_shortcuts(self) -> None:
QShortcut(QKeySequence("Ctrl+T"), self, activated=self._add_dut_tab_auto) bindings = (
QShortcut(QKeySequence("Ctrl+W"), self, activated=self._close_current_tab) ("N", "New DUT tab", Qt.Key_N, self._add_dut_tab_auto),
QShortcut(QKeySequence("Ctrl+F"), self, activated=self._focus_find) ("W", "Close current tab", Qt.Key_W, self._close_current_tab),
QShortcut(QKeySequence("Ctrl+L"), self, activated=self._clear_current_log) ("D", "Connect / Disconnect", Qt.Key_D, self._toggle_connect),
QShortcut(QKeySequence("Ctrl+Return"), self, activated=self._send_current) ("S", "Find in log", Qt.Key_S, self._focus_find),
QShortcut(QKeySequence("Ctrl+Shift+F"), self, activated=self._flash_current) ("C", "Clear log", Qt.Key_C, self._clear_current_log),
QShortcut(QKeySequence("Ctrl+R"), self, activated=self._reset_current) ("Enter", "Send command", Qt.Key_Return, self._send_current),
("F", "Flash firmware", Qt.Key_F, self._flash_current),
("R", "Normal mode", Qt.Key_R, self._reset_current),
)
self._shortcut_labels = tuple((lbl, desc) for lbl, desc, _, _ in bindings)
self._key_map = {key: handler for _, _, key, handler in bindings}
QApplication.instance().installEventFilter(self)
def eventFilter(self, obj, event) -> bool:
etype = event.type()
if etype in self._KEY_EVENTS and event.modifiers() == Qt.ControlModifier:
handler = self._key_map.get(event.key())
if handler:
if etype == QEvent.ShortcutOverride:
event.accept()
return True
handler()
return True
return super().eventFilter(obj, event)
def _close_current_tab(self) -> None: def _close_current_tab(self) -> None:
self._close_tab(self._tabs.currentIndex()) self._close_tab(self._tabs.currentIndex())
@ -146,8 +182,7 @@ class MainWindow(QMainWindow):
def _reset_current(self) -> None: def _reset_current(self) -> None:
ctrl = self._current_controller() ctrl = self._current_controller()
if ctrl: if ctrl:
from ameba_control_panel.config import Mode ctrl.flash.run_mode(Mode.NORMAL)
ctrl.flash.run_mode(Mode.RESET)
def _current_controller(self) -> DeviceTabController | None: def _current_controller(self) -> DeviceTabController | None:
widget = self._tabs.currentWidget() widget = self._tabs.currentWidget()