Skip to content

style(dialogs): unify picker tables (selection, edit, sort, header)#188

Open
facontidavide wants to merge 3 commits into
mainfrom
feat/unify-picker-tables
Open

style(dialogs): unify picker tables (selection, edit, sort, header)#188
facontidavide wants to merge 3 commits into
mainfrom
feat/unify-picker-tables

Conversation

@facontidavide

Copy link
Copy Markdown
Contributor

What

Brings the topic / channel / camera / sequence picker tables across the plugin dialogs onto one consistent idiom, and captures a follow-up SDK design for the one axis that can't live in a .ui file (per-column resize behavior).

Picker idiom (the 5 multi-select lists)

All now share: MultiSelection · SelectRows · NoEditTriggers · sortingEnabled · verticalHeaderVisible=false.

Picker Before Change
foxglove topicsList editable, no sort, header shown + NoEdit, + sort, hide vHeader
pj_bridge topicsList editable, no sort, header shown + NoEdit, + sort, hide vHeader
webrtc camerasList editable, no sort, header shown + NoEdit, + sort, hide vHeader
ros2 listRosTopics ExtendedSelection → MultiSelection
mosaico topicTable ExtendedSelection → MultiSelection

Why MultiSelection over ExtendedSelection: in a picker, a bare click under ExtendedSelection silently clears the previous selection — a footgun when users expect to accumulate picks. MultiSelection makes a bare click toggle/accumulate, which matches the "Select one or multiple…" labels these dialogs already show.

Deliberate exceptions (unchanged):

  • mosaico seqTable stays SingleSelection (single-select by design, now explicit).
  • Both mosaico tables keep sorting off — they filter rows by index via setVisibleRows, and user re-sorting would desync that index mapping.

Not addressed here (tracked)

Per-column resize-to-content (e.g. MCAP Encoding, Msg Count hugging their content while the name column stretches) can't be expressed in a .ui file — column sizing is owned by the host's installTreeLikeHeader(). The second commit adds a design spec + TDD implementation plan for an additive, header-only WidgetData::setColumnResizeMode / WidgetDataView::columnResizeModes protocol addition spanning plotjuggler_sdkPJ4 → this repo. Not implemented in this PR.

Notes

  • These .ui files are embedded verbatim and parsed at runtime (no uic); all 5 were validated well-formed.
  • No code changes — behavior-only .ui attributes + docs.

🤖 Generated with Claude Code

facontidavide and others added 2 commits July 6, 2026 10:36
Bring the topic/channel/camera/sequence picker tables onto one idiom:

- Multi-select pickers all use MultiSelection (bare click toggles/accumulates)
  instead of ExtendedSelection (bare click silently clears the prior pick).
  Converts ros2 listRosTopics and mosaico topicTable.
- Every picker gets NoEditTriggers, SelectRows, verticalHeaderVisible=false,
  and sortingEnabled=true (Qt's default indicator starts the table sorted by
  column 0 ascending). Fixes foxglove/pj_bridge topicsList and webrtc
  camerasList, which were accidentally editable and had no consistent header.
- mosaico seqTable intentionally stays SingleSelection; mosaico sorting stays
  off (its index-based setVisibleRows filtering would desync under re-sort).

Per-column resize-to-content is deliberately not addressed here: column sizing
is owned by the host's installTreeLikeHeader() and cannot be expressed in a
.ui file. That is tracked as a follow-up SDK protocol change (see
docs/superpowers/specs/2026-07-03-dialog-table-column-resize-mode-design.md).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Design and TDD implementation plan for a follow-up SDK protocol addition that
lets a plugin declare per-column Qt resize modes for dialog tables (name column
Stretch, short data columns ResizeToContents) — the one picker-table axis that
cannot be set from a .ui file because column sizing is owned by the host's
installTreeLikeHeader().

Additive, header-only WidgetData::setColumnResizeMode / WidgetDataView::
columnResizeModes across plotjuggler_sdk -> PJ4 -> pj-official-plugins; no
compiled-ABI break. Not yet implemented; this only captures the approved design.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@facontidavide

Copy link
Copy Markdown
Contributor Author

Codex review follow-up (pushed 973a78c)

An independent Codex review flagged a real issue in the original branch: the host realizes plugin .ui files through QUiLoader, which honors sortingEnabled and turns on Qt's built-in sort. Per the SDK dialog guide (dialog-plugin-guide.md), built-in sort desyncs any picker that restores/reads selection by row index (setSelectedRows/selectedRows()), because the sorted view's index no longer maps to the plugin's own unsorted vector.

Per-picker selection contract (verified in code):

Picker Selection restore Safe with built-in sort? This PR
foxglove topicsList text (setSelectedItems) sort on (kept)
pj_bridge topicsList text (setSelectedItems) sort on (kept)
webrtc camerasList index (setSelectedRows) sort removed (was a new regression)
ros2 listRosTopics index (setSelectedRows) pre-existing sort untouched (out of scope)
mcap tableWidget index (setSelectedRows) pre-existing sort untouched (out of scope)
mosaico seqTable/topicTable index (setVisibleRows) correctly off

