8.5.8.1.1. pymodaq_gui.utils.widget_sync.core module
Core widget synchronization classes and enums.
Implementation for syncing widget properties across multiple widgets.
- class pymodaq_gui.utils.widget_sync.core.BaseWidgetSync[source]
Bases:
QObjectBase class for widget synchronization.
Provides common functionality for connection management, callbacks, feedback loop prevention, and widget lifecycle management.
Subclasses must implement: set_value(), value property getter/setter
- Attributes:
connected_widgetsGet list of currently connected widgets.
connection_countGet count of active connections.
data_typeGet the data type for this sync.
valueGet current value - must be implemented by subclasses.
Methods
bind(widget[, signal, getter, setter, mode, ...])Bind a widget to this sync.
disable(widget)Disable a widget's connections temporarily.
enable(widget)Enable a widget's connections.
is_enabled(widget)Check if a widget's connections are enabled.
set_value(new_value[, emit])Set value - must be implemented by subclasses.
unbind(widget)Unbind a widget by reference or ID.
Unbind all connected widgets (both regular and property connections).
check_connection_mode
validate_property_connection
value_changed
- bind(widget, signal=None, getter=None, setter=None, mode=None, to_sync_transform=None, from_sync_transform=None, init_from='sync')[source]
Bind a widget to this sync.
For ValueSync: Binds the widget to the single value. For DictSync: Binds the widget to the entire dict value.
IMPORTANT: When binding with FROM_SYNC or BIDIRECTIONAL mode, the widget is immediately initialized with the current sync value. This is different from enable(), which does NOT update the widget value. One can disable this behavior by using the init_from parameter.
- Parameters:
widget (
QWidget) – The widget to connectsignal (
pyqtSignal|None) – The signal to listen to (required for TO_SYNC/BIDIRECTIONAL modes)getter (
Optional[Callable[[],Any]]) – Function to get value from widget: () -> value Required for TO_SYNC and BIDIRECTIONAL modessetter (
Optional[Callable[[Any],None]]) – Function to set value on widget: (value) -> None Required for FROM_SYNC and BIDIRECTIONAL modesmode (
SyncMode|None) – Synchronization mode. If None, auto-inferred from parametersto_sync_transform (
Optional[Callable[[Any],Any]]) – Transform value from widget to sync: (widget_value) -> sync_valuefrom_sync_transform (
Optional[Callable[[Any],Any]]) – Transform value from sync to widget: (sync_value) -> widget_valueinit_from (
Literal['sync','widget',None]) – Initialization source: ‘sync’ (default - widget gets sync’s value), ‘widget’ (sync gets widget’s value), or None (no initialization). When ‘widget’ is used with BIDIRECTIONAL mode, all other connected widgets also receive the widget’s value.
- Raises:
ValueError – If neither getter nor setter provided, or if mode requirements not met
- Return type:
Example
>>> sync = WidgetSync(initial_value=100) >>> sync.bind(slider, signal=slider.valueChanged, ... getter=slider.value, setter=slider.setValue) >>> slider.value() # Returns 100 - widget was initialized!
- disable(widget)[source]
Disable a widget’s connections temporarily.
When disabled, the widget will not receive updates from the sync, and its changes will not affect other widgets. The connections remain in place and can be re-enabled later.
This disables ALL connections for the widget (both regular and property connections).
This is useful for: - Temporarily pausing sync during complex operations - Conditional syncing based on application state - Testing/debugging
- Parameters:
- Raises:
ValueError – If widget is not connected
- Return type:
Example
>>> sync = WidgetSync.for_slider(slider1) >>> sync.add(slider2) >>> sync.disable(slider2) # slider2 stops syncing >>> slider1.setValue(75) # slider2 doesn't update >>> sync.enable(slider2) # Re-enable slider2 >>> slider2.value() # Still has old value until next update
- enable(widget)[source]
Enable a widget’s connections.
When enabled, the widget will sync bidirectionally with other widgets. This enables ALL connections for the widget (both regular and property connections).
- Parameters:
- Raises:
ValueError – If widget is not connected
- Return type:
- is_enabled(widget)[source]
Check if a widget’s connections are enabled.
Returns True only if ALL connections for the widget are enabled.
- Parameters:
- Returns:
True if all connections are enabled, False otherwise
- Return type:
- Raises:
ValueError – If widget is not connected
- set_value(new_value, emit=True)[source]
Set value - must be implemented by subclasses.
- Return type:
- unbind(widget)[source]
Unbind a widget by reference or ID.
Removes ALL connections for the widget (both regular and property connections).
- unbind_all()[source]
Unbind all connected widgets (both regular and property connections).
- Return type:
- validate_property_connection(mode, setter, getter, signal=None, property_key=None)[source]
- Return type:
- property connected_widgets: list[QWidget]
Get list of currently connected widgets.
Returns only widgets that haven’t been deleted. Includes both regular connections and property connections.
- Returns:
List of active widget references (unique widgets)
- Return type:
list[QWidget]
Example
>>> sync = WidgetSync.for_checkbox(cb1) >>> sync.add(cb2) >>> sync.add(cb3) >>> len(sync.connected_widgets) # Returns 3 3
- property connection_count: int
Get count of active connections.
Includes both regular connections and property connections.
- Returns:
Number of currently connected widgets/properties
- Return type:
Example
>>> sync = WidgetSync.for_spinbox(spin1) >>> sync.add(spin2) >>> sync.connection_count # Returns 2 2
- class pymodaq_gui.utils.widget_sync.core.DataType(*values)[source]
Bases:
EnumSupported data types for widget synchronization.
Each sync instance is associated with a specific data type, ensuring type safety when connecting widgets and transforming values.
- BOOL = <class 'bool'>
- INT = <class 'int'>
- STR = <class 'str'>
- class pymodaq_gui.utils.widget_sync.core.DictSync(initial_value=None, validator=None)[source]
Bases:
BaseWidgetSyncSynchronize dict values with multiple properties or widgets.
DictSync provides three binding methods:
bind() - Bind widgets that work with the entire dict (e.g., JSON editor, config display)
bind_properties() - Bind multiple properties of ONE widget to dict keys (e.g., ComboBox with both ‘items’ and ‘selection’ properties)
bind_dict() - Bind DIFFERENT widgets to different dict keys (e.g., separate R/G/B sliders for a color dict)
Examples
>>> # Bind multiple properties of one widget >>> sync = DictSync({'items': ['A', 'B'], 'current': 'A'}) >>> sync.bind_properties(combobox, property_map={ ... 'items': {'setter': lambda v: (combo.clear(), combo.addItems(v))}, ... 'current': {'property': 'currentText'} ... }) >>> >>> # Bind different widgets to dict keys >>> color_sync = DictSync({'r': 128, 'g': 64, 'b': 192}) >>> color_sync.bind_dict(property_map={ ... 'r': {'widget': r_slider, 'property': 'value'}, ... 'g': {'widget': g_slider, 'property': 'value'}, ... 'b': {'widget': b_slider, 'property': 'value'} ... })
- Attributes:
valueGet current synced dict value.
Methods
append_to_list(key, item[, emit])Append an item to a list value in the dict.
bind_dict(property_map[, init_from])Bind different widgets to different dict keys.
bind_parameter(parameter, property_map[, ...])Bind multiple properties of a Parameter to different dict keys.
bind_properties(widget, property_map[, ...])Bind multiple properties of ONE widget to different dict keys.
pop_from_list(key[, index, emit])Pop an item from a list value in the dict.
remove_from_list(key, item[, emit])Remove an item from a list value in the dict.
set_value(new_value[, emit])Set dict value with optional emission control.
update_key(key, value[, emit])Update a single key in the dict value.
- append_to_list(key, item, emit=True)[source]
Append an item to a list value in the dict.
- Parameters:
- Raises:
- Return type:
Example
>>> sync = DictSync({'items': ['a', 'b'], 'current': 'a'}) >>> sync.append_to_list('items', 'c') >>> sync.value['items'] # Returns ['a', 'b', 'c']
- bind_dict(property_map, init_from='sync')[source]
Bind different widgets to different dict keys.
Each property in the dict value is controlled by its own widget.
Key Difference from bind_properties(): - bind_properties(): Multiple properties of ONE widget → dict keys - bind_dict(): Multiple different widgets → dict keys (one widget per key)
- Parameters:
property_map (
dict[str,dict[str,Any]]) –Mapping of dict keys to widget configurations. Each key maps to a dict that MUST include:
Required: - ‘widget’: QWidget - The widget for this property
Simple syntax (recommended for Qt properties): - ‘property’: str - Qt property name (auto-generates getter/setter) - ‘signal’: Signal | str (optional, auto-detected if omitted) - ‘mode’: SyncMode (optional, default: BIDIRECTIONAL) - ‘init_from’: InitFrom (optional, overrides global init_from for this property)
Advanced syntax (for custom logic): - ‘signal’: Signal | None (required for TO_SYNC/BIDIRECTIONAL) - ‘getter’: callable () -> value (required for TO_SYNC/BIDIRECTIONAL) - ‘setter’: callable (value) -> None (required for FROM_SYNC/BIDIRECTIONAL) - ‘mode’: SyncMode (optional, default: inferred from getter/setter) - ‘init_from’: InitFrom (optional, overrides global init_from for this property)
init_from (
Literal['sync','widget',None]) – Global default for initialization source. Can be overridden per-property by including ‘init_from’ in the property config dict.
- Raises:
TypeError – If sync value is not a dict
ValueError – If property configuration is invalid or missing ‘widget’ key
- Return type:
- bind_parameter(parameter, property_map, init_from='sync')[source]
Bind multiple properties of a Parameter to different dict keys.
IMPORTANT: Use this method instead of bind_properties() for pyqtgraph Parameters. bind_properties() uses blockSignals() which prevents parameter tree widgets from updating. This method uses callback disconnection instead.
- Parameters:
parameter (
Parameter) – The pyqtgraph Parameter to bindproperty_map (
dict[str,dict[str,Any]]) –Mapping of dict keys to parameter property configurations. Each key maps to a dict with:
Shortcut for parameter value (most common case): - ‘param’: Parameter - Automatically uses sigValueChanged, value(), setValue()
This is equivalent to specifying signal/getter/setter manually
OR Manual specification: - ‘getter’: callable () -> value - Function to get parameter value - ‘setter’: callable (value) -> None - Function to set parameter value - ‘signal’: Signal (optional) - Parameter signal for bidirectional sync
(Note: Parameter signals emit (param, value), this is handled automatically)
’mode’: SyncMode (optional) - BIDIRECTIONAL, TO_SYNC, or FROM_SYNC (default: BIDIRECTIONAL if signal provided, else FROM_SYNC)
init_from (
Literal['sync','widget',None]) – Initialization source: ‘sync’ (default), ‘widget’, or ‘none’
- Raises:
TypeError – If sync value is not a dict
ValueError – If property configuration is invalid
- Return type:
Examples
>>> # Shortcut syntax (recommended for simple value sync) >>> sync = WidgetSync(initial_value={'threshold': 0.5}) >>> sync.bind_parameter( ... threshold_param, ... property_map={'threshold': {'param': threshold_param}} ... )
>>> # Manual syntax (for custom getter/setter or limits) >>> algo_sync = WidgetSync(initial_value={'algorithms': [...], 'algorithm': 'FFT'}) >>> algo_sync.bind_parameter( ... algorithm_param, ... property_map={ ... 'algorithms': { ... 'getter': lambda: algorithm_param.opts['limits'], ... 'setter': algorithm_param.setLimits, ... 'mode': SyncMode.FROM_SYNC, ... }, ... 'algorithm': {'param': algorithm_param} # Shortcut for value ... } ... )
See also
bind_propertiesFor regular Qt widgets (uses blockSignals)
bind_dictFor binding different widgets to different dict keys
- bind_properties(widget, property_map, init_from='sync')[source]
Bind multiple properties of ONE widget to different dict keys.
IMPORTANT: This method is designed for synchronizing multiple properties of a SINGLE widget. All properties control the same widget passed as the first parameter.
- Parameters:
widget (
QWidget) – The widget to bind (all properties control THIS widget)property_map (
dict[str,dict[str,Any]]) –Mapping of dict keys to property configurations. Each key maps to a dict with EITHER:
Simple syntax (recommended for Qt properties): - ‘property’: str - Qt property name (auto-generates getter/setter) - ‘signal’: Signal | str (optional, auto-detected if omitted) - ‘mode’: SyncMode (optional, default: BIDIRECTIONAL) - ‘init_from’: InitFrom (optional, overrides global init_from for this property)
Advanced syntax (for custom logic): - ‘signal’: Signal | None (required for TO_SYNC/BIDIRECTIONAL) - ‘getter’: callable () -> value (required for TO_SYNC/BIDIRECTIONAL) - ‘setter’: callable (value) -> None (required for FROM_SYNC/BIDIRECTIONAL) - ‘mode’: SyncMode (optional, default: inferred from getter/setter) - ‘init_from’: InitFrom (optional, overrides global init_from for this property)
init_from (
Literal['sync','widget',None]) – Global default for initialization source. Can be overridden per-property by including ‘init_from’ in the property config dict.
- Raises:
TypeError – If sync value is not a dict
ValueError – If property configuration is invalid
- Return type:
- pop_from_list(key, index=-1, emit=True)[source]
Pop an item from a list value in the dict.
- Parameters:
- Returns:
The popped item
- Return type:
- Raises:
KeyError – If key doesn’t exist in dict
TypeError – If value at key is not a list
IndexError – If index out of range
Example
>>> sync = DictSync({'items': ['a', 'b', 'c'], 'current': 'a'}) >>> removed = sync.pop_from_list('items', 1) # Returns 'b' >>> sync.value['items'] # Returns ['a', 'c']
- remove_from_list(key, item, emit=True)[source]
Remove an item from a list value in the dict.
- Parameters:
- Raises:
KeyError – If key doesn’t exist in dict
TypeError – If value at key is not a list
ValueError – If item not in list
- Return type:
Example
>>> sync = DictSync({'items': ['a', 'b', 'c'], 'current': 'a'}) >>> sync.remove_from_list('items', 'b') >>> sync.value['items'] # Returns ['a', 'c']
- update_key(key, value, emit=True)[source]
Update a single key in the dict value.
Convenience method that handles copying internally.
- Parameters:
- Return type:
Example
>>> sync = DictSync({'items': ['a', 'b'], 'current': 'a'}) >>> sync.update_key('current', 'b')
- property value: dict
Get current synced dict value.
Returns a shallow copy of the internal dict. Modifying dict keys will not affect the sync, but modifying nested mutable objects (lists, dicts) will affect internal state and should be avoided.
For a fully independent copy, use copy.deepcopy(sync.value).
- class pymodaq_gui.utils.widget_sync.core.SyncMode(*values)[source]
Bases:
EnumSynchronization modes for widget connections
- BIDIRECTIONAL = 'bidirectional'
- FROM_SYNC = 'from_sync'
- TO_SYNC = 'to_sync'
- class pymodaq_gui.utils.widget_sync.core.ValueSync(initial_value=None, data_type=None, validator=None)[source]
Bases:
BaseWidgetSyncSynchronize a single value across multiple widgets.
Supports any data type (int, str, bool, float, custom objects). Use bind() to connect widgets.
- Attributes:
valueGet current synced value.
Methods
add(widget[, mode, match, ...])Convenience method to add a widget using auto-detected property sync.
set_value(new_value[, emit])Set value with optional emission control.
- add(widget, mode=SyncMode.BIDIRECTIONAL, match='type', to_sync_transform=None, from_sync_transform=None, init_from='sync')[source]
Convenience method to add a widget using auto-detected property sync.
Automatically detects the property/signal pattern from existing connections.
- Parameters:
widget (
QWidget) – Widget to addmode (
SyncMode) – Sync mode (default: BIDIRECTIONAL)match (
str) – Pattern matching strategy (default: ‘type’): - ‘type’: Match exact widget type (safest) - ‘property’: Match by property/signal namesto_sync_transform (
Optional[Callable[[Any],Any]]) – Transform value from widget to syncfrom_sync_transform (
Optional[Callable[[Any],Any]]) – Transform value from sync to widgetinit_from (
Literal['sync','widget',None]) – Initialization source: ‘sync’ (default), ‘widget’, or None. See bind() for details.
- Raises:
TypeError – If no connection pattern found
ValueError – If match parameter has invalid value
- Return type:
- property value: Any
Get current synced value.
Warning: For mutable values (lists, dicts, custom objects), returns a direct reference to the internal value. Modifying the returned value in-place will affect the sync’s internal state and may cause change detection to fail.
For a fully independent copy of mutable values, use: copy.copy(sync.value) or copy.deepcopy(sync.value)