Files
QtModelsToolkit/demoapp/GenericListView.qml
2025-06-13 16:25:14 +02:00

217 lines
5.6 KiB
QML

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtModelsToolkit 1.0
ListView {
id: root
// adds drag handler to every row, emits moveRequested when item is moved
property bool movable: false
// roles intended to be visualized, all roles when empty
property var roles: []
// custom delegate height, when set to 0, delegate's implicitHeight is used
property int delegateHeight: 0
// skip role for given row if is empty or undefined
property bool skipEmptyRoles: false
// text to be displayed in a list view's header
property string label
// additional component to be instantiated within every delegate
property Component insetComponent
property int margin: 5
ScrollBar.vertical: ScrollBar {}
clip: true
spacing: 10
leftMargin: margin
rightMargin: margin
topMargin: margin
bottomMargin: margin
signal moveRequested(int from, int to)
signal rowClicked(int index, string role)
ListModel {
id: rowModel
function initialize() {
const roleNames = roles.length ? roles
: ModelQuery.roleNames(root.model)
const modelContent = roleNames.map(roleName => ({ roleName }))
clear()
append(modelContent)
}
Component.onCompleted: initialize()
}
Connections {
target: root.model || null
function onRowsInserted() {
if (rowModel.count === 0)
rowModel.initialize()
}
function onModelReset() {
rowModel.initialize()
}
}
Rectangle {
border.color: "lightgray"
color: "transparent"
anchors.fill: parent
}
header: Label {
visible: !!text
height: visible ? undefined : 0
text: root.label
font.bold: true
font.pixelSize: 16
bottomPadding: 20
}
delegate: Item {
id: delegateRoot
width: ListView.view.width
height: root.delegateHeight || delegateRow.implicitHeight
readonly property var topModel: model
RowLayout {
id: delegateRow
Drag.active: dragArea.pressed
Drag.source: dragArea
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
anchors.fill: delegateRoot
states: State {
when: dragArea.pressed
ParentChange {
target: delegateRow
parent: root
}
AnchorChanges {
target: delegateRow
anchors {
horizontalCenter: undefined
verticalCenter: undefined
}
}
}
RoundButton {
text: "↕️"
visible: root.movable
MouseArea {
id: dragArea
property bool held: pressed
readonly property int idx: model.index
anchors.fill: parent
drag.target: pressed ? delegateRow : undefined
drag.axis: Drag.YAxis
}
}
Loader {
readonly property var model: delegateRoot.topModel
sourceComponent: insetComponent
}
Flow {
Layout.fillWidth: true
Layout.fillHeight: true
Repeater {
model: rowModel
delegate: Label {
readonly property var value:
delegateRoot.topModel[roleName]
readonly property bool mayBeImage: !!value &&
(value.toString().startsWith("file:")
|| value.toString().startsWith("data:image"))
readonly property var valueSanitized:
value === undefined ? "-" : value
readonly property bool last: index === rowModel.count - 1
readonly property string separator: last ? "" : ",  "
visible: (value !== undefined && value !== "")
|| !root.skipEmptyRoles
text: `<u>${roleName}</u>: ${mayBeImage ? `<img src="${value}" width="15" height="15">`
: valueSanitized}${separator}`
MouseArea {
anchors.fill: parent
onClicked: root.rowClicked(
delegateRoot.topModel.index,
roleName)
}
}
}
}
Item {
Layout.fillWidth: true
}
}
DropArea {
anchors { fill: parent; margins: 10 }
onEntered: {
const from = drag.source.idx
const to = dragArea.idx
if (from === to)
return
root.moveRequested(from, to)
}
}
Rectangle {
anchors.top: parent.top
anchors.topMargin: -2
width: parent.width
height: 1
color: "lightgray"
}
Rectangle {
anchors.bottom: parent.bottom
anchors.bottomMargin: -2
width: parent.width
height: 1
color: "lightgray"
}
}
}