ADR-009: Slicer Action Mapping

Status

Accepted

Context

MouseMaster needs to map button presses to Slicer actions. Actions can include:

  • Built-in Slicer commands (undo, redo, save)

  • Module-specific actions (Segment Editor effects)

  • View manipulation (zoom, pan, rotate)

  • Custom Python commands

Need to decide:

  • How to discover available actions

  • How to represent actions in presets

  • How to execute actions when buttons are pressed

Decision

Implement an ActionRegistry pattern with categorized actions.

ActionRegistry Singleton

class ActionRegistry:
    _instance = None

    @classmethod
    def get_instance(cls) -> "ActionRegistry":
        if cls._instance is None:
            cls._instance = ActionRegistry()
            cls._instance._register_builtin_actions()
        return cls._instance

    def register(
        self,
        action_id: str,
        handler: ActionHandler,
        category: str,
        description: str
    ) -> None:
        self._actions[action_id] = ActionEntry(
            id=action_id,
            handler=handler,
            category=category,
            description=description
        )

    def execute(self, action_id: str, context: ActionContext) -> bool:
        if action_id not in self._actions:
            return False
        return self._actions[action_id].handler.execute(context)

Action Handler Protocol

class ActionHandler(Protocol):
    def execute(self, context: ActionContext) -> bool:
        """Execute the action. Return True if successful."""
        ...

    def is_available(self, context: ActionContext) -> bool:
        """Check if action is currently available."""
        ...

Built-in Action Categories

Navigation:

  • view_reset_3d: Reset 3D view

  • view_zoom_in, view_zoom_out: Zoom controls

  • view_center_crosshair: Center on crosshair

Editing:

  • edit_undo, edit_redo: Undo/redo

  • edit_delete: Delete selected

Segment Editor:

  • segment_editor_paint, segment_editor_erase: Toggle effects

  • segment_next, segment_previous: Navigate segments

  • segment_toggle_visibility: Show/hide current segment

Custom:

  • python_command: Execute arbitrary Python

  • menu_action: Trigger menu action by name

  • keyboard_shortcut: Simulate key press

Preset Action Reference

{
  "mappings": {
    "back": {
      "action": "edit_undo"
    },
    "forward": {
      "action": "python_command",
      "command": "slicer.util.selectModule('SegmentEditor')"
    },
    "thumb": {
      "action": "menu_action",
      "menuPath": "Edit/Undo"
    }
  }
}

Consequences

Positive

  • Registry pattern allows extension by other modules

  • Categorized actions help UI organization

  • ActionHandler protocol enables custom implementations

  • is_available allows disabling unavailable actions

  • Presets reference actions by stable ID

Negative

  • Discovering all Slicer actions is complex

  • Some actions are module-specific and may not be available

  • Custom Python commands are a security consideration

Neutral

  • Actions can be registered dynamically

  • Need to handle action not found gracefully

References