Properties view: every slot of a component, edited in the editor pane
Right-click a component on the wiresheet or in the tree and Properties takes
over the editor pane, listing every slot the manifest declares rather than the
handful a box paints. Config slots that are not @readonly are white and
editable; everything else is grey.
Rows are built from the slot's facets, read against SedonaDoc/niagara.html:
@trueText/@falseText, @range, @precision, @unit, and @min/@max, which bound
what Save accepts. Three facets it had wrong:
- null is a value, not an absence. control::WriteFloat pairs 'in' with a
setNull action and the sample app stores val="null" on four of them, so
null now shows, parses back, and reads as @nullText where declared.
- @min/@max on a Buf count characters. TcpDevice.ipAddr is a Buf(17) with
@max 16; a longer string is refused rather than overrunning the device.
- an int's implicit range is noise. [-2147483648 - 2147483647] said no more
than "a whole number", so only byte and short print a range they did not
declare.
meta gets a row of its own: the '>>' opens its user groups as checkboxes and
its position as x and y, since typing 822411269 into a field sets neither.
Save writes only the rows that moved, as one undoable SlotValueCommand, and
refuses the lot if any row fails its range check. An edited field paints the
view in the canvas's own dirty tint, and every way of leaving asks before
discarding.
The file name on the tree's root row goes red while anything is unwritten,
counting both a dirty document and unsaved fields. Two things this needed:
the nav tree runs on its own style, because a themed Treeview's selected
foreground beats a row tag's; and the tag is added to or removed from the row
rather than recoloured, because reconfiguring a tag does not repaint rows that
are already drawn.
Also fixes a latent hazard the panel exposed: Del and Backspace are bound
application-wide, so they now stand down while a text field has focus rather
than deleting the component behind it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -87,6 +87,41 @@ def read_summary_facet(slot_element):
|
||||
return True
|
||||
|
||||
|
||||
def read_all_facets(slot_element):
|
||||
"""Every facet on a slot, as {name: value} with each one typed by its own tag.
|
||||
|
||||
The manifest writes facets as <str name="unit" val="celsius"/>, <int name="max"
|
||||
val="1000"/> and so on, so the tag is the type. The Properties dialog needs the lot —
|
||||
unit, precision, min, max, trueText/falseText, range — and there are few enough of
|
||||
them per slot that carrying the whole dict costs less than a lookup per facet.
|
||||
"""
|
||||
facets = {}
|
||||
facet_element = slot_element.find("facets")
|
||||
if facet_element is None:
|
||||
return facets
|
||||
|
||||
for child in facet_element:
|
||||
name = child.get("name")
|
||||
if not name:
|
||||
continue
|
||||
raw = child.get("val", "")
|
||||
if child.tag == "bool":
|
||||
facets[name] = (raw.strip().lower() == "true")
|
||||
elif child.tag == "int":
|
||||
try:
|
||||
facets[name] = int(raw)
|
||||
except ValueError:
|
||||
facets[name] = raw
|
||||
elif child.tag in ("float", "double"):
|
||||
try:
|
||||
facets[name] = float(raw)
|
||||
except ValueError:
|
||||
facets[name] = raw
|
||||
else:
|
||||
facets[name] = raw
|
||||
return facets
|
||||
|
||||
|
||||
def read_readonly_facet(slot_element):
|
||||
"""Reads a slot's '@readonly' facet. Absent means writable."""
|
||||
facets = slot_element.find("facets")
|
||||
@@ -116,10 +151,11 @@ def get_slots_with_inheritance(type_name, kit_schemas):
|
||||
current_slots.append({
|
||||
"name": slot.get("name"),
|
||||
"type": slot.get("type"),
|
||||
"default": slot.get("default", "false"),
|
||||
"default": slot.get("default"), # absent means "the type's own zero"
|
||||
"flags": slot.get("flags", ""),
|
||||
"readonly": read_readonly_facet(slot),
|
||||
"summary": read_summary_facet(slot),
|
||||
"facets": read_all_facets(slot),
|
||||
"declared_id": int(slot.get("id"))
|
||||
})
|
||||
|
||||
@@ -157,7 +193,8 @@ def compile_schema_registry(manifest_paths):
|
||||
"default": slot_data["default"],
|
||||
"flags": slot_data["flags"],
|
||||
"readonly": slot_data["readonly"],
|
||||
"summary": slot_data["summary"]
|
||||
"summary": slot_data["summary"],
|
||||
"facets": slot_data["facets"]
|
||||
})
|
||||
app_state.final_components_registry[type_name] = resolved_type_structure
|
||||
|
||||
@@ -240,6 +277,17 @@ def get_all_slots_for_type(type_str):
|
||||
]
|
||||
|
||||
|
||||
def get_full_slots_for_type(type_str):
|
||||
"""Every slot of a type, whole, in manifest order — nothing filtered out.
|
||||
|
||||
get_slots_for_type hides most of them, which is right for a wiresheet box and wrong
|
||||
for the Properties dialog: a config slot is only editable somewhere, and this is
|
||||
that somewhere. Copies are handed out so a caller cannot edit the registry in place.
|
||||
"""
|
||||
return [dict(slot) for slot in app_state.final_components_registry.get(type_str, [])
|
||||
if slot["name"]]
|
||||
|
||||
|
||||
def get_slots_for_type(type_str, linked_slots=()):
|
||||
"""
|
||||
Returns the slots worth painting for a given type, prefixed with double-digit
|
||||
|
||||
Reference in New Issue
Block a user