""" core/reference_registry.py Generic (device_id, channel_id) reference-rename propagation. A Signal (physical device channel) or a Channel (derived, device_id == "derived") can be renamed by the user. Both kinds are referenced the same way everywhere downstream — plot traces, control targets, filter pipelines, other derived channels' sources — as a plain (device_id, channel_id) pair. Renaming one without rewriting every reference to it leaves those references dangling: they silently stop receiving data (this is exactly the bug that motivated this module — an Arduino analog pin remap changed a Signal's id and orphaned its plot trace). propagate_rename() is the single place that walks every subsystem able to hold such a reference and rewrites it. It expects a MainWindow instance (or anything exposing the same .engine/.processor/._chart/._ctrl/ ._win_config attributes) since that's the one place all of them are reachable from. """ from typing import Tuple Ref = Tuple[str, str] def propagate_rename(app, old_ref: Ref, new_ref: Ref) -> None: if old_ref == new_ref: return # Physical signals live in the acquisition engine's buffers; derived # Channels don't (their buffers live in SignalProcessor._derived_bufs). if old_ref[0] != "derived": app.engine.rename_channel(old_ref[0], old_ref[1], new_ref[1]) # Filter pipelines, other derived channels' sources, latest-value cache, # and (if old_ref is itself a derived Channel) its ring buffer. app.processor.rename_reference(old_ref, new_ref) # Live plot — trace specs in the strip chart's current layout. chart = getattr(app, "_chart", None) changed = False if chart is not None and chart._cfg is not None: for pane in chart._cfg.panes: for tr in pane.traces: if (tr.device_id, tr.channel_id) == old_ref: tr.device_id, tr.channel_id = new_ref changed = True if changed: chart.refresh() # Control panel — saved specs (profile persistence) + live bound widgets # (actual I/O routing — ControlWidget reads its own .device_id/.channel_id # directly in _write()/_check_stale(), not from the spec). ctrl = getattr(app, "_ctrl", None) if ctrl is not None: for spec in ctrl.get_specs(): if (spec.device_id, spec.channel_id) == old_ref: spec.device_id, spec.channel_id = new_ref for widget in getattr(ctrl, "_widgets", []): if (widget.device_id, widget.channel_id) == old_ref: widget.device_id, widget.channel_id = new_ref # Config window tabs, if currently open — refresh so pickers/tables # don't show the stale id until next full refresh. win = getattr(app, "_win_config", None) if win is not None: if chart is not None: win.refresh_plot(chart._cfg) if hasattr(win, "refresh_derived"): win.refresh_derived() if hasattr(win, "refresh_devices"): win.refresh_devices()