3
votes

I'm trying to reorder Repeater items using drag & drop. I'm using a Repeater due to dynamically loaded data. Below is what I have so far using a simple sqlite database with sample data added. What I'm hoping for is to get the re-order set up so when the user releases the dragged object, the database is updated to reflect the new order. The "Change" button reorders the elements as I want, I just can't get it to work property with drag and drop.

The loadData() function simply creates the table and inserts sample data. The "Change" button won't be in the final code, I just wanted to use it to get the re-ordering code to work.

import QtQuick.Controls 2.2 as QC2
import QtQuick 2.10
import QtQuick.Window 2.10
import QtQuick.LocalStorage 2.0

Window {
id: root
width: 320
height: 480

property var db
property int rowHeight: 90

Component.onCompleted: {
    db = LocalStorage.openDatabaseSync("test_db", "1.0", "Database", 100000)
    loadData()
}

property var sql_data: []

function loadData() {
    var sql, rs, len, i
    db.transaction(function(tx) {
        tx.executeSql("DROP TABLE IF EXISTS tbl")
        tx.executeSql("CREATE TABLE IF NOT EXISTS tbl (
            rowId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL, listOrder INTEGER NOT NULL)")
        len = 5
        for (i = 0; i < len; i++)
            tx.executeSql("INSERT INTO tbl (name, listOrder) VALUES ('Item " + i + "', " + i + ")")
        rs = tx.executeSql("SELECT name, listOrder FROM tbl ORDER BY listOrder ASC")
    })
    len = rs.rows.length
    sql_data = new Array(len)
    for (i = 0; i < len; i++) {
        sql_data[i] = {"name": "", "order": ""}
        sql_data[i]["name"] = rs.rows.item(i).name
        sql_data[i]["order"] = rs.rows.item(i).listOrder
    }
    repeater.model = sql_data
}

Flickable {
    id: mainFlick
    anchors.fill: parent
    contentHeight: mainColumn.height

    Column {
        id: mainColumn
        width: parent.width

        QC2.Button {
            text: "Change"
            onClicked: {
                var p1, p2
                var d1 = new Date().getTime()
                var movedEle = 3
                var newPos = 1
                var ele1 = repeater.itemAt(movedEle)
                var ele2 = repeater.itemAt(newPos)
                ele1.y = ele2.y
                root.sql_data[movedEle]["order"] = root.sql_data[newPos]["order"]
                if (newPos < movedEle) {
                    p1 = newPos
                    p2 = movedEle
                } else {
                    p1 = movedEle
                    p2 = newPos
                }

                root.db.transaction(function(tx) {
                    var sql = "UPDATE tbl SET listOrder = " + root.sql_data[p1]["order"] + " "
                    sql += "WHERE listOrder = " + (root.sql_data[p2]["order"])
                    for (var i = p1; i < p2; i++) {
                        if (i !== movedEle) {
                            repeater.itemAt(i).y = repeater.itemAt(i).y + rowHeight
                            root.sql_data[i]["order"] += 1
                            sql = "UPDATE tbl SET listOrder = " + root.sql_data[i]["order"] + " "
                            sql += "WHERE listOrder = " + (root.sql_data[i]["order"] - 1)
                            tx.executeSql(sql)
                        }
                    }
                })
                sql_data = sql_data.sort(function(a,b) {
                    return a["order"] - b["order"]
                })
                repeater.model = sql_data
                var d2 = new Date().getTime()
                console.debug("Seconds: " + (d2 - d1) / 1000)
            }
        }

        Repeater {
            id: repeater
            width: parent.width
            model: []
            delegate: Column {
                id: repeaterItem
                width: parent.width - 20
                height: rowHeight
                anchors.horizontalCenter: parent.horizontalCenter
                property int pos: index

                DropArea {
                    id: dragTarget
                    width: parent.width
                    height: 20

                    Rectangle {
                        id: dropRect
                        anchors.fill: parent
                        color: "green"
                        states: [
                            State {
                                when: dragTarget.containsDrag
                                PropertyChanges {
                                    target: dropRect
                                    color: "red"
                                }
                            }

                        ]
                    }
                }

                Row {
                    id: contentRow
                    width: parent.width
                    height: parent.height
                    z: 1
                    Column {
                        id: itemColumn
                        width: parent.width - 70
                        Text {
                            text: "Name: " + modelData["name"]
                        }
                        Text {
                            text: "Order: " + modelData["order"]
                        }
                        Text {
                            text: "Repeater Index: " + index
                        }
                    } // itemColumn

                    MouseArea {
                        id: dragArea
                        width: 40
                        height: itemColumn.height

                        drag.axis: Drag.YAxis
                        drag.target: dragRect

                        onReleased: parent = ((dragRect.Drag.target !== null) ? dragRect.Drag.target : root)

                        Rectangle {
                            Component.onCompleted: console.debug(dragRect.Drag.target)
                            id: dragRect
                            width: 40
                            height: itemColumn.height
                            color: "grey"
                            Drag.active: dragArea.drag.active
                            Drag.source: dragArea
                            Drag.hotSpot.x: width / 2
                            Drag.hotSpot.y: height / 2
                            states : State {
                                when: dragArea.drag.active
                                ParentChange { target: dragRect; parent: root }
                                AnchorChanges {
                                    target: dragRect
                                    anchors.verticalCenter: undefined
                                    anchors.horizontalCenter: undefined
                                }
                            }
                        }
                    }
                } // contentRow

                Behavior on y {
                    PropertyAnimation {
                        duration: 200
                        easing.type: Easing.Linear
                    }
                }
            } // repeaterItem
        } // repeater
    } // mainColumn
} // mainFlick
} // id

