Compare commits

..

No commits in common. "21e16d9872b192843027737e0c75d8d94587970c" and "eee479b02a130937880393412c11c05d82124355" have entirely different histories.

2 changed files with 23 additions and 74 deletions

View File

@ -4,19 +4,20 @@ import logging
import logging.handlers import logging.handlers
import sys import sys
from PySide6.QtCore import QEvent, Qt from PySide6.QtCore import Qt
from PySide6.QtGui import QFont from PySide6.QtGui import QFont, QKeySequence, QShortcut
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, Mode from ameba_control_panel.config import DeviceProfile
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
@ -38,11 +39,10 @@ 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+N)") add_btn.setToolTip("Add DUT tab (Ctrl+T)")
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,28 +61,21 @@ 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\tCtrl+N", self._add_dut_tab_auto) view_menu.addAction("New Tab", self._add_dut_tab_auto, QKeySequence("Ctrl+T"))
view_menu.addAction("Close Tab\tCtrl+W", self._close_current_tab) view_menu.addAction("Close Tab", self._close_current_tab, QKeySequence("Ctrl+W"))
view_menu.addSeparator() view_menu.addSeparator()
view_menu.addAction("Clear Log\tCtrl+C", self._clear_current_log) view_menu.addAction("Clear Log", self._clear_current_log, QKeySequence("Ctrl+L"))
view_menu.addAction("Find\tCtrl+S", self._focus_find) view_menu.addAction("Find", self._focus_find, QKeySequence("Ctrl+F"))
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":
@ -94,15 +87,6 @@ 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"
@ -112,34 +96,14 @@ 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:
bindings = ( QShortcut(QKeySequence("Ctrl+T"), self, activated=self._add_dut_tab_auto)
("N", "New DUT tab", Qt.Key_N, self._add_dut_tab_auto), QShortcut(QKeySequence("Ctrl+W"), self, activated=self._close_current_tab)
("W", "Close current tab", Qt.Key_W, self._close_current_tab), QShortcut(QKeySequence("Ctrl+F"), self, activated=self._focus_find)
("D", "Connect / Disconnect", Qt.Key_D, self._toggle_connect), QShortcut(QKeySequence("Ctrl+L"), self, activated=self._clear_current_log)
("S", "Find in log", Qt.Key_S, self._focus_find), QShortcut(QKeySequence("Ctrl+Return"), self, activated=self._send_current)
("C", "Clear log", Qt.Key_C, self._clear_current_log), QShortcut(QKeySequence("Ctrl+Shift+F"), self, activated=self._flash_current)
("Enter", "Send command", Qt.Key_Return, self._send_current), QShortcut(QKeySequence("Ctrl+R"), self, activated=self._reset_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())
@ -182,7 +146,8 @@ 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:
ctrl.flash.run_mode(Mode.NORMAL) from ameba_control_panel.config import Mode
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()

View File

@ -36,8 +36,6 @@ class LogManager(QObject):
self._search_worker: Optional[SearchWorker] = None self._search_worker: Optional[SearchWorker] = None
self._matches: List[int] = [] self._matches: List[int] = []
self._match_index = -1 self._match_index = -1
self._last_needle = ""
self._last_case = False
self._flush_timer = QTimer(self) self._flush_timer = QTimer(self)
self._flush_timer.setInterval(config.LOG_FLUSH_INTERVAL_MS) self._flush_timer.setInterval(config.LOG_FLUSH_INTERVAL_MS)
@ -89,8 +87,6 @@ class LogManager(QObject):
visible = [line for line in to_flush if line.direction != Direction.TX] visible = [line for line in to_flush if line.direction != Direction.TX]
if visible: if visible:
self.view.log_view.append_lines(visible) self.view.log_view.append_lines(visible)
if self._matches:
self._invalidate_search()
# Adaptive: slow down flush when queue is heavy to reduce UI stalls # Adaptive: slow down flush when queue is heavy to reduce UI stalls
pending_count = len(self._pending) pending_count = len(self._pending)
if pending_count > 500: if pending_count > 500:
@ -100,17 +96,12 @@ class LogManager(QObject):
else: else:
self._flush_timer.setInterval(config.LOG_FLUSH_INTERVAL_MS) self._flush_timer.setInterval(config.LOG_FLUSH_INTERVAL_MS)
def _invalidate_search(self) -> None:
self._matches.clear()
self._match_index = -1
self._last_needle = ""
self._last_case = False
self.view.log_view.set_matches([], -1)
def clear(self) -> None: def clear(self) -> None:
self.buffer.clear() self.buffer.clear()
self.view.log_view.clear_log() self.view.log_view.clear_log()
self._invalidate_search() self._matches.clear()
self._match_index = -1
self.view.log_view.set_matches([], -1)
def save(self) -> None: def save(self) -> None:
dlg = QFileDialog(self.view, "Save Log", str(Path.home() / "ameba_log.txt")) dlg = QFileDialog(self.view, "Save Log", str(Path.home() / "ameba_log.txt"))
@ -135,21 +126,14 @@ class LogManager(QObject):
if self._search_worker and self._search_worker.isRunning(): if self._search_worker and self._search_worker.isRunning():
return return
needle = self.view.find_input.text() needle = self.view.find_input.text()
case = self.view.case_checkbox.isChecked() self.view.log_view.set_needle(needle, self.view.case_checkbox.isChecked())
# If needle unchanged and we already have matches, just advance
if needle == self._last_needle and case == self._last_case and self._matches:
self.find_next()
return
self._last_needle = needle
self._last_case = case
self.view.log_view.set_needle(needle, case)
if not needle: if not needle:
self.view.log_view.set_matches([], -1) self.view.log_view.set_matches([], -1)
self._matches = [] self._matches = []
self._match_index = -1 self._match_index = -1
return return
lines = [l.as_display() for l in self.view.log_view.displayed_lines()] lines = [l.as_display() for l in self.view.log_view.displayed_lines()]
self._search_worker = SearchWorker(lines, needle, case) self._search_worker = SearchWorker(lines, needle, self.view.case_checkbox.isChecked())
self._search_worker.finished.connect(self._on_search_finished) self._search_worker.finished.connect(self._on_search_finished)
self._search_worker.start() self._search_worker.start()