Add Page Editor toolbar toggles and widen the default window

Edit mode, snap to grid and the widgets/properties pane are now toggles on
the main toolbar while the Page Editor is open. Outside edit mode the page
and the property sheet are read-only; drags snap to the 10px grid when
snapping is on. The default window grows by the right pane's width so the
canvas is not squeezed when the pane shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 17:31:20 +03:00
co-authored by Claude Opus 5.5
parent e5cb04b8cf
commit 7b8c48f7c2
4 changed files with 159 additions and 35 deletions
+47 -3
View File
@@ -574,6 +574,10 @@ class OxWidgetPropertiesPanel:
self.repaint = repaint
self.comp = None
self.rows = []
# Off until the Page Editor's edit mode is switched on: every field is then shown
# but none of them takes input, and no write reaches `commit`.
self.editable = False
self._controls = [] # (widget, state when editable) for every input on the sheet
self.frame = ttk.Frame(parent)
self._name_font = tkfont.Font(family="Segoe UI", size=9)
@@ -637,6 +641,7 @@ class OxWidgetPropertiesPanel:
for child in self._sheet.winfo_children():
child.destroy()
self.rows = []
self._controls = []
self._status_var.set("")
if self.comp is None:
@@ -649,9 +654,36 @@ class OxWidgetPropertiesPanel:
for grid_row, prop in enumerate(build_props(self.comp)):
self.rows.append(self._build_row(grid_row, prop))
self._apply_editable()
self.reload()
self._canvas.yview_moveto(0)
def set_editable(self, editable):
"""Edit mode on or off: the fields take input, or only show their values."""
self.editable = editable
self._apply_editable()
def _apply_editable(self):
for widget, editable_state in self._controls:
state = editable_state if self.editable else "disabled"
# Combobox first: it is an Entry too, and "readonly" would still let it pick.
if isinstance(widget, ttk.Combobox):
widget.configure(state=state)
elif isinstance(widget, tk.Entry):
widget.configure(state="normal" if self.editable else "readonly")
else:
widget.state(["!disabled"] if self.editable else ["disabled"])
if self.comp is not None:
self._status_var.set("" if self.editable else
"View mode — switch on edit mode (✏) to change values.")
def _guarded(self, command):
"""`command`, run only while the sheet is editable."""
def run(*args):
if self.editable:
return command(*args)
return run
def flush_edits(self):
"""Commits a typed value still sitting in its field.
@@ -695,7 +727,9 @@ class OxWidgetPropertiesPanel:
return label
def _dots(self, cell, command):
ttk.Button(cell, text="…", width=3, command=command).pack(side=tk.RIGHT, padx=1)
button = ttk.Button(cell, text="…", width=3, command=self._guarded(command))
button.pack(side=tk.RIGHT, padx=1)
self._controls.append((button, None))
def _build_readonly(self, cell, row):
self._value_label(cell, row, READONLY_FG)
@@ -712,6 +746,10 @@ class OxWidgetPropertiesPanel:
def commit(_event=None):
text = var.get()
if not self.editable:
if text != row["loaded"]:
var.set(row["loaded"])
return
if text != row["loaded"]:
# Taken as loaded first: the write repaints, and the repaint must not
# find this same edit still pending and commit it a second time.
@@ -739,6 +777,7 @@ class OxWidgetPropertiesPanel:
highlightbackground=SHEET_BG)
entry.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=2, pady=1)
self._bind_commit(entry, row)
self._controls.append((entry, None))
def _build_image(self, cell, row):
combo = ttk.Combobox(cell, textvariable=row["var"], font=self._name_font,
@@ -746,6 +785,7 @@ class OxWidgetPropertiesPanel:
combo.pack(fill=tk.BOTH, expand=True, padx=1)
commit = self._bind_commit(combo, row)
combo.bind("<<ComboboxSelected>>", commit)
self._controls.append((combo, "normal"))
def _build_choice(self, cell, row):
prop, var = row["prop"], row["var"]
@@ -759,6 +799,7 @@ class OxWidgetPropertiesPanel:
combo.bind("<<ComboboxSelected>>", selected)
row["load"] = lambda: var.set(prop.read())
self._controls.append((combo, "readonly"))
def _build_colour(self, cell, row):
prop, var = row["prop"], row["var"]
@@ -781,14 +822,14 @@ class OxWidgetPropertiesPanel:
self._dots(cell, choose)
self._value_label(cell, row, READONLY_FG)
swatch.bind("<Button-1>", choose)
swatch.bind("<Button-1>", self._guarded(choose))
row["load"] = load
def _build_dialog(self, cell, row):
opener = getattr(self, f"_open_{row['prop'].dialog}_dialog")
self._dots(cell, opener)
label = self._value_label(cell, row)
label.bind("<Double-Button-1>", lambda e: opener())
label.bind("<Double-Button-1>", self._guarded(lambda e: opener()))
row["load"] = lambda: row["var"].set(row["prop"].read())
def _build_link(self, cell, row):
@@ -815,6 +856,9 @@ class OxWidgetPropertiesPanel:
# ── writing ───────────────────────────────────────────────────
def _write(self, row, text):
if not self.editable:
row["load"]()
return
values, error = row["prop"].write(text)
if error:
self._status_var.set(f"{row['prop'].label}: {error}")