Source code for pyqt_reactive.widgets.shared.checkbox_group_widget

"""
CheckboxGroupWidget for List[Enum] parameters.

Provides explicit type-based dispatch instead of duck typing with monkey-patched methods.
"""

from typing import List, Optional, Type
from enum import Enum

from PyQt6.QtWidgets import QVBoxLayout
from PyQt6.QtCore import pyqtSignal

from pyqt_reactive.widgets import NoneAwareCheckBox
from pyqt_reactive.protocols.widget_adapters import CheckboxGroupAdapter


[docs] class CheckboxGroupWidget(CheckboxGroupAdapter): """ Multi-selection checkbox group for List[Enum] parameters. Uses NoneAwareCheckBox pattern consistently with bool parameters: - Initialize all checkboxes with set_value(None) for placeholder state - Use set_value() instead of setChecked() to properly track placeholder state - Use get_value() in get_selected_values() to distinguish placeholder vs concrete """ # Signal emitted when selection changes selection_changed = pyqtSignal()
[docs] def __init__(self, param_name: str, enum_type: Type[Enum], current_value: Optional[List[Enum]] = None, parent=None): """ Initialize checkbox group widget. Args: param_name: Parameter name for display enum_type: Enum type for checkbox options current_value: Initial selected values (None for placeholder state) parent: Parent widget """ super().__init__(param_name.replace('_', ' ').title(), parent) self._checkboxes = {} self._enum_type = enum_type layout = QVBoxLayout(self) layout.setContentsMargins(8, 8, 8, 8) layout.setSpacing(4) # Create checkbox for each enum value for enum_val in enum_type: checkbox = NoneAwareCheckBox() checkbox.setText(enum_val.name.replace('_', ' ').title()) # Initialize with None (placeholder state) or concrete value if current_value is None: checkbox.set_value(None) else: checkbox.set_value(enum_val in current_value) # Connect signal to emit selection_changed checkbox.stateChanged.connect(self._on_checkbox_changed) layout.addWidget(checkbox) self._checkboxes[enum_val] = checkbox
def _on_checkbox_changed(self): """Handle checkbox state change - convert all checkboxes from placeholder to concrete.""" # When ANY checkbox is clicked, convert ALL checkboxes from placeholder to concrete # This ensures consistent behavior: either all inherit (None) or all are explicit for checkbox in self.checkbox_widgets(): checkbox.convert_placeholder_to_concrete() self.selection_changed.emit()
[docs] def get_selected_values(self) -> Optional[List[Enum]]: """ Get selected enum values, returning None if all checkboxes are in placeholder state. Treats List[Enum] like a list of independent bools: - If ALL checkboxes are in placeholder state → return None (inherit from parent) - If ANY checkbox has been clicked → ALL become concrete, return list of checked items Note: The signal handler ensures that clicking ANY checkbox converts ALL to concrete, so we should never have a mixed state (some placeholder, some concrete). """ # Check if any checkbox has a concrete value (not placeholder) has_concrete_value = any( checkbox.get_value() is not None for checkbox in self._checkboxes.values() ) if not has_concrete_value: # All checkboxes are in placeholder state - return None to inherit from parent return None # All checkboxes are concrete (signal handler converted them) # Return list of enum values where checkbox is checked return [ enum_val for enum_val, checkbox in self._checkboxes.items() if checkbox.get_value() == True ]
[docs] def set_selected_values(self, values: Optional[List[Enum]]) -> None: """ Set selected values. Args: values: List of enum values to select, or None for placeholder state """ if values is None: # Set all checkboxes to placeholder state for checkbox in self._checkboxes.values(): checkbox.set_value(None) else: # Set concrete values for enum_val, checkbox in self._checkboxes.items(): checkbox.set_value(enum_val in values)