From ed79e9af72db3b544d424fa44f8dc48b64e038c0 Mon Sep 17 00:00:00 2001 From: Arda Aydin Date: Fri, 4 Sep 2026 23:41:30 +0300 Subject: [PATCH] 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 --- README.md | 8 +++++ app_state.py | 2 +- main.py | 97 +++++++++++++++++++++++++--------------------------- 3 files changed, 55 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 8fa8dfe..ec9e0cd 100644 --- a/README.md +++ b/README.md @@ -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 ""** alongside Delete diff --git a/app_state.py b/app_state.py index a17d98a..d6a10ab 100644 --- a/app_state.py +++ b/app_state.py @@ -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 diff --git a/main.py b/main.py index 2926319..13d5952 100644 --- a/main.py +++ b/main.py @@ -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,8 +405,16 @@ def on_tree_right_click(event): if parent_id == "": tree_root_context_menu.post(event.x_root, event.y_root) - else: - tree_comp_context_menu.post(event.x_root, event.y_root) + 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) def on_canvas_click(event): @@ -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.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,8 +720,10 @@ 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() - canvas_context_menu.post(event.x_root, event.y_root) + 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) def on_canvas_drag(event): @@ -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)