Much of the drag and drop code came from the Qt Examples site.

Here's what my problems are:

  1. The MouseArea used for dragging the rectangles doesn't seem to change locations. Once a rectangle is actually moved, it stays in its new location but if I want to move it again, I have to click and drag where it was originally when the app loads.
  2. If I switch the target of the Drag Area from the child rectangle to the entire row (repeaterItem), everything moves properly but will no longer interact with the Drop areas.
  3. I think I can get the index of the new location after a row has been dragged simply by getting the y-value of the Drop Area. Would there be a better way to do this?

Since the "Change" button already re-orders the row elements, I only need help getting the rows to properly interact with the Drop Areas and then get the y-position of the Drop Area when the dragged item is released.

1
I think you might be abe to fix your first problem by anchoring the MouseArea to the RectangleAmfasis
@Amfasis But the Rectangle is a child of MouseArea and an element can't be anchored to its child.John
Hmm, misunderstood and didn't fully read the code, but I would say the mouseArea should be anchored to the whole delegate somehow (partly filling it if wanted)Amfasis
@Amfasis Thank you for your reply. Unfortunately, when I anchor the MouseArea to either the Rectangle or the entire row itself, everything gets frozen into place, I can no longer drag anything.John

1 Answers

2
votes

It may not be the most efficient out there but it's a good start for anybody looking to do something similar.

import QtQuick.Controls 2.2 as QC2
import QtQuick 2.10
import QtQuick.Window 2.10
import QtQuick.LocalStorage 2.0

