Addresses a portion of #10619, 2.6 Selection, highlight & inspection#10910
Conversation
Bring the web viewer's inspector to parity with the Qt GUI's editing support (issue The-OpenROAD-Project#10619, table 2.6), using the descriptor Editor/Action API that was already linked but never exposed: - Inspect payloads now carry editable/editor{type,options} per property (from Descriptor::getEditors) and a descriptor actions list filtered by a blocklist (Qt-dialog actions would crash without a QApplication; focus/guides/zoom already have per-session web equivalents). - New set_property request: strings/numbers/bools marshal per the Qt delegate rules (numbers always double); list editors commit by option index so the option's exact std::any reaches the callback, with an option_name guard against stale option lists. - New trigger_action request: re-resolves the action by name at commit time; the returned Selected becomes the inspected object. Delete is confirmed in a themed modal. The reserved "deselect" action now runs whenever the inspected object changes. - ScopedDbuFormat also installs Property::convert_string (the library default returns 0 without setting the ok flag, silently rejecting coordinate edits such as dbInst X/Y). - Multi-client safety: destroying any selectable object (trigger_action or a Tcl command) raises a per-session staleness flag via the odb destroy callbacks; handlers drop the dangling Selected state before the next dereference and clients receive selection_invalidated. Accepted edits broadcast a refresh to every session. This also fixes a pre-existing crash vector for destroys issued from Tcl. - Rejected edits show the reason in a toast instead of Qt's silent revert; editable values get a visible affordance. - Selected::getDescriptorActions() (inline in gui.h) exposes the descriptor's own actions without the "Zoom to" entry appended by Selected::getActions(), which lives in the full gui library and is unavailable to code linking only gui_descriptors. Tested: 16 new gtest cases (SetPropertyTest/TriggerActionTest) and 11 new JS cases; validated live against nangate45/gcd 5_2_route.odb with two concurrent websocket clients. Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>
Bring the web viewer's highlight groups to parity with the Qt GUI (issue The-OpenROAD-Project#10619, table 2.6 — note the Qt GUI has 16 groups, not the 8 the issue's table says): - Per-session state mirrors Qt's HighlightSet: 16 fixed gui::SelectionSet groups colored by gui::Painter::kHighlightColors. The derived overlay shapes are ColoredRects fed through the existing DRC/timing colored-rect rendering (translucent fill + solid outline), appended last so groups paint on top — no renderer changes. - New requests: highlight {group} puts the inspected object into a group, always removing it from any other first (the Qt add path can leave an object in two groups; the web enforces uniqueness); unhighlight removes it; clear_highlights {group|-1} clears one or all (Qt's Clear → Highlights semantics). - Inspect payloads now carry highlight_group (-1..15) so the client shows the state in place — the Qt GUI only shows it in the separate Selections/Highlights dock. - The 16-color palette is served in the tech response straight from gui::Painter::kHighlightColors, so client swatches cannot drift from what the overlay renders. - Inspector toolbar gains a palette button with a one-click 4x4 swatch popover (the Qt GUI opens a modal 16-radio dialog) plus a Remove entry; the button doubles as a badge tinted with the current group's color. Tools menu gains Clear Highlights. - Highlight groups join the multi-client staleness handling: destroys clear them before the next dereference (the Qt GUI keeps stale pointers to deleted objects). Tested: 10 new gtest cases (add/move-unique/unhighlight/clear/invalid/ payload/overlay/staleness) plus a tech-palette check, and 3 new JS cases for the picker UI; validated live against nangate45/gcd 5_2_route.odb with PNG pixel decoding — group 4 renders red and group 0 green in the overlay, groups persist across selection changes, and clear empties the overlay. Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>
Bring the web viewer's selection browser to parity with the Qt GUI's Selected/Highlighted Shapes dock (issue The-OpenROAD-Project#10619, table 2.6), filling the SelectHighlight GoldenLayout stub: - New requests: list_selection returns the selection set and the 16 highlight groups (name/type/bbox per row, capped at 1000 rows per set with a truncated flag — the Qt dock renders everything and freezes on huge selections); inspect_selection {index} / inspect_group {group, index} make a browser row the inspected object by stable set index, never through state.selectables (which belongs to the last inspect-style response and backs the Inspector's clickable links); deselect {index} removes one object from the selection set (Qt parity: MainWindow::removeFromSelected — the web had no per-item removal). - selection-browser.js: Selected/Highlighted tabs with live counters, a real substring filter over name and type (the Qt dock only has keyboardSearch — and its Highlighted tab reads the Selected tab's search box), sortable resizable columns, a color chip + "Group N" per highlighted row, per-row zoom (client-side, the bbox ships with the list) and de-select buttons, row click → inspector, double click → focus the Inspector panel, and a Clear-all-highlights button. Rows carry the stable index from the last fetch, so sorting can never act on the wrong object (the Qt dock's context menus use proxy rows without mapToSource and mis-target under sorting). - Refresh piggybacks on the overlay scheduler: every selection or highlight mutation already refreshes the overlay, so one hook keeps the panel in sync (debounced, skipped while hidden), mirroring the Qt dock's 100 ms coalescing timers. - Panel added to the default layout (LAYOUT_VERSION 3 → 4) and to the Windows menu. Tested: 6 new gtest cases (list/inspect-by-index/deselect/invalid indices/staleness) and 8 JS widget cases; validated live against nangate45/gcd 5_2_route.odb — shift-click multi-select, group listing, row inspection by index, de-select, and the panel mounting in a real headless-Chrome session. Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>
The client's coordinate transforms (designScale/Origin/MaxDXDY) were
frozen at boot from a single bounds fetch, but the server's getBounds()
is dynamic — it is the block bbox inflated by the pin-label margin, and
DB edits change it: moving an instance outside the bbox via
set_property X/Y, deleting an edge instance, or renaming a BTerm (the
longest pin name sizes the label margin). When that happened, tiles
re-rendered against the new georeference while every connected client
kept the old transforms, so clicks selected the wrong object and
selection boxes drew offset from the layout (die-edge instances became
unselectable). A stale tab reconnecting to a restarted server had the
same problem.
- The {"type":"refresh"} broadcast sent after set_property /
trigger_action now carries the current bounds, so clients resync
without an extra round-trip.
- New pure helpers computeBoundsTransforms/boundsEqual (ui-utils) back
an applyBounds/resyncBounds pair in main.js: on a refresh push with
different bounds the client rebuilds its transforms in place, keeping
the DBU point at the center of the view, drops client-side rectangles
that hold stale latlngs, and redraws.
- WebSocketManager gains an onReconnected hook (fired on any reconnect
after the first connection); main.js uses it to re-fetch bounds and,
if they changed — typically a restarted server or different design —
reload through the well-tested boot path.
Tested: gtest asserts the broadcast carries bounds matching
serializeBoundsResponse and documents that bounds follow block-bbox
changes; JS tests cover the transform math, boundsEqual, and the
reconnect hook. Validated live on nangate45/ibex with a real headless
Chrome: after moving an instance outside the bbox from a second
session, the browser's effective transforms (measured by dispatching
real clicks) follow the server; a mutation missed while the socket was
down triggers reload-on-reconnect and realigns.
Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>
Qt parity for the last item of issue The-OpenROAD-Project#10619 table 2.6: - Ctrl+click sends show_connectivity; the server expands the selection with the SIGNAL nets on the picked instance's ITerms (Qt's selectHighlightConnectedNets) and reports connected_added, surfaced as a toast in the client. - ShapeCollector now captures Painter::drawLine, and SessionState keeps highlight_lines rendered by the overlay next to timing lines, so unrouted-net highlights draw their flight lines. - animateSelection replicates Qt's selection animation (outline weight cycling 1-2-3 px with a brush flash, 300 ms ticks, 6 repeats, repeats=0 continuous) as a client-side Leaflet path - no layout repaint per tick. Triggers: cycling a multi-selection, and hovering a selection-browser row (continuous until mouseleave). Respects prefers-reduced-motion by falling back to the one-shot pulse. - Fix writeInspectPayload to use pointer-form any_cast for nets so isNet() wrappers without a bare dbNet* payload cannot throw. - Shift+click and Ctrl+click documented in the Help panel. Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>
Pre-PR pass anticipating the repo's gemini-code-assist and Codex review bots, plus a duplication cleanup. Correctness: - handleOverlayTile read state.drc_lines under selection_mutex while it is written under drc_mutex; move the read back into the drc_mutex block (thread-safety regression from the earlier refactor). - rebuildHighlightGroupShapesLocked dropped ShapeCollector::drawLine output, so highlight-group members that only draw flight lines (e.g. unrouted nets) were invisible in the overlay; collect them into a new highlight_group_lines tinted with the group color. - handleSetProperty/handleTriggerAction built the refresh broadcast (getBounds reads odb) after releasing the STA lock; capture the payload under the lock and broadcast afterwards. - Ctrl+click connectivity now also accepts Cmd+click, so it is reachable on macOS where Ctrl+click is the context-menu gesture. - Selection-browser hover animation could be stranded when a background refresh rebuilt the table under the cursor (mouseleave never fires); stop the animation on table re-render, on updateInspector(null), and on the selection_invalidated push. - The 'refresh' push redrew all layers twice when the bounds changed; resyncBounds now reports whether it already redrew. Simplification: - Extract writeInspectTrailer() for the inspection-response epilogue duplicated across eight handlers (payload + selection cursor fields + selectables swap), removing the copy-paste that had already diverged. Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a new "Selection Browser" component to the web interface, providing parity with the Qt GUI's Selected/Highlighted tabs, and adds support for server-backed property editing, highlight groups, selection animations, and coordinate-transform synchronization. On the backend, it implements corresponding WebSocket handlers and enhances shape collection to support flight lines for unrouted nets. The review feedback suggests optimizing the Selection Browser by checking its visibility state before refreshing to prevent redundant background network requests, and clearing any pending debounced refresh timers upon container destruction to avoid potential memory leaks.
| refresh() { | ||
| if (isStaticMode(this._app) || this._inFlight) return; |
There was a problem hiding this comment.
To prevent unnecessary background network requests and DOM rendering when the Selection Browser tab is hidden, the refresh method should check the visibility state (this._visible) before proceeding.
| refresh() { | |
| if (isStaticMode(this._app) || this._inFlight) return; | |
| refresh() { | |
| if (!this._visible || isStaticMode(this._app) || this._inFlight) return; |
| // GoldenLayout tab visibility — skip background refreshes. | ||
| container.on?.('show', () => { | ||
| this._visible = true; | ||
| this.refresh(); | ||
| }); | ||
| container.on?.('hide', () => { | ||
| this._visible = false; | ||
| }); |
There was a problem hiding this comment.
When the GoldenLayout container is destroyed (e.g., when the user closes the Selection Browser tab), any pending debounced refresh timer (this._pendingRefresh) should be cleared. Failing to do so can cause the timer to fire on a destroyed component, leading to errors and memory leaks.
// GoldenLayout tab visibility — skip background refreshes.
container.on?.('show', () => {
this._visible = true;
this.refresh();
});
container.on?.('hide', () => {
this._visible = false;
});
container.on?.('destroy', () => {
this._visible = false;
if (this._pendingRefresh !== null) {
clearTimeout(this._pendingRefresh);
this._pendingRefresh = null;
}
});- selection-browser: guard refresh() on visibility so a hidden panel (or a debounced refresh that fires after a 'hide') issues no list_selection request or re-render; and clear the pending debounced timer on the GoldenLayout 'destroy' event so it cannot fire on a torn-down panel. Both flagged by gemini-code-assist on The-OpenROAD-Project#10910. - TestRequestHandler: join a line that violated clang-format (CI red). - Add regression tests for the hidden-refresh skip and destroy cleanup. Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>
Addresses a portion of #10619
2.6 Selection, highlight & inspection
Descriptor::getEditors/getActionsset_property/trigger_actioncommands; editable properties become inline fields or dropdowns (X/Y, orient, placement status, master), object actions run from the toolbar (e.g. Delete behind a styled confirm modal) filtered by a blocklist, and rejected edits surface the reason via a toast — where the Qt GUI reverts silentlyHighlightSetcolored byPainter::kHighlightColorshighlight/unhighlight/clear_highlightscommands with a 4×4 color-picker popover and a tinted badge; the server keepshighlight_groupsplus derived overlay shapes (rects and flight lines for unrouted nets), and the 16-color palette is served in the tech response. Note: 16 groups, not 8 as the issue statesselectHighlightWindow(2 tabs)selectionAnimation(QTimer 300 ms, outline weight cycling 1→2→3 px with a brush flash, 6 repeats)animateSelectionreplicating the Qt cadence on a Leaflet path with no per-tick layout repaint; triggers on multi-selection cycling and on selection-browser row hover (continuous), and respectsprefers-reduced-motionby falling back to a single pulseselectHighlightConnectedNetsshow_connectivity; the server pulls the clicked instance's SIGNAL nets into the selection and reportsconnected_added(surfaced as a toast), with unrouted nets rendered as flight lines in the overlay