diff --git a/client/gui_client.py b/client/gui_client.py index 90e1b7a..8e1ae4b 100644 --- a/client/gui_client.py +++ b/client/gui_client.py @@ -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 ──────────────────────────────