476 lines
14 KiB
QML
476 lines
14 KiB
QML
|
import QtQuick 2.15
|
||
|
import QtQuick.Controls 2.15
|
||
|
import QtQuick.Layouts 1.15
|
||
|
|
||
|
import StatusQ 0.1
|
||
|
|
||
|
import SortFilterProxyModel 0.2
|
||
|
|
||
|
Item {
|
||
|
id: root
|
||
|
|
||
|
ListModel {
|
||
|
id: simpleSourceModel
|
||
|
|
||
|
ListElement {
|
||
|
name: "entry 0"
|
||
|
position: 0
|
||
|
}
|
||
|
ListElement {
|
||
|
name: "entry 1"
|
||
|
position: 1
|
||
|
}
|
||
|
ListElement {
|
||
|
name: "entry 2"
|
||
|
position: 2
|
||
|
}
|
||
|
ListElement {
|
||
|
name: "entry 3"
|
||
|
position: 3
|
||
|
}
|
||
|
ListElement {
|
||
|
name: "entry 4"
|
||
|
position: 7
|
||
|
}
|
||
|
ListElement {
|
||
|
name: "entry 5"
|
||
|
position: 6
|
||
|
}
|
||
|
ListElement {
|
||
|
name: "entry 6"
|
||
|
position: 5
|
||
|
}
|
||
|
ListElement {
|
||
|
name: "entry 7"
|
||
|
position: 4
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SortFilterProxyModel {
|
||
|
id: sorted
|
||
|
|
||
|
sorters: RoleSorter {
|
||
|
roleName: sortByPositionRadioButton.checked ? "position" : "name"
|
||
|
sortOrder: descendingCheckBox.checked ? Qt.DescendingOrder
|
||
|
: Qt.AscendingOrder
|
||
|
}
|
||
|
|
||
|
sourceModel: simpleSourceModel
|
||
|
}
|
||
|
|
||
|
MovableModel {
|
||
|
id: movableModel
|
||
|
|
||
|
sourceModel: sorted
|
||
|
}
|
||
|
|
||
|
ColumnLayout {
|
||
|
anchors.fill: parent
|
||
|
anchors.margins: 10
|
||
|
spacing: 5
|
||
|
|
||
|
Item {
|
||
|
Layout.fillWidth: true
|
||
|
Layout.fillHeight: true
|
||
|
|
||
|
ColumnLayout {
|
||
|
id: sourceColumn
|
||
|
|
||
|
anchors.top: parent.top
|
||
|
anchors.bottom: parent.bottom
|
||
|
anchors.left: parent.left
|
||
|
anchors.margins: 20
|
||
|
spacing: 20
|
||
|
|
||
|
width: parent.width / 3
|
||
|
|
||
|
Label {
|
||
|
text: "SOURCE MODEL"
|
||
|
|
||
|
font.bold: true
|
||
|
font.pixelSize: 17
|
||
|
}
|
||
|
|
||
|
ListView {
|
||
|
id: sourceListView
|
||
|
|
||
|
spacing: 5
|
||
|
clip: true
|
||
|
|
||
|
Layout.fillWidth: true
|
||
|
Layout.fillHeight: true
|
||
|
|
||
|
model: simpleSourceModel
|
||
|
|
||
|
ScrollBar.vertical: ScrollBar {}
|
||
|
|
||
|
delegate: Item {
|
||
|
id: sourceDelegateRoot
|
||
|
|
||
|
anchors {
|
||
|
left: parent ? parent.left : undefined
|
||
|
right: parent ? parent.right : undefined
|
||
|
}
|
||
|
|
||
|
height: sourceContent.implicitHeight
|
||
|
|
||
|
RowLayout {
|
||
|
id: sourceContent
|
||
|
|
||
|
Rectangle {
|
||
|
color: "lightgray"
|
||
|
Layout.fillHeight: true
|
||
|
Layout.preferredWidth: 40
|
||
|
|
||
|
Label {
|
||
|
anchors.centerIn: parent
|
||
|
text: "↕️"
|
||
|
}
|
||
|
|
||
|
MouseArea {
|
||
|
id: sourceDragArea
|
||
|
|
||
|
property bool held: false
|
||
|
readonly property int idx: model.index
|
||
|
|
||
|
anchors.fill: parent
|
||
|
|
||
|
drag.target: held ? sourceContent : undefined
|
||
|
drag.axis: Drag.YAxis
|
||
|
|
||
|
onPressed: held = true
|
||
|
onReleased: held = false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
width: sourceDelegateRoot.width
|
||
|
|
||
|
Drag.active: sourceDragArea.held
|
||
|
Drag.source: sourceDragArea
|
||
|
Drag.hotSpot.x: width / 2
|
||
|
Drag.hotSpot.y: height / 2
|
||
|
|
||
|
states: State {
|
||
|
when: sourceDragArea.held
|
||
|
|
||
|
ParentChange {
|
||
|
target: sourceContent
|
||
|
parent: root
|
||
|
}
|
||
|
|
||
|
AnchorChanges {
|
||
|
target: sourceContent
|
||
|
anchors {
|
||
|
horizontalCenter: undefined
|
||
|
verticalCenter: undefined
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Label {
|
||
|
Layout.fillWidth: true
|
||
|
|
||
|
font.bold: true
|
||
|
text: model.name + ", position: " + model.position
|
||
|
}
|
||
|
|
||
|
RoundButton {
|
||
|
text: "❌"
|
||
|
|
||
|
onClicked: simpleSourceModel.remove(model.index)
|
||
|
}
|
||
|
|
||
|
RoundButton {
|
||
|
text: "✎"
|
||
|
|
||
|
onClicked: simpleSourceModel.setProperty(
|
||
|
index, "name", simpleSourceModel.get(index).name + "_")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DropArea {
|
||
|
anchors { fill: parent; margins: 10 }
|
||
|
|
||
|
onEntered: {
|
||
|
const from = drag.source.idx
|
||
|
const to = sourceDragArea.idx
|
||
|
|
||
|
if (from === to)
|
||
|
return
|
||
|
|
||
|
simpleSourceModel.move(from, to, 1)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ColumnLayout {
|
||
|
id: sfpmColumn
|
||
|
|
||
|
anchors.top: parent.top
|
||
|
anchors.bottom: parent.bottom
|
||
|
anchors.left: sourceColumn.right
|
||
|
anchors.margins: 20
|
||
|
|
||
|
spacing: 20
|
||
|
|
||
|
width: parent.width / 3
|
||
|
|
||
|
Label {
|
||
|
text: "SFPM MODEL"
|
||
|
|
||
|
font.bold: true
|
||
|
font.pixelSize: 17
|
||
|
}
|
||
|
|
||
|
ListView {
|
||
|
id: sfpmListView
|
||
|
|
||
|
spacing: 5
|
||
|
clip: true
|
||
|
|
||
|
Layout.fillWidth: true
|
||
|
Layout.fillHeight: true
|
||
|
|
||
|
model: sorted
|
||
|
|
||
|
ScrollBar.vertical: ScrollBar {}
|
||
|
|
||
|
delegate: RowLayout {
|
||
|
width: ListView.view.width
|
||
|
|
||
|
Label {
|
||
|
Layout.fillWidth: true
|
||
|
|
||
|
font.bold: true
|
||
|
text: model.name + ", position: " + model.position
|
||
|
}
|
||
|
|
||
|
// to keep the same delegate height as in other list views
|
||
|
RoundButton {
|
||
|
enabled: false
|
||
|
opacity: 0
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ColumnLayout {
|
||
|
anchors.top: parent.top
|
||
|
anchors.bottom: parent.bottom
|
||
|
anchors.left: sfpmColumn.right
|
||
|
anchors.right: parent.right
|
||
|
anchors.margins: 20
|
||
|
|
||
|
spacing: 20
|
||
|
|
||
|
Label {
|
||
|
text: "MOVABLE MODEL"
|
||
|
font.bold: true
|
||
|
font.pixelSize: 17
|
||
|
}
|
||
|
|
||
|
ListView {
|
||
|
id: transformedListView
|
||
|
|
||
|
Layout.fillWidth: true
|
||
|
Layout.fillHeight: true
|
||
|
|
||
|
spacing: 5
|
||
|
clip: true
|
||
|
|
||
|
model: movableModel
|
||
|
|
||
|
ScrollBar.vertical: ScrollBar {}
|
||
|
|
||
|
delegate: Item {
|
||
|
id: delegateRoot
|
||
|
|
||
|
anchors {
|
||
|
left: parent ? parent.left : undefined
|
||
|
right: parent ? parent.right : undefined
|
||
|
}
|
||
|
|
||
|
height: content.implicitHeight
|
||
|
|
||
|
RowLayout {
|
||
|
id: content
|
||
|
|
||
|
Rectangle {
|
||
|
|
||
|
color: "lightgray"
|
||
|
Layout.fillHeight: true
|
||
|
Layout.preferredWidth: 40
|
||
|
|
||
|
Label {
|
||
|
anchors.centerIn: parent
|
||
|
text: "↕️"
|
||
|
}
|
||
|
|
||
|
MouseArea {
|
||
|
id: dragArea
|
||
|
|
||
|
property bool held: false
|
||
|
readonly property int idx: model.index
|
||
|
|
||
|
anchors.fill: parent
|
||
|
|
||
|
drag.target: held ? content : undefined
|
||
|
drag.axis: Drag.YAxis
|
||
|
|
||
|
onPressed: held = true
|
||
|
onReleased: held = false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
width: delegateRoot.width
|
||
|
|
||
|
Drag.active: dragArea.held
|
||
|
Drag.source: dragArea
|
||
|
Drag.hotSpot.x: width / 2
|
||
|
Drag.hotSpot.y: height / 2
|
||
|
|
||
|
states: State {
|
||
|
when: dragArea.held
|
||
|
|
||
|
ParentChange {
|
||
|
target: content
|
||
|
parent: root
|
||
|
}
|
||
|
|
||
|
AnchorChanges {
|
||
|
target: content
|
||
|
anchors {
|
||
|
horizontalCenter: undefined
|
||
|
verticalCenter: undefined
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Label {
|
||
|
Layout.fillWidth: true
|
||
|
|
||
|
font.bold: true
|
||
|
text: model.name + ", position: " + model.position
|
||
|
}
|
||
|
|
||
|
RoundButton {
|
||
|
text: "⬆️"
|
||
|
enabled: index > 0
|
||
|
|
||
|
onClicked: movableModel.move(index, 0)
|
||
|
}
|
||
|
RoundButton {
|
||
|
text: "⬇️"
|
||
|
enabled: index < transformedListView.count - 1
|
||
|
|
||
|
onClicked: movableModel.move(
|
||
|
index, transformedListView.count - 1)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DropArea {
|
||
|
anchors { fill: parent; margins: 10 }
|
||
|
|
||
|
onEntered: {
|
||
|
const from = drag.source.idx
|
||
|
const to = dragArea.idx
|
||
|
|
||
|
if (from === to)
|
||
|
return
|
||
|
|
||
|
movableModel.move(from, to)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Rectangle {
|
||
|
width: 1
|
||
|
color: "gray"
|
||
|
anchors.top: parent.top
|
||
|
anchors.bottom: parent.bottom
|
||
|
anchors.left: sourceColumn.right
|
||
|
anchors.leftMargin: 10
|
||
|
}
|
||
|
|
||
|
Rectangle {
|
||
|
width: 1
|
||
|
color: "gray"
|
||
|
anchors.top: parent.top
|
||
|
anchors.bottom: parent.bottom
|
||
|
anchors.left: sfpmColumn.right
|
||
|
anchors.leftMargin: 10
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Button {
|
||
|
Layout.alignment: Qt.AlignHCenter
|
||
|
|
||
|
text: "SAVE ORDER"
|
||
|
font.pixelSize: 30
|
||
|
|
||
|
onClicked: {
|
||
|
const count = simpleSourceModel.count
|
||
|
const newOrder = movableModel.order()
|
||
|
const newOrderInverted = []
|
||
|
const sourceIndexes = []
|
||
|
|
||
|
for (let i = 0; i < count; i++)
|
||
|
newOrderInverted[newOrder[i]] = i
|
||
|
|
||
|
for (let j = 0; j < count; j++)
|
||
|
sourceIndexes.push(sorted.mapToSource(j))
|
||
|
|
||
|
for (let k = 0; k < count; k++)
|
||
|
simpleSourceModel.setProperty(sourceIndexes[k], "position",
|
||
|
newOrderInverted[k])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
RowLayout {
|
||
|
Layout.fillHeight: false
|
||
|
|
||
|
RadioButton {
|
||
|
text: "Sort by name"
|
||
|
}
|
||
|
|
||
|
RadioButton {
|
||
|
id: sortByPositionRadioButton
|
||
|
|
||
|
text: "Sort by position"
|
||
|
checked: true
|
||
|
}
|
||
|
|
||
|
CheckBox {
|
||
|
id: descendingCheckBox
|
||
|
text: "descending"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
RowLayout {
|
||
|
Layout.fillHeight: false
|
||
|
|
||
|
Button {
|
||
|
text: "append to source model"
|
||
|
|
||
|
onClicked: simpleSourceModel.append({ name: "X" })
|
||
|
}
|
||
|
|
||
|
Button {
|
||
|
text: "detach order explicitely"
|
||
|
|
||
|
onClicked: movableModel.detach()
|
||
|
}
|
||
|
|
||
|
Label {
|
||
|
text: `Detached: <b>${movableModel.detached}</b>`
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// category: Models
|