Fix in 973a78c: drop the sortingEnabled I had added to camerasList (with an inline comment so it isn't re-added), and correct the resize-mode docs (foxglove topicsList has 3 columns incl. Encoding, not 2).

Follow-up (not this PR): mcap and ros2 already ship sortingEnabled=true with index-based selection — a latent instance of the same issue. Making those genuinely sortable the SDK-correct way needs plugin-owned onHeaderClicked re-sorting; worth tracking separately.

@facontidavide

Copy link
Copy Markdown
Contributor Author

Follow-up: mcap + ros2 sort now fixed in-PR (0b994d3)

Digging into the two pre-existing index-based pickers, the desync turned out to be latent behind a coincidence, not a live bug: both row vectors are already name-sorted (mcap std::sort by topic; ros2 std::map iteration) and the widget's default sort is column-0 ascending, so restore indices happen to line up in the common path. It desyncs only when the user sorts by another column, on a std::string-vs-QString collation edge, or (ros2) during the 1 Hz live rescan while re-sorted.

Rather than leave that fragility, per request this PR now drops sortingEnabled from both. The row order stays alphabetical (the vectors are pre-sorted), so the only thing lost is click-sort-by-other-column — which was exactly the broken path. Genuinely sortable columns remain a possible follow-up via plugin-owned onHeaderClicked re-sorting.

Final state — built-in sort is on iff selection restore is text-based:

Picker Restore sortingEnabled
foxglove topicsList text ✅ on
pj_bridge topicsList text ✅ on
webrtc camerasList index ❌ off
ros2 listRosTopics index ❌ off
mcap tableWidget index ❌ off
mosaico seqTable/topicTable index ❌ off

The pickers now keep Qt's built-in column sorting (sortingEnabled, default
column-0 ascending) AND restore selection correctly, by identifying the
selection by first-column text instead of row index.

Background: the host realizes the .ui via QUiLoader, which honors sortingEnabled
and gives the widget a *sorted* view. Any selection restored by row index
(setSelectedRows) then desyncs — the plugin's row vector stays in its own order
while the widget re-orders. Restoring by text (setSelectedItems) is
sort-agnostic: the host matches items by their first-column string. This is the
same pattern foxglove/pj_bridge already use.

- ros2 listRosTopics: drop the name->index translation (and the now-unused
  ordered_names vector); hand the host the selected topic names directly.
- webrtc camerasList: collect the selected rows' unique display labels (1:1 with
  the stream id within a render) instead of row indices. Read-back already maps
  labels back to ids, so the round-trip stays lossless.

Neither plugin uses setVisibleRows/setDisabledRows, so selection was their only
index dependency — converting it makes built-in sort fully safe. Verified:
webrtc compiles clean under -Werror -Wconversion.

Deferred (need cross-repo work, not in this PR): mcap tableWidget also disables
empty channels by row index (setDisabledRows, no text-keyed equivalent yet), and
mosaico filters by row index (setVisibleRows). Making those sort-correct wants
plugin-owned onHeaderClicked sorting, which needs the host to wire
QHeaderView::sectionClicked -> onHeaderClicked (declared in the SDK but currently
undelivered for tables) plus a sort-indicator API.

Docs: correct foxglove topicsList to 3 columns {Topic Name, DataType, Encoding}.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@facontidavide facontidavide force-pushed the feat/unify-picker-tables branch from 0b994d3 to 9c41ae1 Compare July 6, 2026 09:28
@facontidavide

Copy link
Copy Markdown
Contributor Author

Course correction: pickers are now genuinely sortable (force-pushed, 9c41ae1)

I initially misread the goal and removed built-in sorting to dodge the index-desync bug. That was backwards — the ask is for sortable columns (default column-0). I've dropped that detour (force-pushed) and instead fixed the root cause the right way: restore selection by first-column text, not row index, so it survives Qt's built-in sort. This is the same pattern foxglove/pj_bridge already use.

Now sortable (built-in sort on, default column 0):

  • foxglove topicsList, pj_bridge topicsList — already were (text-keyed).
  • ros2 listRosTopics — converted setSelectedRowssetSelectedItems (topic name).
  • webrtc camerasList — converted to setSelectedItems (unique display label, 1:1 with stream id). Verified compiles under -Werror -Wconversion.

Deferred to a follow-up (need cross-repo work):

  • mcap tableWidget also disables empty channels by row index (setDisabledRows) — no text-keyed equivalent, so it would desync under sort.
  • mosaico filters by row index (setVisibleRows) — same class.
  • The clean fix for both is plugin-owned sorting via onHeaderClicked, but the host doesn't currently deliver header clicks for tables (onHeaderClicked is declared in the SDK but QHeaderView::sectionClicked is never wired) and there's no sort-indicator API. That's a PJ4 host + SDK addition.

Branch (3 commits): unify pickers → resize-mode docs → make ros2/webrtc sortable. mcap/mosaico left exactly as they were on main (unchanged by this PR).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant