feat: add keyboard shortcuts help popup (Ctrl+Shift+/)
This commit is contained in:
@@ -17,6 +17,7 @@ from illogical.ui.loading_overlay import LoadingOverlay
|
|||||||
from illogical.ui.menu_bar import MenuBar
|
from illogical.ui.menu_bar import MenuBar
|
||||||
from illogical.ui.plugin_table import PluginTableView
|
from illogical.ui.plugin_table import PluginTableView
|
||||||
from illogical.ui.restore_backup_window import RestoreBackupWindow
|
from illogical.ui.restore_backup_window import RestoreBackupWindow
|
||||||
|
from illogical.ui.shortcuts_help_window import ShortcutsHelpWindow
|
||||||
from illogical.ui.sidebar import Sidebar
|
from illogical.ui.sidebar import Sidebar
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -56,6 +57,7 @@ class MainWindow(QMainWindow):
|
|||||||
self._menu_bar.backup_now_triggered.connect(self._on_backup_now)
|
self._menu_bar.backup_now_triggered.connect(self._on_backup_now)
|
||||||
self._menu_bar.restore_backup_triggered.connect(self._on_restore_backup)
|
self._menu_bar.restore_backup_triggered.connect(self._on_restore_backup)
|
||||||
self._menu_bar.backup_settings_triggered.connect(self._on_backup_settings)
|
self._menu_bar.backup_settings_triggered.connect(self._on_backup_settings)
|
||||||
|
self._menu_bar.shortcuts_help_triggered.connect(self._show_shortcuts_help)
|
||||||
|
|
||||||
def _setup_ui(self) -> None:
|
def _setup_ui(self) -> None:
|
||||||
self._central = QWidget()
|
self._central = QWidget()
|
||||||
@@ -113,6 +115,7 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
self._restore_window: RestoreBackupWindow | None = None
|
self._restore_window: RestoreBackupWindow | None = None
|
||||||
self._settings_window: BackupSettingsWindow | None = None
|
self._settings_window: BackupSettingsWindow | None = None
|
||||||
|
self._shortcuts_window: ShortcutsHelpWindow | None = None
|
||||||
|
|
||||||
def showEvent(self, event: QShowEvent) -> None: # noqa: N802
|
def showEvent(self, event: QShowEvent) -> None: # noqa: N802
|
||||||
super().showEvent(event)
|
super().showEvent(event)
|
||||||
@@ -470,6 +473,16 @@ class MainWindow(QMainWindow):
|
|||||||
self._sidebar.clear_manufacturer_search()
|
self._sidebar.clear_manufacturer_search()
|
||||||
self._sidebar.select_show_all()
|
self._sidebar.select_show_all()
|
||||||
|
|
||||||
|
def _show_shortcuts_help(self) -> None:
|
||||||
|
if self._shortcuts_window is not None:
|
||||||
|
self._shortcuts_window.close()
|
||||||
|
self._shortcuts_window = ShortcutsHelpWindow()
|
||||||
|
self._shortcuts_window.destroyed.connect(self._on_shortcuts_window_destroyed)
|
||||||
|
self._shortcuts_window.show_centered(self)
|
||||||
|
|
||||||
|
def _on_shortcuts_window_destroyed(self) -> None:
|
||||||
|
self._shortcuts_window = None
|
||||||
|
|
||||||
def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802
|
def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802
|
||||||
self._service.shutdown()
|
self._service.shutdown()
|
||||||
self._backup_service.shutdown()
|
self._backup_service.shutdown()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class MenuBar(QMenuBar):
|
|||||||
backup_now_triggered = Signal()
|
backup_now_triggered = Signal()
|
||||||
restore_backup_triggered = Signal()
|
restore_backup_triggered = Signal()
|
||||||
backup_settings_triggered = Signal()
|
backup_settings_triggered = Signal()
|
||||||
|
shortcuts_help_triggered = Signal()
|
||||||
|
|
||||||
def __init__(self, main_window: QMainWindow | None = None) -> None:
|
def __init__(self, main_window: QMainWindow | None = None) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -90,4 +91,9 @@ class MenuBar(QMenuBar):
|
|||||||
backup_menu.addAction(settings_action)
|
backup_menu.addAction(settings_action)
|
||||||
|
|
||||||
def _setup_help_menu(self) -> None:
|
def _setup_help_menu(self) -> None:
|
||||||
self.addMenu("Help")
|
help_menu = self.addMenu("Help")
|
||||||
|
|
||||||
|
shortcuts_action = QAction("Keyboard Shortcuts", self)
|
||||||
|
shortcuts_action.setShortcut(QKeySequence("Ctrl+Shift+/"))
|
||||||
|
shortcuts_action.triggered.connect(self.shortcuts_help_triggered)
|
||||||
|
help_menu.addAction(shortcuts_action)
|
||||||
|
|||||||
169
src/illogical/ui/shortcuts_help_window.py
Normal file
169
src/illogical/ui/shortcuts_help_window.py
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import pyqt_liquidglass as glass
|
||||||
|
from PySide6.QtCore import Qt, QTimer
|
||||||
|
from PySide6.QtWidgets import QGridLayout, QHBoxLayout, QLabel, QVBoxLayout, QWidget
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from PySide6.QtGui import QKeyEvent
|
||||||
|
|
||||||
|
SHORTCUTS: dict[str, list[tuple[str, str]]] = {
|
||||||
|
"Navigation": [
|
||||||
|
("J / ↓", "Move down"),
|
||||||
|
("K / ↑", "Move up"),
|
||||||
|
("H / ←", "Collapse (categories)"),
|
||||||
|
("L / →", "Expand (categories)"),
|
||||||
|
("↩", "Enter category"),
|
||||||
|
],
|
||||||
|
"Visual Mode": [("⇧V", "Select lines"), ("Esc", "Exit mode")],
|
||||||
|
"Quick Access": [
|
||||||
|
("⌘1", "Show All"),
|
||||||
|
("⌘2", "Uncategorized"),
|
||||||
|
("⌘3", "Focus categories"),
|
||||||
|
("⌘4", "Focus manufacturers"),
|
||||||
|
("⌘F", "Search"),
|
||||||
|
],
|
||||||
|
"Actions": [
|
||||||
|
("⌥↩", "Context menu"),
|
||||||
|
("⌥⇧↑", "Move category up"),
|
||||||
|
("⌥⇧↓", "Move category down"),
|
||||||
|
("⌘A", "Select all"),
|
||||||
|
],
|
||||||
|
"Drag & Drop": [("⇧+Drop", "Add (don't move)")],
|
||||||
|
"Backup": [("⌘B", "Create backup"), ("⌘⇧R", "Restore")],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ShortcutsHelpWindow(QWidget):
|
||||||
|
def __init__(self, parent: QWidget | None = None) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
self._setup_window()
|
||||||
|
self._setup_ui()
|
||||||
|
|
||||||
|
def _setup_window(self) -> None:
|
||||||
|
self.setWindowTitle("Keyboard Shortcuts")
|
||||||
|
self.setWindowFlags(Qt.WindowType.Tool | Qt.WindowType.WindowStaysOnTopHint)
|
||||||
|
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
|
||||||
|
|
||||||
|
def _setup_ui(self) -> None:
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
layout.setContentsMargins(40, 36, 40, 32)
|
||||||
|
layout.setSpacing(24)
|
||||||
|
|
||||||
|
title = QLabel("Keyboard Shortcuts")
|
||||||
|
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
title.setStyleSheet("""
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
|
background: transparent;
|
||||||
|
""")
|
||||||
|
layout.addWidget(title)
|
||||||
|
|
||||||
|
grid = QGridLayout()
|
||||||
|
grid.setSpacing(32)
|
||||||
|
grid.setColumnStretch(0, 1)
|
||||||
|
grid.setColumnStretch(1, 1)
|
||||||
|
|
||||||
|
sections = list(SHORTCUTS.items())
|
||||||
|
for i, (section_name, shortcuts) in enumerate(sections):
|
||||||
|
col = i % 2
|
||||||
|
row = i // 2
|
||||||
|
section_widget = self._create_section(section_name, shortcuts)
|
||||||
|
grid.addWidget(section_widget, row, col, Qt.AlignmentFlag.AlignTop)
|
||||||
|
|
||||||
|
layout.addLayout(grid)
|
||||||
|
layout.addStretch()
|
||||||
|
|
||||||
|
hint = QLabel("Press Esc to close")
|
||||||
|
hint.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
hint.setStyleSheet("""
|
||||||
|
font-size: 11px;
|
||||||
|
color: rgba(255, 255, 255, 0.4);
|
||||||
|
background: transparent;
|
||||||
|
""")
|
||||||
|
layout.addWidget(hint)
|
||||||
|
|
||||||
|
def _create_section(self, title: str, shortcuts: list[tuple[str, str]]) -> QWidget:
|
||||||
|
section = QWidget()
|
||||||
|
section.setStyleSheet("background: transparent;")
|
||||||
|
layout = QVBoxLayout(section)
|
||||||
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
layout.setSpacing(10)
|
||||||
|
|
||||||
|
header = QLabel(title)
|
||||||
|
header.setStyleSheet("""
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
background: transparent;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
""")
|
||||||
|
layout.addWidget(header)
|
||||||
|
|
||||||
|
for key, description in shortcuts:
|
||||||
|
row = self._create_shortcut_row(key, description)
|
||||||
|
layout.addWidget(row)
|
||||||
|
|
||||||
|
return section
|
||||||
|
|
||||||
|
def _create_shortcut_row(self, key: str, description: str) -> QWidget:
|
||||||
|
row = QWidget()
|
||||||
|
row.setStyleSheet("background: transparent;")
|
||||||
|
layout = QHBoxLayout(row)
|
||||||
|
layout.setContentsMargins(0, 4, 0, 4)
|
||||||
|
layout.setSpacing(14)
|
||||||
|
|
||||||
|
key_label = QLabel(key)
|
||||||
|
key_label.setStyleSheet("""
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: 'SF Mono', 'Menlo', monospace;
|
||||||
|
color: rgba(255, 255, 255, 0.95);
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
""")
|
||||||
|
|
||||||
|
desc_label = QLabel(description)
|
||||||
|
desc_label.setStyleSheet("""
|
||||||
|
font-size: 13px;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
background: transparent;
|
||||||
|
""")
|
||||||
|
|
||||||
|
layout.addWidget(key_label)
|
||||||
|
layout.addWidget(desc_label, 1)
|
||||||
|
|
||||||
|
return row
|
||||||
|
|
||||||
|
def keyPressEvent(self, event: QKeyEvent) -> None: # noqa: N802
|
||||||
|
if event.key() == Qt.Key.Key_Escape:
|
||||||
|
self.close()
|
||||||
|
event.accept()
|
||||||
|
return
|
||||||
|
super().keyPressEvent(event)
|
||||||
|
|
||||||
|
def focusOutEvent(self, _event: object) -> None: # noqa: N802
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def show_centered(self, parent: QWidget | None = None) -> None:
|
||||||
|
glass.prepare_window_for_glass(self, frameless=True)
|
||||||
|
self.adjustSize()
|
||||||
|
self.show()
|
||||||
|
self.activateWindow()
|
||||||
|
self.setFocus()
|
||||||
|
|
||||||
|
if parent:
|
||||||
|
parent_geo = parent.geometry()
|
||||||
|
x = parent_geo.center().x() - self.width() // 2
|
||||||
|
y = parent_geo.center().y() - self.height() // 2
|
||||||
|
self.move(x, y)
|
||||||
|
|
||||||
|
QTimer.singleShot(0, self._apply_glass)
|
||||||
|
|
||||||
|
def _apply_glass(self) -> None:
|
||||||
|
glass.apply_glass_to_window(
|
||||||
|
self, options=glass.GlassOptions(corner_radius=16.0)
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user