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
|
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"
|
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
|
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>)
|
canvas_link_map = {} # Canvas link tag -> ET.Element (<link>)
|
||||||
xml_root_element = None # Keep a persistent root reference to redraw tree layouts cleanly
|
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
|
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"
|
return "break"
|
||||||
|
|
||||||
|
|
||||||
|
NL = chr(10) # newline, for the multi-line message boxes
|
||||||
|
|
||||||
|
|
||||||
def do_tidy_layout(event=None):
|
def do_tidy_layout(event=None):
|
||||||
"""Menu command: re-flow the open sheet so nothing overlaps."""
|
"""Menu command: re-flow the open sheet so nothing overlaps."""
|
||||||
if not app_state.canvas_comp_map:
|
if not app_state.canvas_comp_map:
|
||||||
@@ -49,15 +52,28 @@ def do_tidy_layout(event=None):
|
|||||||
return "break"
|
return "break"
|
||||||
|
|
||||||
moved, clamped = wiresheet.tidy_layout()
|
moved, clamped = wiresheet.tidy_layout()
|
||||||
|
multiplier = app_state.meta_scale // app_state.GRID_SIZE
|
||||||
|
|
||||||
if moved == 0:
|
if moved == 0:
|
||||||
messagebox.showinfo("Tidy Layout", "Nothing to move — no boxes overlap.")
|
messagebox.showinfo("Tidy Layout", "Nothing to move — no boxes overlap.")
|
||||||
elif clamped:
|
elif clamped:
|
||||||
messagebox.showwarning(
|
messagebox.showwarning(
|
||||||
"Tidy Layout",
|
"Tidy Layout",
|
||||||
f"Repositioned {moved} component(s).\n\n"
|
f"Repositioned {moved} component(s).\n\n"
|
||||||
f"{clamped} hit the coordinate limit of {wiresheet.META_COORD_MAX} grid units "
|
f"{clamped} still hit the coordinate limit of {wiresheet.META_COORD_MAX} "
|
||||||
"and were clamped, because meta stores x and y in one byte each. "
|
"steps, because meta stores x and y in one byte each. This sheet is already "
|
||||||
"Lower the grid size in Preferences to fit more in."
|
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"
|
return "break"
|
||||||
|
|
||||||
@@ -179,6 +195,12 @@ def on_canvas_click(event):
|
|||||||
app_state.drag_data["y"] = cy
|
app_state.drag_data["y"] = cy
|
||||||
app_state.selected_canvas_tag = group_tag
|
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.
|
# Snapshot the pre-drag meta so the move can be pushed as one undoable step.
|
||||||
dragged = app_state.canvas_comp_map.get(group_tag)
|
dragged = app_state.canvas_comp_map.get(group_tag)
|
||||||
meta_prop = dragged.find("prop[@name='meta']") if dragged is not None else None
|
meta_prop = dragged.find("prop[@name='meta']") if dragged is not None else None
|
||||||
@@ -444,18 +466,40 @@ 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)
|
||||||
|
|
||||||
delta_x = cx - app_state.drag_data["x"]
|
box_x = app_state.drag_data.get("box_x")
|
||||||
delta_y = cy - app_state.drag_data["y"]
|
origin = wiresheet.get_box_origin(group_tag)
|
||||||
|
|
||||||
canvas.move(app_state.drag_data["tag"], delta_x, delta_y)
|
|
||||||
|
|
||||||
|
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["x"] = cx
|
||||||
app_state.drag_data["y"] = cy
|
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()
|
wiresheet.draw_links()
|
||||||
|
|
||||||
|
|
||||||
@@ -464,16 +508,16 @@ def on_canvas_release(event):
|
|||||||
return
|
return
|
||||||
|
|
||||||
canvas = app_state.canvas
|
canvas = app_state.canvas
|
||||||
grid_size = app_state.GRID_SIZE
|
step = app_state.meta_scale
|
||||||
group_tag = app_state.drag_data["tag"]
|
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:
|
if origin is not None:
|
||||||
coords = canvas.coords(outline_item[0])
|
current_x, current_y = origin
|
||||||
current_x, current_y = coords[0], coords[1]
|
|
||||||
|
|
||||||
snapped_x = round(current_x / grid_size) * grid_size
|
margin = wiresheet.layout_margin(step)
|
||||||
snapped_y = round(current_y / grid_size) * grid_size
|
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_x = snapped_x - current_x
|
||||||
shift_y = snapped_y - current_y
|
shift_y = snapped_y - current_y
|
||||||
@@ -482,8 +526,8 @@ def on_canvas_release(event):
|
|||||||
canvas.move(group_tag, shift_x, shift_y)
|
canvas.move(group_tag, shift_x, shift_y)
|
||||||
app_state.mark_dirty()
|
app_state.mark_dirty()
|
||||||
|
|
||||||
raw_x = int(snapped_x / grid_size)
|
raw_x = min(wiresheet.META_COORD_MAX, int(round(snapped_x / step)))
|
||||||
raw_y = int(snapped_y / grid_size)
|
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 not None:
|
||||||
@@ -591,6 +635,22 @@ def open_preferences_dialog():
|
|||||||
f"{app_state.MAX_ALLOWED_BOX_WIDTH} pixels."
|
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.BOX_WIDTH = width
|
||||||
app_state.GRID_SIZE = val
|
app_state.GRID_SIZE = val
|
||||||
app_state.ADD_TIMESTAMP = var_timestamp.get()
|
app_state.ADD_TIMESTAMP = var_timestamp.get()
|
||||||
@@ -693,6 +753,21 @@ def verify_startup_configurations():
|
|||||||
"before trying to view or load profile source files."
|
"before trying to view or load profile source files."
|
||||||
)
|
)
|
||||||
open_preferences_dialog()
|
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 ---
|
# --- 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_command(label="Redo", command=do_redo, accelerator=f"{accel_mod}+Y")
|
||||||
menu_edit.add_separator()
|
menu_edit.add_separator()
|
||||||
menu_edit.add_command(label="Delete Component", command=delete_selected_component, accelerator="Del")
|
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_bar.add_cascade(label="Edit", menu=menu_edit)
|
||||||
|
|
||||||
menu_kits = tk.Menu(menu_bar, tearoff=0)
|
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_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 = tk.Menu(menu_bar, tearoff=0)
|
||||||
menu_app_info.add_command(label="App Info", command=show_app_info)
|
menu_app_info.add_command(label="App Info", command=show_app_info)
|
||||||
menu_bar.add_cascade(label="App", menu=menu_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_x = canvas.canvasx(event.x_root - canvas.winfo_rootx())
|
||||||
drop_y = canvas.canvasy(event.y_root - canvas.winfo_rooty())
|
drop_y = canvas.canvasy(event.y_root - canvas.winfo_rooty())
|
||||||
|
|
||||||
grid_size = app_state.GRID_SIZE
|
step = app_state.meta_scale
|
||||||
grid_x = max(0, min(wiresheet.META_COORD_MAX, round(drop_x / grid_size)))
|
min_step = wiresheet.layout_margin(step) // step
|
||||||
grid_y = max(0, min(wiresheet.META_COORD_MAX, round(drop_y / grid_size)))
|
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)
|
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