Add link management: create, select, delete with undo/redo

Implement full link workflow between component slots on the wiresheet:
- Link Mark / Link From / Link To context menu items for creating links
- Link dialog with slot type validation and source/target swap button
- Selectable link wires (click to highlight in red) with right-click delete
- Undo/redo support for both add and delete link operations
- Component deletion already cascades to remove related links

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 16:34:17 +03:00
co-authored by Claude Opus 4.6
parent e43c5e17a4
commit 424caf777c
6 changed files with 342 additions and 8 deletions
+33 -3
View File
@@ -180,10 +180,16 @@ def draw_knob(x, y):
)
LINK_SELECT_COLOR = "#e00000"
LINK_SELECT_WIDTH = 2
def draw_links():
"""Paints wires between slots on this sheet and knobs for links leaving it."""
canvas = app_state.canvas
canvas.delete("link")
app_state.canvas_link_map.clear()
app_state.selected_link_tag = None
if app_state.xml_root_element is None:
return
@@ -200,6 +206,7 @@ def draw_links():
boxes = [b for b in (get_box_bounds(t) for t in path_to_tag.values()) if b]
link_index = 0
for link in links_elem.findall("link"):
from_ref = link.get("from", "")
to_ref = link.get("to", "")
@@ -218,19 +225,21 @@ def draw_links():
if src_box is None or dst_box is None:
continue
wire_tag = f"link_wire_{link_index}"
link_index += 1
app_state.canvas_link_map[wire_tag] = link
points = route_link_path(src_box, src_y, dst_box, dst_y, boxes)
flat = [coord for point in points for coord in point]
canvas.create_line(
*flat, fill=LINK_COLOR, width=1,
arrow=tk.LAST, arrowshape=(8, 9, 3), tags=("link",)
arrow=tk.LAST, arrowshape=(8, 9, 3), tags=("link", wire_tag)
)
elif src_tag:
# Source is here, target is on another sheet.
src_box, src_y = resolve_slot_anchor(src_tag, from_slot)
if src_box is not None:
draw_knob(src_box[2] + KNOB_RADIUS, src_y)
elif dst_tag:
# Target is here, source is on another sheet.
dst_box, dst_y = resolve_slot_anchor(dst_tag, to_slot)
if dst_box is not None:
draw_knob(dst_box[0] - KNOB_RADIUS, dst_y)
@@ -240,6 +249,27 @@ def draw_links():
canvas.tag_lower("link", "component")
def select_link(wire_tag):
"""Highlights a link wire in red."""
canvas = app_state.canvas
deselect_link()
if wire_tag and wire_tag in app_state.canvas_link_map:
canvas.itemconfig(wire_tag, fill=LINK_SELECT_COLOR, width=LINK_SELECT_WIDTH)
canvas.tag_raise(wire_tag)
app_state.selected_link_tag = wire_tag
def deselect_link():
"""Resets the selected link wire back to its default colour."""
canvas = app_state.canvas
if app_state.selected_link_tag:
try:
canvas.itemconfig(app_state.selected_link_tag, fill=LINK_COLOR, width=1)
except tk.TclError:
pass
app_state.selected_link_tag = None
# --- Component painting ---
def render_components(components_to_render):