Wiresheet: right-drag rectangle selects boxes, Ctrl adds to selection
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -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.176"
|
||||
VERSION = "0.0.0.177"
|
||||
PROPERTIES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "editor.properties")
|
||||
DEFAULT_GRID_SIZE = 10
|
||||
DEFAULT_ADD_TIMESTAMP = True
|
||||
|
||||
+80
-9
@@ -11,7 +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 left press
|
||||
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
|
||||
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.
|
||||
"""
|
||||
@@ -33,11 +35,15 @@ HOVER_COLOR = "#1f4e79"
|
||||
DROP_OK_COLOR = "#2e9d3a"
|
||||
DROP_BAD_COLOR = "#d62828"
|
||||
RUBBER_COLOR = "#2f6fba"
|
||||
MARQUEE_COLOR = "#2f6fba"
|
||||
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"}
|
||||
_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)
|
||||
|
||||
|
||||
@@ -301,14 +307,16 @@ def _anchor(hit):
|
||||
|
||||
def clear():
|
||||
"""Drops any hover ring, rubber band and drag state; called on every repaint."""
|
||||
global _press, _drag
|
||||
global _press, _drag, _marquee
|
||||
canvas = app_state.canvas
|
||||
if canvas is None:
|
||||
return
|
||||
canvas.delete("slot_hover")
|
||||
canvas.delete("link_rubber")
|
||||
canvas.delete("marquee")
|
||||
_press = None
|
||||
_drag = None
|
||||
_marquee = None
|
||||
_set_cursor(None)
|
||||
|
||||
|
||||
@@ -426,19 +434,79 @@ def _finish(event):
|
||||
return "link"
|
||||
|
||||
|
||||
# Right button: on a slot it drags a link, and a press that never moves is the menu.
|
||||
# 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.
|
||||
|
||||
def _marquee_motion(event):
|
||||
"""Draws the selection rectangle once the pointer has moved far enough."""
|
||||
global _marquee
|
||||
if _marquee is None:
|
||||
return
|
||||
if not _marquee["active"]:
|
||||
if abs(event.x - _marquee["x"]) < DRAG_THRESHOLD and \
|
||||
abs(event.y - _marquee["y"]) < DRAG_THRESHOLD:
|
||||
return
|
||||
_marquee["active"] = True
|
||||
app_state.canvas.delete("slot_hover")
|
||||
canvas = app_state.canvas
|
||||
canvas.delete("marquee")
|
||||
canvas.create_rectangle(_marquee["cx"], _marquee["cy"],
|
||||
canvas.canvasx(event.x), canvas.canvasy(event.y),
|
||||
outline=MARQUEE_COLOR, width=1, dash=(4, 2),
|
||||
tags=("marquee",))
|
||||
canvas.tag_raise("marquee")
|
||||
|
||||
|
||||
def _marquee_finish(event):
|
||||
"""Ends a rectangle drag. Returns True if it selected, False if it was a click."""
|
||||
global _marquee
|
||||
marquee, _marquee = _marquee, None
|
||||
canvas = app_state.canvas
|
||||
canvas.delete("marquee")
|
||||
if marquee is None or not marquee["active"]:
|
||||
return False
|
||||
cx, cy = canvas.canvasx(event.x), canvas.canvasy(event.y)
|
||||
left, right = sorted((marquee["cx"], cx))
|
||||
top, bottom = sorted((marquee["cy"], cy))
|
||||
inside = []
|
||||
for comp_tag in app_state.canvas_comp_map:
|
||||
box = wiresheet.get_box_bounds(comp_tag)
|
||||
if box and left <= box[0] and top <= box[1] and box[2] <= right and box[3] <= bottom:
|
||||
inside.append(comp_tag)
|
||||
adding = (marquee["state"] | event.state) & CTRL_MASK
|
||||
wiresheet.deselect_link() # links and boxes are never selected together
|
||||
if adding:
|
||||
current = [t for t in app_state.selected_canvas_tags if t not in inside]
|
||||
_select_boxes(current + inside)
|
||||
else:
|
||||
_select_boxes(inside)
|
||||
count = len(app_state.selected_canvas_tags)
|
||||
app_state.set_status(f"{count} component{'s' if count != 1 else ''} selected.")
|
||||
return True
|
||||
|
||||
|
||||
def on_right_press(event):
|
||||
global _marquee
|
||||
_marquee = None
|
||||
if _start_press(event) is None:
|
||||
_menu_callback(event)
|
||||
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):
|
||||
_drag_motion(event)
|
||||
if _marquee is not None:
|
||||
_marquee_motion(event)
|
||||
else:
|
||||
_drag_motion(event)
|
||||
|
||||
|
||||
def on_right_release(event):
|
||||
if _finish(event) == "click":
|
||||
if _marquee is not None:
|
||||
if not _marquee_finish(event):
|
||||
_menu_callback(event)
|
||||
elif _finish(event) == "click":
|
||||
_menu_callback(event)
|
||||
|
||||
|
||||
@@ -474,16 +542,19 @@ def on_left_release(event):
|
||||
_box_handlers = {}
|
||||
|
||||
|
||||
def install(canvas, menu_callback, box_press, box_motion, box_release, link_dialog):
|
||||
def install(canvas, menu_callback, box_press, box_motion, box_release, link_dialog,
|
||||
select_boxes):
|
||||
"""Binds hover, link dragging on both buttons, and the box handlers it wraps.
|
||||
|
||||
menu_callback opens the context menu; box_* are the sheet's own left-button
|
||||
select-and-move handlers, which still run whenever a press is not on a slot row;
|
||||
link_dialog opens the Link dialog when a link is dropped on a title bar.
|
||||
link_dialog opens the Link dialog when a link is dropped on a title bar;
|
||||
select_boxes makes a list of comp tags the selection after a rectangle drag.
|
||||
"""
|
||||
global _menu_callback, _link_dialog
|
||||
global _menu_callback, _link_dialog, _select_boxes
|
||||
_menu_callback = menu_callback
|
||||
_link_dialog = link_dialog
|
||||
_select_boxes = select_boxes
|
||||
_box_handlers.update(press=box_press, motion=box_motion, release=box_release)
|
||||
canvas.bind("<Motion>", on_motion)
|
||||
canvas.bind("<Leave>", on_leave, add="+")
|
||||
|
||||
@@ -3761,7 +3761,7 @@ canvas.bind("<Configure>", lambda e: wiresheet.draw_grid())
|
||||
# selects and moves boxes and a plain right click is the menu, as before.
|
||||
link_drag.install(canvas, on_canvas_right_click,
|
||||
on_canvas_click, on_canvas_drag, on_canvas_release,
|
||||
link_dialog=open_link_dialog)
|
||||
link_dialog=open_link_dialog, select_boxes=select_canvas_boxes)
|
||||
canvas.bind("<ButtonPress-2>", on_canvas_right_click)
|
||||
canvas.bind("<Double-1>", on_canvas_double_click)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user