gui_client: highlight changed rows on each new record

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 21:15:59 +02:00
co-authored by Claude Sonnet 4.6
parent 74379e55d6
commit 73f6b85c05
+38 -3
View File
@@ -180,6 +180,23 @@ def _is_phys(v) -> bool:
return isinstance(v, dict) and "value" in v and "unit" in v return isinstance(v, dict) and "value" in v and "unit" in v
_CHANGED_BG = QColor("#1e3a00")
def _flatten_values(data: dict, prefix: str = "") -> dict[str, object]:
"""Flat dict of path → value for every physical leaf, using '/' separator."""
result = {}
for k, v in data.items():
if k == "ts":
continue
path = f"{prefix}/{k}" if prefix else k
if _is_phys(v):
result[path] = v.get("value")
elif isinstance(v, dict):
result.update(_flatten_values(v, path))
return result
def _extract_phys(data: dict, prefix: str = "") -> dict[str, dict]: def _extract_phys(data: dict, prefix: str = "") -> dict[str, dict]:
"""Return a flat dict of path → {value, unit} for every physical leaf.""" """Return a flat dict of path → {value, unit} for every physical leaf."""
result = {} result = {}
@@ -302,10 +319,15 @@ class DashboardTab(QWidget):
self._tree.setAlternatingRowColors(True) self._tree.setAlternatingRowColors(True)
self._tree.setRootIsDecorated(True) self._tree.setRootIsDecorated(True)
layout.addWidget(self._tree) layout.addWidget(self._tree)
self._prev_flat: dict = {}
def update(self, snapshot: dict): def update(self, snapshot: dict):
self._ts.setText(f"Last update: {snapshot.get('ts', '')}") self._ts.setText(f"Last update: {snapshot.get('ts', '')}")
curr_flat = _flatten_values(snapshot)
changed = {p for p, v in curr_flat.items() if v != self._prev_flat.get(p)}
self._prev_flat = curr_flat
expanded = self._expanded_paths() expanded = self._expanded_paths()
self._tree.clear() self._tree.clear()
@@ -320,38 +342,51 @@ class DashboardTab(QWidget):
d_item.setFont(0, bold) d_item.setFont(0, bold)
d_item.setForeground(0, domain_color) d_item.setForeground(0, domain_color)
for obj_name, fields in domain_data.items(): for obj_name, fields in domain_data.items():
path = f"{domain}/{obj_name}"
if _is_phys(fields): if _is_phys(fields):
item = QTreeWidgetItem([obj_name, str(fields["value"]), fields["unit"], fields.get("str_state", "")]) item = QTreeWidgetItem([obj_name, str(fields["value"]), fields["unit"], fields.get("str_state", "")])
item.setTextAlignment(1, Qt.AlignRight | Qt.AlignVCenter) item.setTextAlignment(1, Qt.AlignRight | Qt.AlignVCenter)
if path in changed:
for col in range(4):
item.setBackground(col, _CHANGED_BG)
d_item.addChild(item) d_item.addChild(item)
elif isinstance(fields, dict): elif isinstance(fields, dict):
obj_item = QTreeWidgetItem([obj_name]) obj_item = QTreeWidgetItem([obj_name])
obj_item.setFont(0, bold) obj_item.setFont(0, bold)
self._add_fields(obj_item, fields, f"{domain}/{obj_name}") self._add_fields(obj_item, fields, path, changed)
d_item.addChild(obj_item) d_item.addChild(obj_item)
else: else:
item = QTreeWidgetItem([obj_name, str(fields), "", ""]) item = QTreeWidgetItem([obj_name, str(fields), "", ""])
item.setTextAlignment(1, Qt.AlignRight | Qt.AlignVCenter) item.setTextAlignment(1, Qt.AlignRight | Qt.AlignVCenter)
if path in changed:
for col in range(4):
item.setBackground(col, _CHANGED_BG)
d_item.addChild(item) d_item.addChild(item)
self._tree.addTopLevelItem(d_item) self._tree.addTopLevelItem(d_item)
self._tree.expandAll() self._tree.expandAll()
self._restore_expanded(expanded) self._restore_expanded(expanded)
def _add_fields(self, parent: QTreeWidgetItem, data: dict, path: str): def _add_fields(self, parent: QTreeWidgetItem, data: dict, path: str, changed: set):
for k, v in data.items(): for k, v in data.items():
child_path = f"{path}/{k}" child_path = f"{path}/{k}"
if _is_phys(v): if _is_phys(v):
item = QTreeWidgetItem([k, str(v["value"]), v["unit"], v.get("str_state", "")]) item = QTreeWidgetItem([k, str(v["value"]), v["unit"], v.get("str_state", "")])
item.setTextAlignment(1, Qt.AlignRight | Qt.AlignVCenter) item.setTextAlignment(1, Qt.AlignRight | Qt.AlignVCenter)
if child_path in changed:
for col in range(4):
item.setBackground(col, _CHANGED_BG)
parent.addChild(item) parent.addChild(item)
elif isinstance(v, dict): elif isinstance(v, dict):
node = QTreeWidgetItem([k]) node = QTreeWidgetItem([k])
self._add_fields(node, v, child_path) self._add_fields(node, v, child_path, changed)
parent.addChild(node) parent.addChild(node)
else: else:
item = QTreeWidgetItem([k, str(v), "", ""]) item = QTreeWidgetItem([k, str(v), "", ""])
item.setTextAlignment(1, Qt.AlignRight | Qt.AlignVCenter) item.setTextAlignment(1, Qt.AlignRight | Qt.AlignVCenter)
if child_path in changed:
for col in range(4):
item.setBackground(col, _CHANGED_BG)
parent.addChild(item) parent.addChild(item)
# ── preserve expand state across refreshes ────────────────────────────── # ── preserve expand state across refreshes ──────────────────────────────