From 34c12b2b5b280206890cd03153b1c95116acc85e Mon Sep 17 00:00:00 2001 From: Christian Kolset Date: Thu, 13 Aug 2026 13:39:25 -0600 Subject: Channels tab drag-to-reorder, Namespace absolute references, fix stale Channel ID renames Generalized the Plot pane drag-to-reorder (_PaneDragHandle/_PlotDropContainer -> _DragHandle/_DragReorderContainer) and reused it for the Channels tab's derived-channel list; reordering also updates SignalProcessor's evaluation order (list order matters for chained Channels) via new reorder_derived(). Namespace panel: added an ID column (bare channel_id, matching the actual channels.get(...) script convention) and fixed the Source column for Channels to show resolved absolute references (dev_id/ch_id for a Signal, bare id for a chained Channel) instead of the operation kind, so a chain is traceable by eye. Fixed a real bug this surfaced: renaming a Channel's ID never propagated to other Channels' sources, leaving dangling ("derived", old_id) references that silently stopped updating. Added SignalProcessor.rename_derived() to fix up sources plus carry the ring buffer/latest-value cache to the new key, wired to DerivedBlock's Apply. Co-Authored-By: Claude Sonnet 5 --- ui/namespace_panel.py | 48 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 13 deletions(-) (limited to 'ui/namespace_panel.py') diff --git a/ui/namespace_panel.py b/ui/namespace_panel.py index 7d8c42d..44750c6 100644 --- a/ui/namespace_panel.py +++ b/ui/namespace_panel.py @@ -28,10 +28,11 @@ from core.signal_processor import SignalProcessor _VARS_POLL_MS = 400 _C_KIND = 0 -_C_NAME = 1 -_C_DIR = 2 -_C_VALUE = 3 -_C_SOURCE = 4 +_C_ID = 1 +_C_NAME = 2 +_C_DIR = 3 +_C_VALUE = 4 +_C_SOURCE = 5 def _fmt_value(v) -> str: @@ -42,6 +43,16 @@ def _fmt_value(v) -> str: return str(v) +def _fmt_ref(dev_id: str, ch_id: str) -> str: + """Absolute reference for a source — device_id/channel_id for a Signal + (device_id is stable across machines even when the COM/tty port isn't, + see ProfileManager.apply()'s port re-detection), or the bare channel_id + for another Channel (derived channels have no device to qualify with).""" + if dev_id == "derived": + return ch_id + return f"{dev_id}/{ch_id}" + + class NamespacePanel(QWidget): def __init__(self, registry: DeviceRegistry, processor: SignalProcessor): super().__init__() @@ -73,13 +84,14 @@ class NamespacePanel(QWidget): self._table = QTableWidget() self._table.setObjectName("channelTable") - self._table.setColumnCount(5) + self._table.setColumnCount(6) self._table.setHorizontalHeaderLabels( - ["Kind", "Name", "Dir", "Value", "Source"] + ["Kind", "ID", "Name", "Dir", "Value", "Source"] ) hdr = self._table.horizontalHeader() hdr.setSectionResizeMode(_C_SOURCE, QHeaderView.ResizeMode.Stretch) hdr.setSectionResizeMode(_C_KIND, QHeaderView.ResizeMode.ResizeToContents) + hdr.setSectionResizeMode(_C_ID, QHeaderView.ResizeMode.ResizeToContents) hdr.setSectionResizeMode(_C_NAME, QHeaderView.ResizeMode.ResizeToContents) hdr.setSectionResizeMode(_C_DIR, QHeaderView.ResizeMode.ResizeToContents) hdr.setSectionResizeMode(_C_VALUE, QHeaderView.ResizeMode.ResizeToContents) @@ -122,23 +134,33 @@ class NamespacePanel(QWidget): ch = dev.get_channel(ch_id) if dev else None name = ch.name if ch else ch_id is_wr = bool(ch and ch.writable) - self._set_row(row, "Signal", name, "WRITE" if is_wr else "READ", - None, f"{dev_id}/{ch_id}") + self._set_row(row, "Signal", ch_id, name, "WRITE" if is_wr else "READ", + None, _fmt_ref(dev_id, ch_id)) elif kind == "channel": _, channel_id = key dc = next((d for d in self.processor.get_derived() if d.channel_id == channel_id), None) name = dc.name if dc else channel_id - src = dc.kind if dc else "" - self._set_row(row, "Channel", name, "READ", None, src) + if dc and dc.sources: + # Absolute reference(s) this Channel is chained from — same + # format as another row's ID column, so a chain (Channel 1 -> + # Channel 2 -> Channel 3) can be traced by eye: Channel 3's + # Source literally matches Channel 2's ID. + src = ", ".join(_fmt_ref(d, c) for d, c in dc.sources) + else: + src = dc.kind if dc else "" + self._set_row(row, "Channel", channel_id, name, "READ", None, src) else: # var _, name = key val = self.processor.get_var(name, None) - self._set_row(row, "var", name, "R/W", val, "shared (vars)") + self._set_row(row, "var", name, name, "R/W", val, "shared (vars)") - def _set_row(self, row: int, kind: str, name: str, direction: str, - value, source: str): + def _set_row(self, row: int, kind: str, id_ref: str, name: str, + direction: str, value, source: str): self._table.setItem(row, _C_KIND, QTableWidgetItem(kind)) + id_item = QTableWidgetItem(id_ref) + id_item.setForeground(QColor("#94a3b8")) + self._table.setItem(row, _C_ID, id_item) self._table.setItem(row, _C_NAME, QTableWidgetItem(name)) dir_item = QTableWidgetItem(direction) dir_item.setForeground(_dir_color(direction)) -- cgit v1.2.3