diff --git a/app_state.py b/app_state.py index bc08e23..2cda3bf 100644 --- a/app_state.py +++ b/app_state.py @@ -8,7 +8,7 @@ so later rebinding would only ever be seen by the module that did it. import configparser import os -VERSION = "0.0.0.108" +VERSION = "0.0.0.113" PROPERTIES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "editor.properties") DEFAULT_GRID_SIZE = 10 DEFAULT_ADD_TIMESTAMP = True diff --git a/device_session.py b/device_session.py index 375aaa2..06fc96b 100644 --- a/device_session.py +++ b/device_session.py @@ -53,9 +53,10 @@ class DeviceSession: self._load_slot_registry(parts) root_comp = self.sox_client.load(0) - # Pre-load immediate children so the tree shows the first level if root_comp and root_comp.children: - self.load_children(root_comp.id) + children = self.load_children(root_comp.id) + if children: + self.sox_client.update(children, CONFIG | RUNTIME) return root_comp def disconnect(self): @@ -283,8 +284,7 @@ class DeviceSession: app_elem = ET.SubElement(sax_root, "app") if root_comp is not None: - # Build children of root directly under , matching .sax format - # where IS the root container (not a ) + app_elem.set("id", str(root_comp.id)) for child_id in root_comp.children: child_comp = self.sox_client.cache_get(child_id) if child_comp is not None: diff --git a/live_hooks.py b/live_hooks.py index ede93d5..f318658 100644 --- a/live_hooks.py +++ b/live_hooks.py @@ -213,6 +213,12 @@ def _find_slot(ds, comp_id, slot_name): comp = ds.sox_client.cache_get(comp_id) if comp is None: return None + type_name = ds.sox_client.type_name_map.get((comp.kit_id, comp.type_id)) + if type_name and type_name in app_state.final_components_registry: + for s in app_state.final_components_registry[type_name]: + if s["name"] == slot_name: + return {"name": s["name"], "id": s["slot_id"], + "type_name": s.get("type", "int")} slots = ds.sox_client.slot_registry.get((comp.kit_id, comp.type_id)) if slots is None: return None diff --git a/main.py b/main.py index d792866..d9312af 100644 --- a/main.py +++ b/main.py @@ -11,6 +11,7 @@ import app_state import connect_dialog import device_session import link_view +import live_hooks import manage_kits import palette import properties @@ -191,7 +192,15 @@ def refresh_all_views(): def _component_in_document(comp): root = app_state.xml_root_element - return root is not None and any(element is comp for element in root.iter()) + if root is None: + return False + if any(element is comp for element in root.iter()): + return True + if app_state.live_mode: + comp_id = comp.get("id") + if comp_id is not None: + return any(e.get("id") == comp_id for e in root.iter()) + return False def delete_component(comp): @@ -1329,10 +1338,16 @@ def on_canvas_release(event): if orig_x != raw_x or orig_y != raw_y: meta_before = app_state.drag_data.get("meta_before", meta_str) update_meta_property(comp_element, meta_str, raw_x, raw_y) - undo.push(undo.MetaMoveCommand( - [(comp_element, meta_before, raw_x, raw_y)], - label=f"move {comp_element.get('name', 'component')}" - )) + if live_hooks.is_live(): + new_meta = comp_element.find("prop[@name='meta']") + if new_meta is not None: + live_hooks.live_set_prop_value( + comp_element, "meta", new_meta.get("val", "0")) + if not app_state.live_mode: + undo.push(undo.MetaMoveCommand( + [(comp_element, meta_before, raw_x, raw_y)], + label=f"move {comp_element.get('name', 'component')}" + )) app_state.mark_dirty() app_state.drag_data["tag"] = None diff --git a/properties.py b/properties.py index 2cf51e3..9e672fd 100644 --- a/properties.py +++ b/properties.py @@ -518,6 +518,8 @@ class PropertiesPanel: # sibling nothing at all. self._build_header(comp_type) self._build_footer() + if app_state.live_mode and app_state.device_session is not None: + self._refresh_from_device() self._loading = True self._build_rows_area() self._loading = False @@ -1056,7 +1058,13 @@ class PropertiesPanel: # -- the two buttons -- def refresh(self): - """Re-reads every value from the DOM, discarding edits that were not saved.""" + """Re-reads every value from the DOM, discarding edits that were not saved. + + In live mode, fetches fresh values from the device first. + """ + if app_state.live_mode and app_state.device_session is not None: + self._refresh_from_device() + self._loading = True for row in self.rows: self._load_row(row) @@ -1066,7 +1074,30 @@ class PropertiesPanel: self._load_meta_row() self._load_path() self._update_tint() - self.status_var.set("Refreshed from the document.") + if app_state.live_mode: + self.status_var.set("Refreshed from device.") + else: + self.status_var.set("Refreshed from the document.") + + def _refresh_from_device(self): + """Reads fresh config+runtime values from the device into the DOM element.""" + from sox import CONFIG, RUNTIME + ds = app_state.device_session + comp_id = self.comp.get("id") + if comp_id is None: + return + try: + comp_id = int(comp_id) + except ValueError: + return + sox_comp = ds.sox_client.cache_get(comp_id) + if sox_comp is None: + return + try: + ds.sox_client.update([sox_comp], CONFIG | RUNTIME) + except Exception: + pass + ds.update_element_from_cache(comp_id, app_state.xml_root_element) def save(self): """Writes the edited rows back, as one undoable step. @@ -1137,9 +1168,9 @@ class PropertiesPanel: for element, prop_name, _before, after in changes: if prop_name != "meta": set_prop_value(element, prop_name, after) - if live_hooks.is_live(): - if not live_hooks.live_set_prop_value(element, prop_name, after): - live_failures.append(prop_name) + if live_hooks.is_live(): + if not live_hooks.live_set_prop_value(element, prop_name, after): + live_failures.append(prop_name) if not app_state.live_mode: undo.push(undo.SlotValueCommand(list(changes))) @@ -1174,7 +1205,16 @@ class PropertiesPanel: root = app_state.xml_root_element if root is None: return False - return any(element is self.comp for element in root.iter()) + if any(element is self.comp for element in root.iter()): + return True + if app_state.live_mode: + comp_id = self.comp.get("id") + if comp_id is not None: + for element in root.iter(): + if element.get("id") == comp_id: + self.comp = element + return True + return False def _sync_bar(self, bar, first, last): """Moves a scrollbar's thumb, and hides the bar entirely when it spans the whole.""" diff --git a/sax_file.py b/sax_file.py index b7f1699..ba77131 100644 --- a/sax_file.py +++ b/sax_file.py @@ -8,6 +8,7 @@ import xml.etree.ElementTree as ET import app_state import live_hooks import palette +import properties import schema import undo import wiresheet @@ -69,6 +70,7 @@ def refresh_treeview_from_dom(xml_root): app_element = xml_root.find("app") if app_element is not None: + app_state.tree_element_map[root_node] = app_element parse_xml_to_tree(tree, root_node, app_element) else: parse_xml_to_tree(tree, root_node, xml_root) @@ -754,6 +756,28 @@ def reconcile_schema_kits_before_save(): return True +def _build_config_values(full_type): + """Builds SOX config_values list from manifest defaults for an add command.""" + from sox import type_id_for_slot + from live_hooks import _coerce_value + + slots = app_state.final_components_registry.get(full_type, []) + config_values = [] + for slot in slots: + if "c" not in slot["flags"]: + continue + if slot["type"] == "void": + continue + val = slot["default"] + if val is None: + val = properties.type_zero(slot["type"]) + if val is None: + continue + tid = type_id_for_slot(slot["type"]) + config_values.append((tid, _coerce_value(tid, val))) + return config_values + + def add_component(kit_name, type_name, grid_x, grid_y): """Places a new component of kit::type at a grid position. Returns the element.""" parent = current_sheet_container() @@ -784,7 +808,10 @@ def add_component(kit_name, type_name, grid_x, grid_y): comp_name = unique_child_name(parent, type_name) if live_hooks.is_live(): - new_id = live_hooks.live_add_component(parent, kit_name, type_name, comp_name) + full_type = f"{kit_name}::{type_name}" + config_values = _build_config_values(full_type) + new_id = live_hooks.live_add_component( + parent, kit_name, type_name, comp_name, config_values or None) if new_id is None: return None comp_id = new_id @@ -796,6 +823,23 @@ def add_component(kit_name, type_name, grid_x, grid_y): element.set("id", str(comp_id)) element.set("type", f"{kit_name}::{type_name}") + full_type = f"{kit_name}::{type_name}" + slots = app_state.final_components_registry.get(full_type, []) + for slot in slots: + flags = slot["flags"] + if "a" in flags or "r" in flags: + continue + if slot["type"] == "void": + continue + val = slot["default"] + if val is None: + val = properties.type_zero(slot["type"]) + if val is None: + continue + prop = ET.SubElement(element, "prop") + prop.set("name", slot["name"]) + prop.set("val", val) + index = len(list(parent)) parent.append(element)