fix(qt): Shift and Ctrl work on every select-mode click target, not only the row body
Excel-style selection (750c59034) read the modifiers only where a row BODY took the click. Every other target a select-mode surface draws dropped them, so a Shift-click there was a plain toggle: - the track row's checkbox disc passed Qt.NoModifier; - QbzCheckbox and SelectCheck emitted toggled() with no argument, so the Library album rows, the local album rows and the folder rows, which already expected `function (mods)`, received undefined; - AlbumCard emitted selectToggled() with no argument, and the paged local albums grid (299dcde5b) spelled the drop out as Qt.NoModifier. Some targets were never selection targets at all: in select mode the artist and album names on a track row, the artist and label on a Library album row and the artist line of an album card still navigated, leaving the page and the selection mid-gesture. The row links stand down in select mode so the click reaches the row body with its modifiers, and the card's artist line toggles like its artwork and title. A hover-only tooltip on the pulled but cached glyph no longer eats the row's click. Reproduced on the release binary in a nested KWin with injected input: in Local Library Tracks, the checkbox discs of rows 1, Shift 4 and Ctrl 6 selected 3 rows; the same clicks on the row bodies selected 5. No list view, Flickable or wheel code changes; tst_wheel passes unchanged. tst_multi_select clicks each of these targets through the real TrackRow, AlbumListRow, AlbumCard, QbzCheckbox and SelectCheck. cc
This commit is contained in:
12 files changed
+360
-23
No files matched your search
@@ -213,7 +213,10 @@ Rectangle {
|
|||||||
/// Park the select indicator bottom-right of the art instead of top-right.
|
/// Park the select indicator bottom-right of the art instead of top-right.
|
||||||
property bool selectMarkBottom: false
|
property bool selectMarkBottom: false
|
||||||
property bool selected: false
|
property bool selected: false
|
||||||
signal selectToggled()
|
/// `modifiers` rides straight off the click: a grid host turns Shift into
|
||||||
|
/// a range (controls/SelectionModel.qml). Every select-mode target on the
|
||||||
|
/// card — artwork, title, artist line — emits it.
|
||||||
|
signal selectToggled(int modifiers)
|
||||||
|
|
||||||
// Album blacklist ("Block this album") — LIVE since QbzBlacklist landed
|
// Album blacklist ("Block this album") — LIVE since QbzBlacklist landed
|
||||||
// (`blockAlbum(id, title, artist, coverUrl)`). Kept as a property, not
|
// (`blockAlbum(id, title, artist, coverUrl)`). Kept as a property, not
|
||||||
@@ -561,7 +564,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
// .slint:169 — in select mode the card click TOGGLES.
|
// .slint:169 — in select mode the card click TOGGLES.
|
||||||
if (root.selectMode) {
|
if (root.selectMode) {
|
||||||
root.selectToggled()
|
root.selectToggled(mouse.modifiers)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (root.pulledDead)
|
if (root.pulledDead)
|
||||||
@@ -993,7 +996,7 @@ Rectangle {
|
|||||||
// .slint:465 — the title carries the SAME target
|
// .slint:465 — the title carries the SAME target
|
||||||
// as the artwork, select mode included.
|
// as the artwork, select mode included.
|
||||||
if (root.selectMode) {
|
if (root.selectMode) {
|
||||||
root.selectToggled()
|
root.selectToggled(mouse.modifiers)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (root.pulledDead)
|
if (root.pulledDead)
|
||||||
@@ -1009,7 +1012,8 @@ Rectangle {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
height: 18
|
height: 18
|
||||||
text: root.artist
|
text: root.artist
|
||||||
color: (root.artistId !== "" || root.hostArtistLink)
|
color: !root.selectMode
|
||||||
|
&& (root.artistId !== "" || root.hostArtistLink)
|
||||||
&& artistArea.containsMouse
|
&& artistArea.containsMouse
|
||||||
? theme.textPrimary : theme.textMuted
|
? theme.textPrimary : theme.textMuted
|
||||||
font.pixelSize: theme.fontLink - 1
|
font.pixelSize: theme.fontLink - 1
|
||||||
@@ -1019,9 +1023,15 @@ Rectangle {
|
|||||||
id: artistArea
|
id: artistArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: root.artistId !== "" || root.hostArtistLink
|
cursorShape: root.selectMode || root.artistId !== "" || root.hostArtistLink
|
||||||
? Qt.PointingHandCursor : Qt.ArrowCursor
|
? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
onClicked: {
|
onClicked: function (mouse) {
|
||||||
|
// In select mode the artist line is the card too:
|
||||||
|
// opening the artist mid-selection would drop it.
|
||||||
|
if (root.selectMode) {
|
||||||
|
root.selectToggled(mouse.modifiers)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (root.artistId !== "")
|
if (root.artistId !== "")
|
||||||
QbzArtist.openArtist(root.artistId)
|
QbzArtist.openArtist(root.artistId)
|
||||||
else if (root.hostArtistLink)
|
else if (root.hostArtistLink)
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
// QbzCheckbox (primitives/QbzCheckbox.slint) — 18px square, r4, accent fill
|
// QbzCheckbox (primitives/QbzCheckbox.slint) — 18px square, r4, accent fill
|
||||||
// with an accent-text check when on, muted border when off. Emits toggled();
|
// with an accent-text check when on, muted border when off. Emits
|
||||||
// like the Slint original it never self-flips (the owner of the state does).
|
// toggled(modifiers); like the Slint original it never self-flips (the owner of
|
||||||
|
// the state does).
|
||||||
|
//
|
||||||
|
// `modifiers` is the click's (or the key's), straight through: a multi-select
|
||||||
|
// host turns Shift into a range (controls/SelectionModel.qml). Hosts that only
|
||||||
|
// flip a flag keep a plain expression handler and never look at it.
|
||||||
//
|
//
|
||||||
// Used by Settings > Local Library (folder selection) and the Plex library
|
// Used by Settings > Local Library (folder selection) and the Plex library
|
||||||
// picker, i.e. every place the Slint uses its own QbzCheckbox.
|
// picker, i.e. every place the Slint uses its own QbzCheckbox.
|
||||||
@@ -15,7 +20,7 @@ Rectangle {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool checked: false
|
property bool checked: false
|
||||||
signal toggled()
|
signal toggled(int modifiers)
|
||||||
|
|
||||||
QbzTheme { id: theme }
|
QbzTheme { id: theme }
|
||||||
|
|
||||||
@@ -30,14 +35,14 @@ Rectangle {
|
|||||||
activeFocusOnTab: root.enabled
|
activeFocusOnTab: root.enabled
|
||||||
Accessible.role: Accessible.CheckBox
|
Accessible.role: Accessible.CheckBox
|
||||||
Accessible.checked: root.checked
|
Accessible.checked: root.checked
|
||||||
Accessible.onToggleAction: if (root.enabled) root.toggled()
|
Accessible.onToggleAction: if (root.enabled) root.toggled(Qt.NoModifier)
|
||||||
|
|
||||||
Keys.onPressed: function (event) {
|
Keys.onPressed: function (event) {
|
||||||
if (root.enabled && !event.isAutoRepeat
|
if (root.enabled && !event.isAutoRepeat
|
||||||
&& (event.key === Qt.Key_Space
|
&& (event.key === Qt.Key_Space
|
||||||
|| event.key === Qt.Key_Return
|
|| event.key === Qt.Key_Return
|
||||||
|| event.key === Qt.Key_Enter)) {
|
|| event.key === Qt.Key_Enter)) {
|
||||||
root.toggled()
|
root.toggled(event.modifiers)
|
||||||
event.accepted = true
|
event.accepted = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,6 +71,6 @@ Rectangle {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onPressed: root.forceActiveFocus()
|
onPressed: root.forceActiveFocus()
|
||||||
onClicked: root.toggled()
|
onClicked: function (mouse) { root.toggled(mouse.modifiers) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -749,7 +749,10 @@ Rectangle {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: root.toggleSelect(Qt.NoModifier)
|
// The disc is where a select-mode click most often lands,
|
||||||
|
// so it hands over the modifiers exactly like the body:
|
||||||
|
// a plain `NoModifier` here made Shift-click a toggle.
|
||||||
|
onClicked: function (mouse) { root.toggleSelect(mouse.modifiers) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
@@ -969,6 +972,9 @@ Rectangle {
|
|||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
|
// Hover only: a tooltip must not eat the click the
|
||||||
|
// row body (play, or a select-mode toggle) owns.
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
ToolTip.visible: containsMouse
|
ToolTip.visible: containsMouse
|
||||||
ToolTip.delay: 300
|
ToolTip.delay: 300
|
||||||
// NEW msgid — the one string this state needs and
|
// NEW msgid — the one string this state needs and
|
||||||
@@ -1078,7 +1084,12 @@ Rectangle {
|
|||||||
MouseArea {
|
MouseArea {
|
||||||
id: artistLinkArea
|
id: artistLinkArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: root.artistLink && (artistLine.linkMode || !!root.item.artistId)
|
// In select mode the whole row is a selection target
|
||||||
|
// (TrackRow.slint:174): the names stop being links so the
|
||||||
|
// click — and its Shift / Ctrl — reaches the row body
|
||||||
|
// instead of opening an artist page mid-selection.
|
||||||
|
enabled: !root.selectMode
|
||||||
|
&& root.artistLink && (artistLine.linkMode || !!root.item.artistId)
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: (artistLine.linkMode
|
cursorShape: (artistLine.linkMode
|
||||||
? (artistLine.hotSpan >= 0 && artistLine.spans[artistLine.hotSpan].href !== "")
|
? (artistLine.hotSpan >= 0 && artistLine.spans[artistLine.hotSpan].href !== "")
|
||||||
@@ -1088,6 +1099,9 @@ Rectangle {
|
|||||||
artistLine.hotSpan = artistLine.spanAt(mouse.x)
|
artistLine.hotSpan = artistLine.spanAt(mouse.x)
|
||||||
}
|
}
|
||||||
onExited: artistLine.hotSpan = -1
|
onExited: artistLine.hotSpan = -1
|
||||||
|
// Entering select mode under the pointer disables the area
|
||||||
|
// without an `exited`; the hot name must not stay lit.
|
||||||
|
onEnabledChanged: if (!enabled) artistLine.hotSpan = -1
|
||||||
// Same routing as the menu's "Go to artist" — Slint drives
|
// Same routing as the menu's "Go to artist" — Slint drives
|
||||||
// both through one handler, so gating only the menu would
|
// both through one handler, so gating only the menu would
|
||||||
// leave the LINK navigating to the track's own artist.
|
// leave the LINK navigating to the track's own artist.
|
||||||
@@ -1116,7 +1130,8 @@ Rectangle {
|
|||||||
MouseArea {
|
MouseArea {
|
||||||
id: albumArea
|
id: albumArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: !!root.item.albumId && !root.releaseGone
|
// Not a link in select mode, for the artist line's reason.
|
||||||
|
enabled: !root.selectMode && !!root.item.albumId && !root.releaseGone
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
// The names ride along so a release the catalog lost can
|
// The names ride along so a release the catalog lost can
|
||||||
|
|||||||
@@ -304,8 +304,11 @@ Rectangle {
|
|||||||
id: artistArea
|
id: artistArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
// Only a real artist id is clickable — otherwise the
|
// Only a real artist id is clickable — otherwise the
|
||||||
// pointer promises a page that cannot open.
|
// pointer promises a page that cannot open. In select
|
||||||
enabled: (root.item.artistId || "") !== ""
|
// mode the whole row selects (Shift / Ctrl included), so
|
||||||
|
// the name stops being a link and the click falls to the
|
||||||
|
// row body underneath.
|
||||||
|
enabled: !root.selectMode && (root.item.artistId || "") !== ""
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: QbzArtist.openArtist(root.item.artistId)
|
onClicked: QbzArtist.openArtist(root.item.artistId)
|
||||||
@@ -331,7 +334,8 @@ Rectangle {
|
|||||||
MouseArea {
|
MouseArea {
|
||||||
id: labelArea
|
id: labelArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: (root.item.labelId || "") !== ""
|
// Not a link in select mode, for the artist name's reason.
|
||||||
|
enabled: !root.selectMode && (root.item.labelId || "") !== ""
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: QbzHome.openLabel(root.item.labelId)
|
onClicked: QbzHome.openLabel(root.item.labelId)
|
||||||
|
|||||||
@@ -593,11 +593,14 @@ Item {
|
|||||||
selected: root.nativeActive
|
selected: root.nativeActive
|
||||||
? cardCell.slot.selected === true
|
? cardCell.slot.selected === true
|
||||||
: root.selected[cardCell.slot.id] === true
|
: root.selected[cardCell.slot.id] === true
|
||||||
onSelectToggled: {
|
// The card's modifiers, straight through: the
|
||||||
|
// paged grid (299dcde5b) passed NoModifier here,
|
||||||
|
// so a Shift-click on a card was a toggle.
|
||||||
|
onSelectToggled: function (mods) {
|
||||||
if (root.nativeActive)
|
if (root.nativeActive)
|
||||||
root.nativeToggleSelect(cardCell.slot.nativeIndex, Qt.NoModifier)
|
root.nativeToggleSelect(cardCell.slot.nativeIndex, mods)
|
||||||
else
|
else
|
||||||
root.toggleSelect(cardCell.slot.id, Qt.NoModifier)
|
root.toggleSelect(cardCell.slot.id, mods)
|
||||||
}
|
}
|
||||||
// Non-select mode only — the card routes a
|
// Non-select mode only — the card routes a
|
||||||
// select-mode click to `selectToggled` and never
|
// select-mode click to `selectToggled` and never
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
// Slint declares this inline inside TreeRow; the Qt port also needs it on
|
// Slint declares this inline inside TreeRow; the Qt port also needs it on
|
||||||
// the album cards and the track rows in multi-select, so it is its own file
|
// the album cards and the track rows in multi-select, so it is its own file
|
||||||
// (three call sites, one shape).
|
// (three call sites, one shape).
|
||||||
|
//
|
||||||
|
// `toggled` carries the click's modifiers: this disc IS a select-mode click
|
||||||
|
// target, and one that dropped them turned every Shift-click on it into a
|
||||||
|
// plain toggle (controls/SelectionModel.qml).
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import com.blitzfc.qbz
|
import com.blitzfc.qbz
|
||||||
@@ -16,7 +20,7 @@ Rectangle {
|
|||||||
property bool on: false
|
property bool on: false
|
||||||
property bool partial: false
|
property bool partial: false
|
||||||
property int diameter: 13
|
property int diameter: 13
|
||||||
signal toggled()
|
signal toggled(int modifiers)
|
||||||
|
|
||||||
QbzTheme { id: theme }
|
QbzTheme { id: theme }
|
||||||
|
|
||||||
@@ -52,6 +56,6 @@ Rectangle {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: root.toggled()
|
onClicked: function (mouse) { root.toggled(mouse.modifiers) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
pragma Singleton
|
||||||
|
import QtQuick
|
||||||
|
QtObject {
|
||||||
|
property var opened: []
|
||||||
|
function openAlbum(id) { opened = opened.concat([String(id)]) }
|
||||||
|
function openAlbumFrom(id, title, artist) { opened = opened.concat([String(id)]) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
pragma Singleton
|
||||||
|
import QtQuick
|
||||||
|
QtObject {
|
||||||
|
property var opened: []
|
||||||
|
function openArtist(id) { opened = opened.concat([String(id)]) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
pragma Singleton
|
||||||
|
import QtQuick
|
||||||
|
QtObject {
|
||||||
|
property var labels: []
|
||||||
|
function openLabel(id) { labels = labels.concat([String(id)]) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
pragma Singleton
|
||||||
|
import QtQuick
|
||||||
|
QtObject {
|
||||||
|
property var submitted: []
|
||||||
|
function searchSubmit(text) { submitted = submitted.concat([String(text)]) }
|
||||||
|
}
|
||||||
@@ -9,3 +9,7 @@ singleton QbzPurchases 1.0 QbzPurchases.qml
|
|||||||
singleton QbzOrbit 1.0 QbzOrbit.qml
|
singleton QbzOrbit 1.0 QbzOrbit.qml
|
||||||
|
|
||||||
singleton QbzAbout 1.0 QbzAbout.qml
|
singleton QbzAbout 1.0 QbzAbout.qml
|
||||||
|
singleton QbzArtist 1.0 QbzArtist.qml
|
||||||
|
singleton QbzAlbum 1.0 QbzAlbum.qml
|
||||||
|
singleton QbzSearch 1.0 QbzSearch.qml
|
||||||
|
singleton QbzHome 1.0 QbzHome.qml
|
||||||
@@ -8,11 +8,278 @@ import QtQuick
|
|||||||
import QtTest
|
import QtTest
|
||||||
import com.blitzfc.qbz
|
import com.blitzfc.qbz
|
||||||
import "../../crates/qbz-qt/qml/controls"
|
import "../../crates/qbz-qt/qml/controls"
|
||||||
|
import "../../crates/qbz-qt/qml/rows"
|
||||||
|
import "../../crates/qbz-qt/qml/cards"
|
||||||
|
import "../../crates/qbz-qt/qml/views" as Views
|
||||||
|
import "../../crates/qbz-qt/qml/views/local" as Local
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
width: 1200; height: 1400
|
width: 1200; height: 1400
|
||||||
|
|
||||||
|
function track(i) {
|
||||||
|
return { "id": "t" + i, "title": "Track " + i, "artist": "Artist " + i,
|
||||||
|
"artistId": "a" + i, "album": "Album " + i, "albumId": "al" + i,
|
||||||
|
"duration": "3:00", "qualityTier": "", "qualityDetail": "" }
|
||||||
|
}
|
||||||
|
function album(i) {
|
||||||
|
return { "id": "b" + i, "title": "Record " + i, "artist": "Band " + i,
|
||||||
|
"artistId": "band" + i, "label": "Label " + i, "labelId": "l" + i,
|
||||||
|
"year": "2001", "qualityTier": "", "qualityDetail": "" }
|
||||||
|
}
|
||||||
|
function range(n, make) {
|
||||||
|
var out = []
|
||||||
|
for (var i = 0; i < n; i++)
|
||||||
|
out.push(make(i))
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- the track list (AlbumView / PlaylistView wiring) -------------------
|
||||||
|
property var tracks: range(8, track)
|
||||||
|
property var trackSelected: ({})
|
||||||
|
SelectionModel { id: trackSel }
|
||||||
|
function toggleTrack(id, mods) {
|
||||||
|
root.trackSelected = trackSel.next(root.trackSelected, id, root.tracks,
|
||||||
|
mods === undefined ? Qt.NoModifier : mods)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: trackColumn
|
||||||
|
width: 1000
|
||||||
|
Repeater {
|
||||||
|
id: trackRepeater
|
||||||
|
model: root.tracks
|
||||||
|
delegate: TrackRow {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
width: 1000
|
||||||
|
item: modelData
|
||||||
|
number: index + 1
|
||||||
|
artistLink: true
|
||||||
|
showAlbum: true
|
||||||
|
selectMode: true
|
||||||
|
checked: root.trackSelected[modelData.id] === true
|
||||||
|
onToggleSelect: function (mods) { root.toggleTrack(modelData.id, mods) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- the album list (Library albums list wiring) ------------------------
|
||||||
|
property var albums: range(6, album)
|
||||||
|
property var albumSelected: ({})
|
||||||
|
SelectionModel { id: albumSel }
|
||||||
|
function toggleAlbum(id, mods) {
|
||||||
|
root.albumSelected = albumSel.next(root.albumSelected, id, root.albums,
|
||||||
|
mods === undefined ? Qt.NoModifier : mods)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: albumColumn
|
||||||
|
y: 420
|
||||||
|
width: 1000
|
||||||
|
Repeater {
|
||||||
|
id: albumRepeater
|
||||||
|
model: root.albums
|
||||||
|
delegate: Views.AlbumListRow {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
width: 1000
|
||||||
|
item: modelData
|
||||||
|
rowIndex: index
|
||||||
|
selectMode: true
|
||||||
|
checked: root.albumSelected[modelData.id] === true
|
||||||
|
onToggleSelect: function (mods) { root.toggleAlbum(modelData.id, mods) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- one album card and the two checkbox controls ------------------------
|
||||||
|
property var cardMods: []
|
||||||
|
AlbumCard {
|
||||||
|
id: card
|
||||||
|
x: 1000; y: 0
|
||||||
|
albumId: "card"
|
||||||
|
title: "Card title"
|
||||||
|
artist: "Card artist"
|
||||||
|
artistId: "card-artist"
|
||||||
|
selectMode: true
|
||||||
|
onSelectToggled: function (mods) { root.cardMods = root.cardMods.concat([mods]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
property var checkboxMods: []
|
||||||
|
QbzCheckbox {
|
||||||
|
id: checkbox
|
||||||
|
x: 1000; y: 320
|
||||||
|
onToggled: function (mods) { root.checkboxMods = root.checkboxMods.concat([mods]) }
|
||||||
|
}
|
||||||
|
property var selectCheckMods: []
|
||||||
|
Local.SelectCheck {
|
||||||
|
id: selectCheck
|
||||||
|
x: 1040; y: 320
|
||||||
|
onToggled: function (mods) { root.selectCheckMods = root.selectCheckMods.concat([mods]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
TestCase {
|
||||||
|
name: "MultiSelect"
|
||||||
|
when: windowShown
|
||||||
|
|
||||||
|
function find(item, pred) {
|
||||||
|
if (!item)
|
||||||
|
return null
|
||||||
|
if (pred(item))
|
||||||
|
return item
|
||||||
|
var kids = item.children || []
|
||||||
|
for (var i = 0; i < kids.length; i++) {
|
||||||
|
var hit = find(kids[i], pred)
|
||||||
|
if (hit)
|
||||||
|
return hit
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
function shown(item) {
|
||||||
|
for (var it = item; it; it = it.parent)
|
||||||
|
if (!it.visible)
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
function textItem(host, text) {
|
||||||
|
var t = find(host, function (it) {
|
||||||
|
return it.text === text && it.contentWidth !== undefined && shown(it)
|
||||||
|
})
|
||||||
|
verify(t !== null, "text '" + text + "' is drawn")
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
// Clicks go through the window at the item's position, so whichever
|
||||||
|
// MouseArea really sits on top there receives them — the point of
|
||||||
|
// using the real components.
|
||||||
|
function clickOn(item, x, y, mods) {
|
||||||
|
var p = item.mapToItem(root, x, y)
|
||||||
|
mouseClick(root, p.x, p.y, Qt.LeftButton, mods === undefined ? Qt.NoModifier : mods)
|
||||||
|
wait(20)
|
||||||
|
}
|
||||||
|
function selectedIds(map) {
|
||||||
|
return Object.keys(map).sort().join(",")
|
||||||
|
}
|
||||||
|
function trackRow(i) { return trackRepeater.itemAt(i) }
|
||||||
|
function albumRow(i) { return albumRepeater.itemAt(i) }
|
||||||
|
function disc(row) {
|
||||||
|
var d = find(row, function (it) {
|
||||||
|
return it.width === 14 && it.height === 14 && it.hoverEnabled !== undefined
|
||||||
|
&& it.cursorShape !== undefined && shown(it)
|
||||||
|
})
|
||||||
|
verify(d !== null, "the select-mode disc is drawn")
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
root.trackSelected = ({})
|
||||||
|
trackSel.anchorId = ""
|
||||||
|
root.albumSelected = ({})
|
||||||
|
albumSel.anchorId = ""
|
||||||
|
root.cardMods = []
|
||||||
|
root.checkboxMods = []
|
||||||
|
root.selectCheckMods = []
|
||||||
|
QbzArtist.opened = []
|
||||||
|
QbzAlbum.opened = []
|
||||||
|
QbzHome.labels = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// A point of the row with nothing drawn over it but the body: the
|
||||||
|
// duration cell.
|
||||||
|
function bodyPoint(row) {
|
||||||
|
var duration = textItem(row, "3:00")
|
||||||
|
return duration.mapToItem(row, duration.width / 2, duration.height / 2)
|
||||||
|
}
|
||||||
|
function clickBody(row, mods) {
|
||||||
|
var p = bodyPoint(row)
|
||||||
|
clickOn(row, p.x, p.y, mods)
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_row_body_shift_and_ctrl() {
|
||||||
|
clickBody(trackRow(1))
|
||||||
|
clickBody(trackRow(4), Qt.ShiftModifier)
|
||||||
|
compare(selectedIds(root.trackSelected), "t1,t2,t3,t4")
|
||||||
|
clickBody(trackRow(6), Qt.ControlModifier)
|
||||||
|
compare(selectedIds(root.trackSelected), "t1,t2,t3,t4,t6")
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_checkbox_disc_shift_and_ctrl() {
|
||||||
|
clickOn(disc(trackRow(0)), 7, 7)
|
||||||
|
clickOn(disc(trackRow(3)), 7, 7, Qt.ShiftModifier)
|
||||||
|
compare(selectedIds(root.trackSelected), "t0,t1,t2,t3",
|
||||||
|
"Shift on the disc selects the range")
|
||||||
|
clickOn(disc(trackRow(5)), 7, 7, Qt.ControlModifier)
|
||||||
|
clickOn(disc(trackRow(7)), 7, 7, Qt.ControlModifier | Qt.ShiftModifier)
|
||||||
|
compare(selectedIds(root.trackSelected), "t0,t1,t2,t3,t5,t6,t7",
|
||||||
|
"Ctrl adds a row and Ctrl+Shift adds a second group")
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_artist_link_selects_in_select_mode() {
|
||||||
|
clickBody(trackRow(2))
|
||||||
|
var name = textItem(trackRow(5), "Artist 5")
|
||||||
|
clickOn(name, 10, name.height / 2, Qt.ShiftModifier)
|
||||||
|
compare(QbzArtist.opened.length, 0, "no artist page opens in select mode")
|
||||||
|
compare(selectedIds(root.trackSelected), "t2,t3,t4,t5")
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_album_link_selects_in_select_mode() {
|
||||||
|
var name = textItem(trackRow(6), "Album 6")
|
||||||
|
clickOn(name, 10, name.height / 2)
|
||||||
|
compare(QbzAlbum.opened.length, 0, "no album page opens in select mode")
|
||||||
|
compare(selectedIds(root.trackSelected), "t6")
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_album_row_checkbox_shift_range() {
|
||||||
|
var first = find(albumRow(0), function (it) {
|
||||||
|
return it.checked !== undefined && it.width === 18 && it.height === 18 && shown(it)
|
||||||
|
})
|
||||||
|
var fourth = find(albumRow(3), function (it) {
|
||||||
|
return it.checked !== undefined && it.width === 18 && it.height === 18 && shown(it)
|
||||||
|
})
|
||||||
|
verify(first !== null && fourth !== null, "the album row checkboxes are drawn")
|
||||||
|
clickOn(first, 9, 9)
|
||||||
|
clickOn(fourth, 9, 9, Qt.ShiftModifier)
|
||||||
|
compare(selectedIds(root.albumSelected), "b0,b1,b2,b3")
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_album_row_links_select_in_select_mode() {
|
||||||
|
clickOn(albumRow(1), 600, 32)
|
||||||
|
var band = textItem(albumRow(4), "Band 4")
|
||||||
|
clickOn(band, 10, band.height / 2, Qt.ShiftModifier)
|
||||||
|
compare(QbzArtist.opened.length, 0, "no artist page opens in select mode")
|
||||||
|
var label = textItem(albumRow(5), "Label 5")
|
||||||
|
clickOn(label, 10, label.height / 2, Qt.ControlModifier)
|
||||||
|
compare(QbzHome.labels.length, 0, "no label page opens in select mode")
|
||||||
|
compare(selectedIds(root.albumSelected), "b1,b2,b3,b4,b5")
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_album_card_forwards_modifiers() {
|
||||||
|
clickOn(card, 100, 100, Qt.ShiftModifier)
|
||||||
|
var title = textItem(card, "Card title")
|
||||||
|
clickOn(title, 10, title.height / 2, Qt.ControlModifier)
|
||||||
|
var artist = textItem(card, "Card artist")
|
||||||
|
clickOn(artist, 10, artist.height / 2, Qt.ShiftModifier)
|
||||||
|
compare(QbzArtist.opened.length, 0, "no artist page opens in select mode")
|
||||||
|
compare(root.cardMods.length, 3)
|
||||||
|
verify((root.cardMods[0] & Qt.ShiftModifier) !== 0, "artwork carries Shift")
|
||||||
|
verify((root.cardMods[1] & Qt.ControlModifier) !== 0, "title carries Ctrl")
|
||||||
|
verify((root.cardMods[2] & Qt.ShiftModifier) !== 0, "artist line carries Shift")
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_checkbox_controls_forward_modifiers() {
|
||||||
|
clickOn(checkbox, 9, 9, Qt.ShiftModifier)
|
||||||
|
clickOn(selectCheck, 6, 6, Qt.ControlModifier)
|
||||||
|
compare(root.checkboxMods.length, 1)
|
||||||
|
verify((root.checkboxMods[0] & Qt.ShiftModifier) !== 0, "QbzCheckbox carries Shift")
|
||||||
|
compare(root.selectCheckMods.length, 1)
|
||||||
|
verify((root.selectCheckMods[0] & Qt.ControlModifier) !== 0, "SelectCheck carries Ctrl")
|
||||||
|
checkbox.forceActiveFocus()
|
||||||
|
keyClick(Qt.Key_Space)
|
||||||
|
compare(root.checkboxMods.length, 2)
|
||||||
|
compare(root.checkboxMods[1] & Qt.ShiftModifier, 0, "the keyboard toggle is a plain toggle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---- the selection rule itself ---------------------------------------------
|
// ---- the selection rule itself ---------------------------------------------
|
||||||
SelectionModel { id: rule }
|
SelectionModel { id: rule }
|
||||||
SelectionModel { id: keyedRule; idKey: "trackId" }
|
SelectionModel { id: keyedRule; idKey: "trackId" }
|
||||||
|
|||||||
Reference in new issue
Block a user