Window {
id: root
width: 320
height: 580

property var db
property int rowHeight: 90
property int len

Component.onCompleted: {
    db = LocalStorage.openDatabaseSync("test_db", "1.0", "Database", 100000)
    loadData()
}

property var sql_data: []

function loadData() {
    var sql, rs, i
    db.transaction(function(tx) {
        tx.executeSql("DROP TABLE IF EXISTS tbl")
        tx.executeSql("CREATE TABLE IF NOT EXISTS tbl (
            rowId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL, listOrder INTEGER NOT NULL)")
        len = 15
        for (i = 0; i < len; i++)
            tx.executeSql("INSERT INTO tbl (name, listOrder) VALUES ('Item " + i + "', " + i + ")")
        rs = tx.executeSql("SELECT name, listOrder FROM tbl ORDER BY listOrder ASC")
    })
    len = rs.rows.length
    sql_data = new Array(len)
    for (i = 0; i < len; i++) {
        sql_data[i] = {"name": "", "order": ""}
        sql_data[i]["name"] = rs.rows.item(i).name
        sql_data[i]["order"] = rs.rows.item(i).listOrder
    }
    repeater.model = sql_data
}


Flickable {
    id: mainFlick
    anchors.fill: parent
    contentHeight: mainColumn.height
    rebound: Transition {
        NumberAnimation {
            properties: "y"
            duration: 500
            easing.type: Easing.OutCirc
        }
    }

    Column {
        id: mainColumn
        width: parent.width

        function moveEle(start, end, f) {
            if (f === false) {
                var p1, p2, ele1, ele2
                var c = false
                var d1 = new Date().getTime()
                ele1 = repeater.itemAt(start)
                console.debug(f)
                ele2 = repeater.itemAt(end)
                root.sql_data[start]["order"] = root.sql_data[end]["order"]
                if (end < start) {
                    p1 = end
                    p2 = start
                } else {
                    p1 = start
                    p2 = end
                    c = true
                }

                root.db.transaction(function(tx) {
                    var sql = "UPDATE tbl SET listOrder = " + root.sql_data[p1]["order"] + " "
                    sql += "WHERE listOrder = " + (root.sql_data[p2]["order"])
                    for (var i = p1; i < p2; i++) {
                        if (c === false) {
                            if (i !== start) {
                                root.sql_data[i]["order"]++
                                sql = "UPDATE tbl SET listOrder = " + root.sql_data[i]["order"] + " "
                                sql += "WHERE listOrder = " + (root.sql_data[i]["order"] - 1)
                                tx.executeSql(sql)
                            }
                        } else {
                            root.sql_data[i]["order"]--
                            sql = "UPDATE tbl SET listOrder = " + root.sql_data[i]["order"] + " "
                            sql += "WHERE listOrder = " + (root.sql_data[i]["order"] - 1)
                            tx.executeSql(sql)
                        }
                    }
                })
            } else if (f === true) {
                c = false
                d1 = new Date().getTime()
                ele1 = repeater.itemAt(start)
                console.debug(f)
                end--
                ele2 = repeater.itemAt(end)
                root.sql_data[start]["order"] = root.sql_data[end]["order"]
                p1 = start
                p2 = end
                c = true

                root.db.transaction(function(tx) {
                    var sql = "UPDATE tbl SET listOrder = " + root.sql_data[p1]["order"] + " "
                    sql += "WHERE listOrder = " + (root.sql_data[p2]["order"])
                    tx.executeSql(sql)
                    for (var i = p1; i <= p2; i++) {
                        if (i !== start) {
                            root.sql_data[i]["order"]--
                            sql = "UPDATE tbl SET listOrder = " + root.sql_data[i]["order"] + " "
                            sql += "WHERE listOrder = " + (root.sql_data[i]["order"] - 1)
                            tx.executeSql(sql)
                        }
                    }
                })
            }

            sql_data = sql_data.sort(function(a,b) {
                return a["order"] - b["order"]
            })
            repeater.model = sql_data
            var d2 = new Date().getTime()
            console.debug("Seconds: " + (d2 - d1) / 1000)
        }

        Repeater {
            id: repeater
            width: parent.width
            model: []
            delegate: Column {
                id: repeaterItem
                width: parent.width
                height: rowHeight
                anchors.horizontalCenter: parent.horizontalCenter
                z: dragArea.drag.active ? 2 : 1
                property int pos: index

                DropArea {
                    id: dragTarget
                    width: parent.width
                    height: 15
                    property int ind: index
                    onEntered: {
                        drag.source.ind = index
                        drag.source.f = false
                        if (drag.source.ind !== drag.source.oldInd && drag.source.ind !== drag.source.oldInd + 1)
                            drag.source.caught = true
                    }
                    onExited: drag.source.caught = false

                    Rectangle {
                        id: dropRect
                        anchors.fill: parent
                        z: 0
                        states: [
                            State {
                                when: dragTarget.containsDrag
                                PropertyChanges {
                                    target: dropRect
                                    color: "grey"
                                }
                                PropertyChanges {
                                    target: dropRectLine
                                    visible: false
                                }
                            }
                        ]
                        Rectangle {
                            id: dropRectLine
                            width: parent.width
                            height: 1
                            anchors.verticalCenter: parent.verticalCenter
                            color: "black"
                        }
                    }
                }
                Row {
                    id: contentRow
                    width: parent.width
                    height: parent.height
                    Drag.active: dragArea.drag.active
                    Drag.source: dragArea
                    Drag.hotSpot.x: width / 2
                    Drag.hotSpot.y: height / 2
                    Column {
                        id: itemColumn
                        width: parent.width - 70
                        Text {
                            text: "Name: " + modelData["name"]
                        }
                        Text {
                            text: "Order: " + modelData["order"]
                        }
                        Text {
                            text: "Repeater Index: " + index
                        }
                    } // itemColumn

                MouseArea {
                    id: dragArea
                    width: 40 //repeater.width
                    height: itemColumn.height

                    drag.axis: Drag.YAxis
                    drag.target: contentRow
                    property point beginDrag
                    property point dropTarget
                    property bool caught: false
                    property int ind
                    property int oldInd: index
                    property bool f
                    onPressed: {
                        dragArea.beginDrag = Qt.point(contentRow.x, contentRow.y);
                    }
                    onReleased: {
                        if (dragArea.caught) {
                            dropRectFinal.color = "white"
                            dropRectLineFinal.visible = true
                            mainColumn.moveEle(index,dragArea.ind, dragArea.f)
                        } else {
                            backAnimY.from = contentRow.y;
                            backAnimY.to = beginDrag.y;
                            backAnim.start()
                        }
                    }

                        Rectangle {
                            id: dragRect
                            width: 40
                            height: itemColumn.height
                            color: "grey"
                        }
                    } // contentRow
                } // dragArea

                ParallelAnimation {
                    id: backAnim
                    SpringAnimation { id: backAnimY; target: contentRow; property: "y"; duration: 300; spring: 2; damping: 0.2 }
                }
            } // repeaterItem
        } // repeater

        DropArea {
            id: dragTargetFinal
            width: parent.width
            height: 15
            property int ind: root.len
            onEntered: {
                drag.source.ind = ind
                drag.source.f = true
                if (drag.source.ind !== drag.source.oldInd && drag.source.ind !== drag.source.oldInd + 1)
                    drag.source.caught = true
            }
            onExited: drag.source.caught = false

            Rectangle {
                id: dropRectFinal
                anchors.fill: parent
                states: [
                    State {
                        when: dragTargetFinal.containsDrag
                        PropertyChanges {
                            target: dropRectFinal
                            color: "grey"
                        }
                        PropertyChanges {
                            target: dropRectLineFinal
                            visible: false
                        }
                    }
                ]
                Rectangle {
                    id: dropRectLineFinal
                    width: parent.width
                    height: 1
                    anchors.verticalCenter: parent.verticalCenter
                    color: "black"
                }
            }
        }
    } // mainColumn
    QC2.ScrollBar.vertical: QC2.ScrollBar { active: scrollAnim.running ? true : false }
} // mainFlick
DropArea {
    id: scrollUp
    width: parent.width
    height: 50
    anchors.top: parent.top
    visible: {
        var visible = false
        if (mainFlick.contentY > 0)
            visible = true
        visible
    }
    onEntered: {
        scrollAnim.from = mainFlick.contentY
        scrollAnim.to = 0
        scrollAnim.start()
    }
    onExited: scrollAnim.stop()
}
DropArea {
    id: scrollDown
    width: parent.width
    height: 50
    anchors.bottom: parent.bottom
    visible: {
        var visible = false
        if (mainFlick.contentY < mainColumn.height - Window.height)
            visible = true
        visible
    }
    onEntered: {
        scrollAnim.from = mainFlick.contentY
        scrollAnim.to = mainColumn.height - Window.height
        scrollAnim.start()
    }
    onExited: scrollAnim.stop()
}
SmoothedAnimation {
    id: scrollAnim
    target: mainFlick
    property: "contentY"
    velocity: 400
    loops: 1
    maximumEasingTime: 10
}
} // root