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: QObject

Base 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_widgets

Get list of currently connected widgets.

connection_count

Get count of active connections.

data_type

Get the data type for this sync.

value

Get 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()

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 connect

  • signal (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 modes

  • setter (Optional[Callable[[Any], None]]) – Function to set value on widget: (value) -> None Required for FROM_SYNC and BIDIRECTIONAL modes

  • mode (SyncMode | None) – Synchronization mode. If None, auto-inferred from parameters

  • to_sync_transform (Optional[Callable[[Any], Any]]) – Transform value from widget to sync: (widget_value) -> sync_value

  • from_sync_transform (Optional[Callable[[Any], Any]]) – Transform value from sync to widget: (sync_value) -> widget_value

  • init_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:

None

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!
check_connection_mode(mode, setter, getter, property_key=None)[source]
Return type:

SyncMode

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:

widget (QWidget | int) – The widget to disable, or its ID

Raises:

ValueError – If widget is not connected

Return type:

None

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:

widget (QWidget | int) – The widget to enable, or its ID

Raises:

ValueError – If widget is not connected

Return type:

None

is_enabled(widget)[source]

Check if a widget’s connections are enabled.

Returns True only if ALL connections for the widget are enabled.

Parameters:

widget (QWidget | int) – The widget to check, or its ID

Returns:

True if all connections are enabled, False otherwise

Return type:

bool

Raises:

ValueError – If widget is not connected

set_value(new_value, emit=True)[source]

Set value - must be implemented by subclasses.

Return type:

None

unbind(widget)[source]

Unbind a widget by reference or ID.

Removes ALL connections for the widget (both regular and property connections).

Parameters:

widget (QWidget | int) – The widget to unbind, or its ID

Return type:

None

unbind_all()[source]

Unbind all connected widgets (both regular and property connections).

Return type:

None

validate_property_connection(mode, setter, getter, signal=None, property_key=None)[source]
Return type:

None

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:

int

Example

>>> sync = WidgetSync.for_spinbox(spin1)
>>> sync.add(spin2)
>>> sync.connection_count  # Returns 2
2
property data_type: Type

Get the data type for this sync.

Returns:

Python type (bool, int, float, str, object)

Return type:

Type

property value: Any

Get current value - must be implemented by subclasses.

class pymodaq_gui.utils.widget_sync.core.DataType(*values)[source]

Bases: Enum

Supported 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: BaseWidgetSync

Synchronize dict values with multiple properties or widgets.

DictSync provides three binding methods:

  1. bind() - Bind widgets that work with the entire dict (e.g., JSON editor, config display)

  2. bind_properties() - Bind multiple properties of ONE widget to dict keys (e.g., ComboBox with both ‘items’ and ‘selection’ properties)

  3. 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:
value

Get 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:
  • key (str) – The dict key containing the list

  • item (Any) – The item to append

  • emit (bool) – Whether to emit value_changed signal (default: True)

Raises:
  • KeyError – If key doesn’t exist in dict

  • TypeError – If value at key is not a list

Return type:

None

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:

None

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 bind

  • property_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:

None

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_properties

For regular Qt widgets (uses blockSignals)

bind_dict

For 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:

None

pop_from_list(key, index=-1, emit=True)[source]

Pop an item from a list value in the dict.

Parameters:
  • key (str) – The dict key containing the list

  • index (int) – Index to pop (default: -1, last item)

  • emit (bool) – Whether to emit value_changed signal (default: True)

Returns:

The popped item

Return type:

Any

Raises:

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:
  • key (str) – The dict key containing the list

  • item (Any) – The item to remove

  • emit (bool) – Whether to emit value_changed signal (default: True)

Raises:
Return type:

None

Example

>>> sync = DictSync({'items': ['a', 'b', 'c'], 'current': 'a'})
>>> sync.remove_from_list('items', 'b')
>>> sync.value['items']  # Returns ['a', 'c']
set_value(new_value, emit=True)[source]

Set dict value with optional emission control.

Parameters:
  • new_value (dict) – The new dict value to set

  • emit (bool) – Whether to emit value_changed signal (default: True)

Raises:

TypeError – If new_value is not a dict

Return type:

None

update_key(key, value, emit=True)[source]

Update a single key in the dict value.

Convenience method that handles copying internally.

Parameters:
  • key (str) – The dict key to update

  • value (Any) – The new value for the key

  • emit (bool) – Whether to emit value_changed signal (default: True)

Return type:

None

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: Enum

Synchronization 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: BaseWidgetSync

Synchronize a single value across multiple widgets.

Supports any data type (int, str, bool, float, custom objects). Use bind() to connect widgets.

Attributes:
value

Get 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 add

  • mode (SyncMode) – Sync mode (default: BIDIRECTIONAL)

  • match (str) – Pattern matching strategy (default: ‘type’): - ‘type’: Match exact widget type (safest) - ‘property’: Match by property/signal names

  • to_sync_transform (Optional[Callable[[Any], Any]]) – Transform value from widget to sync

  • from_sync_transform (Optional[Callable[[Any], Any]]) – Transform value from sync to widget

  • init_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:

None

set_value(new_value, emit=True)[source]

Set value with optional emission control.

Parameters:
  • new_value (Any) – The new value to set

  • emit (bool) – Whether to emit value_changed signal (default: True)

Raises:

TypeError – If new_value doesn’t match expected data type

Return type:

None

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)