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 sys
from PySide6.QtCore import Qt
from PySide6.QtGui import QFont, QKeySequence, QShortcut
from PySide6.QtCore import QEvent, Qt
from PySide6.QtGui import QFont
from PySide6.QtWidgets import (
QApplication,
QInputDialog,
QMainWindow,
QMenuBar,
QMessageBox,
QTabWidget,
QToolButton,
)
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.controllers.device_tab_controller import DeviceTabController
from ameba_control_panel.services.session_store import SessionStore
@ -39,10 +38,11 @@ class MainWindow(QMainWindow):
self._settings = Settings()
self._dut_controllers: list[DeviceTabController] = []
self._next_dut_num = 1
self._settings_applied = False
add_btn = QToolButton()
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)
self._tabs.setCornerWidget(add_btn, Qt.TopLeftCorner)
@ -61,21 +61,28 @@ class MainWindow(QMainWindow):
else:
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:
mb = self.menuBar()
view_menu = mb.addMenu("&View")
view_menu.addAction("New Tab", self._add_dut_tab_auto, QKeySequence("Ctrl+T"))
view_menu.addAction("Close Tab", self._close_current_tab, QKeySequence("Ctrl+W"))
view_menu.addAction("New Tab\tCtrl+N", self._add_dut_tab_auto)
view_menu.addAction("Close Tab\tCtrl+W", self._close_current_tab)
view_menu.addSeparator()
view_menu.addAction("Clear Log", self._clear_current_log, QKeySequence("Ctrl+L"))
view_menu.addAction("Find", self._focus_find, QKeySequence("Ctrl+F"))
view_menu.addAction("Clear Log\tCtrl+C", self._clear_current_log)
view_menu.addAction("Find\tCtrl+S", self._focus_find)
settings_menu = mb.addMenu("&Settings")
settings_menu.addAction("Preferences...", self._open_settings)
help_menu = mb.addMenu("&Help")
help_menu.addAction("About", self._show_about)
help_menu.addAction("Shortcuts", self._show_shortcuts)
def _toggle_theme(self) -> None:
if self._current_palette.name == "light":
@ -87,6 +94,15 @@ class MainWindow(QMainWindow):
for ctrl in self._dut_controllers:
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:
QMessageBox.about(self, config.APP_NAME,
f"{config.APP_NAME}\n"
@ -96,14 +112,34 @@ class MainWindow(QMainWindow):
f"Licensed under the Apache License, Version 2.0\n"
f"All rights reserved. For internal use only.")
_KEY_EVENTS = frozenset({QEvent.KeyPress, QEvent.ShortcutOverride})
def _setup_shortcuts(self) -> None:
QShortcut(QKeySequence("Ctrl+T"), self, activated=self._add_dut_tab_auto)
QShortcut(QKeySequence("Ctrl+W"), self, activated=self._close_current_tab)
QShortcut(QKeySequence("Ctrl+F"), self, activated=self._focus_find)
QShortcut(QKeySequence("Ctrl+L"), self, activated=self._clear_current_log)
QShortcut(QKeySequence("Ctrl+Return"), self, activated=self._send_current)
QShortcut(QKeySequence("Ctrl+Shift+F"), self, activated=self._flash_current)
QShortcut(QKeySequence("Ctrl+R"), self, activated=self._reset_current)
bindings = (
("N", "New DUT tab", Qt.Key_N, self._add_dut_tab_auto),
("W", "Close current tab", Qt.Key_W, self._close_current_tab),
("D", "Connect / Disconnect", Qt.Key_D, self._toggle_connect),
("S", "Find in log", Qt.Key_S, self._focus_find),
("C", "Clear log", Qt.Key_C, self._clear_current_log),
("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:
self._close_tab(self._tabs.currentIndex())
@ -146,8 +182,7 @@ class MainWindow(QMainWindow):
def _reset_current(self) -> None:
ctrl = self._current_controller()
if ctrl:
from ameba_control_panel.config import Mode
ctrl.flash.run_mode(Mode.RESET)
ctrl.flash.run_mode(Mode.NORMAL)
def _current_controller(self) -> DeviceTabController | None:
widget = self._tabs.currentWidget()