Scope Window Factory ==================== Overview -------- The scope window factory provides a pattern-based handler mechanism for creating windows based on ``scope_id`` values. This system replaces monolithic factory classes with a flexible registration pattern. **Module**: ``pyqt_reactive.services.scope_window_factory`` Core Components --------------- ScopeWindowRegistry ~~~~~~~~~~~~~~~~~~ Central registry that maps regex patterns to handler functions. .. code-block:: python from pyqt_reactive.services.scope_window_factory import ScopeWindowRegistry # Register handlers at application startup ScopeWindowRegistry.register_handler( pattern=r"^/path/to/plate$", handler=create_plate_config_window ) Scope ID Pattern Matching ~~~~~~~~~~~~~~~~~~~~~~~~ Window creation is triggered by matching ``scope_id`` strings against registered patterns: - **Global config**: ``""`` (empty string) - **Plate configs**: ``/path/to/plate`` (no ``::`` separator) - **Plate list root**: ``__plates__`` (special root state) - **Step editors**: ``/path/to/plate::step_N`` (``::step_N`` suffix) - **Function scopes**: ``/path/to/plate::step_N::func_M`` (additional ``::func_M``) Registration Order ~~~~~~~~~~~~~~~~~ Patterns are evaluated in registration order. More specific patterns should be registered first: .. code-block:: python def register_window_handlers(): # Order matters - more specific patterns first # Step/function editors (match ::step_N or ::step_N::func_M) ScopeWindowRegistry.register_handler( pattern=r"^.*::step_\d+(::func_\d+)?$", handler=create_step_editor_window ) # Plate configs (match /path - no :: separator) ScopeWindowRegistry.register_handler( pattern=r"^/[^:]*$", handler=create_plate_config_window ) # Plates root list ScopeWindowRegistry.register_handler( pattern=r"^__plates__$", handler=create_plates_root_window ) # Global config (empty string) ScopeWindowRegistry.register_handler( pattern=r"^$", handler=create_global_config_window ) Handler Functions ---------------- Handler functions create and show windows for a given ``scope_id`` and optional ``object_state``. **Signature**: .. code-block:: python def handler(scope_id: str, object_state=None) -> Optional[QWidget]: """Create and show a window for given scope_id. Args: scope_id: The scope identifier (pattern-matched) object_state: Optional ObjectState instance (for time-travel) Returns: QWidget: The created window, or None if no window should be created """ **Example Handler**: .. code-block:: python from pyqt_reactive.services.scope_window_factory import ScopeWindowRegistry def create_global_config_window(scope_id: str, object_state=None): """Create GlobalPipelineConfig editor window.""" from my_windows import ConfigWindow from my_config import GlobalPipelineConfig from my_services import ( get_current_global_config, set_global_config_for_editing, ) current_config = ( get_current_global_config(GlobalPipelineConfig) or GlobalPipelineConfig() ) def handle_save(new_config): set_global_config_for_editing(GlobalPipelineConfig, new_config) window = ConfigWindow( config_class=GlobalPipelineConfig, current_config=current_config, on_save_callback=handle_save, scope_id=scope_id, ) window.show() window.raise_() window.activateWindow() return window Window Creation --------------- Via WindowFactory ~~~~~~~~~~~~~~~~~ Use the generic ``WindowFactory`` class: .. code-block:: python from pyqt_reactive.services import WindowFactory # Create window for a scope window = WindowFactory.create_window_for_scope(scope_id, object_state) Via WindowManager (with Time-Travel) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For windows that should be managed as singletons: .. code-block:: python from my_services import WindowManager def show_config_window(): def factory(): return ConfigWindow(...) window = WindowManager.show_or_focus(scope_id, factory) Time-Travel Integration ----------------------- The scope window factory integrates with time-travel through the ``object_state`` parameter: 1. **Time-Travel Reopens Windows**: When time-traveling to a dirty state: - System calls ``WindowFactory.create_window_for_scope(scope_id, object_state)`` - Handler receives the dirty ``object_state`` for proper reconstruction - Window is shown and focused 2. **ObjectState for Context**: Handlers use ``object_state`` to reconstruct UI state: .. code-block:: python def create_step_editor_window(scope_id: str, object_state=None): """Create step editor with time-travel support.""" # Get step from object_state (if provided) if object_state: step = object_state.object_instance else: # Find step by token (provenance navigation) step = find_step_by_token(plate_path, step_token) window = DualEditorWindow( step_data=step, orchestrator=orchestrator, scope_id=scope_id, ) window.show() return window Handler Implementation Guide -------------------------- Plate Config Handler ~~~~~~~~~~~~~~~~~~~~ .. code-block:: python def create_plate_config_window(scope_id: str, object_state=None): from my_windows import ConfigWindow from my_config import PipelineConfig from pyqt_reactive.services import ServiceRegistry from my_widgets import PlateManagerWidget # Get plate manager from ServiceRegistry plate_manager = ServiceRegistry.get(PlateManagerWidget) if not plate_manager: return None orchestrator = ObjectStateRegistry.get_object(scope_id) if not orchestrator: return None window = ConfigWindow( config_class=PipelineConfig, current_config=orchestrator.pipeline_config, on_save_callback=None, # ObjectState handles save scope_id=scope_id, ) window.show() window.raise_() window.activateWindow() return window Step Editor Handler ~~~~~~~~~~~~~~~~~~~ .. code-block:: python def create_step_editor_window(scope_id: str, object_state=None): from my_windows import DualEditorWindow parts = scope_id.split("::") if len(parts) < 2: return None plate_path = parts[0] step_token = parts[1] is_function_scope = len(parts) >= 3 orchestrator = ObjectStateRegistry.get_object(plate_path) if not orchestrator: return None # Get step from object_state (time-travel) or find by token step = None if object_state: step_state = ObjectStateRegistry.get_by_scope(step_scope_id) step = step_state.object_instance if step_state else None else: step = find_step_by_token(plate_manager, plate_path, step_token) if not step: return None window = DualEditorWindow( step_data=step, is_new=False, on_save_callback=None, orchestrator=orchestrator, parent=None, ) if is_function_scope and window.tab_widget: window.tab_widget.setCurrentIndex(1) window.show() window.raise_() window.activateWindow() return window Special Case: No Window ~~~~~~~~~~~~~~~~~~~~~~~~ Some scopes (like ``__plates__``) represent state without a window: .. code-block:: python def create_plates_root_window(scope_id: str, object_state=None): """Root plate list state - no window to create.""" logger.debug(f"[WINDOW_FACTORY] Skipping window creation for __plates__ scope") return None Best Practices -------------- Pattern Specificity ~~~~~~~~~~~~~~~~~~ - **Specific first**: Register patterns with ``::`` separators before generic paths - **Global last**: Register ``^$`` (empty string) handler last - **Test order**: Verify patterns match expected scopes in correct order Error Handling ~~~~~~~~~~~~~~ - Return ``None`` when window cannot be created - Log warnings for missing dependencies - Validate scope_id format before processing Window Display ~~~~~~~~~~~~~~ Always call these three methods after creating a window: .. code-block:: python window.show() window.raise_() window.activateWindow() Scope ID Design ~~~~~~~~~~~~~~~~ - Use filesystem-like paths: ``/path/to/plate`` - Use ``::`` for hierarchy: ``plate::step_N::func_M`` - Keep identifiers stable (don't change scope IDs for existing data) Integration Points ------------------ WindowManager Integration ~~~~~~~~~~~~~~~~~~~~~~~~ ``WindowManager`` calls ``WindowFactory.create_window_for_scope()`` internally: .. code-block:: python # WindowManager uses scope window factory for time-travel window = WindowFactory.create_window_for_scope(scope_id, state) Provenance Navigation ~~~~~~~~~~~~~~~~~~~~~~ Scope IDs appear in provenance tracking. Clicking a provenance entry: 1. Calls ``WindowManager.focus_and_navigate(scope_id)`` 2. If window doesn't exist, calls ``WindowFactory.create_window_for_scope()`` 3. Handler creates window and navigates to field See Also -------- - :doc:`service_registry` - Service management and widget resolution - :doc:`ui_services_architecture` - Service layer architecture - :doc:`flash_animation_system` - Visual feedback system