gui_client: highlight changed rows on each new record
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+38
-3
@@ -180,6 +180,23 @@ def _is_phys(v) -> bool:
|
||||
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]:
|
||||
"""Return a flat dict of path → {value, unit} for every physical leaf."""
|
||||
result = {}
|
||||
@@ -302,10 +319,15 @@ class DashboardTab(QWidget):
|
||||
self._tree.setAlternatingRowColors(True)
|
||||
self._tree.setRootIsDecorated(True)
|
||||
layout.addWidget(self._tree)
|
||||
self._prev_flat: dict = {}
|
||||
|
||||
def update(self, snapshot: dict):
|
||||
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()
|
||||
self._tree.clear()
|
||||
|
||||
@@ -320,38 +342,51 @@ class DashboardTab(QWidget):
|
||||
d_item.setFont(0, bold)
|
||||
d_item.setForeground(0, domain_color)
|
||||
for obj_name, fields in domain_data.items():
|
||||
path = f"{domain}/{obj_name}"
|
||||
if _is_phys(fields):
|
||||
item = QTreeWidgetItem([obj_name, str(fields["value"]), fields["unit"], fields.get("str_state", "")])
|
||||
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)
|
||||
elif isinstance(fields, dict):
|
||||
obj_item = QTreeWidgetItem([obj_name])
|
||||
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)
|
||||
else:
|
||||
item = QTreeWidgetItem([obj_name, str(fields), "", ""])
|
||||
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)
|
||||
self._tree.addTopLevelItem(d_item)
|
||||
|
||||
self._tree.expandAll()
|
||||
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():
|
||||
child_path = f"{path}/{k}"
|
||||
if _is_phys(v):
|
||||
item = QTreeWidgetItem([k, str(v["value"]), v["unit"], v.get("str_state", "")])
|
||||
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)
|
||||
elif isinstance(v, dict):
|
||||
node = QTreeWidgetItem([k])
|
||||
self._add_fields(node, v, child_path)
|
||||
self._add_fields(node, v, child_path, changed)
|
||||
parent.addChild(node)
|
||||
else:
|
||||
item = QTreeWidgetItem([k, str(v), "", ""])
|
||||
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)
|
||||
|
||||
# ── preserve expand state across refreshes ──────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user