Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e587a111dc | ||
|
|
e2cd13b9a0 |
@@ -44,6 +44,9 @@ selected_canvas_tag = None # Tracks the currently clicked/highlighted canvas
|
||||
selected_tree_item_id = None # Tracks the currently right-clicked Treeview item
|
||||
link_mark_element = None # Component marked as a link endpoint via "Link Mark"
|
||||
selected_link_tag = None # Canvas tag of the currently selected (red-highlighted) link wire
|
||||
unroutable_link_count = 0 # Links drawn as knobs because no wire could avoid a component
|
||||
meta_scale = DEFAULT_GRID_SIZE # Pixels per meta coordinate step on the painted sheet
|
||||
knob_link_ends = {} # wire tag -> (x1, y1, x2, y2) for a link shown as two knobs
|
||||
canvas_link_map = {} # Canvas link tag -> ET.Element (<link>)
|
||||
xml_root_element = None # Keep a persistent root reference to redraw tree layouts cleanly
|
||||
current_sheet_parent = None # Element whose children are painted; None means the app root
|
||||
|
||||
@@ -42,6 +42,9 @@ def do_save(event=None):
|
||||
return "break"
|
||||
|
||||
|
||||
NL = chr(10) # newline, for the multi-line message boxes
|
||||
|
||||
|
||||
def do_tidy_layout(event=None):
|
||||
"""Menu command: re-flow the open sheet so nothing overlaps."""
|
||||
if not app_state.canvas_comp_map:
|
||||
@@ -49,15 +52,28 @@ def do_tidy_layout(event=None):
|
||||
return "break"
|
||||
|
||||
moved, clamped = wiresheet.tidy_layout()
|
||||
multiplier = app_state.meta_scale // app_state.GRID_SIZE
|
||||
|
||||
if moved == 0:
|
||||
messagebox.showinfo("Tidy Layout", "Nothing to move — no boxes overlap.")
|
||||
elif clamped:
|
||||
messagebox.showwarning(
|
||||
"Tidy Layout",
|
||||
f"Repositioned {moved} component(s).\n\n"
|
||||
f"{clamped} hit the coordinate limit of {wiresheet.META_COORD_MAX} grid units "
|
||||
"and were clamped, because meta stores x and y in one byte each. "
|
||||
"Lower the grid size in Preferences to fit more in."
|
||||
f"{clamped} still hit the coordinate limit of {wiresheet.META_COORD_MAX} "
|
||||
"steps, because meta stores x and y in one byte each. This sheet is already "
|
||||
f"laid out at {multiplier} grid squares per step, the widest meta can reach, "
|
||||
"so it does not fit however it is spaced. Split it across sub-sheets."
|
||||
)
|
||||
elif multiplier > 1:
|
||||
messagebox.showinfo(
|
||||
"Tidy Layout",
|
||||
f"Repositioned {moved} component(s)." + NL + NL +
|
||||
f"This sheet needs more width than meta's {wiresheet.META_COORD_MAX} "
|
||||
f"coordinate steps allow at one grid square each, so it is laid out at "
|
||||
f"{multiplier} grid squares per step ({app_state.meta_scale} px). Boxes "
|
||||
f"settle on every {multiplier} grid lines rather than every one. Your grid "
|
||||
"size in Preferences is unchanged."
|
||||
)
|
||||
return "break"
|
||||
|
||||
@@ -179,6 +195,12 @@ def on_canvas_click(event):
|
||||
app_state.drag_data["y"] = cy
|
||||
app_state.selected_canvas_tag = group_tag
|
||||
|
||||
# Where the box starts out. The drag is measured as an offset from this pair
|
||||
# rather than accumulated per motion event, so repeated snapping cannot drift.
|
||||
origin = wiresheet.get_box_origin(group_tag)
|
||||
app_state.drag_data["box_x"] = origin[0] if origin else None
|
||||
app_state.drag_data["box_y"] = origin[1] if origin else None
|
||||
|
||||
# Snapshot the pre-drag meta so the move can be pushed as one undoable step.
|
||||
dragged = app_state.canvas_comp_map.get(group_tag)
|
||||
meta_prop = dragged.find("prop[@name='meta']") if dragged is not None else None
|
||||
@@ -444,18 +466,40 @@ def on_canvas_drag(event):
|
||||
return
|
||||
|
||||
canvas = app_state.canvas
|
||||
group_tag = app_state.drag_data["tag"]
|
||||
cx = canvas.canvasx(event.x)
|
||||
cy = canvas.canvasy(event.y)
|
||||
|
||||
delta_x = cx - app_state.drag_data["x"]
|
||||
delta_y = cy - app_state.drag_data["y"]
|
||||
|
||||
canvas.move(app_state.drag_data["tag"], delta_x, delta_y)
|
||||
box_x = app_state.drag_data.get("box_x")
|
||||
origin = wiresheet.get_box_origin(group_tag)
|
||||
|
||||
if box_x is None or origin is None:
|
||||
# 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["y"] = cy
|
||||
wiresheet.draw_links()
|
||||
return
|
||||
|
||||
# Wires are not part of the dragged group, so they are re-routed against the new positions.
|
||||
# Snap to whole meta steps, which is the grid on an ordinary sheet and a multiple of
|
||||
# it on one too wide for meta's range -- a position between steps cannot be stored.
|
||||
step = app_state.meta_scale
|
||||
margin = wiresheet.layout_margin(step)
|
||||
|
||||
# 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"])
|
||||
target_x = max(margin, round(wanted_x / step) * step)
|
||||
target_y = max(margin, round(wanted_y / step) * step)
|
||||
|
||||
shift_x = target_x - origin[0]
|
||||
shift_y = target_y - origin[1]
|
||||
|
||||
if shift_x or shift_y:
|
||||
canvas.move(group_tag, shift_x, shift_y)
|
||||
# Wires are not part of the dragged group, so they are re-routed against the
|
||||
# new positions.
|
||||
wiresheet.draw_links()
|
||||
|
||||
|
||||
@@ -464,16 +508,16 @@ def on_canvas_release(event):
|
||||
return
|
||||
|
||||
canvas = app_state.canvas
|
||||
grid_size = app_state.GRID_SIZE
|
||||
step = app_state.meta_scale
|
||||
group_tag = app_state.drag_data["tag"]
|
||||
outline_item = canvas.find_withtag(f"{group_tag} && block_outline")
|
||||
origin = wiresheet.get_box_origin(group_tag)
|
||||
|
||||
if outline_item:
|
||||
coords = canvas.coords(outline_item[0])
|
||||
current_x, current_y = coords[0], coords[1]
|
||||
if origin is not None:
|
||||
current_x, current_y = origin
|
||||
|
||||
snapped_x = round(current_x / grid_size) * grid_size
|
||||
snapped_y = round(current_y / grid_size) * grid_size
|
||||
margin = wiresheet.layout_margin(step)
|
||||
snapped_x = max(margin, round(current_x / step) * step)
|
||||
snapped_y = max(margin, round(current_y / step) * step)
|
||||
|
||||
shift_x = snapped_x - current_x
|
||||
shift_y = snapped_y - current_y
|
||||
@@ -482,8 +526,8 @@ def on_canvas_release(event):
|
||||
canvas.move(group_tag, shift_x, shift_y)
|
||||
app_state.mark_dirty()
|
||||
|
||||
raw_x = int(snapped_x / grid_size)
|
||||
raw_y = int(snapped_y / grid_size)
|
||||
raw_x = min(wiresheet.META_COORD_MAX, int(round(snapped_x / step)))
|
||||
raw_y = min(wiresheet.META_COORD_MAX, int(round(snapped_y / step)))
|
||||
|
||||
comp_element = app_state.canvas_comp_map.get(group_tag)
|
||||
if comp_element is not None:
|
||||
@@ -591,6 +635,22 @@ def open_preferences_dialog():
|
||||
f"{app_state.MAX_ALLOWED_BOX_WIDTH} pixels."
|
||||
)
|
||||
|
||||
# The width cap is applied after each box is fitted to its text, so a cap
|
||||
# that is not a multiple of the grid leaves the widest boxes off-grid.
|
||||
proposed = wiresheet.suggest_box_width_for_grid(width, val)
|
||||
if proposed != width:
|
||||
if not messagebox.askyesno(
|
||||
"Box Width Off Grid",
|
||||
f"A box width of {width} px is not a multiple of the {val} px grid, "
|
||||
f"so the widest boxes would not line up with it.\n\n"
|
||||
f"Change the box width to {proposed} px?",
|
||||
parent=dialog,
|
||||
):
|
||||
return
|
||||
width = proposed
|
||||
entry_box_width.delete(0, tk.END)
|
||||
entry_box_width.insert(0, str(width))
|
||||
|
||||
app_state.BOX_WIDTH = width
|
||||
app_state.GRID_SIZE = val
|
||||
app_state.ADD_TIMESTAMP = var_timestamp.get()
|
||||
@@ -693,6 +753,21 @@ def verify_startup_configurations():
|
||||
"before trying to view or load profile source files."
|
||||
)
|
||||
open_preferences_dialog()
|
||||
return
|
||||
|
||||
# editor.properties can be hand-edited, so the pair is reconciled here too.
|
||||
proposed = wiresheet.suggest_box_width_for_grid(app_state.BOX_WIDTH, app_state.GRID_SIZE)
|
||||
if proposed != app_state.BOX_WIDTH and messagebox.askyesno(
|
||||
"Box Width Off Grid",
|
||||
f"The saved box width of {app_state.BOX_WIDTH} px is not a multiple of the "
|
||||
f"{app_state.GRID_SIZE} px grid, so the widest boxes will not line up with it.\n\n"
|
||||
f"Change the box width to {proposed} px?",
|
||||
):
|
||||
app_state.BOX_WIDTH = proposed
|
||||
app_state.save_preferences()
|
||||
wiresheet.draw_grid()
|
||||
if app_state.active_components_list:
|
||||
wiresheet.render_components(app_state.active_components_list)
|
||||
|
||||
|
||||
# --- UI Setup ---
|
||||
@@ -767,6 +842,8 @@ menu_edit.add_command(label="Undo", command=do_undo, accelerator=f"{accel_mod}+Z
|
||||
menu_edit.add_command(label="Redo", command=do_redo, accelerator=f"{accel_mod}+Y")
|
||||
menu_edit.add_separator()
|
||||
menu_edit.add_command(label="Delete Component", command=delete_selected_component, accelerator="Del")
|
||||
menu_edit.add_separator()
|
||||
menu_edit.add_command(label="Tidy Layout", command=do_tidy_layout)
|
||||
menu_bar.add_cascade(label="Edit", menu=menu_edit)
|
||||
|
||||
menu_kits = tk.Menu(menu_bar, tearoff=0)
|
||||
@@ -776,10 +853,6 @@ menu_kits.add_command(
|
||||
)
|
||||
menu_bar.add_cascade(label="Kits", menu=menu_kits)
|
||||
|
||||
menu_layout = tk.Menu(menu_bar, tearoff=0)
|
||||
menu_layout.add_command(label="Tidy Layout", command=do_tidy_layout)
|
||||
menu_bar.add_cascade(label="Layout", menu=menu_layout)
|
||||
|
||||
menu_app_info = tk.Menu(menu_bar, tearoff=0)
|
||||
menu_app_info.add_command(label="App Info", command=show_app_info)
|
||||
menu_bar.add_cascade(label="App", menu=menu_app_info)
|
||||
@@ -993,9 +1066,10 @@ def on_palette_release(event):
|
||||
drop_x = canvas.canvasx(event.x_root - canvas.winfo_rootx())
|
||||
drop_y = canvas.canvasy(event.y_root - canvas.winfo_rooty())
|
||||
|
||||
grid_size = app_state.GRID_SIZE
|
||||
grid_x = max(0, min(wiresheet.META_COORD_MAX, round(drop_x / grid_size)))
|
||||
grid_y = max(0, min(wiresheet.META_COORD_MAX, round(drop_y / grid_size)))
|
||||
step = app_state.meta_scale
|
||||
min_step = wiresheet.layout_margin(step) // step
|
||||
grid_x = max(min_step, min(wiresheet.META_COORD_MAX, round(drop_x / step)))
|
||||
grid_y = max(min_step, min(wiresheet.META_COORD_MAX, round(drop_y / step)))
|
||||
|
||||
sax_file.add_component(kit_name, type_name, grid_x, grid_y)
|
||||
|
||||
|
||||
+744
-132
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user