diff --git a/app_state.py b/app_state.py index 8628dd6..9ed0eea 100644 --- a/app_state.py +++ b/app_state.py @@ -23,6 +23,12 @@ MAX_ALLOWED_BOX_WIDTH = 400 window = None canvas = None tree = None +status_label = None # the window's bottom status bar + +# Status bar tones. A hint that something needs attention later is worth a colour, so a +# placement that leaves the schema out of step is not read as a plain confirmation. +STATUS_INFO_COLOUR = "#333333" +STATUS_WARN_COLOUR = "#a04b00" # Maps to hold tracking states tree_element_map = {} # Treeview item ID -> ET.Element @@ -47,7 +53,12 @@ drag_data = {"x": 0, "y": 0, "tag": None} # Global component slot registries compiled dynamically per file load final_components_registry = {} -active_schema_kits = {} +active_schema_kits = {} # kit name -> checksum, as the open file's declares them + +# Kits a placed component needs that the file's does not declare yet, as +# kit name -> checksum resolved from Sedona home. Filled in when a palette component is +# placed and read back when saving, which is where the block is reconciled. +pending_schema_kits = {} def load_preferences(): @@ -102,6 +113,21 @@ def base_window_title(): return f"Sedona SAX Tree View Viewer & Program Editor [v{VERSION}]" +def set_status(message, warn=False): + """Posts a one-line note to the status bar. A no-op before the UI exists.""" + if status_label is None: + return + status_label.config( + text=message, + foreground=STATUS_WARN_COLOUR if warn else STATUS_INFO_COLOUR + ) + + +def clear_status(): + """Empties the status bar, for when the note no longer describes anything on screen.""" + set_status("") + + def mark_dirty(): """Single funnel for 'the document changed', including the unsaved-state tint.""" global is_dirty diff --git a/main.py b/main.py index 62f7413..f3c0594 100644 --- a/main.py +++ b/main.py @@ -782,6 +782,12 @@ menu_bar.add_cascade(label="Preferences", menu=menu_prefs) window.config(menu=menu_bar) +# Packed before the paned window: pack hands out the cavity in order, and the paned +# window expands into whatever is left, so a status bar added afterwards would get no room. +status_label = ttk.Label(window, text="", anchor="w", foreground=app_state.STATUS_INFO_COLOUR) +status_label.pack(side=tk.BOTTOM, fill=tk.X, padx=8, pady=(0, 4)) +app_state.status_label = status_label + paned_window = ttk.PanedWindow(window, orient=tk.HORIZONTAL) paned_window.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) diff --git a/palette.py b/palette.py index 0c7c01b..b82adfa 100644 --- a/palette.py +++ b/palette.py @@ -140,6 +140,17 @@ def resolve_checksum(kit_name): return filename[len(kit_name) + 1:-4] +def version_for_checksum(kit_name, checksum): + """The version a kit's checksum stands for, read from the .kit filenames. + + Returns "" when Sedona home has no .kit for that checksum, which is the honest + answer for a file pinned to a build this machine does not hold. + """ + if not checksum: + return "" + return kit_versions(kit_name).get(checksum, "") + + def _find_type(qualified_name): if "::" not in qualified_name: return None diff --git a/sax_file.py b/sax_file.py index 626f5c8..0970684 100644 --- a/sax_file.py +++ b/sax_file.py @@ -76,9 +76,11 @@ def reset_to_startup_state(): app_state.canvas_link_map.clear() app_state.final_components_registry.clear() app_state.active_schema_kits.clear() + app_state.pending_schema_kits.clear() undo.reset() app_state.mark_clean() + app_state.clear_status() if app_state.tree is not None: for item in app_state.tree.get_children(): @@ -110,6 +112,8 @@ def open_file(): try: app_state.current_file_path = file_path app_state.active_components_list.clear() + app_state.pending_schema_kits.clear() + app_state.clear_status() app_state.selected_canvas_tag = None app_state.current_sheet_parent = None undo.reset() @@ -204,6 +208,9 @@ def execute_direct_overwrite_save(): try: sync_live_metadata_to_dom() + if not reconcile_schema_kits_before_save(): + return + with open(app_state.current_file_path, "wb") as f: f.write(b'\n') ET.ElementTree(app_state.xml_root_element).write(f, encoding="utf-8", xml_declaration=False) @@ -211,6 +218,7 @@ def execute_direct_overwrite_save(): app_state.mark_clean() undo.mark_saved() refresh_treeview_from_dom(app_state.xml_root_element) + app_state.set_status(f"Saved {os.path.basename(app_state.current_file_path)}.") messagebox.showinfo("Success", f"Direct changes saved onto:\n{os.path.basename(app_state.current_file_path)}") except Exception as e: @@ -250,6 +258,9 @@ def save_file_as(): else: target_path = app_state.current_file_path + if not reconcile_schema_kits_before_save(): + return + with open(target_path, "wb") as f: f.write(b'\n') ET.ElementTree(app_state.xml_root_element).write(f, encoding="utf-8", xml_declaration=False) @@ -261,6 +272,7 @@ def save_file_as(): folder_name = os.path.basename(os.path.dirname(target_path)) file_name = os.path.basename(target_path) + app_state.set_status(f"Saved {file_name}.") messagebox.showinfo("Success", f"File Saved\nFolder: {folder_name}\nFile: {file_name}") @@ -316,44 +328,193 @@ def current_sheet_container(): return app_element if app_element is not None else app_state.xml_root_element -def ensure_kit_declared(kit_name): - """Adds a entry to when placing a component from an undeclared kit. +def note_kit_used(kit_name): + """Records that a placed component needs a kit the file's does not declare. - The new entry carries the checksum of the manifest the palette resolved, so the file - records exactly which kit build the component was authored against. Entries already in - the file are left as they are — a missing checksum there is a deliberate tolerance on - load, not something to backfill. + Nothing is written to the DOM here. The block is reconciled when the file is + saved, which is the one moment that can report the complete kit list the file will + carry rather than interrupting every placement with a partial one. - Returns the element that was added, or None when the kit was already declared. A kit - absent from the schema also has to exist in the device's scode, so the caller warns. + Returns True when this call is what put the kit on the pending list. """ if app_state.xml_root_element is None: - return None + return False + if kit_name in app_state.active_schema_kits: + return False + if kit_name in app_state.pending_schema_kits: + return False + + app_state.pending_schema_kits[kit_name] = palette.resolve_checksum(kit_name) + return True + + +def used_kit_names(): + """Every kit the app's components need, taken from the type of each .""" + names = set() + if app_state.xml_root_element is None: + return names + + for comp in app_state.xml_root_element.iter("comp"): + type_str = comp.get("type") or "" + if "::" in type_str: + names.add(type_str.split("::", 1)[0]) + return names + + +def declare_used_kits(): + """Adds a entry for every used kit the block does not list. + + The DOM is the authority on what is used, not the pending list, so a placement that + was later undone cannot leave a kit declared for nothing. Pending entries only supply + the checksum of the manifest the palette resolved when the component was placed. + + Entries already in the file are left as they are — a missing checksum there is a + deliberate tolerance on load, not something to backfill. + + Returns (added elements in the order written, whitespace to put back if the caller + rolls the insertion out). + """ + if app_state.xml_root_element is None: + return [], [] schema_elem = app_state.xml_root_element.find("schema") if schema_elem is None: + return [], [] + + declared = {kit.get("name") for kit in schema_elem.findall("kit")} + + added = [] + tail_restores = [] + for name in sorted(used_kit_names() - declared): + kit_element = ET.Element("kit") + kit_element.set("name", name) + + checksum = app_state.pending_schema_kits.get(name) or palette.resolve_checksum(name) + if checksum: + kit_element.set("checksum", checksum) + + # Added after the last existing kit. The block is not alphabetical — sys comes + # first by convention — so appending is safer than guessing a sort position. + insert_at = len(list(schema_elem)) + for index, existing in enumerate(list(schema_elem)): + if existing.tag == "kit": + insert_at = index + 1 + schema_elem.insert(insert_at, kit_element) + restore = indent_kit_entry(schema_elem, kit_element) + if restore is not None: + tail_restores.append(restore) + + app_state.active_schema_kits[name] = checksum + added.append(kit_element) + + return added, tail_restores + + +def indent_kit_entry(schema_elem, kit_element): + """Gives a freshly inserted the surrounding block's line break and indent. + + ElementTree keeps whitespace in each element's ``tail``, so an inserted element lands + flush against its neighbour unless the tails are handed on: the new entry takes over + the tail that used to close the block, and the entry before it takes the tail that + separates two kits. + + Handing a tail on means writing to a neighbour, so the pair needed to put that + neighbour back is returned for a caller that may have to undo the insertion. It is + (element, tail) for a block that already held kits, and None for one that did not. + """ + kits = [child for child in schema_elem if child.tag == "kit" and child is not kit_element] + + if not kits: + # A blank block keeps whatever whitespace it had as the new entry's closer, so an + # all-on-one-line still gains a line break rather than losing one. + existing_text = schema_elem.text or "" + kit_element.tail = existing_text if existing_text.strip() == "" and existing_text else "\n" + schema_elem.text = "\n " return None + previous = kits[-1] + restore = (previous, previous.tail) + kit_element.tail = previous.tail + previous.tail = kits[-2].tail if len(kits) >= 2 else "\n " + return restore + + +def format_kit_row(marker, kit_name, checksum): + """One line of the save-time kit report: name, version and checksum. + + Version comes from the .kit filenames in Sedona home rather than from the file, which + records only a checksum; '(unknown)' means this machine holds no .kit for that build. + """ + version = palette.version_for_checksum(kit_name, checksum) or "(unknown)" + return f" {marker} {kit_name} version {version} checksum {checksum or '(none)'}" + + +def describe_schema_kits(added_elements): + """The save-time report: the kits just added, then every kit the file will declare.""" + schema_elem = app_state.xml_root_element.find("schema") + added_names = [kit.get("name") for kit in added_elements] + + kit_word = "kit is" if len(added_names) == 1 else "kits are" + lines = [ + f"The following {kit_word} used by a component but not declared in this app's " + "schema, and has been added to it:", + "", + ] + lines.extend( + format_kit_row("+", name, app_state.active_schema_kits.get(name)) + for name in added_names + ) + lines += [ + "", + "The device's scode must also contain " + + ", ".join(added_names) + + " or the application will not run.", + "", + "Schema kits after this save:", + "", + ] + for kit in schema_elem.findall("kit"): - if kit.get("name") == kit_name: - return None + name = kit.get("name") + if not name: + continue + marker = "+" if name in added_names else "•" + lines.append(format_kit_row(marker, name, kit.get("checksum"))) - kit_element = ET.Element("kit") - kit_element.set("name", kit_name) + return "\n".join(lines) - checksum = palette.resolve_checksum(kit_name) - if checksum: - kit_element.set("checksum", checksum) - # Added after the last existing kit. The block is not alphabetical — sys comes first - # by convention — so appending is safer than guessing a sort position. - insert_at = len(list(schema_elem)) - for index, existing in enumerate(list(schema_elem)): - if existing.tag == "kit": - insert_at = index + 1 - schema_elem.insert(insert_at, kit_element) +def reconcile_schema_kits_before_save(): + """Brings in step with the components, warning before anything is written. - return kit_element + Returns False when the user backs out, in which case the added entries are taken back + out and the save is abandoned. + """ + added, tail_restores = declare_used_kits() + if not added: + return True + + proceed = messagebox.askokcancel( + "Kits added to schema", + describe_schema_kits(added) + "\n\nSave with these kits?", + icon=messagebox.WARNING + ) + + if not proceed: + schema_elem = app_state.xml_root_element.find("schema") + for kit_element in added: + if schema_elem is not None and kit_element in list(schema_elem): + schema_elem.remove(kit_element) + app_state.active_schema_kits.pop(kit_element.get("name"), None) + # Oldest first, so the entry that ends the block gets the last word on its tail. + for element, tail in reversed(tail_restores): + element.tail = tail + return False + + # Declared in the file now, so nothing about them is pending any more. + for kit_element in added: + app_state.pending_schema_kits.pop(kit_element.get("name"), None) + return True def add_component(kit_name, type_name, grid_x, grid_y): @@ -363,21 +524,9 @@ def add_component(kit_name, type_name, grid_x, grid_y): messagebox.showwarning("Add Component", "Open a SAX file first.") return None - kit_added = ensure_kit_declared(kit_name) - if kit_added is not None: - proceed = messagebox.askokcancel( - "Kit not in schema", - f"'{kit_name}' is not declared in this app's schema.\n\n" - f"It will be added, but the device's scode must also contain {kit_name} " - "or the application will not run.\n\nContinue?" - ) - if not proceed: - schema_elem = app_state.xml_root_element.find("schema") - if schema_elem is not None: - schema_elem.remove(kit_added) - return None - - # New kit means new types to resolve before the box can be painted. + if note_kit_used(kit_name): + # An undeclared kit brings types the registry has never resolved, and the box + # cannot be painted until it has them. try: schema.load_schema_kits_and_manifests() except schema.MissingManifestError as missing: @@ -386,11 +535,9 @@ def add_component(kit_name, type_name, grid_x, grid_y): f"'{kit_name}' was not added.\n\n" + schema.describe_missing_manifests(missing) ) - # The kit entry goes back out and the registry is rebuilt from the kits that - # were resolvable a moment ago, so only this placement is abandoned. - schema_elem = app_state.xml_root_element.find("schema") - if schema_elem is not None: - schema_elem.remove(kit_added) + # The kit comes back off the pending list and the registry is rebuilt from + # what was resolvable a moment ago, so only this placement is abandoned. + app_state.pending_schema_kits.pop(kit_name, None) try: schema.load_schema_kits_and_manifests() except schema.MissingManifestError: @@ -414,8 +561,6 @@ def add_component(kit_name, type_name, grid_x, grid_y): undo.push(undo.AddComponentCommand( parent, index, element, - schema_parent=app_state.xml_root_element.find("schema"), - kit_element=kit_added, label=f"add {comp_name}" )) @@ -423,6 +568,17 @@ def add_component(kit_name, type_name, grid_x, grid_y): refresh_treeview_from_dom(app_state.xml_root_element) wiresheet.render_current_sheet() + # The schema is only reconciled on save, so the placement says so here rather than + # letting the save dialog be the first the user hears of it. + if kit_name in app_state.pending_schema_kits: + app_state.set_status( + f"{comp_name} ({kit_name}::{type_name}) added — '{kit_name}' is not in this " + "app's schema and will be declared when you save.", + warn=True + ) + else: + app_state.set_status(f"{comp_name} ({kit_name}::{type_name}) added.") + return element diff --git a/schema.py b/schema.py index 6d84adc..a8b7506 100644 --- a/schema.py +++ b/schema.py @@ -128,8 +128,39 @@ def compile_schema_registry(manifest_paths): app_state.final_components_registry[type_name] = resolved_type_structure +def find_manifest_path(kit_name, checksum): + """The manifest file describing a kit, or None when Sedona home holds none. + + A declared checksum is honoured when its file exists; otherwise the last manifest by + filename is taken, the tolerance that lets a file with no checksums still open. + """ + manifest_dir = os.path.join(app_state.SEDONA_HOME, "manifests", kit_name) + + if checksum: + target_file = os.path.join(manifest_dir, f"{kit_name}-{checksum}.xml") + if os.path.exists(target_file): + return target_file + + if os.path.exists(manifest_dir): + files = [ + f for f in os.listdir(manifest_dir) + if f.startswith(f"{kit_name}-") and f.endswith(".xml") + ] + if files: + files.sort() + return os.path.join(manifest_dir, files[-1]) + + return None + + def load_schema_kits_and_manifests(): - """Scans the schema block of the SAX file and maps/loads corresponding manifest definitions.""" + """Loads the manifests for the file's kits, plus any kit still only pending. + + A component placed from the palette records its kit in ``pending_schema_kits`` rather + than writing to straight away — that reconciliation happens on save. Its + types still have to resolve now, or the box could not be painted, so pending kits are + compiled into the registry alongside the declared ones. + """ app_state.active_schema_kits.clear() if app_state.xml_root_element is None: return @@ -138,29 +169,19 @@ def load_schema_kits_and_manifests(): if schema_elem is None: return - manifest_paths = [] - missing_kits = [] for kit in schema_elem.findall("kit"): name = kit.get("name") - checksum = kit.get("checksum") - if not name: - continue - app_state.active_schema_kits[name] = checksum + if name: + app_state.active_schema_kits[name] = kit.get("checksum") - manifest_dir = os.path.join(app_state.SEDONA_HOME, "manifests", name) - manifest_path = None - - if checksum: - target_file = os.path.join(manifest_dir, f"{name}-{checksum}.xml") - if os.path.exists(target_file): - manifest_path = target_file - - if not manifest_path and os.path.exists(manifest_dir): - files = [f for f in os.listdir(manifest_dir) if f.startswith(f"{name}-") and f.endswith(".xml")] - if files: - files.sort() - manifest_path = os.path.join(manifest_dir, files[-1]) + kits_to_load = dict(app_state.active_schema_kits) + for name, checksum in app_state.pending_schema_kits.items(): + kits_to_load.setdefault(name, checksum) + manifest_paths = [] + missing_kits = [] + for name, checksum in kits_to_load.items(): + manifest_path = find_manifest_path(name, checksum) if manifest_path: manifest_paths.append(manifest_path) else: diff --git a/testSax/checksumYok.sax b/testSax/checksumYok.sax index 8adcfdf..aa42368 100644 --- a/testSax/checksumYok.sax +++ b/testSax/checksumYok.sax @@ -1,1315 +1,1314 @@ - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + - - - + + + - + - + - + - - - + + + - - + + - + - - + + - - - - + + + + - - - + + + - - - + + + - + - - - + + + - + - - + + - + - - - + + + - + - - - + + + - + - - - + + + - - - + + + - + - - - + + + - + - - + + - + - - - + + + - + - - - + + + - + - - - + + + - - - + + + - + - - - + + + - + - - + + - + - - - + + + - + - - - + + + - + - - - + + + - - - + + + - + - - - + + + - + - - + + - + - - - + + + - + - - - + + + - + - - - + + + - - - + + + - + - - - + + + - + - - - + + + - + - - - + + + - + - - - + + + - - + + - - + + - - + + - - + + - + - - - - - - - - + + + + + + + + - + - - - + + + - + - - - + + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - - + + - + - - + + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - - + + - + - - + + - + - - - + + + - + - - + + - + - - - + + + - + - - - - + + + + - + - - + + - + - - - + + + - + - - - + + + - + - - - - + + + + - + - - + + - - - - - - - - + + + + + + + + - + - - - + + + - + - - + + - + - - + + - + - + - + - - + + - + - - + + - + - - + + - + - - + + - - + + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - - + + - + - - + + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - - + + - + - + - + - - + + - + - - + + - + - + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - - + + - - - - - - - - + + + + + + + + - + - - - + + + - + - - + + - + - - + + - + - + - + - - + + - + - - + + - + - - + + - - + + - + - + - + - + - + - - + + - + - + - + - - + + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - - + + - + - + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - - + + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - + - + - + - + - - + + - + - - + + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - - + + - - - + + + - + - - - + + + - + - - + + - + - - + + - - - + + + - + - - - + + + - + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/testSax/checksumYok.sax.bak b/testSax/checksumYok.sax.bak new file mode 100644 index 0000000..3e56e49 --- /dev/null +++ b/testSax/checksumYok.sax.bak @@ -0,0 +1,1315 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/undo.py b/undo.py index 96f3c3d..37d04a1 100644 --- a/undo.py +++ b/undo.py @@ -99,27 +99,24 @@ class MetaMoveCommand: class AddComponentCommand: - """Placing a component, plus the entry that placing it may have required.""" + """Placing a component. - def __init__(self, parent, index, element, schema_parent=None, kit_element=None, label="add"): + Nothing here touches . The kit a placement needs is declared when the file is + saved, from the component types actually in the DOM, so undoing a placement takes its + kit out of that reckoning without this command having to unwind anything. + """ + + def __init__(self, parent, index, element, label="add"): self.parent = parent self.index = index self.element = element - self.schema_parent = schema_parent - self.kit_element = kit_element self.label = label def undo(self): if self.element in list(self.parent): self.parent.remove(self.element) - if self.kit_element is not None and self.schema_parent is not None: - if self.kit_element in list(self.schema_parent): - self.schema_parent.remove(self.kit_element) def redo(self): - if self.kit_element is not None and self.schema_parent is not None: - if self.kit_element not in list(self.schema_parent): - self.schema_parent.append(self.kit_element) if self.element not in list(self.parent): self.parent.insert(self.index, self.element)