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 configparser
|
||||||
import os
|
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")
|
PROPERTIES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "editor.properties")
|
||||||
DEFAULT_GRID_SIZE = 10
|
DEFAULT_GRID_SIZE = 10
|
||||||
DEFAULT_ADD_TIMESTAMP = True
|
DEFAULT_ADD_TIMESTAMP = True
|
||||||
|
|||||||
+79
-8
@@ -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
|
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.
|
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
|
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.
|
it draws a link instead, as in Workbench.
|
||||||
"""
|
"""
|
||||||
@@ -33,11 +35,15 @@ HOVER_COLOR = "#1f4e79"
|
|||||||
DROP_OK_COLOR = "#2e9d3a"
|
DROP_OK_COLOR = "#2e9d3a"
|
||||||
DROP_BAD_COLOR = "#d62828"
|
DROP_BAD_COLOR = "#d62828"
|
||||||
RUBBER_COLOR = "#2f6fba"
|
RUBBER_COLOR = "#2f6fba"
|
||||||
|
MARQUEE_COLOR = "#2f6fba"
|
||||||
|
CTRL_MASK = 0x0004
|
||||||
|
|
||||||
_cursors = {} # role -> Tk cursor spec, built on first use
|
_cursors = {} # role -> Tk cursor spec, built on first use
|
||||||
_press = None # pending right press: {"hit", "x", "y", "event"}
|
_press = None # pending right press: {"hit", "x", "y", "event"}
|
||||||
_drag = None # live link drag: {"hit", "anchor"}
|
_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
|
_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)
|
_link_dialog = None # opens the Link dialog: (source, target, src_slot, tgt_slot)
|
||||||
|
|
||||||
|
|
||||||
@@ -301,14 +307,16 @@ def _anchor(hit):
|
|||||||
|
|
||||||
def clear():
|
def clear():
|
||||||
"""Drops any hover ring, rubber band and drag state; called on every repaint."""
|
"""Drops any hover ring, rubber band and drag state; called on every repaint."""
|
||||||
global _press, _drag
|
global _press, _drag, _marquee
|
||||||
canvas = app_state.canvas
|
canvas = app_state.canvas
|
||||||
if canvas is None:
|
if canvas is None:
|
||||||
return
|
return
|
||||||
canvas.delete("slot_hover")
|
canvas.delete("slot_hover")
|
||||||
canvas.delete("link_rubber")
|
canvas.delete("link_rubber")
|
||||||
|
canvas.delete("marquee")
|
||||||
_press = None
|
_press = None
|
||||||
_drag = None
|
_drag = None
|
||||||
|
_marquee = None
|
||||||
_set_cursor(None)
|
_set_cursor(None)
|
||||||
|
|
||||||
|
|
||||||
@@ -426,19 +434,79 @@ def _finish(event):
|
|||||||
return "link"
|
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):
|
def on_right_press(event):
|
||||||
|
global _marquee
|
||||||
|
_marquee = None
|
||||||
if _start_press(event) is 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):
|
def on_right_motion(event):
|
||||||
|
if _marquee is not None:
|
||||||
|
_marquee_motion(event)
|
||||||
|
else:
|
||||||
_drag_motion(event)
|
_drag_motion(event)
|
||||||
|
|
||||||
|
|
||||||
def on_right_release(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)
|
_menu_callback(event)
|
||||||
|
|
||||||
|
|
||||||
@@ -474,16 +542,19 @@ def on_left_release(event):
|
|||||||
_box_handlers = {}
|
_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.
|
"""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
|
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;
|
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
|
_menu_callback = menu_callback
|
||||||
_link_dialog = link_dialog
|
_link_dialog = link_dialog
|
||||||
|
_select_boxes = select_boxes
|
||||||
_box_handlers.update(press=box_press, motion=box_motion, release=box_release)
|
_box_handlers.update(press=box_press, motion=box_motion, release=box_release)
|
||||||
canvas.bind("<Motion>", on_motion)
|
canvas.bind("<Motion>", on_motion)
|
||||||
canvas.bind("<Leave>", on_leave, add="+")
|
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.
|
# selects and moves boxes and a plain right click is the menu, as before.
|
||||||
link_drag.install(canvas, on_canvas_right_click,
|
link_drag.install(canvas, on_canvas_right_click,
|
||||||
on_canvas_click, on_canvas_drag, on_canvas_release,
|
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("<ButtonPress-2>", on_canvas_right_click)
|
||||||
canvas.bind("<Double-1>", on_canvas_double_click)
|
canvas.bind("<Double-1>", on_canvas_double_click)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user