From abd8ea2195982f6793ac2cab7d561ace68ff1bfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 4 Aug 2022 21:52:56 +0200 Subject: [PATCH] chore(StatusChatList): Keep sync with underlying model after reordering (#825) --- .../src/StatusQ/Components/StatusChatList.qml | 62 ++++++++++--- .../StatusChatListAndCategories.qml | 87 ++++++++++++------- ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml | 23 +++++ 3 files changed, 128 insertions(+), 44 deletions(-) diff --git a/ui/StatusQ/src/StatusQ/Components/StatusChatList.qml b/ui/StatusQ/src/StatusQ/Components/StatusChatList.qml index 487e701d142..bc5e0ea0ca9 100644 --- a/ui/StatusQ/src/StatusQ/Components/StatusChatList.qml +++ b/ui/StatusQ/src/StatusQ/Components/StatusChatList.qml @@ -9,7 +9,7 @@ import StatusQ.Components 0.1 import StatusQ.Controls 0.1 Column { - id: statusChatList + id: root spacing: 4 width: 288 @@ -33,13 +33,33 @@ Column { } } + onDraggableItemsChanged: delegateModel.items.setGroups(0, delegateModel.items.count, "unsorted") + + QtObject { + id: d + + property int destinationPosition: -1 + } + DelegateModel { id: delegateModel - model: statusChatList.model + model: root.model + + items.includeByDefault: !root.draggableItems + + groups: DelegateModelGroup { + id: unsortedItems + + name: "unsorted" + includeByDefault: root.draggableItems + onChanged: Utils.delegateModelSort(unsortedItems, delegateModel.items, + (a, b) => a.position < b.position) + } + delegate: Item { id: draggable objectName: model.name - width: statusChatList.width + width: root.width height: statusChatListItem.height property alias chatListItem: statusChatListItem @@ -50,7 +70,7 @@ Column { cursorShape: active ? Qt.ClosedHandCursor : Qt.PointingHandCursor hoverEnabled: true pressAndHoldInterval: 150 - enabled: statusChatList.draggableItems + enabled: root.draggableItems property bool active: false property real startY: 0 @@ -66,8 +86,8 @@ Column { } onPressAndHold: active = true onReleased: { - if (active) { - statusChatList.chatItemReordered(statusChatListItem.chatId, statusChatListItem.originalOrder, statusChatListItem.originalOrder) + if (active && d.destinationPosition !== -1 && statusChatListItem.originalOrder !== d.destinationPosition) { + root.chatItemReordered(statusChatListItem.chatId, statusChatListItem.originalOrder, d.destinationPosition) } active = false } @@ -81,6 +101,7 @@ Column { active = true } } + onActiveChanged: d.destinationPosition = -1 StatusChatListItem { id: statusChatListItem @@ -96,7 +117,7 @@ Column { hasUnreadMessages: model.hasUnreadMessages notificationsCount: model.notificationsCount highlightWhenCreated: !!model.highlight - selected: (model.active && statusChatList.highlightItem) + selected: (model.active && root.highlightItem) icon.emoji: model.emoji icon.color: !!model.color ? model.color : Theme.palette.userCustomizationColors[model.colorId] @@ -105,10 +126,26 @@ Column { ringSettings.ringSpecModel: model.colorHash sensor.cursorShape: dragSensor.cursorShape + + Connections { + target: statusChatListItem + enabled: root.draggableItems + + function onOriginalOrderChanged() { + Qt.callLater(() => { + if (!delegateModel) + return + + delegateModel.items.setGroups(0, delegateModel.items.count, "unsorted") + }) + } + + } + onClicked: { highlightWhenCreated = false - if (mouse.button === Qt.RightButton && !!statusChatList.popupMenu) { + if (mouse.button === Qt.RightButton && !!root.popupMenu) { statusChatListItem.highlighted = true let originalOpenHandler = popupMenuSlot.item.openHandler @@ -136,10 +173,10 @@ Column { return } if (!statusChatListItem.selected) { - statusChatList.chatItemSelected(model.parentItemId, model.itemId) + root.chatItemSelected(model.parentItemId, model.itemId) } } - onUnmute: statusChatList.chatItemUnmuted(model.itemId) + onUnmute: root.chatItemUnmuted(model.itemId) } } @@ -150,7 +187,6 @@ Column { keys: ["chat-item-category-" + statusChatListItem.categoryId] onEntered: reorderDelay.start() - onDropped: statusChatList.chatItemReordered(statusChatListItem.chatId, drag.source.originalOrder, statusChatListItem.DelegateModel.itemsIndex) Timer { id: reorderDelay @@ -158,7 +194,7 @@ Column { repeat: false onTriggered: { if (dropArea.containsDrag) { - dropArea.drag.source.chatListItem.originalOrder = statusChatListItem.originalOrder + d.destinationPosition = delegateModel.model.get(draggable.DelegateModel.itemsIndex).position delegateModel.items.move(dropArea.drag.source.DelegateModel.itemsIndex, draggable.DelegateModel.itemsIndex) } } @@ -208,6 +244,6 @@ Column { Loader { id: popupMenuSlot - active: !!statusChatList.popupMenu + active: !!root.popupMenu } } diff --git a/ui/StatusQ/src/StatusQ/Components/StatusChatListAndCategories.qml b/ui/StatusQ/src/StatusQ/Components/StatusChatListAndCategories.qml index 57d37985a7f..01338885eb2 100644 --- a/ui/StatusQ/src/StatusQ/Components/StatusChatListAndCategories.qml +++ b/ui/StatusQ/src/StatusQ/Components/StatusChatListAndCategories.qml @@ -10,7 +10,7 @@ import StatusQ.Core 0.1 import SortFilterProxyModel 0.2 Item { - id: statusChatListAndCategories + id: root implicitHeight: chatListsAndCategories.height implicitWidth: chatListsAndCategories.width @@ -53,11 +53,11 @@ Item { MouseArea { id: sensor anchors.top: parent.top - width: statusChatListAndCategories.width - height: statusChatListAndCategories.height + width: root.width + height: root.height acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: { - if (mouse.button === Qt.RightButton && showPopupMenu && !!statusChatListAndCategories.popupMenu) { + if (mouse.button === Qt.RightButton && showPopupMenu && !!root.popupMenu) { popupMenuSlot.item.popup(mouse.x + 4, mouse.y + 6) return } @@ -73,31 +73,44 @@ Item { StatusChatList { id: statusChatList visible: statusChatList.model.count > 0 - onChatItemSelected: statusChatListAndCategories.chatItemSelected(categoryId, id) - onChatItemUnmuted: statusChatListAndCategories.chatItemUnmuted(id) - onChatItemReordered: statusChatListAndCategories.chatItemReordered(categoryId, id, from, to) - draggableItems: statusChatListAndCategories.draggableItems + onChatItemSelected: root.chatItemSelected(categoryId, id) + onChatItemUnmuted: root.chatItemUnmuted(id) + onChatItemReordered: root.chatItemReordered(categoryId, id, from, to) + draggableItems: root.draggableItems model: SortFilterProxyModel { - sourceModel: statusChatListAndCategories.model + sourceModel: root.model filters: ValueFilter { roleName: "isCategory"; value: false } sorters: RoleSorter { roleName: "position" } } - popupMenu: statusChatListAndCategories.chatListPopupMenu + popupMenu: root.chatListPopupMenu } DelegateModel { id: delegateModel + property int destinationPosition: -1 + model: SortFilterProxyModel { - sourceModel: statusChatListAndCategories.model + sourceModel: root.model filters: ValueFilter { roleName: "isCategory"; value: true } sorters: RoleSorter { roleName: "position" } } + items.includeByDefault: false + + groups: DelegateModelGroup { + id: unsortedItems + + name: "unsorted" + includeByDefault: true + onChanged: Utils.delegateModelSort(unsortedItems, delegateModel.items, + (a, b) => a.position < b.position) + } + delegate: Item { id: draggable width: statusChatListCategory.width @@ -117,13 +130,13 @@ Item { dragSensor.drag.threshold: 0.1 dragSensor.drag.filterChildren: true dragSensor.onPressAndHold: { - if (statusChatListAndCategories.draggableCategories) { + if (root.draggableCategories) { dragActive = true } } dragSensor.onReleased: { - if (dragActive) { - statusChatListAndCategories.chatListCategoryReordered(statusChatListCategory.categoryId, statusChatListCategory.originalOrder, statusChatListCategory.originalOrder) + if (dragActive && delegateModel.destinationPosition !== -1 && statusChatListCategory.originalOrder !== delegateModel.destinationPosition) { + root.chatListCategoryReordered(statusChatListCategory.categoryId, statusChatListCategory.originalOrder, delegateModel.destinationPosition) } dragActive = false } @@ -133,42 +146,44 @@ Item { startX = dragSensor.mouseX } dragSensor.onMouseYChanged: { - if (statusChatListAndCategories.draggableCategories && (Math.abs(startY - dragSensor.mouseY) > 1) && dragSensor.pressed) { + if (root.draggableCategories && (Math.abs(startY - dragSensor.mouseY) > 1) && dragSensor.pressed) { dragActive = true } } dragSensor.onMouseXChanged: { - if (statusChatListAndCategories.draggableCategories && (Math.abs(startX - dragSensor.mouseX) > 1) && dragSensor.pressed) { + if (root.draggableCategories && (Math.abs(startX - dragSensor.mouseX) > 1) && dragSensor.pressed) { dragActive = true } } + onDragActiveChanged: delegateModel.destinationPosition = -1 - addButton.tooltip: statusChatListAndCategories.categoryAddButtonToolTip - menuButton.tooltip: statusChatListAndCategories.categoryMenuButtonToolTip + addButton.tooltip: root.categoryAddButtonToolTip + menuButton.tooltip: root.categoryMenuButtonToolTip originalOrder: model.position categoryId: model.itemId name: model.name - showActionButtons: statusChatListAndCategories.showCategoryActionButtons - addButton.onClicked: statusChatListAndCategories.categoryAddButtonClicked(model.itemId) + + showActionButtons: root.showCategoryActionButtons + addButton.onClicked: root.categoryAddButtonClicked(model.itemId) chatList.model: SortFilterProxyModel { sourceModel: model.subItems sorters: RoleSorter { roleName: "position" } } - chatList.onChatItemSelected: statusChatListAndCategories.chatItemSelected(categoryId, id) - chatList.onChatItemUnmuted: statusChatListAndCategories.chatItemUnmuted(id) - chatList.onChatItemReordered: statusChatListAndCategories.chatItemReordered(model.itemId, id, from, to) - chatList.draggableItems: statusChatListAndCategories.draggableItems + chatList.onChatItemSelected: root.chatItemSelected(categoryId, id) + chatList.onChatItemUnmuted: root.chatItemUnmuted(id) + chatList.onChatItemReordered: root.chatItemReordered(model.itemId, id, from, to) + chatList.draggableItems: root.draggableItems - popupMenu: statusChatListAndCategories.categoryPopupMenu - chatListPopupMenu: statusChatListAndCategories.chatListPopupMenu + popupMenu: root.categoryPopupMenu + chatListPopupMenu: root.chatListPopupMenu // Used to set the initial value of "opened" when the // model is bound/changed. opened: { - let openedState = statusChatListAndCategories.openedCategoryState[model.itemId] + let openedState = root.openedCategoryState[model.itemId] return openedState !== undefined ? openedState : true // defaults to open } @@ -177,7 +192,18 @@ Item { // as the state would be lost each time the model is // changed. onOpenedChanged: { - statusChatListAndCategories.openedCategoryState[model.itemId] = statusChatListCategory.opened + root.openedCategoryState[model.itemId] = statusChatListCategory.opened + } + + Connections { + function onOriginalOrderChanged() { + Qt.callLater(() => { + if (!delegateModel) + return + + delegateModel.items.setGroups(0, delegateModel.items.count, "unsorted") + }) + } } } @@ -188,7 +214,6 @@ Item { keys: ["chat-category"] onEntered: reorderDelay.start() - onDropped: statusChatListAndCategories.chatListCategoryReordered(statusChatListCategory.categoryId, drag.source.originalOrder, statusChatListCategory.DelegateModel.itemsIndex) Timer { id: reorderDelay @@ -196,7 +221,7 @@ Item { repeat: false onTriggered: { if (dropArea.containsDrag) { - dropArea.drag.source.chatListCategory.originalOrder = statusChatListCategory.originalOrder + delegateModel.destinationPosition = delegateModel.model.get(draggable.DelegateModel.itemsIndex).position delegateModel.items.move(dropArea.drag.source.DelegateModel.itemsIndex, draggable.DelegateModel.itemsIndex) } } @@ -242,6 +267,6 @@ Item { Loader { id: popupMenuSlot - active: !!statusChatListAndCategories.popupMenu + active: !!root.popupMenu } } diff --git a/ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml b/ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml index 3718fbe0f4a..270a5b9d0ee 100644 --- a/ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml +++ b/ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml @@ -150,6 +150,29 @@ QtObject { context.stroke(); } } + + function delegateModelSort(srcGroup, dstGroup, lessThan) { + const insertPosition = (lessThan, item) => { + let lower = 0 + let upper = dstGroup.count + while (lower < upper) { + const middle = Math.floor(lower + (upper - lower) / 2) + const result = lessThan(item.model, dstGroup.get(middle).model); + if (result) + upper = middle + else + lower = middle + 1 + } + return lower + } + + while (srcGroup.count > 0) { + const item = srcGroup.get(0) + const index = insertPosition(lessThan, item) + item.groups = dstGroup.name + dstGroup.move(item.itemsIndex, index) + } + } }