Commit Graph
3 Commits
Author SHA1 Message Date
vicrodh ad07d8c14e feat(qt): Library Explorer columns select a run with Shift
A facet column already took Ctrl as "add or remove one" and a plain click
as "only this". Shift now picks the run from the last clicked value and
Ctrl+Shift adds that run, the file-list rule (SelectionModel.nextExclusive),
each column over its values as listed. A column whose values are rebuilt by
a change upstream starts without an anchor.

cc
2026-09-14 10:31:48 -06:00
vicrodh 7d1a144c89 fix(qt): a selection anchor finds numeric ids, and ranges skip header rows
controls/SelectionModel.qml kept the anchor in a string property and then
looked it up with a strict comparison, so a list whose rows carry numeric
ids never found its anchor again and every Shift-click there degraded to a
plain toggle. Ids now compare as strings, which is what the selection map
keys already were.

The range fill also wrote `m[rows[i].id]` for every row in the span, so a
list that mixes group headers into its rows gained an "undefined" key and a
count one above the ticks on screen. Rows without an id are skipped.

`idKey` names the identifying field for rows that do not call it `id`.

scripts/qml-tests/tst_multi_select.qml starts with the rule's own cases and
joins qt-runtime-gate.sh and test-crates.yml.

cc
2026-09-14 10:30:18 -06:00
vicrodh 750c59034b feat(qt): Excel-style multi-select — Shift ranges across every surface
Selection existed but had no keyboard: every click was a single toggle,
so picking 40 tracks meant 40 clicks. Slint and Tauri have had ranges
since 2026-06; this is that port.

NINE surfaces, not the five the track views suggested: Album, Artist,
Playlist, Label, Local Album, plus Library's tracks and albums and Local
Library's tracks and albums. Each carried its own hand-copied `selected`
map and its own `toggleSelected`, so the rule went into
controls/SelectionModel.qml and the maps stayed where they were — the
smallest change that stops it being copied a tenth time.

The rules are Tauri's `applyShiftRange`, unchanged: a plain click
toggles one row without clearing the rest, Shift fills the range from
the anchor ADDITIVELY (so re-dragging a range cannot eat the set) and
leaves the anchor where it is, and Ctrl/Cmd is the same as a plain click
— not a shortcut, but the behaviour both references already have,
because an accumulating click is what "add without disturbing" means.

Qt makes this CHEAPER than Slint, and the difference is worth recording:
Slint had no modifiers at click time and had to read a thread-local set
from a ModifiersChanged handler, with a documented stale-read edge case
when the window lost focus mid-chord. A QML MouseArea delivers
`mouse.modifiers` on the event itself, so the modifier and the click can
never disagree and no such seam exists here.

Three judgement calls, stated because they are not forced:

  - Library gets TWO models, one per kind. Its rows are mixed, and one
    anchor would let a Shift-click on a track sweep up the albums drawn
    between it and the anchor.
  - Ranges run over the VISIBLE rows. Local Library's search box is a
    view filter, and a range over rows the user cannot see is not the
    range they asked for — the same call `select-all` already made.
  - The Local Library tree rail keeps plain toggles. Its selection lives
    in Rust (QbzLocal.treeToggle*Select), so there is no QML map to hang
    an anchor off; teaching the Rust side about an anchor is its own
    change, and the file says so where a reader would expect otherwise.

The anchor is an ID, not an index, because every one of these views can
re-sort or filter under a live selection — an index anchor silently
points at a different track the moment the order changes. Slint's
selection.rs made the same call for the same reason.

Verified by driving the app: in Abbey Road, click row 1 then Shift-click
row 5 selects exactly rows 1-5 and leaves 6-8 untouched, with the bar
reading "5 selected"; the same in a playlist, additively over a row that
was already selected.

cc
2026-08-22 02:03:10 -06:00