Wiresheet: left-drag rectangle selection, group move, Ctrl+A select all

Rectangle selection moves to the left button on bare sheet (Ctrl adds);
right-click is the menu again. Dragging a selected box by its title bar
moves the whole selection as one undoable step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-18 14:29:13 +03:00
co-authored by Claude Opus 5.5
parent 81c4a1d31d
commit 75df6d0a6e
3 changed files with 188 additions and 103 deletions
+51 -33
View File
@@ -11,9 +11,9 @@ either end. While dragging, the cursor points along the flow of the link over a
would take it, and turns into a no-entry sign over one that would not, so a bad drop is
visible before the button is released.
A right press that never moves still opens the component's context menu. A right drag
that starts off a slot row draws a selection rectangle instead, and selects the boxes
wholly inside it on release; with Ctrl held they join the current selection. A left press
A right press that never moves still opens the component's context menu. A left drag
that starts on bare sheet draws a selection rectangle, and selects the boxes wholly
inside it on release; with Ctrl held they join the current selection. A left press
anywhere on a box selects it, but only a press on its title bar moves it; on a slot row
it draws a link instead, as in Workbench.
"""
@@ -41,7 +41,7 @@ CTRL_MASK = 0x0004
_cursors = {} # role -> Tk cursor spec, built on first use
_press = None # pending right press: {"hit", "x", "y", "event"}
_drag = None # live link drag: {"hit", "anchor"}
_marquee = None # right press off a slot: {"x", "y", "cx", "cy", "state", "active"}
_marquee = None # left press on bare sheet: {"x", "y", "cx", "cy", "state", "active"}
_menu_callback = None # opens the ordinary right-click menu for an event
_select_boxes = None # makes a list of comp tags the box selection
_link_dialog = None # opens the Link dialog: (source, target, src_slot, tgt_slot)
@@ -434,9 +434,34 @@ def _finish(event):
return "link"
# Right button: on a slot it drags a link. Anywhere else, dragging pulls out a selection
# rectangle that selects every box wholly inside it on release (Ctrl adds them to the
# current selection). A press that never moves is the menu.
# Right button: on a slot it drags a link, and a press that never moves is the menu.
def on_right_press(event):
if _start_press(event) is None:
_menu_callback(event)
def on_right_motion(event):
_drag_motion(event)
def on_right_release(event):
if _finish(event) == "click":
_menu_callback(event)
# Left button on bare sheet: dragging pulls out a selection rectangle that selects every
# box wholly inside it on release (Ctrl adds them to the current selection).
def _on_bare_sheet(cx, cy):
"""True if no box or link lies under a canvas point."""
canvas = app_state.canvas
for item in canvas.find_overlapping(cx - 3, cy - 3, cx + 3, cy + 3):
tags = canvas.gettags(item)
if "component" in tags or "link" in tags:
return False
return True
def _marquee_motion(event):
"""Draws the selection rectangle once the pointer has moved far enough."""
@@ -486,30 +511,6 @@ def _marquee_finish(event):
return True
def on_right_press(event):
global _marquee
_marquee = None
if _start_press(event) is None:
canvas = app_state.canvas
_marquee = {"x": event.x, "y": event.y, "cx": canvas.canvasx(event.x),
"cy": canvas.canvasy(event.y), "state": event.state, "active": False}
def on_right_motion(event):
if _marquee is not None:
_marquee_motion(event)
else:
_drag_motion(event)
def on_right_release(event):
if _marquee is not None:
if not _marquee_finish(event):
_menu_callback(event)
elif _finish(event) == "click":
_menu_callback(event)
# Left button: anywhere on a box selects it. Only the title bar moves it; a slot row
# drags out a link, and the rest of the box does nothing more than select.
@@ -519,6 +520,19 @@ def _on_title_bar(comp_tag, cy):
def on_left_press(event):
global _marquee
_marquee = None
canvas = app_state.canvas
cx, cy = canvas.canvasx(event.x), canvas.canvasy(event.y)
if _on_bare_sheet(cx, cy):
app_state.drag_data["tag"] = None
if not event.state & CTRL_MASK:
# A plain click on bare sheet still clears the selection, drag or not.
wiresheet.deselect_link()
_select_boxes([])
_marquee = {"x": event.x, "y": event.y, "cx": cx, "cy": cy,
"state": event.state, "active": False}
return
_box_handlers["press"](event) # selection, and arms a box move
if _start_press(event) is not None:
app_state.canvas.delete("slot_hover")
@@ -528,14 +542,18 @@ def on_left_press(event):
def on_left_motion(event):
if _press is not None or _drag is not None:
if _marquee is not None:
_marquee_motion(event)
elif _press is not None or _drag is not None:
_drag_motion(event)
else:
_box_handlers["motion"](event)
def on_left_release(event):
if _finish(event) is None:
if _marquee is not None:
_marquee_finish(event)
elif _finish(event) in (None, "click"):
_box_handlers["release"](event)