Kit manager menu oncesi
This commit is contained in:
+27
-1
@@ -23,6 +23,12 @@ MAX_ALLOWED_BOX_WIDTH = 400
|
|||||||
window = None
|
window = None
|
||||||
canvas = None
|
canvas = None
|
||||||
tree = 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
|
# Maps to hold tracking states
|
||||||
tree_element_map = {} # Treeview item ID -> ET.Element
|
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
|
# Global component slot registries compiled dynamically per file load
|
||||||
final_components_registry = {}
|
final_components_registry = {}
|
||||||
active_schema_kits = {}
|
active_schema_kits = {} # kit name -> checksum, as the open file's <schema> declares them
|
||||||
|
|
||||||
|
# Kits a placed component needs that the file's <schema> 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 <schema> block is reconciled.
|
||||||
|
pending_schema_kits = {}
|
||||||
|
|
||||||
|
|
||||||
def load_preferences():
|
def load_preferences():
|
||||||
@@ -102,6 +113,21 @@ def base_window_title():
|
|||||||
return f"Sedona SAX Tree View Viewer & Program Editor [v{VERSION}]"
|
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():
|
def mark_dirty():
|
||||||
"""Single funnel for 'the document changed', including the unsaved-state tint."""
|
"""Single funnel for 'the document changed', including the unsaved-state tint."""
|
||||||
global is_dirty
|
global is_dirty
|
||||||
|
|||||||
@@ -782,6 +782,12 @@ menu_bar.add_cascade(label="Preferences", menu=menu_prefs)
|
|||||||
|
|
||||||
window.config(menu=menu_bar)
|
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 = ttk.PanedWindow(window, orient=tk.HORIZONTAL)
|
||||||
paned_window.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
paned_window.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||||
|
|
||||||
|
|||||||
+11
@@ -140,6 +140,17 @@ def resolve_checksum(kit_name):
|
|||||||
return filename[len(kit_name) + 1:-4]
|
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):
|
def _find_type(qualified_name):
|
||||||
if "::" not in qualified_name:
|
if "::" not in qualified_name:
|
||||||
return None
|
return None
|
||||||
|
|||||||
+202
-46
@@ -76,9 +76,11 @@ def reset_to_startup_state():
|
|||||||
app_state.canvas_link_map.clear()
|
app_state.canvas_link_map.clear()
|
||||||
app_state.final_components_registry.clear()
|
app_state.final_components_registry.clear()
|
||||||
app_state.active_schema_kits.clear()
|
app_state.active_schema_kits.clear()
|
||||||
|
app_state.pending_schema_kits.clear()
|
||||||
|
|
||||||
undo.reset()
|
undo.reset()
|
||||||
app_state.mark_clean()
|
app_state.mark_clean()
|
||||||
|
app_state.clear_status()
|
||||||
|
|
||||||
if app_state.tree is not None:
|
if app_state.tree is not None:
|
||||||
for item in app_state.tree.get_children():
|
for item in app_state.tree.get_children():
|
||||||
@@ -110,6 +112,8 @@ def open_file():
|
|||||||
try:
|
try:
|
||||||
app_state.current_file_path = file_path
|
app_state.current_file_path = file_path
|
||||||
app_state.active_components_list.clear()
|
app_state.active_components_list.clear()
|
||||||
|
app_state.pending_schema_kits.clear()
|
||||||
|
app_state.clear_status()
|
||||||
app_state.selected_canvas_tag = None
|
app_state.selected_canvas_tag = None
|
||||||
app_state.current_sheet_parent = None
|
app_state.current_sheet_parent = None
|
||||||
undo.reset()
|
undo.reset()
|
||||||
@@ -204,6 +208,9 @@ def execute_direct_overwrite_save():
|
|||||||
try:
|
try:
|
||||||
sync_live_metadata_to_dom()
|
sync_live_metadata_to_dom()
|
||||||
|
|
||||||
|
if not reconcile_schema_kits_before_save():
|
||||||
|
return
|
||||||
|
|
||||||
with open(app_state.current_file_path, "wb") as f:
|
with open(app_state.current_file_path, "wb") as f:
|
||||||
f.write(b'<?xml version="1.0" encoding="UTF-8"?>\n')
|
f.write(b'<?xml version="1.0" encoding="UTF-8"?>\n')
|
||||||
ET.ElementTree(app_state.xml_root_element).write(f, encoding="utf-8", xml_declaration=False)
|
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()
|
app_state.mark_clean()
|
||||||
undo.mark_saved()
|
undo.mark_saved()
|
||||||
refresh_treeview_from_dom(app_state.xml_root_element)
|
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)}")
|
messagebox.showinfo("Success", f"Direct changes saved onto:\n{os.path.basename(app_state.current_file_path)}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -250,6 +258,9 @@ def save_file_as():
|
|||||||
else:
|
else:
|
||||||
target_path = app_state.current_file_path
|
target_path = app_state.current_file_path
|
||||||
|
|
||||||
|
if not reconcile_schema_kits_before_save():
|
||||||
|
return
|
||||||
|
|
||||||
with open(target_path, "wb") as f:
|
with open(target_path, "wb") as f:
|
||||||
f.write(b'<?xml version="1.0" encoding="UTF-8"?>\n')
|
f.write(b'<?xml version="1.0" encoding="UTF-8"?>\n')
|
||||||
ET.ElementTree(app_state.xml_root_element).write(f, encoding="utf-8", xml_declaration=False)
|
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))
|
folder_name = os.path.basename(os.path.dirname(target_path))
|
||||||
file_name = os.path.basename(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}")
|
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
|
return app_element if app_element is not None else app_state.xml_root_element
|
||||||
|
|
||||||
|
|
||||||
def ensure_kit_declared(kit_name):
|
def note_kit_used(kit_name):
|
||||||
"""Adds a <kit> entry to <schema> when placing a component from an undeclared kit.
|
"""Records that a placed component needs a kit the file's <schema> does not declare.
|
||||||
|
|
||||||
The new entry carries the checksum of the manifest the palette resolved, so the file
|
Nothing is written to the DOM here. The <schema> block is reconciled when the file is
|
||||||
records exactly which kit build the component was authored against. Entries already in
|
saved, which is the one moment that can report the complete kit list the file will
|
||||||
the file are left as they are — a missing checksum there is a deliberate tolerance on
|
carry rather than interrupting every placement with a partial one.
|
||||||
load, not something to backfill.
|
|
||||||
|
|
||||||
Returns the element that was added, or None when the kit was already declared. A kit
|
Returns True when this call is what put the kit on the pending list.
|
||||||
absent from the schema also has to exist in the device's scode, so the caller warns.
|
|
||||||
"""
|
"""
|
||||||
if app_state.xml_root_element is None:
|
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 <comp>."""
|
||||||
|
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 <kit> entry for every used kit the <schema> 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")
|
schema_elem = app_state.xml_root_element.find("schema")
|
||||||
if schema_elem is None:
|
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 <kit> 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 <schema></schema> 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
|
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"):
|
for kit in schema_elem.findall("kit"):
|
||||||
if kit.get("name") == kit_name:
|
name = kit.get("name")
|
||||||
return None
|
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")
|
return "\n".join(lines)
|
||||||
kit_element.set("name", kit_name)
|
|
||||||
|
|
||||||
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
|
def reconcile_schema_kits_before_save():
|
||||||
# by convention — so appending is safer than guessing a sort position.
|
"""Brings <schema> in step with the components, warning before anything is written.
|
||||||
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)
|
|
||||||
|
|
||||||
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):
|
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.")
|
messagebox.showwarning("Add Component", "Open a SAX file first.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
kit_added = ensure_kit_declared(kit_name)
|
if note_kit_used(kit_name):
|
||||||
if kit_added is not None:
|
# An undeclared kit brings types the registry has never resolved, and the box
|
||||||
proceed = messagebox.askokcancel(
|
# cannot be painted until it has them.
|
||||||
"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.
|
|
||||||
try:
|
try:
|
||||||
schema.load_schema_kits_and_manifests()
|
schema.load_schema_kits_and_manifests()
|
||||||
except schema.MissingManifestError as missing:
|
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"
|
f"'{kit_name}' was not added.\n\n"
|
||||||
+ schema.describe_missing_manifests(missing)
|
+ schema.describe_missing_manifests(missing)
|
||||||
)
|
)
|
||||||
# The kit entry goes back out and the registry is rebuilt from the kits that
|
# The kit comes back off the pending list and the registry is rebuilt from
|
||||||
# were resolvable a moment ago, so only this placement is abandoned.
|
# what was resolvable a moment ago, so only this placement is abandoned.
|
||||||
schema_elem = app_state.xml_root_element.find("schema")
|
app_state.pending_schema_kits.pop(kit_name, None)
|
||||||
if schema_elem is not None:
|
|
||||||
schema_elem.remove(kit_added)
|
|
||||||
try:
|
try:
|
||||||
schema.load_schema_kits_and_manifests()
|
schema.load_schema_kits_and_manifests()
|
||||||
except schema.MissingManifestError:
|
except schema.MissingManifestError:
|
||||||
@@ -414,8 +561,6 @@ def add_component(kit_name, type_name, grid_x, grid_y):
|
|||||||
|
|
||||||
undo.push(undo.AddComponentCommand(
|
undo.push(undo.AddComponentCommand(
|
||||||
parent, index, element,
|
parent, index, element,
|
||||||
schema_parent=app_state.xml_root_element.find("schema"),
|
|
||||||
kit_element=kit_added,
|
|
||||||
label=f"add {comp_name}"
|
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)
|
refresh_treeview_from_dom(app_state.xml_root_element)
|
||||||
wiresheet.render_current_sheet()
|
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
|
return element
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -128,8 +128,39 @@ def compile_schema_registry(manifest_paths):
|
|||||||
app_state.final_components_registry[type_name] = resolved_type_structure
|
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():
|
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 <schema> kits, plus any kit still only pending.
|
||||||
|
|
||||||
|
A component placed from the palette records its kit in ``pending_schema_kits`` rather
|
||||||
|
than writing to <schema> 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()
|
app_state.active_schema_kits.clear()
|
||||||
if app_state.xml_root_element is None:
|
if app_state.xml_root_element is None:
|
||||||
return
|
return
|
||||||
@@ -138,29 +169,19 @@ def load_schema_kits_and_manifests():
|
|||||||
if schema_elem is None:
|
if schema_elem is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
manifest_paths = []
|
|
||||||
missing_kits = []
|
|
||||||
for kit in schema_elem.findall("kit"):
|
for kit in schema_elem.findall("kit"):
|
||||||
name = kit.get("name")
|
name = kit.get("name")
|
||||||
checksum = kit.get("checksum")
|
if name:
|
||||||
if not name:
|
app_state.active_schema_kits[name] = kit.get("checksum")
|
||||||
continue
|
|
||||||
app_state.active_schema_kits[name] = checksum
|
|
||||||
|
|
||||||
manifest_dir = os.path.join(app_state.SEDONA_HOME, "manifests", name)
|
kits_to_load = dict(app_state.active_schema_kits)
|
||||||
manifest_path = None
|
for name, checksum in app_state.pending_schema_kits.items():
|
||||||
|
kits_to_load.setdefault(name, checksum)
|
||||||
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])
|
|
||||||
|
|
||||||
|
manifest_paths = []
|
||||||
|
missing_kits = []
|
||||||
|
for name, checksum in kits_to_load.items():
|
||||||
|
manifest_path = find_manifest_path(name, checksum)
|
||||||
if manifest_path:
|
if manifest_path:
|
||||||
manifest_paths.append(manifest_path)
|
manifest_paths.append(manifest_path)
|
||||||
else:
|
else:
|
||||||
|
|||||||
+891
-892
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -99,27 +99,24 @@ class MetaMoveCommand:
|
|||||||
|
|
||||||
|
|
||||||
class AddComponentCommand:
|
class AddComponentCommand:
|
||||||
"""Placing a component, plus the <kit> 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 <schema>. 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.parent = parent
|
||||||
self.index = index
|
self.index = index
|
||||||
self.element = element
|
self.element = element
|
||||||
self.schema_parent = schema_parent
|
|
||||||
self.kit_element = kit_element
|
|
||||||
self.label = label
|
self.label = label
|
||||||
|
|
||||||
def undo(self):
|
def undo(self):
|
||||||
if self.element in list(self.parent):
|
if self.element in list(self.parent):
|
||||||
self.parent.remove(self.element)
|
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):
|
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):
|
if self.element not in list(self.parent):
|
||||||
self.parent.insert(self.index, self.element)
|
self.parent.insert(self.index, self.element)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user