Add Rename to the component right-click menu
Rename is offered from both the canvas and the navigation tree. Names are checked against Sedona's Component.checkName rules (1-7 ASCII chars, a letter first, then letters, digits or '_') and must be unique among siblings. In file mode link paths through the old name are rewritten and the rename is one undo step; in live mode it goes to the device via Sox and warns if the device rejects it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+86
@@ -344,6 +344,92 @@ def save_file_as():
|
||||
MAX_NAME_LENGTH = 7 # every component name in a Sedona app observes this
|
||||
|
||||
|
||||
def check_component_name(name, parent=None, element=None):
|
||||
"""Why `name` cannot name a component, or None if it can.
|
||||
|
||||
Mirrors Sedona's Component.checkName -- 1 to 7 ASCII characters, a letter first, then
|
||||
letters, digits or '_' -- plus the uniqueness among siblings that OfflineApp enforces.
|
||||
`element` is the component being renamed, which may keep its own name.
|
||||
"""
|
||||
if len(name) == 0:
|
||||
return "The name is empty."
|
||||
if len(name) > MAX_NAME_LENGTH:
|
||||
return f"The name is longer than {MAX_NAME_LENGTH} characters."
|
||||
for i, c in enumerate(name):
|
||||
if "A" <= c <= "Z" or "a" <= c <= "z":
|
||||
continue
|
||||
if i == 0:
|
||||
return "The name must start with a letter (A-Z or a-z)."
|
||||
if "0" <= c <= "9" or c == "_":
|
||||
continue
|
||||
return f"'{c}' is not allowed; use only letters, digits and '_'."
|
||||
if parent is not None:
|
||||
for sibling in parent:
|
||||
if sibling.tag == "comp" and sibling is not element and sibling.get("name") == name:
|
||||
return f"'{name}' is already used by another component in the same parent."
|
||||
return None
|
||||
|
||||
|
||||
def rename_component(comp_element, new_name):
|
||||
"""Renames a component and rewrites the link paths that ran through it.
|
||||
|
||||
Returns True when the rename happened. The name is validated here as well as in the
|
||||
dialog, so nothing can put a name into the file that Sedona would refuse to load.
|
||||
"""
|
||||
root = app_state.xml_root_element
|
||||
if root is None or comp_element.tag != "comp":
|
||||
return False
|
||||
|
||||
old_name = comp_element.get("name", "")
|
||||
if new_name == old_name:
|
||||
return False
|
||||
|
||||
parent = find_parent_element(root, comp_element)
|
||||
err = check_component_name(new_name, parent, comp_element)
|
||||
if err is not None:
|
||||
messagebox.showwarning("Rename Component", f"'{new_name}' is not a valid name.\n\n{err}")
|
||||
return False
|
||||
|
||||
if app_state.live_mode:
|
||||
if not live_hooks.live_rename_component(comp_element, new_name):
|
||||
live_hooks.warn_failure("Rename Component",
|
||||
f"{old_name} was not renamed on the device")
|
||||
return False
|
||||
|
||||
old_path = get_component_path(comp_element, root)
|
||||
new_path = old_path[:-len(old_name)] + new_name
|
||||
|
||||
# Links address components by path, so every one through the old name moves with it.
|
||||
link_changes = []
|
||||
links_elem = root.find("links")
|
||||
if links_elem is not None:
|
||||
for link in links_elem:
|
||||
if link.tag != "link":
|
||||
continue
|
||||
for attr in ("from", "to"):
|
||||
ref = link.get(attr, "")
|
||||
if ref.startswith(old_path + ".") or ref.startswith(old_path + "/"):
|
||||
link_changes.append((link, attr, ref, new_path + ref[len(old_path):]))
|
||||
|
||||
comp_element.set("name", new_name)
|
||||
for link, attr, _, new_ref in link_changes:
|
||||
link.set(attr, new_ref)
|
||||
|
||||
if not app_state.live_mode:
|
||||
undo.push(undo.RenameComponentCommand(
|
||||
comp_element, old_name, new_name, link_changes,
|
||||
label=f"rename {old_name} to {new_name}"
|
||||
))
|
||||
|
||||
app_state.mark_dirty()
|
||||
refresh_treeview_from_dom(root)
|
||||
wiresheet.render_current_sheet()
|
||||
|
||||
where = " on the device" if app_state.live_mode else ""
|
||||
app_state.set_status(f"{old_name} renamed to {new_name}{where}.")
|
||||
return True
|
||||
|
||||
|
||||
def allocate_component_id():
|
||||
"""Lowest unused id, matching how this app's ids are dense with no gaps."""
|
||||
used = set()
|
||||
|
||||
Reference in New Issue
Block a user