diff --git a/app_state.py b/app_state.py index ad2b0f9..b4d5648 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.147" +VERSION = "0.0.0.148" 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/main.py b/main.py index a19f019..623b187 100644 --- a/main.py +++ b/main.py @@ -1232,18 +1232,23 @@ def _open_special_view(comp): def on_tree_double_click(event): - """Double-click on a tree node opens its special view (or Properties).""" + """Double-click on a tree node opens its special view (or Properties). + + Returns "break" so ttk's own double-click does not also toggle the row: only the + +/- sign opens or closes a node. + """ tree = app_state.tree item_id = tree.identify_row(event.y) if not item_id: - return + return "break" comp = app_state.tree_element_map.get(item_id) if comp is None: - return + return "break" if tree.parent(item_id) == "": show_properties(comp) else: _open_special_view(comp) + return "break" def on_canvas_double_click(event): diff --git a/sax_file.py b/sax_file.py index 92baff6..297e208 100644 --- a/sax_file.py +++ b/sax_file.py @@ -59,9 +59,53 @@ def parse_xml_to_tree(treeview, parent_node, xml_element): parse_xml_to_tree(treeview, node_id, child) +def _expansion_key(treeview, item, parent_key): + """Names a tree row in a way that survives a rebuild of the tree. + + A component's id is stable across edits and across a fresh DOM read from the + device; a row without one (the root, or an id-less file component) falls back to + its name under its parent's key. + """ + elem = app_state.tree_element_map.get(item) + comp_id = elem.get("id") if elem is not None else None + if comp_id is not None and treeview.parent(item) != "": + return ("id", comp_id) + name = elem.get("name", "") if elem is not None else treeview.item(item, "text") + return ("path", parent_key, name) + + +def _collect_open_keys(treeview, parent="", parent_key=None, found=None): + """Returns the keys of every row that is expanded right now.""" + if found is None: + found = set() + for item in treeview.get_children(parent): + key = _expansion_key(treeview, item, parent_key) + if treeview.item(item, "open"): + found.add(key) + _collect_open_keys(treeview, item, key, found) + return found + + +def _reopen_keys(treeview, open_keys, parent="", parent_key=None): + """Expands again the rows that were expanded before the rebuild. + + Setting the open flag in code does not raise <>, so this never + starts a live child load of its own. + """ + for item in treeview.get_children(parent): + key = _expansion_key(treeview, item, parent_key) + if key in open_keys: + treeview.item(item, open=True) + _reopen_keys(treeview, open_keys, item, key) + + def refresh_treeview_from_dom(xml_root): - """Rebuilds navigation tree structures using current XML DOM trees.""" + """Rebuilds navigation tree structures using current XML DOM trees. + + Rows the user had expanded stay expanded; only the user's own "-" closes one. + """ tree = app_state.tree + open_keys = _collect_open_keys(tree) app_state.tree_element_map.clear() for item in tree.get_children(): @@ -88,6 +132,8 @@ def refresh_treeview_from_dom(xml_root): else: parse_xml_to_tree(tree, root_node, xml_root) + _reopen_keys(tree, open_keys) + if _tree_filled is not None: _tree_filled()