diff options
| author | Christian Kolset <ckolset@colostate.edu> | 2026-08-13 13:39:25 -0600 |
|---|---|---|
| committer | Christian Kolset <ckolset@colostate.edu> | 2026-08-13 13:39:25 -0600 |
| commit | 34c12b2b5b280206890cd03153b1c95116acc85e (patch) | |
| tree | 9b3dee7e0818d94c0bae87a35e7bf930a612ca2c /ui/namespace_panel.py | |
| parent | a6e812dcbf3f2fc27565e52b6c0c6f9b7e4363ee (diff) | |
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 <noreply@anthropic.com>
Diffstat (limited to 'ui/namespace_panel.py')
| -rw-r--r-- | ui/namespace_panel.py | 48 |
1 files changed, 35 insertions, 13 deletions
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)) |
