"""
Consolidated Signal Service.
Merges:
- SignalBlockingService: Context managers for widget signal blocking
- SignalConnectionService: Signal wiring for ParameterFormManager
- CrossWindowRegistration: Context manager for cross-window registration
Key features:
1. Context manager guarantees signal unblocking
2. Supports single or multiple widgets
3. Consolidates all signal wiring logic
4. Cross-window registration with RAII cleanup
"""
from contextlib import contextmanager
from typing import Any, Callable, Optional, TYPE_CHECKING
from PyQt6.QtWidgets import QWidget, QCheckBox, QLineEdit, QComboBox, QSpinBox, QDoubleSpinBox
import logging
if TYPE_CHECKING:
from pyqt_reactive.forms.parameter_form_manager import ParameterFormManager
logger = logging.getLogger(__name__)
[docs]
class SignalService:
"""
Consolidated service for signal blocking, connection, and cross-window registration.
Examples:
# Block signals (context manager):
with SignalService.block_signals(checkbox):
checkbox.setChecked(True)
# Multiple widgets:
with SignalService.block_signals(widget1, widget2):
widget1.setValue(1)
widget2.setValue(2)
# Connect all signals for a manager:
SignalService.connect_all_signals(manager)
# Cross-window registration:
with SignalService.cross_window_registration(manager):
dialog.exec()
"""
# ========== SIGNAL BLOCKING (from SignalBlockingService) ==========
[docs]
@staticmethod
@contextmanager
def block_signals(*widgets: QWidget):
"""Context manager for blocking widget signals."""
for widget in widgets:
if widget is not None:
widget.blockSignals(True)
logger.debug(f"Blocked signals on {type(widget).__name__}")
try:
yield
finally:
for widget in widgets:
if widget is not None:
widget.blockSignals(False)
logger.debug(f"Unblocked signals on {type(widget).__name__}")
[docs]
@staticmethod
def with_signals_blocked(widget: QWidget, operation: Callable) -> None:
"""Execute operation with widget signals blocked (lambda-based)."""
with SignalService.block_signals(widget):
operation()
[docs]
@staticmethod
@contextmanager
def block_signals_if(condition: bool, *widgets: QWidget):
"""Conditionally block signals based on a condition."""
if condition:
with SignalService.block_signals(*widgets):
yield
else:
yield
# ========== SIGNAL CONNECTION (from SignalConnectionService) ==========
[docs]
@staticmethod
def connect_all_signals(manager: Any) -> None:
"""Wire all signals for the manager.
DISPATCHER ARCHITECTURE: Most signal handling moved to FieldChangeDispatcher.
This method now only handles:
- Cleanup on destroy
"""
manager.destroyed.connect(manager.unregister_from_cross_window_updates)
[docs]
@staticmethod
def register_cross_window_signals(manager: Any) -> None:
"""Register manager for cross-window updates (only root managers).
SIMPLIFIED: No N×N signal wiring. LiveContextService.increment_token()
notifies all listeners via simple callbacks.
"""
if manager._parent_manager is not None:
return
# Snapshot initial values for change detection (non-None only)
manager._initial_values_on_open = {k: v for k, v in manager.state.parameters.items() if v is not None}
from objectstate import ObjectStateRegistry
logger.debug(f"🔍 REGISTER: {manager.field_id} (total: {len(ObjectStateRegistry.get_all())})")
# ========== CROSS-WINDOW REGISTRATION (from CrossWindowRegistration) ==========
[docs]
@staticmethod
@contextmanager
def cross_window_registration(manager: 'ParameterFormManager'):
"""
Context manager for cross-window registration.
Ensures proper registration and cleanup of form managers for cross-window updates.
"""
if manager._parent_manager is not None:
yield manager
return
try:
SignalService.register_cross_window_signals(manager)
yield manager
finally:
manager.unregister_from_cross_window_updates()