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.

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:

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:

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:

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:

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:

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:

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

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

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:

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:

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:

# 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