summaryrefslogtreecommitdiff
path: root/ui/windows/channels_window.py
diff options
context:
space:
mode:
Diffstat (limited to 'ui/windows/channels_window.py')
-rw-r--r--ui/windows/channels_window.py105
1 files changed, 55 insertions, 50 deletions
diff --git a/ui/windows/channels_window.py b/ui/windows/channels_window.py
index 87d7840..f581851 100644
--- a/ui/windows/channels_window.py
+++ b/ui/windows/channels_window.py
@@ -15,7 +15,7 @@ Classes:
PipelineTab — splitter content (physical + virtual sections)
ChannelPipelineBlock — collapsible filter+math stack for one physical channel
ChannelPickerDialog — checkbox dialog for selecting channels to add
- DerivedBlock — editor for one virtual/derived channel
+ ChannelBlock — editor for one virtual/derived channel
FilterRow — single filter stage row inside a pipeline block
SignalsListTab — unused; kept for profile-load compatibility
"""
@@ -35,7 +35,7 @@ from PyQt6.QtGui import QCloseEvent, QFont, QFontMetrics
from devices.device_registry import DeviceRegistry
from core.signal_processor import (
- SignalProcessor, ChannelPipeline, DerivedChannel,
+ SignalProcessor, ChannelPipeline, Channel,
FilterBase, FILTER_CLASSES,
)
from ui.windows.plot_window import _DragHandle, _DragReorderContainer
@@ -60,8 +60,8 @@ def _channel_display_name(registry: DeviceRegistry,
else:
for dev in registry.all_instances():
if dev.info.device_id == dev_id:
- for ch in dev.info.channels:
- if ch.channel_id == ch_id:
+ for ch in dev.info.signals:
+ if ch.signal_id == ch_id:
return ch.name if ch.name else ch_id
return ch_id
@@ -72,16 +72,16 @@ def _channel_combo(registry: DeviceRegistry,
show_unit: bool = False) -> QComboBox:
cb = QComboBox(); cb.setObjectName("channelPickerCb")
for dev in registry.all_instances():
- for ch in dev.info.channels:
+ for ch in dev.info.signals:
if not ch.enabled:
continue
- label = f"{ch.name} ({dev.info.device_id}/{ch.channel_id})"
+ label = f"Signal: {ch.name} ({dev.info.device_id}/{ch.signal_id})"
if show_unit and ch.unit:
label += f" [{ch.unit}]"
- cb.addItem(label, userData=(dev.info.device_id, ch.channel_id))
+ cb.addItem(label, userData=(dev.info.device_id, ch.signal_id))
if include_derived and processor:
for dc in processor.get_derived():
- label = f"[derived] {dc.channel_id} ({dc.name})"
+ label = f"Channel: {dc.channel_id} ({dc.name})"
if show_unit and dc.unit:
label += f" [{dc.unit}]"
cb.addItem(label, userData=("derived", dc.channel_id))
@@ -110,17 +110,17 @@ class ChannelPickerDialog(QDialog):
any_available = False
for dev in registry.all_instances():
- for ch in dev.info.channels:
+ for ch in dev.info.signals:
if not ch.enabled:
continue
- if (dev.info.device_id, ch.channel_id) in already_shown:
+ if (dev.info.device_id, ch.signal_id) in already_shown:
continue
any_available = True
- label = f"{ch.name} ({dev.info.device_id}/{ch.channel_id})"
+ label = f"{ch.name} ({dev.info.device_id}/{ch.signal_id})"
if ch.unit:
label += f" [{ch.unit}]"
chk = QCheckBox(label)
- self._checks.append((chk, dev.info.device_id, ch.channel_id))
+ self._checks.append((chk, dev.info.device_id, ch.signal_id))
cl.addWidget(chk)
if not any_available:
@@ -237,7 +237,7 @@ class ChannelPipelineBlock(QFrame):
self._unit = unit
outer = QVBoxLayout(self); outer.setContentsMargins(0, 0, 0, 0); outer.setSpacing(0)
- # Header (omitted when embedded in another block, e.g. DerivedBlock's
+ # Header (omitted when embedded in another block, e.g. ChannelBlock's
# Filters section — the parent already provides a header/toggle)
if self._show_header:
hdr = QWidget(); hdr.setObjectName("plotBlockHeader"); hdr.setFixedHeight(32)
@@ -405,6 +405,7 @@ class ChannelPipelineBlock(QFrame):
class PipelineTab(QWidget):
changed = pyqtSignal()
derived_changed = pyqtSignal()
+ channel_renamed = pyqtSignal(str, str, str, str) # old_dev, old_id, new_dev, new_id
def __init__(self, registry: DeviceRegistry, processor: SignalProcessor):
super().__init__()
@@ -412,7 +413,7 @@ class PipelineTab(QWidget):
self.processor = processor
self._shown: Set[Tuple[str, str]] = set()
self._blocks: dict[Tuple[str, str], ChannelPipelineBlock] = {}
- self._derived_blocks: List[DerivedBlock] = []
+ self._derived_blocks: List[ChannelBlock] = []
self._color_idx = 0
self._build()
@@ -494,9 +495,9 @@ class PipelineTab(QWidget):
QTimer.singleShot(0, _init_split)
for dev in self.registry.all_instances():
- for ch in dev.info.channels:
+ for ch in dev.info.signals:
if ch.enabled:
- self._add_block(dev.info.device_id, ch.channel_id, ch.name, ch.unit)
+ self._add_block(dev.info.device_id, ch.signal_id, ch.name, ch.unit)
# ── Physical channel block management ──────────────────────────────────────
@@ -516,7 +517,7 @@ class PipelineTab(QWidget):
if key not in self._shown:
dev = self.registry.get_instance(dev_id)
if dev:
- ch = dev.get_channel(ch_id)
+ ch = dev.get_signal(ch_id)
if ch:
self._add_block(dev_id, ch_id, ch.name, ch.unit)
else:
@@ -541,7 +542,7 @@ class PipelineTab(QWidget):
if dev is None:
self.on_device_removed(dev_id)
return
- valid = {ch.channel_id for ch in dev.info.channels}
+ valid = {ch.signal_id for ch in dev.info.signals}
keys = [(d, c) for (d, c) in list(self._shown)
if d == dev_id and c not in valid]
for key in keys:
@@ -560,7 +561,7 @@ class PipelineTab(QWidget):
else:
dev = self.registry.get_instance(dev_id)
if dev:
- ch = dev.get_channel(ch_id)
+ ch = dev.get_signal(ch_id)
if ch and ch.enabled:
self._add_block(dev_id, ch_id, ch_name, ch.unit)
self._refresh_src_cb()
@@ -580,13 +581,13 @@ class PipelineTab(QWidget):
self._src_cb.clear()
self._src_cb.insertItem(0, "Signal: none (empty channel)", userData=None)
for dev in self.registry.all_instances():
- for ch in dev.info.channels:
+ for ch in dev.info.signals:
if not ch.enabled:
continue
- label = f"{ch.name} ({dev.info.device_id}/{ch.channel_id})"
- self._src_cb.addItem(label, userData=(dev.info.device_id, ch.channel_id))
+ label = f"Signal: {ch.name} ({dev.info.device_id}/{ch.signal_id})"
+ self._src_cb.addItem(label, userData=(dev.info.device_id, ch.signal_id))
for dc in self.processor.get_derived():
- label = f"[derived] {dc.channel_id} ({dc.name})"
+ label = f"Channel: {dc.channel_id} ({dc.name})"
self._src_cb.addItem(label, userData=("derived", dc.channel_id))
if current:
for i in range(self._src_cb.count()):
@@ -607,7 +608,7 @@ class PipelineTab(QWidget):
dev = self.registry.get_instance(dev_id)
if not dev:
continue
- ch = dev.get_channel(ch_id)
+ ch = dev.get_signal(ch_id)
if not ch:
continue
self._add_block(dev_id, ch_id, ch.name, ch.unit)
@@ -635,23 +636,27 @@ class PipelineTab(QWidget):
src_name = f"Channel {len(self._derived_blocks) + 1}"
name = _dedupe_name(src_name, existing_names)
channel_id = _slug_id(name, existing_ids)
- dc = DerivedChannel(channel_id=channel_id, name=name,
+ dc = Channel(channel_id=channel_id, name=name,
kind="expression", color=color)
blk = self._make_derived_block(dc)
self._virt_inner.insertWidget(self._virt_inner.count() - 1, blk)
if src:
blk._add_src(src)
- def _make_derived_block(self, dc: DerivedChannel) -> DerivedBlock:
- blk = DerivedBlock(dc, self.registry, self.processor)
+ def _make_derived_block(self, dc: Channel) -> ChannelBlock:
+ blk = ChannelBlock(dc, self.registry, self.processor)
blk.removed.connect(self._rm_virtual)
blk.applied.connect(lambda _: self.derived_changed.emit())
- blk.id_renamed.connect(self.refresh_derived)
+ blk.id_renamed.connect(self._on_block_id_renamed)
blk._drag_handle.set_drag_source(self, blk)
self._derived_blocks.append(blk)
return blk
- def _rm_virtual(self, blk: DerivedBlock):
+ def _on_block_id_renamed(self, old_id: str, new_id: str):
+ self.refresh_derived()
+ self.channel_renamed.emit("derived", old_id, "derived", new_id)
+
+ def _rm_virtual(self, blk: ChannelBlock):
self.processor.remove_derived(blk.dc.channel_id)
self._derived_blocks.remove(blk)
self._virt_inner.removeWidget(blk); blk.deleteLater()
@@ -659,7 +664,7 @@ class PipelineTab(QWidget):
# ── Drag-to-reorder — see _DragReorderContainer in ui/windows/plot_window.py
- def _index_of(self, block: DerivedBlock) -> int:
+ def _index_of(self, block: ChannelBlock) -> int:
return self._derived_blocks.index(block)
def _drop_index(self, y: float) -> int:
@@ -708,7 +713,7 @@ class PipelineTab(QWidget):
self.derived_changed.emit()
def refresh_derived(self):
- """Rebuild DerivedBlock widgets from processor state (called after profile load)."""
+ """Rebuild ChannelBlock widgets from processor state (called after profile load)."""
for blk in self._derived_blocks:
self._virt_inner.removeWidget(blk); blk.deleteLater()
self._derived_blocks.clear()
@@ -723,7 +728,7 @@ class PipelineTab(QWidget):
# ══════════════════════════════════════════════════════════════════════════════
-# Derived editor for one DerivedChannel
+# Derived editor for one Channel
# ══════════════════════════════════════════════════════════════════════════════
_BUILTIN = ["none", "scale_offset", "derivative", "second_derivative", "power", "rms", "difference", "sum"]
@@ -806,13 +811,17 @@ def _sub_xrefs(code: str, names: list) -> str:
return re.sub(r'\bx\[(\d+)\]', repl, code)
-class DerivedBlock(QFrame):
+class ChannelBlock(QFrame):
removed = pyqtSignal(object)
- applied = pyqtSignal(object) # DerivedChannel
- id_renamed = pyqtSignal() # this Channel's ID changed — other blocks'
- # source pickers may now be stale
-
- def __init__(self, dc: DerivedChannel, registry: DeviceRegistry,
+ applied = pyqtSignal(object) # Channel
+ id_renamed = pyqtSignal(str, str) # old_id, new_id — this Channel's ID
+ # changed; other blocks' source
+ # pickers, and everything outside
+ # this processor referencing it
+ # (plot traces, controls), may now
+ # be stale — see propagate_rename()
+
+ def __init__(self, dc: Channel, registry: DeviceRegistry,
processor: SignalProcessor):
super().__init__()
self.dc = dc; self.registry = registry; self.processor = processor
@@ -1192,12 +1201,8 @@ class DerivedBlock(QFrame):
self._st.setStyleSheet("color:#ef4444;")
return
renamed = channel_id != self._orig_id
+ old_id = self._orig_id
if renamed:
- # Other channels sourced from this one under the old ID would
- # otherwise keep pointing at a reference that no longer exists —
- # fix their sources up front, carry the ring buffer + latest
- # value over so the rename doesn't reset their plotted history.
- self.processor.rename_derived(self._orig_id, channel_id)
self._orig_id = channel_id
sources = [cb.currentData() for cb in self._src_combos if cb.currentData()]
if self.dc.kind in ("derivative", "second_derivative"):
@@ -1215,11 +1220,11 @@ class DerivedBlock(QFrame):
elif self.dc.kind in ("function", "custom_script"):
self.dc.script = self._code.toPlainText()
err = self.processor.add_derived(self.dc)
- # The rename above already committed regardless of whether this
- # channel's own expression/script compiles — other blocks' source
- # pickers need to refresh either way.
+ # Emitted regardless of whether this channel's own expression/script
+ # compiles — propagate_rename() (via id_renamed) still needs to run
+ # so other channels' sources / plot traces / controls don't dangle.
if renamed:
- self.id_renamed.emit()
+ self.id_renamed.emit(old_id, channel_id)
if err:
self._st.setText(f"⚠ {err[:100]}")
self._st.setStyleSheet("color:#ef4444;")
@@ -1261,12 +1266,12 @@ class SignalsListTab(QWidget):
if it.widget(): it.widget().deleteLater()
for dev in self.registry.all_instances():
- for ch in dev.info.channels:
+ for ch in dev.info.signals:
dev_id_cap = dev.info.device_id
- ch_id_cap = ch.channel_id
+ ch_id_cap = ch.signal_id
self._lay.addWidget(self._make_row(
color = ch.color,
- name = f"{ch.channel_id} — {ch.name}",
+ name = f"{ch.signal_id} — {ch.name}",
unit = ch.unit,
source = dev_id_cap,
enabled = ch.enabled,