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_Nsuffix)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:
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 dirtyobject_statefor proper reconstruction - Window is shown and focusedObjectState for Context: Handlers use
object_stateto 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 pathsGlobal last: Register
^$(empty string) handler lastTest order: Verify patterns match expected scopes in correct order
Error Handling
Return
Nonewhen window cannot be createdLog 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/plateUse
::for hierarchy:plate::step_N::func_MKeep 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)
See Also
Service Registry - Service management and widget resolution
UI Services Architecture - Service layer architecture
Flash Animation System - Visual feedback system