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:
+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.177"
|
VERSION = "0.0.0.178"
|
||||||
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
|
||||||
|
|||||||
+51
-33
@@ -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
|
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 right drag
|
A right press that never moves still opens the component's context menu. A left drag
|
||||||
that starts off a slot row draws a selection rectangle instead, and selects the boxes
|
that starts on bare sheet draws a selection rectangle, and selects the boxes wholly
|
||||||
wholly inside it on release; with Ctrl held they join the current selection. A left press
|
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.
|
||||||
"""
|
"""
|
||||||
@@ -41,7 +41,7 @@ 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"}
|
_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
|
_menu_callback = None # opens the ordinary right-click menu for an event
|
||||||
_select_boxes = None # makes a list of comp tags the box selection
|
_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)
|
||||||
@@ -434,9 +434,34 @@ def _finish(event):
|
|||||||
return "link"
|
return "link"
|
||||||
|
|
||||||
|
|
||||||
# Right button: on a slot it drags a link. Anywhere else, dragging pulls out a selection
|
# Right button: on a slot it drags a link, and a press that never moves is the menu.
|
||||||
# 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 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):
|
def _marquee_motion(event):
|
||||||
"""Draws the selection rectangle once the pointer has moved far enough."""
|
"""Draws the selection rectangle once the pointer has moved far enough."""
|
||||||
@@ -486,30 +511,6 @@ def _marquee_finish(event):
|
|||||||
return True
|
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
|
# 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.
|
# 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):
|
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
|
_box_handlers["press"](event) # selection, and arms a box move
|
||||||
if _start_press(event) is not None:
|
if _start_press(event) is not None:
|
||||||
app_state.canvas.delete("slot_hover")
|
app_state.canvas.delete("slot_hover")
|
||||||
@@ -528,14 +542,18 @@ def on_left_press(event):
|
|||||||
|
|
||||||
|
|
||||||
def on_left_motion(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)
|
_drag_motion(event)
|
||||||
else:
|
else:
|
||||||
_box_handlers["motion"](event)
|
_box_handlers["motion"](event)
|
||||||
|
|
||||||
|
|
||||||
def on_left_release(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)
|
_box_handlers["release"](event)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1344,6 +1344,24 @@ def select_link_on_open_sheet(link_elem):
|
|||||||
app_state.set_status("That link is not painted on this sheet.", warn=True)
|
app_state.set_status("That link is not painted on this sheet.", warn=True)
|
||||||
|
|
||||||
|
|
||||||
|
def select_all_boxes(event=None):
|
||||||
|
"""Ctrl+A: selects every box on the open wiresheet."""
|
||||||
|
# Bound application-wide like Del, so a text field keeps its own select-all.
|
||||||
|
if editor_panel_showing():
|
||||||
|
return None
|
||||||
|
focused = app_state.window.focus_get()
|
||||||
|
if focused is not None:
|
||||||
|
if focused.winfo_toplevel() is not app_state.window:
|
||||||
|
return None
|
||||||
|
if focused.winfo_class() in TEXT_ENTRY_CLASSES:
|
||||||
|
return None
|
||||||
|
wiresheet.deselect_link() # links and boxes are never selected together
|
||||||
|
select_canvas_boxes(list(app_state.canvas_comp_map))
|
||||||
|
count = len(app_state.selected_canvas_tags)
|
||||||
|
app_state.set_status(f"{count} component{'s' if count != 1 else ''} selected.")
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
|
||||||
def delete_selected_component(event=None):
|
def delete_selected_component(event=None):
|
||||||
"""Deletes the highlighted canvas component (or selected link) and all its descendants recursively."""
|
"""Deletes the highlighted canvas component (or selected link) and all its descendants recursively."""
|
||||||
# Del is bound application-wide, so it also reaches a Properties field being typed in
|
# Del is bound application-wide, so it also reaches a Properties field being typed in
|
||||||
@@ -1532,6 +1550,15 @@ def on_canvas_click(event):
|
|||||||
_toggle_canvas_box(group_tag)
|
_toggle_canvas_box(group_tag)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Pressing one of several selected boxes keeps them all selected, so a drag moves
|
||||||
|
# them together; a press that never moves narrows the selection to it on release.
|
||||||
|
keep_group = (group_tag and not wire_tag and group_tag in app_state.selected_canvas_tags
|
||||||
|
and len(app_state.selected_canvas_tags) > 1)
|
||||||
|
app_state.drag_data["group"] = []
|
||||||
|
app_state.drag_data["moved"] = False
|
||||||
|
app_state.drag_data["collapse_to"] = group_tag if keep_group else None
|
||||||
|
|
||||||
|
if not keep_group:
|
||||||
select_canvas_boxes([])
|
select_canvas_boxes([])
|
||||||
if wire_tag:
|
if wire_tag:
|
||||||
wiresheet.select_link(wire_tag)
|
wiresheet.select_link(wire_tag)
|
||||||
@@ -1541,20 +1568,26 @@ def on_canvas_click(event):
|
|||||||
app_state.drag_data["tag"] = group_tag
|
app_state.drag_data["tag"] = group_tag
|
||||||
app_state.drag_data["x"] = cx
|
app_state.drag_data["x"] = cx
|
||||||
app_state.drag_data["y"] = cy
|
app_state.drag_data["y"] = cy
|
||||||
|
if keep_group:
|
||||||
|
# Del and the menus still act on the box pressed last.
|
||||||
|
app_state.selected_canvas_tag = group_tag
|
||||||
|
else:
|
||||||
select_canvas_boxes([group_tag])
|
select_canvas_boxes([group_tag])
|
||||||
|
|
||||||
# Where the box starts out. The drag is measured as an offset from this pair
|
# Where each moving box starts out. The drag is measured as an offset from these
|
||||||
# rather than accumulated per motion event, so repeated snapping cannot drift.
|
# rather than accumulated per motion event, so repeated snapping cannot drift.
|
||||||
origin = wiresheet.get_box_origin(group_tag)
|
# Each entry also snapshots the pre-drag meta, so the move is one undoable step.
|
||||||
app_state.drag_data["box_x"] = origin[0] if origin else None
|
moving = list(app_state.selected_canvas_tags) if keep_group else [group_tag]
|
||||||
app_state.drag_data["box_y"] = origin[1] if origin else None
|
for tag in moving:
|
||||||
|
origin = wiresheet.get_box_origin(tag)
|
||||||
# Snapshot the pre-drag meta so the move can be pushed as one undoable step.
|
comp = app_state.canvas_comp_map.get(tag)
|
||||||
dragged = app_state.canvas_comp_map.get(group_tag)
|
meta_prop = comp.find("prop[@name='meta']") if comp is not None else None
|
||||||
meta_prop = dragged.find("prop[@name='meta']") if dragged is not None else None
|
app_state.drag_data["group"].append({
|
||||||
app_state.drag_data["meta_before"] = meta_prop.get("val") if meta_prop is not None else None
|
"tag": tag, "origin": origin,
|
||||||
|
"meta_before": meta_prop.get("val") if meta_prop is not None else None,
|
||||||
# Bring the clicked box clear of any neighbour it happens to sit under.
|
})
|
||||||
|
# Bring the moving boxes clear of any neighbour they happen to sit under.
|
||||||
|
canvas.tag_raise(tag)
|
||||||
canvas.tag_raise(group_tag)
|
canvas.tag_raise(group_tag)
|
||||||
wiresheet.redraw_links()
|
wiresheet.redraw_links()
|
||||||
|
|
||||||
@@ -2071,18 +2104,17 @@ def on_canvas_drag(event):
|
|||||||
return
|
return
|
||||||
|
|
||||||
canvas = app_state.canvas
|
canvas = app_state.canvas
|
||||||
group_tag = app_state.drag_data["tag"]
|
|
||||||
cx = canvas.canvasx(event.x)
|
cx = canvas.canvasx(event.x)
|
||||||
cy = canvas.canvasy(event.y)
|
cy = canvas.canvasy(event.y)
|
||||||
|
group = app_state.drag_data.get("group") or []
|
||||||
|
|
||||||
box_x = app_state.drag_data.get("box_x")
|
if not group or any(entry["origin"] is None for entry in group):
|
||||||
origin = wiresheet.get_box_origin(group_tag)
|
# Nothing to align against, so the boxes just follow the pointer one to one.
|
||||||
|
for tag in [entry["tag"] for entry in group] or [app_state.drag_data["tag"]]:
|
||||||
if box_x is None or origin is None:
|
canvas.move(tag, cx - app_state.drag_data["x"], cy - app_state.drag_data["y"])
|
||||||
# Nothing to align against, so the box just follows the pointer one to one.
|
|
||||||
canvas.move(group_tag, cx - app_state.drag_data["x"], cy - app_state.drag_data["y"])
|
|
||||||
app_state.drag_data["x"] = cx
|
app_state.drag_data["x"] = cx
|
||||||
app_state.drag_data["y"] = cy
|
app_state.drag_data["y"] = cy
|
||||||
|
app_state.drag_data["moved"] = True
|
||||||
wiresheet.redraw_links()
|
wiresheet.redraw_links()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2091,40 +2123,66 @@ def on_canvas_drag(event):
|
|||||||
# Canvas coordinates carry the sheet's zoom, so the step and the margin do too.
|
# Canvas coordinates carry the sheet's zoom, so the step and the margin do too.
|
||||||
step = app_state.meta_scale * app_state.zoom
|
step = app_state.meta_scale * app_state.zoom
|
||||||
margin = wiresheet.layout_margin(app_state.meta_scale) * app_state.zoom
|
margin = wiresheet.layout_margin(app_state.meta_scale) * app_state.zoom
|
||||||
|
# A box's corner also stays on the sheet, the furthest meta can store it.
|
||||||
# Follow the pointer, but land the box on a step every step of the way, and keep it
|
|
||||||
# out of the margin strip along the top and left canvas edges.
|
|
||||||
wanted_x = box_x + (cx - app_state.drag_data["x"])
|
|
||||||
wanted_y = app_state.drag_data["box_y"] + (cy - app_state.drag_data["y"])
|
|
||||||
# The box's corner also stays on the sheet, the furthest meta can store it.
|
|
||||||
limit = wiresheet.META_COORD_MAX * step
|
limit = wiresheet.META_COORD_MAX * step
|
||||||
target_x = min(limit, max(margin, round(wanted_x / step) * step))
|
|
||||||
target_y = min(limit, max(margin, round(wanted_y / step) * step))
|
|
||||||
|
|
||||||
|
# Follow the pointer, but keep every moving box out of the margin strip along the
|
||||||
|
# top and left canvas edges and on the sheet -- the whole group stops together, so
|
||||||
|
# the boxes keep their places relative to one another.
|
||||||
|
dx = cx - app_state.drag_data["x"]
|
||||||
|
dy = cy - app_state.drag_data["y"]
|
||||||
|
xs = [entry["origin"][0] for entry in group]
|
||||||
|
ys = [entry["origin"][1] for entry in group]
|
||||||
|
dx = min(limit - max(xs), max(margin - min(xs), dx))
|
||||||
|
dy = min(limit - max(ys), max(margin - min(ys), dy))
|
||||||
|
|
||||||
|
shifted = False
|
||||||
|
for entry in group:
|
||||||
|
# Land each box on a step every step of the way.
|
||||||
|
start_x, start_y = entry["origin"]
|
||||||
|
target_x = min(limit, max(margin, round((start_x + dx) / step) * step))
|
||||||
|
target_y = min(limit, max(margin, round((start_y + dy) / step) * step))
|
||||||
|
origin = wiresheet.get_box_origin(entry["tag"])
|
||||||
|
if origin is None:
|
||||||
|
continue
|
||||||
shift_x = target_x - origin[0]
|
shift_x = target_x - origin[0]
|
||||||
shift_y = target_y - origin[1]
|
shift_y = target_y - origin[1]
|
||||||
|
|
||||||
if shift_x or shift_y:
|
if shift_x or shift_y:
|
||||||
canvas.move(group_tag, shift_x, shift_y)
|
canvas.move(entry["tag"], shift_x, shift_y)
|
||||||
# Wires are not part of the dragged group, so they are re-routed against the
|
shifted = True
|
||||||
|
|
||||||
|
if shifted:
|
||||||
|
app_state.drag_data["moved"] = True
|
||||||
|
# Wires are not part of the dragged groups, so they are re-routed against the
|
||||||
# new positions.
|
# new positions.
|
||||||
wiresheet.redraw_links()
|
wiresheet.redraw_links()
|
||||||
|
|
||||||
|
|
||||||
def on_canvas_release(event):
|
def on_canvas_release(event):
|
||||||
|
collapse_to = app_state.drag_data.pop("collapse_to", None)
|
||||||
|
if collapse_to and not app_state.drag_data.get("moved"):
|
||||||
|
# A click that never moved on one of several selected boxes picks it alone.
|
||||||
|
select_canvas_boxes([collapse_to])
|
||||||
|
|
||||||
if not app_state.drag_data["tag"]:
|
if not app_state.drag_data["tag"]:
|
||||||
return
|
return
|
||||||
|
|
||||||
canvas = app_state.canvas
|
canvas = app_state.canvas
|
||||||
step = app_state.meta_scale * app_state.zoom
|
step = app_state.meta_scale * app_state.zoom
|
||||||
group_tag = app_state.drag_data["tag"]
|
|
||||||
origin = wiresheet.get_box_origin(group_tag)
|
|
||||||
|
|
||||||
if origin is not None:
|
|
||||||
current_x, current_y = origin
|
|
||||||
|
|
||||||
margin = wiresheet.layout_margin(app_state.meta_scale) * app_state.zoom
|
margin = wiresheet.layout_margin(app_state.meta_scale) * app_state.zoom
|
||||||
limit = wiresheet.META_COORD_MAX * step
|
limit = wiresheet.META_COORD_MAX * step
|
||||||
|
group = app_state.drag_data.get("group") or [
|
||||||
|
{"tag": app_state.drag_data["tag"], "meta_before": None}]
|
||||||
|
|
||||||
|
changes = []
|
||||||
|
failed = []
|
||||||
|
for entry in group:
|
||||||
|
group_tag = entry["tag"]
|
||||||
|
origin = wiresheet.get_box_origin(group_tag)
|
||||||
|
if origin is None:
|
||||||
|
continue
|
||||||
|
current_x, current_y = origin
|
||||||
|
|
||||||
snapped_x = min(limit, max(margin, round(current_x / step) * step))
|
snapped_x = min(limit, max(margin, round(current_x / step) * step))
|
||||||
snapped_y = min(limit, max(margin, round(current_y / step) * step))
|
snapped_y = min(limit, max(margin, round(current_y / step) * step))
|
||||||
|
|
||||||
@@ -2139,15 +2197,17 @@ def on_canvas_release(event):
|
|||||||
raw_y = min(wiresheet.META_COORD_MAX, int(round(snapped_y / step)))
|
raw_y = min(wiresheet.META_COORD_MAX, int(round(snapped_y / step)))
|
||||||
|
|
||||||
comp_element = app_state.canvas_comp_map.get(group_tag)
|
comp_element = app_state.canvas_comp_map.get(group_tag)
|
||||||
if comp_element is not None:
|
if comp_element is None:
|
||||||
|
continue
|
||||||
meta_str = None
|
meta_str = None
|
||||||
orig_prop = comp_element.find("prop[@name='meta']")
|
orig_prop = comp_element.find("prop[@name='meta']")
|
||||||
if orig_prop is not None:
|
if orig_prop is not None:
|
||||||
meta_str = orig_prop.get("val")
|
meta_str = orig_prop.get("val")
|
||||||
|
|
||||||
orig_x, orig_y, _, _, _, _, _, _ = read_meta_property(meta_str)
|
orig_x, orig_y, _, _, _, _, _, _ = read_meta_property(meta_str)
|
||||||
if orig_x != raw_x or orig_y != raw_y:
|
if orig_x == raw_x and orig_y == raw_y:
|
||||||
meta_before = app_state.drag_data.get("meta_before", meta_str)
|
continue
|
||||||
|
meta_before = entry.get("meta_before") or meta_str
|
||||||
update_meta_property(comp_element, meta_str, raw_x, raw_y)
|
update_meta_property(comp_element, meta_str, raw_x, raw_y)
|
||||||
if app_state.live_mode:
|
if app_state.live_mode:
|
||||||
new_meta = comp_element.find("prop[@name='meta']")
|
new_meta = comp_element.find("prop[@name='meta']")
|
||||||
@@ -2158,21 +2218,26 @@ def on_canvas_release(event):
|
|||||||
remove_prop(comp_element, "meta")
|
remove_prop(comp_element, "meta")
|
||||||
else:
|
else:
|
||||||
set_prop_value(comp_element, "meta", meta_str)
|
set_prop_value(comp_element, "meta", meta_str)
|
||||||
app_state.drag_data["tag"] = None
|
failed.append(comp_element.get("name", "?"))
|
||||||
wiresheet.render_current_sheet()
|
continue
|
||||||
live_hooks.warn_failure(
|
changes.append((comp_element, meta_before, raw_x, raw_y))
|
||||||
"Move", f"{comp_element.get('name', 'The component')} was not "
|
|
||||||
"moved on the device")
|
|
||||||
return
|
|
||||||
command_class = (undo.LiveMetaMoveCommand if app_state.live_mode
|
|
||||||
else undo.MetaMoveCommand)
|
|
||||||
undo.push(command_class(
|
|
||||||
[(comp_element, meta_before, raw_x, raw_y)],
|
|
||||||
label=f"move {comp_element.get('name', 'component')}"
|
|
||||||
))
|
|
||||||
app_state.mark_dirty()
|
|
||||||
|
|
||||||
app_state.drag_data["tag"] = None
|
app_state.drag_data["tag"] = None
|
||||||
|
app_state.drag_data["group"] = []
|
||||||
|
|
||||||
|
if changes:
|
||||||
|
command_class = (undo.LiveMetaMoveCommand if app_state.live_mode
|
||||||
|
else undo.MetaMoveCommand)
|
||||||
|
label = (f"move {changes[0][0].get('name', 'component')}" if len(changes) == 1
|
||||||
|
else f"move {len(changes)} components")
|
||||||
|
undo.push(command_class(changes, label=label))
|
||||||
|
app_state.mark_dirty()
|
||||||
|
|
||||||
|
if failed:
|
||||||
|
wiresheet.render_current_sheet()
|
||||||
|
live_hooks.warn_failure("Move", f"{', '.join(failed)} not moved on the device")
|
||||||
|
return
|
||||||
|
|
||||||
wiresheet.redraw_links()
|
wiresheet.redraw_links()
|
||||||
|
|
||||||
|
|
||||||
@@ -3249,6 +3314,8 @@ SHORTCUTS = [
|
|||||||
("9", do_zoom_fit),
|
("9", do_zoom_fit),
|
||||||
("KP_9", do_zoom_fit),
|
("KP_9", do_zoom_fit),
|
||||||
("p", wiresheet.print_sheet),
|
("p", wiresheet.print_sheet),
|
||||||
|
("a", select_all_boxes),
|
||||||
|
("A", select_all_boxes),
|
||||||
]
|
]
|
||||||
for key, handler in SHORTCUTS:
|
for key, handler in SHORTCUTS:
|
||||||
window.bind_all(f"<Command-{key}>", handler)
|
window.bind_all(f"<Command-{key}>", handler)
|
||||||
|
|||||||
Reference in New Issue
Block a user