frame fix
This commit is contained in:
92
cht/ui/keyboard.py
Normal file
92
cht/ui/keyboard.py
Normal file
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
KeyboardManager: centralized keyboard shortcut handling.
|
||||
|
||||
Captures all key events at the window level before any child widget.
|
||||
Routes to registered handlers based on keyval.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Callable
|
||||
|
||||
import gi
|
||||
gi.require_version("Gtk", "4.0")
|
||||
from gi.repository import Gtk, Gdk
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
SHIFT = Gdk.ModifierType.SHIFT_MASK
|
||||
CTRL = Gdk.ModifierType.CONTROL_MASK
|
||||
|
||||
KEY_LEFT = Gdk.KEY_Left
|
||||
KEY_RIGHT = Gdk.KEY_Right
|
||||
KEY_UP = Gdk.KEY_Up
|
||||
KEY_DOWN = Gdk.KEY_Down
|
||||
KEY_RETURN = Gdk.KEY_Return
|
||||
KEY_KP_ENTER = Gdk.KEY_KP_Enter
|
||||
KEY_ESCAPE = Gdk.KEY_Escape
|
||||
KEY_DELETE = Gdk.KEY_Delete
|
||||
|
||||
|
||||
class KeyboardManager:
|
||||
"""Captures key events at window level before child widgets.
|
||||
|
||||
Usage:
|
||||
kb = KeyboardManager()
|
||||
kb.bind(KEY_LEFT, on_left)
|
||||
kb.bind(KEY_UP, on_up)
|
||||
kb.set_passthrough(lambda: isinstance(window.get_focus(), Gtk.Entry))
|
||||
kb.attach(window)
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._bindings: dict[int, Callable] = {}
|
||||
self._passthrough: Callable[[], bool] | None = None
|
||||
self._window = None
|
||||
|
||||
def bind(self, keyval: int, handler: Callable):
|
||||
"""Register a handler for a key. Handler receives shift=bool."""
|
||||
self._bindings[keyval] = handler
|
||||
|
||||
def set_passthrough(self, check: Callable[[], bool]):
|
||||
"""When check() returns True, keys pass through to focused widget."""
|
||||
self._passthrough = check
|
||||
|
||||
def attach(self, window):
|
||||
"""Attach to a GTK4 window."""
|
||||
self._window = window
|
||||
|
||||
# EventControllerKey on capture phase
|
||||
key_ctrl = Gtk.EventControllerKey()
|
||||
key_ctrl.set_propagation_phase(Gtk.PropagationPhase.CAPTURE)
|
||||
key_ctrl.connect("key-pressed", self._on_key_pressed)
|
||||
window.add_controller(key_ctrl)
|
||||
|
||||
# Reclaim focus from non-interactive widgets on click
|
||||
click_ctrl = Gtk.GestureClick()
|
||||
click_ctrl.set_propagation_phase(Gtk.PropagationPhase.CAPTURE)
|
||||
click_ctrl.connect("released", self._on_click)
|
||||
window.add_controller(click_ctrl)
|
||||
|
||||
def _on_click(self, gesture, n_press, x, y):
|
||||
"""After any click, if focus landed on a non-text widget, clear it."""
|
||||
if not self._window:
|
||||
return
|
||||
focus = self._window.get_focus()
|
||||
if focus and not isinstance(focus, (Gtk.Entry, Gtk.TextView)):
|
||||
self._window.set_focus(None)
|
||||
|
||||
def _on_key_pressed(self, controller, keyval, keycode, state):
|
||||
if self._passthrough and self._passthrough():
|
||||
return False
|
||||
|
||||
handler = self._bindings.get(keyval)
|
||||
if handler is None:
|
||||
return False
|
||||
|
||||
shift = bool(state & SHIFT)
|
||||
try:
|
||||
result = handler(shift=shift)
|
||||
return result if result is not None else True
|
||||
except TypeError:
|
||||
result = handler()
|
||||
return result if result is not None else True
|
||||
Reference in New Issue
Block a user