Source code for wolfhece.plugins.types

"""Declarative menu and action types for companion plugins."""
from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum, IntEnum
from typing import TYPE_CHECKING, Callable, Optional, Union

from .._action_kind import ActionKind

if TYPE_CHECKING:
    from .._viewer_plugin_handlers import MouseContext


[docs] class Keys(IntEnum): """Complete set of key codes, mirroring ``wx.WXK_*`` values. Use these constants in key handlers instead of raw integers to avoid any dependency on wx being imported at module level:: def _key(self, kb: KeyboardSnapshot) -> bool | StepTransition: if kb.key_code == Keys.ESCAPE: return StepTransition.CANCEL if kb.key_code == Keys.CONTROL_Z: self._undo() return False Aliases (same integer value as another member) are listed with a comment. """ # --- Control characters (Ctrl+letter, values 0–27) ---
[docs] NONE = 0
[docs] CONTROL_A = 1
[docs] CONTROL_B = 2
[docs] CONTROL_C = 3
[docs] CONTROL_D = 4
[docs] CONTROL_E = 5
[docs] CONTROL_F = 6
[docs] CONTROL_G = 7
[docs] BACK = 8 # Backspace key (WXK_BACK); CONTROL_H shares this value
[docs] BACKSPACE = 8 # alias for BACK
[docs] TAB = 9 # also CONTROL_I
[docs] CONTROL_J = 10
[docs] CONTROL_K = 11
[docs] CONTROL_L = 12
[docs] RETURN = 13 # also CONTROL_M
[docs] CONTROL_N = 14
[docs] CONTROL_O = 15
[docs] CONTROL_P = 16
[docs] CONTROL_Q = 17
[docs] CONTROL_R = 18
[docs] CONTROL_S = 19
[docs] CONTROL_T = 20
[docs] CONTROL_U = 21
[docs] CONTROL_V = 22
[docs] CONTROL_W = 23
[docs] CONTROL_X = 24
[docs] CONTROL_Y = 25
[docs] CONTROL_Z = 26
[docs] ESCAPE = 27
[docs] SPACE = 32
[docs] DELETE = 127
# --- Special / system keys ---
[docs] START = 300
[docs] LBUTTON = 301
[docs] RBUTTON = 302
[docs] CANCEL = 303
[docs] MBUTTON = 304
[docs] CLEAR = 305
[docs] SHIFT = 306
[docs] ALT = 307
[docs] CONTROL = 308 # also COMMAND, RAW_CONTROL
[docs] MENU = 309
[docs] PAUSE = 310
[docs] CAPITAL = 311 # Caps Lock
# --- Navigation ---
[docs] END = 312
[docs] HOME = 313
[docs] LEFT = 314
[docs] UP = 315
[docs] RIGHT = 316
[docs] DOWN = 317
[docs] SELECT = 318
[docs] PRINT = 319
[docs] EXECUTE = 320
[docs] SNAPSHOT = 321
[docs] INSERT = 322
[docs] HELP = 323
# --- Numpad digits ---
[docs] NUMPAD0 = 324
[docs] NUMPAD1 = 325
[docs] NUMPAD2 = 326
[docs] NUMPAD3 = 327
[docs] NUMPAD4 = 328
[docs] NUMPAD5 = 329
[docs] NUMPAD6 = 330
[docs] NUMPAD7 = 331
[docs] NUMPAD8 = 332
[docs] NUMPAD9 = 333
# --- Arithmetic operators (main keyboard) ---
[docs] MULTIPLY = 334
[docs] ADD = 335
[docs] SEPARATOR = 336
[docs] SUBTRACT = 337
[docs] DECIMAL = 338
[docs] DIVIDE = 339
# --- Function keys ---
[docs] F1 = 340
[docs] F2 = 341
[docs] F3 = 342
[docs] F4 = 343
[docs] F5 = 344
[docs] F6 = 345
[docs] F7 = 346
[docs] F8 = 347
[docs] F9 = 348
[docs] F10 = 349
[docs] F11 = 350
[docs] F12 = 351
[docs] F13 = 352
[docs] F14 = 353
[docs] F15 = 354
[docs] F16 = 355
[docs] F17 = 356
[docs] F18 = 357
[docs] F19 = 358
[docs] F20 = 359
[docs] F21 = 360
[docs] F22 = 361
[docs] F23 = 362
[docs] F24 = 363
# --- Lock keys ---
[docs] NUMLOCK = 364
[docs] SCROLL = 365
# --- Page navigation ---
[docs] PAGEUP = 366
[docs] PAGEDOWN = 367
# --- Extended numpad ---
[docs] NUMPAD_SPACE = 368
[docs] NUMPAD_TAB = 369
[docs] NUMPAD_ENTER = 370
[docs] NUMPAD_F1 = 371
[docs] NUMPAD_F2 = 372
[docs] NUMPAD_F3 = 373
[docs] NUMPAD_F4 = 374
[docs] NUMPAD_HOME = 375
[docs] NUMPAD_LEFT = 376
[docs] NUMPAD_UP = 377
[docs] NUMPAD_RIGHT = 378
[docs] NUMPAD_DOWN = 379
[docs] NUMPAD_PAGEUP = 380
[docs] NUMPAD_PAGEDOWN = 381
[docs] NUMPAD_END = 382
[docs] NUMPAD_BEGIN = 383
[docs] NUMPAD_INSERT = 384
[docs] NUMPAD_DELETE = 385
[docs] NUMPAD_EQUAL = 386
[docs] NUMPAD_MULTIPLY = 387
[docs] NUMPAD_ADD = 388
[docs] NUMPAD_SEPARATOR = 389
[docs] NUMPAD_SUBTRACT = 390
[docs] NUMPAD_DECIMAL = 391
[docs] NUMPAD_DIVIDE = 392
# --- Windows keys ---
[docs] WINDOWS_LEFT = 393
[docs] WINDOWS_RIGHT = 394
[docs] WINDOWS_MENU = 395
# --- OEM / special purpose ---
[docs] SPECIAL1 = 397
[docs] SPECIAL2 = 398
[docs] SPECIAL3 = 399
[docs] SPECIAL4 = 400
[docs] SPECIAL5 = 401
[docs] SPECIAL6 = 402
[docs] SPECIAL7 = 403
[docs] SPECIAL8 = 404
[docs] SPECIAL9 = 405
[docs] SPECIAL10 = 406
[docs] SPECIAL11 = 407
[docs] SPECIAL12 = 408
[docs] SPECIAL13 = 409
[docs] SPECIAL14 = 410
[docs] SPECIAL15 = 411
[docs] SPECIAL16 = 412
[docs] SPECIAL17 = 413
[docs] SPECIAL18 = 414
[docs] SPECIAL19 = 415
[docs] SPECIAL20 = 416
# --- Browser keys ---
[docs] BROWSER_BACK = 417
[docs] BROWSER_FORWARD = 418
[docs] BROWSER_REFRESH = 419
[docs] BROWSER_STOP = 420
[docs] BROWSER_FAVORITES = 422
[docs] BROWSER_HOME = 423
# --- Volume / media ---
[docs] VOLUME_MUTE = 424
[docs] VOLUME_DOWN = 425
[docs] VOLUME_UP = 426
[docs] MEDIA_NEXT_TRACK = 427
[docs] MEDIA_PREV_TRACK = 428
[docs] MEDIA_STOP = 429
[docs] MEDIA_PLAY_PAUSE = 430
# --- Launch keys ---
[docs] LAUNCH_MAIL = 431
[docs] LAUNCH_0 = 432
[docs] LAUNCH_1 = 433
[docs] LAUNCH_2 = 434
[docs] LAUNCH_3 = 435
[docs] LAUNCH_4 = 436
[docs] LAUNCH_5 = 437
[docs] LAUNCH_6 = 438
[docs] LAUNCH_7 = 439
[docs] LAUNCH_8 = 440
[docs] LAUNCH_9 = 441
[docs] LAUNCH_A = 442 # also LAUNCH_APP1
[docs] LAUNCH_B = 443 # also LAUNCH_APP2
[docs] LAUNCH_C = 444
[docs] LAUNCH_D = 445
[docs] LAUNCH_E = 446
[docs] LAUNCH_F = 447
# --- Letter keys (ASCII ordinal of the uppercase letter) --- # wx key events report letters as their uppercase ASCII value (65–90) # regardless of Shift state; use kb.shift to distinguish case.
[docs] A = 65
[docs] B = 66
[docs] C = 67
[docs] D = 68
[docs] E = 69
[docs] F = 70
[docs] G = 71
[docs] H = 72
[docs] I = 73
[docs] J = 74
[docs] K = 75
[docs] L = 76
[docs] M = 77
[docs] N = 78
[docs] O = 79
[docs] P = 80
[docs] Q = 81
[docs] R = 82
[docs] S = 83
[docs] T = 84
[docs] U = 85
[docs] V = 86
[docs] W = 87
[docs] X = 88
[docs] Y = 89
[docs] Z = 90
# --- Digit keys (ASCII ordinal) ---
[docs] KEY_0 = 48
[docs] KEY_1 = 49
[docs] KEY_2 = 50
[docs] KEY_3 = 51
[docs] KEY_4 = 52
[docs] KEY_5 = 53
[docs] KEY_6 = 54
[docs] KEY_7 = 55
[docs] KEY_8 = 56
[docs] KEY_9 = 57
@dataclass
[docs] class Separator: """Singleton sentinel that inserts a separator line in menus."""
[docs] _instance: 'Separator | None' = None
def __new__(cls) -> 'Separator': if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance def __repr__(self) -> str: return 'SEPARATOR'
[docs] SEPARATOR: Separator = Separator()
@dataclass @dataclass
[docs] class ActionSpec: """Declarative interactive-action entry."""
[docs] action_id: str | ActionKind
[docs] rdown: Optional[Callable] = None
[docs] motion: Optional[Callable] = None
[docs] ldown: Optional[Callable] = None
[docs] key: Optional[Callable] = None
[docs] paint: Optional[Callable] = None
[docs] overload: bool = False
[docs] primary: bool = False
[docs] start_message: str = ''
[docs] class StepTransition(str, Enum): """Transition directives returned by multi-step handlers."""
[docs] NONE = 'none'
[docs] NEXT = 'next'
[docs] FINISH = 'finish'
[docs] CANCEL = 'cancel'
@dataclass
[docs] class StepSpec: """Single step declaration used by :class:`MultiStepSpec`."""
[docs] hint: str = ''
[docs] rdown: Optional[Callable] = None
[docs] motion: Optional[Callable] = None
[docs] ldown: Optional[Callable] = None
[docs] key: Optional[Callable] = None
[docs] paint: Optional[Callable] = None
@dataclass
[docs] class MultiStepSpec: """Declarative multi-step interactive action. Phase 1 behaviour: - startup selection is declarative (``primary`` + ``start_message``), - action registration uses handlers from the first step. """
[docs] action_id: str | ActionKind
[docs] steps: list[StepSpec]
[docs] overload: bool = False
[docs] primary: bool = False
[docs] start_message: str = ''
[docs] finish_message: str = ''
def __post_init__(self) -> None: if not self.steps: raise ValueError('MultiStepSpec.steps must contain at least one StepSpec') @property
[docs] def first_step(self) -> StepSpec: return self.steps[0]
@property
[docs] def effective_start_message(self) -> str: return self.start_message or self.first_step.hint