The save and file-open requests used their own fixed resend intervals
(5 s / 15 s) and the hello asked for 256-byte packets. Ontrol's Sedona
Java stack does neither and works with the P-ION: the standard backoff
(1, 2, 4, 8 s) sends a busy device a few copies with the same seqNum,
which its receive window drops. Both are removed.
Also fixes a race in SendWindow.send(): the packet entered the window
before its payload and sent_time were set, so housekeeping could resend
it with an empty payload, and the device would then drop the real one
as a duplicate.
Tested on a P-ION at 192.168.1.252, App > Get with File: save 3.2 s
(2 resends), app.sab + kits.scode in 11.3 s with 500-byte chunks
(was 14.6 s at 256), device stayed up, files identical to a 256-byte
backup.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A real P-ION (fw 1.32) restarted on every Sox file get. Ontrol's Niagara 4
driver gets app.sab from the same device, so its settings are copied:
- DASP hello asks for 256-byte packets (idealMax/absMax), so file chunks
are 238 bytes
- the message-length header (0x81) goes to Orion only, decided once the
platform id is read
- the app is saved on the device before app.sab is read, the save resent
every 5 s rather than 1 s (a P-ION save takes 2-3+ s)
- the file open is resent every 15 s; DASP gains a per-packet resend
interval for this
Tested on a P-ION at 192.168.1.252: save 3.2 s, app.sab + kits.scode +
app.sax in 15.8 s, no resends, device stayed up. The unsaved-changes
question became a notice, since the app is now always saved first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reads app.sab and kits.scode over Sox file transfer, decodes app.sab to
app.sax (byte-identical to sedonac), and on Orion/Rion also fetches the
images and fonts the app names, with their sources from Sedona home's
banks. Offers to save the app on the device first when it is dirty, and
asks where to save with <Sedona home>/store/apps and
app-<device>-<app>-<timestamp>.zip filled in.
DASP trace lines are now printed by their own thread: printing from the
network threads to an unread stdout pipe blocked the receiver and made
file transfers time out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Connect and Discover stay enabled while a device is connected. Pressing
Connect in the dialog drops the current device before the new one is
tried; cancelling the dialog leaves the connection alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opening a file now drops the live connection once a file is chosen, without
asking. File > Exit and the window close box go through quit_app, which
closes the Sox session properly instead of leaving it for the device to
time out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hovering a slot row highlights the half under the pointer and shows a
direction cursor. Pressing on a slot drags a rubber band; dropping on
another slot links them, dropping on a title bar opens the Link dialog
with the dragged slot preselected, and a no-entry cursor marks a slot
that would refuse the link. A right click that never moves still opens
the context menu.
The palette now leaves out types whose facets carry @palette=false, and
kits with nothing placeable left.
Also points the default device host at 192.168.1.251.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The schema built from a Sox session spelled checksums as signed decimals,
so no kit matched a manifest: Installed showed raw checksums and Action
read "Keep at checksum ...". They are now 8 hex digits like a .sax file,
and checksums compare regardless of zero-padding.
A device session only loads folders as they are opened, so only sys
counted as used. Used kits now come from the whole component tree, read
one request at a time so a small controller is not flooded into dropping
the session.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rename is offered from both the canvas and the navigation tree. Names are
checked against Sedona's Component.checkName rules (1-7 ASCII chars, a
letter first, then letters, digits or '_') and must be unique among
siblings. In file mode link paths through the old name are rewritten and
the rename is one undo step; in live mode it goes to the device via Sox
and warns if the device rejects it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In live mode the device is now written first and the document only takes
what the device accepted. Every live_hooks failure records a reason, and
warn_failure() shows it in a dialog and the status bar instead of failing
silently.
- Move, properties, OX page editor: rejected values are rolled back and listed
- Add/remove link: no longer edits the DOM alone when an end can't be resolved
- Add/delete component: report why the device refused
- Tidy Layout: sends each new position to the device, reverts refusals,
marks the document dirty, and pushes no undo step in live mode
- User Manager: add (one Sox add carrying cred/perm/prov), edit and delete
now go to the device
- Binary Buf slots (e.g. User.cred) are sent as Buf bytes, not as Str text
- Bump version to 0.0.0.136
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The root tree row now names the app component, "app (<host:port>)" or
"app (<file>)", and offers Properties from its context menu and on
double-click. The live DOM's <app> element now carries its type and slot
values, and the refresh-by-id lookup finds it, so the view shows real
sys::App values.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The device builds the schema and version replies in a fixed 512-byte
packet and drops whatever does not fit. Read strings defensively, recover
a cut-off checksum from the one local manifest matching its prefix, and
fail with a clear message when the kit list cannot be recovered.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The font and image rows are now dropdowns filled from <sedona home>/ontrol:
fontBank/imageBank on Rion, fontBankXL/imageBankXL on Orion. Orion lists
.oxfl fonts in landscape and .oxfp fonts in portrait, read from the
platform service's screenOrientation (loaded from the device in live mode
when the tree has not reached it). Images show their file extension while
the slot keeps the bare name. The font dialog is gone.
In portrait the Page Editor draws a 480x800 screen in
frame_orion_portrait.png, and the canvas renders text with the .oxfp
fonts, whose glyphs are stored a quarter turn anticlockwise.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
The Page Editor canvas now paints widgets WYSIWYG instead of as
labelled placeholder boxes, following the ontrolOx draw() code and the
R-ION firmware:
- text drawn with the device's own bitmap fonts (fontBank .bin /
fontBankXL .oxfl), Windows font fallback; alignment, clipping at the
box edge, half-size subscript digits, transparent vs filled boxes;
the line is centred on the box even when the font is taller
- each widget shows the text its `in` selects: NumericLabel formats,
lookup items, Boolean true/false text and colours, date and time
- images from the local image banks, centred or cropped; missing ones
drawn as the device's red/white checker; page background image
- 10x10 pixel grid over the page colour, with a Grid toggle
- widgets hit-tested by layout box so transparent labels stay
clickable; hidden widgets shown as a dashed outline
- layout row gets an X/Y/Width/Height dialog
- scaled device frame cached per zoom (redraws ~15 ms)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Page Editor's right pane now shows the selected widget's properties
under the widget tree, laid out like Workbench's Ox editor rather than
as raw Sedona slots:
- layout (x, y, width, height), font (name, size, style)
- alignment / transparent / subscriptDigits from the attributes bits
- shape, RGB565 colours with a swatch and picker
- DateLabelSet date options split out of dateFormatOptions
- lookup labelText editor (prefix, items, suffix) and NumericLabel
printf-format editor, min/max as one row
- image name picker, slot link chooser for in/blink/visibility/touch
- Buf text decoded from base64 (Latin-1), plain text in live mode
Edits commit on Enter/focus-out/selection as one undo step and are
written to the device in live mode. Canvas drag-moves now use the same
path, so they are undoable and reach the device too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Types with intermediate parents (e.g. ontrolOx::PushButtonImage ->
Widget -> Service -> Component) only got Component + own slots, so
config/runtime streams were decoded against the wrong slot list and
x/y/imageFileName came out wrong.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Makes the 320x240 screen cutout at offset (76,62) transparent,
matching the Orion frame style so the page background color
shows through correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Toolbar zoom buttons (in/out/reset/fit) and Ctrl+MouseWheel now
control the page editor canvas when it is active. Frame image is
scaled via PIL for smooth rendering at any zoom level.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three PNG frames in assets/ with transparent screen cutouts:
- frame_orion.png (1144x849, screen at 191,169 = 800x480)
- frame_rion.png (433x396, screen centered at 76,62 = 320x240)
- frame_rion_portrait.png (856x1134, screen at 190,170 = 480x800)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New ox_page_view.py with OxPageViewPanel: canvas rendering at device
resolution (800x480 Orion, 320x240 Rion), RGB565 color conversion,
widget tree, selection handles, drag-move, device frame image support.
Wired into main.py with context menu, history, panel lifecycle.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Permissions dialog now uses two-level UI: compact Edit User with >> buttons
opening a separate grid dialog (Groups x Operator/Admin columns) with
Clear All / Set All and auto-cascade (write implies read, admin implies operator)
- Provisioning has its own >> popup dialog
- Removed live mode restrictions for add/edit/delete user operations
- Broadened type detection and added safe int conversion for live mode values
- Double-click on tree node or canvas box opens special view (User Manager for
UserService, Properties for other components)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New user_service_view.py with table of sys::User children, add/edit/delete
dialogs, SHA-1 credential hashing, 32-bit permission bitmask (4 groups x 7
flags), 8-bit provisioning byte, and undo support. Wired into main.py with
context menu entry, panel management, history navigation, and live-mode DOM
resolution for correct display on connected devices.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Populate prop elements with manifest default values when adding
components in both offline and live mode
- Send config values from manifest in SOX add command for live devices
- Refresh button in properties panel now fetches fresh values from
the connected device before re-reading the DOM
- Properties panel fetches device values on open in live mode
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix add component from palette in live mode: resolve <app> element
as comp_id 0, fix type_id lookup to use type_name_map, set type_name
on newly added SoxComponent, sync device values into DOM after add,
write meta position back to device, and subscribe new component to COV.
Fix delete component in live mode: clean up subscriptions for deleted
component and all its descendants.
Add tree root context menu with device app actions (save, hibernate,
quit, restart, reboot) in live mode. Device save clears dirty state.
Clean up connection lost handling to do a full disconnect.
Fix slot registry to copy base slots and renumber IDs correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Authentication failures now show "check username and password", busy
devices say "device is busy", etc.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Discover dialog shows a device list with Port, Wait Time fields and a
Discover button. Selecting a device and clicking Connect (or double-
clicking) opens a credentials prompt for username/password, then
connects. The Connect dialog is reverted to its original simple form.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Send DISCOVER broadcast (type 0) to 255.255.255.255 and the Sedona
multicast address 239.255.18.76 to find devices on the network.
Responses are listed in a Treeview showing IP and platform ID.
Selecting a device fills the Host field; double-click connects.
Wait time is configurable (default 10s, range 1-60s).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The dispatch check compared the raw hostname string against the UDP
source IP, silently dropping responses when they differed (e.g.
"localhost" vs "127.0.0.1"). Resolve hostname to IP at session init.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
In live mode the DOM is rebuilt from the device when switching sheets,
creating new element objects. The pending link from the old tree could
not be found by identity in canvas_link_map. Re-resolve it by from/to
attributes after the rebuild so the selection matches.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
_slot_name_by_id() now resolves SOX link slot IDs against the
inheritance-flattened final_components_registry instead of the
per-type slot_registry whose local IDs were wrong for types with
intermediate parents (e.g. RegFloat→Register→Component). This
produced incorrect link endpoints, inflated linked-slot visibility,
and different box sizes between file and live modes.
Also: tighten routing constants for denser wiring, add wiresheet
PNG print/export (Ctrl+P), sort link routing jobs by gap distance,
remove debug route logging, and fix live tree-expand re-selection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Slots with no flags (sensor, controlSignal, out_*) were not skipped
during config property reads, consuming bytes that the device never
sent and misaligning every subsequent config value (setpoint,
proportionalBand, etc.) into tiny denormalized floats like 2.36875e-41.
Changed the filter in _apply_props from "skip if runtime" to "skip if
not config" so only explicitly config-flagged slots are read from config
messages. Also switched iter("slot") to findall("slot") in the slot
registry builder to avoid picking up nested descendants.
Float display now defaults to 2 decimal places everywhere (wiresheet
and properties panel), honouring the precision facet when one exists.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds _fmt_float() helper that formats 32-bit floats with :.6g precision
but appends .0 when the result has no decimal point, matching Niagara
and .sax file display conventions (e.g. 175.0 not 175, 0.0 not 0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sox reads 32-bit floats that Python promotes to 64-bit doubles, producing
display artifacts like 22.011558532714844. Using :.6g format gives clean
values matching .sax file precision (22.0116, -19.9574, 175, 14).
Applied to both initial DOM build and COV update paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Opening a file while connected to a device prompts to disconnect first.
Connecting to a device while a file is open prompts to save unsaved
changes, then closes the file before showing the connect dialog.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Build root children directly under <app> instead of wrapping them in
a <comp name="app"> element. This matches the .sax DOM structure
where <app> is the container, so component paths like /AHU/TmpCont/X
match between link references and get_component_path.
- Fix _comp_path to produce /AHU/TmpCont/X format (skip root id 0,
leading /) matching the .sax link path convention.
- Format float property values to 4 decimal places instead of full
Python float precision (22.0116 vs 22.011558532714844).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add type_name_map on SoxClient so components loaded after connect
resolve proper names (e.g. control::And2) instead of kit::typeN.
This lets the slot registry and final_components_registry match,
so component boxes show their slots and property values.
- Load live schema gracefully: skip missing kit manifests instead of
aborting, compile the kits we have, and warn about missing ones in
the status bar.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three fixes so live wiresheets render identically to offline .sax files:
1. Inherit sys::Component base slots (meta) into every type's slot
registry so component x/y positions are read from the device.
2. Place <link> elements under a <links> container element instead of
directly under <app>, matching the .sax structure that draw_links
and build_linked_slot_index expect.
3. Deduplicate links (each link appears on both endpoints in Sox) so
wires are not drawn twice.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Swap Connect/Cancel button order so Connect is the default (leftmost).
Guard background thread against cancelled state so a cancelled connect
does not leave the app in live mode. Cancel during connection now
disconnects the session cleanly in a cleanup thread.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On connect, also loads root's immediate children so the first tree level
shows real names. For deeper levels, inserts placeholder <comp> elements
for uncached children so the Treeview shows + expand arrows for folders
that have children on the device.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
_on_tree_open used tree.selection() which may differ from the opened
node; switched to tree.focus(). Network calls in _live_tree_expand now
run in a daemon thread so the UI stays responsive during lazy loading.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After open_wiresheet rebuilds the DOM and tree, re-find the target
element by its comp id (not the old tree item ID which no longer
exists) and re-select it with the restoring flag to prevent recursion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use RLock instead of Lock for _close_lock so close() can call
_shutdown() without deadlocking — previously the session never
marked itself closed, causing infinite keepalive sends.
Mask kit checksums to unsigned 32-bit for hex display so negative
i4 values render as proper 8-digit hex (e.g. d3984c51 not -2c67b3af).
Tested against live device at 192.168.1.251 — full handshake,
schema read, and component tree load confirmed working.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New checkbox in Preferences (default off). When enabled, every DASP
send (>>>) and receive (<<<) prints message type, seq/ack numbers,
payload size, and remote address to stdout.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Guard tree.get_children() with tree.exists() so a stale item ID from
a COV or lazy-load rebuild does not raise TclError.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Port the DASP transport and Sox protocol stack from sedonaOntrol Java
to Python, enabling live device editing alongside the existing offline
SAX editor. Lazy-loads components on navigation, caches them, and
subscribes to COV events for the active wiresheet.
New files: dasp.py, sox.py, device_session.py, connect_dialog.py,
live_hooks.py. Modified: app_state.py (live-mode state), main.py
(Device menu, COV polling, lazy tree expand), properties.py (Sox
write-through), sax_file.py (structural mutations route through
live_hooks), schema.py (find_manifest helper).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Scan sedona/platforms/db/ to find which native kits are compiled for
the SAX's platform. Kits with native code for other platforms, kits
providing a PlatformService for a different device, and their
transitive dependents are placed in a "Not supported" section with a
red-cross checkbox that cannot be toggled.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The save dialog now suggests e.g. normal_kits_20260911_143200.xlsx
so successive exports never collide.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Saves the kit table to an Excel file with section headers, check
marks, and all four data columns. Default filename is based on the
open SAX file.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Renamed "Kits to be installed" to "Kits available on the computer"
for kits whose dependencies are all in the app. Kits with unmet
dependencies go into a greyed-out "Other kits on the computer" section
that cannot be selected or ticked.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
No columns stretch — sizes are set for the longest kit name
(ontrolModbusTcpSlaveSmart) and action text (Keep with no checksum).
A horizontal scrollbar appears when the pane is narrower than the
total column width.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The boxes dict was missing the (False, False) key — an unchecked kit
that is also required — which caused a KeyError when the Kit Manager
opened automatically for files with incomplete checksums.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a SAX file is opened and any declared kit is missing a checksum,
the Kit Manager panel opens automatically and the palette stays empty
until the checksums are resolved.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The sidebar palette combo now lists kits from the open file's schema
rather than every kit installed in Sedona home, with their resolved
version labels. Falls back to all installed kits when no file is open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Manage Kits now opens inside the editor pane (like Properties and Link
View) instead of a separate Toplevel window. Also normalize hex
checksums by stripping leading zeros so .kit and manifest filenames
that disagree on padding (e.g. 462d43e vs 0462d43e) still resolve to
a version.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Opening a Properties or Link view was invisible to the history, so Back
from one did two things at once: it took the panel down and stepped to the
sheet before the one it was opened over. There was no way back to a
picture you had just been reading.
A history entry is now a (kind, element) pair naming what the editor pane
held -- a sheet, a Properties view, or a Link View -- and restoring one
puts that back: a sheet entry selects its tree node, a panel entry
re-opens the panel. So opening a panel is a move like opening a sheet.
Back from a Properties view lands on whatever came before it and Forward
brings the panel back, and following a link out of the Link View now has a
way home: Back returns to the picture the link was followed from, and a
second Back to the sheet that was open before that.
The panel's own Close is deliberately not recorded. It means "leave this
view", and recording it would put a sheet entry after the panel's, so Back
would re-open the panel that had just been closed. Leaving the position
alone means Back steps to what came before the panel and Forward re-opens
it, which is what the arrows are for.
Both arrows still go through close_editor_panel first, so unsaved
Properties edits are asked about exactly as before and a refusal leaves
the view and the position where they were. Pruning now has two kinds of
entry to check -- a sheet needs a tree node, a panel needs its component
still in the document -- and collapses any pair it leaves naming the same
view, since a Back that visibly does nothing is worse than a shorter
trail.
sheet_history and its functions are renamed for what they now hold.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The picture was right and unreadable: eleven links round one component
drew twelve wire crossings, because each column was stacked in the order
the file happens to list its links and the lanes were handed out by how
far a wire had to travel.
Both columns are now stacked in the order of the chosen component's own
slots -- what feeds its first slot at the top, what feeds its fifth below
that. Nothing here is read from meta, so the view was always free to put
the peers where they read best, and this is the order that turns the set
of wires on one side into a ladder: both ends run the same way down the
page, so no wire has any reason to cross another.
That leaves which lane each wire turns in. For a ladder there is an order
that cannot produce a crossing, and read off the slots rather than off the
direction the line is drawn in, it is the same on both sides -- two fans,
each nesting outwards from the component's own row:
reaching a peer slot above its focus slot -> turns nearer the focus
the higher up the focus it lands
reaching a peer slot below its focus slot -> turns nearer the focus
the lower down the focus it lands
Two wires of one fan, the outer going round the inner, pass above and
below its vertical but never through it; and a wire of one fan can never
share a stretch of the page with a wire of the other, so the two fans
reuse each other's lanes and the gap stays as narrow as the picture
allows. Where several wires leave the same slot no order of focus heights
separates them, so the longest branch turns first and the rest carry on
along the shared run beside the pin -- a wiresheet's own fan-out. Where
two slots already line up the wire is one straight line and is settled
before anything has to be fitted past it; the layout is rounded to whole
pixels so that a pair that ought to line up does.
The reasoning is not trusted on its own: every wire is checked against the
ones already routed (routes_meet) and takes the next lane out if it
clashes. One thing genuinely breaks the ladder -- a peer whose own slots
run counter to the focus's, linking its row 4 to row 3 and its row 3 to
row 4. Ends inverted like that have to cross wherever they are drawn,
since neither box's slot order is this view's to change.
Measured over 461 linked components in four real files, 1113 wires: no
wire passes through a box at all, and ten crossings remain in six
components, every one of them an inverted pair of that kind. Before this
the same four files drew 45.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Right-clicking a component now offers Link View alongside Properties, from
the wiresheet and from the navigation tree alike. It takes over the editor
pane the same way and answers one question -- what is wired to this
component, and where does it go.
The chosen component sits in the middle wearing the stroke a selected box
wears on the sheet, with what links into it on the left and what it links
out to on the right, so every arrow points the way the signal travels.
Pale yellow ground and no grid: nothing here can be positioned, because
the layout is derived from the links every time it is drawn and no part of
it is stored in the file. Read-only throughout -- no drag, no add, no
delete -- so a component's wiring can be read without the risk of
rearranging it.
Every line is grey, since this is not a wiresheet and a wire-blue line
would invite the reader to treat it as one. A link on the same sheet is
solid; one that crosses is dashed, keeps the grey knob that sheet paints
on the slot, and has the far component drawn in with its sheet named
beneath it. A reference the document cannot resolve gets a placeholder
naming the path, because a link the view silently dropped would be the one
thing a reader could not find out from here.
Clicking a wire or a component follows the link: it opens the sheet of the
end that is not the component the view is about, with the link selected in
red. A link that stays on one sheet therefore goes back to that sheet
where both ends are drawn, and one that crosses opens the other sheet --
the end the view could only show as a knob.
Supporting changes:
- draw_component_box is factored out of render_components, so the sheet
and this view paint a component with the same code and a box cannot read
differently between them. draw_knob takes its canvas for the same
reason.
- The editor pane's hand-over is now shared: _take_editor_pane,
_give_editor_pane_back and close_editor_panel replace the
Properties-only versions, so "you have not saved that" is still asked
once, in one place, whichever panel has the pane.
- Fixes the jump to a link's other sheet, which arrived with the link not
selected. An after_idle callback is not late enough -- ttk dispatches
its own selection event later than one of those, so the repaint wiped
the selection. The link is left in _pending_link_selection for
on_tree_select to honour once it has painted.
- Each gap is sized to hold a lane per wire crossing it, and lanes are
handed out nearest-target-first, so a fan of a dozen links nests instead
of crowding a dozen verticals into a 30px band.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The four .sax test files live in EditorTestSax under the Sedona test
apps directory; the copies here were byte-identical duplicates and no
code referenced them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Names the four columns -- Name, Type, Value, Unit -- in grey at row size over a
hairline rule, so it labels the table without competing with it.
A fifth column of raw flags= letters was tried alongside it and taken out
again. c, a, s and o are the manifest's own shorthand, and a column of them
told the reader nothing the row was not already saying: c is what the white
field means, a is what an "action" value means. What the flags decide is still
decided by them; it is just not spelled out twice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@hidden is now filtered by default. niagara.html says hidden slots "are
designed to be invisible to the user" and that interfaces should rarely
display them; every one under Sedona home is a void action a kit uses
internally, such as HdwAI.checkType. A "Hidden slots (n)" checkbox in the
footer brings them back and the answer sticks for the session. The checkbox
only appears on a type that has some, and the rows are always built -- the
toggle grid_removes and re-grids them, so an emptied grid row collapses rather
than leaving a gap.
A bool now says which state it is in by the colour of its field rather than by
a dot beside it that had to be learnt: green true, red false, blue null, with
the words left black so they read as easily as every other value. Dropping the
dot also lets a bool's field line up with the numbers above and below it.
Two things that needed changing to get there:
- a dropdown is offered only where there is a choice to make. A read-only
bool or enum gets a plain field, since a disabled dropdown is an arrow
that does nothing -- and a read-only bool is still filled, because a
runtime bool has a state too.
- the dropdown is a tk.OptionMenu, not a ttk.Combobox. On the Windows themes
a Combobox draws its field with a native element and ignores
fieldbackground outright, so a bool could never be filled with its colour.
An OptionMenu reports through its variable rather than an event, which means
loading a row looks exactly like choosing one -- so the load is flagged and the
unsaved-state tint left alone until every row is in, or the view would open
already claiming edits nobody made.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Right-clicking a component in the navigation tree offered Delete Component
alone, while the same component on the wiresheet also offered Link Mark and
Link From / Link To. Which list a component was picked from should not change
what can be done with it, so both popups are now built by
fill_component_popup.
Its entries close over the component instead of reading a selection back,
which is what lets each popup act on the row or box that was actually clicked
-- the canvas commands went through app_state.selected_canvas_tag, which a
tree row never sets. do_link_mark, do_link_from_mark, do_link_to_mark and
delete_tree_selected_component fall out with it, and Link Mark now says in the
status bar what it marked, since nothing else on screen shows that.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Right-clicking a wire or a knob now offers Go to Link Wire Sheet "<name>",
naming the sheet the far end is on -- the end a grey knob stands in for. A link
with both ends on the open sheet has nowhere to go, so the entry is left off
rather than greyed out.
find_component_by_path turns a link's from/to reference back into the component
it points at, the inverse of get_component_path, and the sheet is that
component's parent; a parent of <app> is the file's root node, which the popup
names after the file.
Arriving selects the same link and scrolls it to the middle of the view. It is
found by its element rather than by the tag it was picked on, since a repaint
hands out fresh wire tags, and it waits for that repaint from an after_idle
callback: ttk queues its selection event rather than dispatching it, so the
sheet is not painted when the tree node is selected -- and painting a sheet
clears the selected link, which was wiping the selection this feature had just
made. The jump is an ordinary sheet opening, so Back returns to where the link
was picked.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fits the whole painted sheet in the visible canvas and scrolls it into view,
from the View menu, Ctrl/Cmd+9, or the four-corners button after the zoom
percentage. The ladder gains 25% and 33% at the bottom, so a wide sheet has
somewhere to fit into.
Fit is the one scale that is not on the ladder -- a sheet fits at whatever
scale it fits at -- so zoom_step now takes the first level above or below the
scale in force rather than the neighbour of the nearest rung.
The sheet is measured from item coordinates rather than canvas.bbox(), which
is inflated by the outline stroke: a stroke is the same two pixels at every
zoom, so a bbox divided back to 1:1 reports a slightly smaller sheet the
further in it is zoomed, and fitting an already fitted sheet crept from 288%
to 291% instead of standing still. Anything painted left of or above the
canvas origin is measured from the origin, since no amount of scrolling brings
it into view.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A ladder of scales from 50% to 300%, stepped by Zoom In / Zoom Out / Zoom 100%,
by the two buttons beside the navigation arrows, and by Ctrl and the wheel,
which holds the point under the pointer still. A menu or keyboard zoom holds
the middle of the view instead, so it closes in on what is already being
looked at.
Painting and routing stay at 1:1, because that is where every metric is tuned:
box header and row heights, wire stubs, lane gaps, standoffs, knob radii. Zoom
scales the finished picture, so a zoomed sheet keeps the proportions of a 1:1
one and the router reaches the same decisions at every scale. Text comes along
because the three box fonts are resized once the boxes have been measured with
them.
redraw_links replaces the direct draw_links calls a drag or a drop makes: the
router reads box positions off the canvas, so the boxes go back to 1:1 for the
routing pass and the result is scaled again. Canvas coordinates carry the zoom,
which is why the drag, release and drop handlers now snap against
meta_scale * zoom -- a box dragged the same distance across the screen lands on
the same meta step at 50%, 100% and 200%.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>