Tree rows get the same right-click actions as canvas boxes

Right-clicking a component in the navigation tree offered Delete Component
alone, while the same component on the wiresheet also offered Link Mark and
Link From / Link To. Which list a component was picked from should not change
what can be done with it, so both popups are now built by
fill_component_popup.

Its entries close over the component instead of reading a selection back,
which is what lets each popup act on the row or box that was actually clicked
-- the canvas commands went through app_state.selected_canvas_tag, which a
tree row never sets. do_link_mark, do_link_from_mark, do_link_to_mark and
delete_tree_selected_component fall out with it, and Link Mark now says in the
status bar what it marked, since nothing else on screen shows that.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-04 23:41:30 +03:00
co-authored by Claude Opus 5
parent a1f8059c49
commit ed79e9af72
3 changed files with 55 additions and 52 deletions
+8
View File
@@ -128,6 +128,14 @@ element has left the document, keeping the position on screen. Walking the histo
the tree selection, which is what paints the sheet, and is suppressed from recording so a
walk cannot rewrite the trail it is following.
### The component popup
Right-clicking a component offers the same actions from the wiresheet and from the
navigation tree -- Delete Component, Link Mark, and Link From / Link To naming the marked
component once one is marked. `fill_component_popup` builds both, and its entries close
over the component rather than reading a selection back, which is what lets each popup act
on the row or box that was actually clicked. The tree's root row keeps its own popup.
### Following a link to its other sheet
Right-clicking a wire or a knob offers **Go to Link Wire Sheet "<name>"** alongside Delete
+1 -1
View File
@@ -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.035"
VERSION = "0.0.0.036"
PROPERTIES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "editor.properties")
DEFAULT_GRID_SIZE = 10
DEFAULT_ADD_TIMESTAMP = True
+43 -48
View File
@@ -392,16 +392,6 @@ def delete_selected_component(event=None):
sax_file.execute_cascading_deletion(comp_element)
def delete_tree_selected_component():
"""Triggered by the context popup menu to delete an item selected in the Treeview architecture."""
if not app_state.selected_tree_item_id:
return
comp_element = app_state.tree_element_map.get(app_state.selected_tree_item_id)
if comp_element is not None:
sax_file.execute_cascading_deletion(comp_element)
def on_tree_right_click(event):
"""Intercepts right-clicks on the treeview architecture to render branch context panels."""
tree = app_state.tree
@@ -415,7 +405,15 @@ def on_tree_right_click(event):
if parent_id == "":
tree_root_context_menu.post(event.x_root, event.y_root)
else:
return
comp_element = app_state.tree_element_map.get(item_id)
if comp_element is None:
return
# The same actions the canvas offers, on the component the tree row names: which
# list a component was picked from should not change what can be done with it.
fill_component_popup(tree_comp_context_menu, comp_element)
tree_comp_context_menu.post(event.x_root, event.y_root)
@@ -464,13 +462,14 @@ def on_canvas_click(event):
wiresheet.redraw_links()
def do_link_mark():
"""Marks the currently selected canvas component as a link endpoint."""
if not app_state.selected_canvas_tag:
def set_link_mark(comp):
"""Marks a component as one end of a link still to be made."""
if comp is None:
return
comp = app_state.canvas_comp_map.get(app_state.selected_canvas_tag)
if comp is not None:
app_state.link_mark_element = comp
app_state.set_status(
f"Marked '{comp.get('name', '?')}' — right-click the other component to link it."
)
def _slot_linkable(slot_flags, slot_type, readonly, is_target):
@@ -668,22 +667,33 @@ def open_link_dialog(source_element, target_element):
ttk.Button(btn_frame, text="Cancel", width=10, command=dialog.destroy).pack(side=tk.LEFT, padx=5)
def do_link_from_mark():
"""Link From: marked component is source, right-clicked component is target."""
if app_state.link_mark_element is None or not app_state.selected_canvas_tag:
return
target = app_state.canvas_comp_map.get(app_state.selected_canvas_tag)
if target is not None:
open_link_dialog(app_state.link_mark_element, target)
def fill_component_popup(menu, comp):
"""Fills a popup with the actions that apply to one component.
Shared by the canvas and the navigation tree, so a component offers the same
actions wherever it was right-clicked. The entries close over the component rather
than reading a selection back, which is what lets the two popups act on the thing
that was actually clicked.
"""
menu.delete(0, tk.END)
menu.add_command(
label="Delete Component",
command=lambda: sax_file.execute_cascading_deletion(comp),
)
menu.add_separator()
menu.add_command(label="Link Mark", command=lambda: set_link_mark(comp))
def do_link_to_mark():
"""Link To: right-clicked component is source, marked component is target."""
if app_state.link_mark_element is None or not app_state.selected_canvas_tag:
return
source = app_state.canvas_comp_map.get(app_state.selected_canvas_tag)
if source is not None:
open_link_dialog(source, app_state.link_mark_element)
marked = app_state.link_mark_element
if marked is not None:
mark_name = marked.get("name", "?")
menu.add_command(
label=f'Link From "{mark_name}"',
command=lambda: open_link_dialog(marked, comp),
)
menu.add_command(
label=f'Link To "{mark_name}"',
command=lambda: open_link_dialog(comp, marked),
)
def on_canvas_right_click(event):
@@ -710,7 +720,9 @@ def on_canvas_right_click(event):
app_state.selected_canvas_tag = group_tag
canvas.itemconfig(f"{group_tag} && block_outline", outline="#1f4e79", width=3)
_rebuild_canvas_context_menu()
comp_element = app_state.canvas_comp_map.get(group_tag)
if comp_element is not None:
fill_component_popup(canvas_context_menu, comp_element)
canvas_context_menu.post(event.x_root, event.y_root)
@@ -1381,24 +1393,7 @@ def _rebuild_link_context_menu():
)
def _rebuild_canvas_context_menu():
"""Rebuilds the canvas context menu with dynamic Link From/Link To labels."""
canvas_context_menu.delete(0, tk.END)
canvas_context_menu.add_command(label="Delete Component", command=delete_selected_component)
canvas_context_menu.add_separator()
canvas_context_menu.add_command(label="Link Mark", command=do_link_mark)
if app_state.link_mark_element is not None:
mark_name = app_state.link_mark_element.get("name", "?")
canvas_context_menu.add_command(
label=f'Link From "{mark_name}"', command=do_link_from_mark
)
canvas_context_menu.add_command(
label=f'Link To "{mark_name}"', command=do_link_to_mark
)
tree_comp_context_menu = tk.Menu(window, tearoff=0)
tree_comp_context_menu.add_command(label="Delete Component", command=delete_tree_selected_component)
tree_root_context_menu = tk.Menu(window, tearoff=0)
tree_root_context_menu.add_command(label="Save", command=sax_file.execute_direct_overwrite_save)