From 6fd51adab5eb57a78432482c85a1839fdd59cefe Mon Sep 17 00:00:00 2001 From: zsien Date: Fri, 16 Aug 2024 15:18:36 +0800 Subject: [PATCH] chore: switch tray items to listview MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * tray 使用 listview 实现 * stashed 使用 grid 实现 * 修改 traysortordermodel Issues: linuxdeepin/developer-center#10198 --- .../ActionLegacyTrayPluginDelegate.qml | 47 +- .../tray/package/ActionShowStashDelegate.qml | 22 + .../package/ActionToggleCollapseDelegate.qml | 19 + panels/dock/tray/package/DebugRectangle.qml | 13 + panels/dock/tray/package/StashContainer.qml | 59 +- panels/dock/tray/package/TrayContainer.qml | 124 ++-- .../dock/tray/package/TrayItemPositioner.qml | 65 +- panels/dock/tray/package/tray.qml | 8 - panels/dock/tray/traysortordermodel.cpp | 638 ++++++++++++------ panels/dock/tray/traysortordermodel.h | 46 +- 10 files changed, 646 insertions(+), 395 deletions(-) create mode 100644 panels/dock/tray/package/DebugRectangle.qml diff --git a/panels/dock/tray/package/ActionLegacyTrayPluginDelegate.qml b/panels/dock/tray/package/ActionLegacyTrayPluginDelegate.qml index 2de86f893..9579fea87 100644 --- a/panels/dock/tray/package/ActionLegacyTrayPluginDelegate.qml +++ b/panels/dock/tray/package/ActionLegacyTrayPluginDelegate.qml @@ -26,6 +26,8 @@ AppletItemButton { required property bool itemVisible property bool dragable: true + property string dropOnSurfaceId: "" + property bool dropOnBefore: true padding: 0 @@ -160,7 +162,7 @@ AppletItemButton { Drag.dragType: Drag.Automatic DQuickDrag.overlay: overlayWindow DQuickDrag.active: Drag.active - DQuickDrag.hotSpotScale: Qt.size(0.5, 1) + DQuickDrag.hotSpotScale: Qt.size(0.5, 0.5) Drag.mimeData: { "text/x-dde-shell-tray-dnd-surfaceId": model.surfaceId, "text/x-dde-shell-tray-dnd-sectionType": model.sectionType @@ -172,6 +174,11 @@ AppletItemButton { // reset position on drop Qt.callLater(() => { x = 0; y = 0; }); } + + if (!Drag.active && dropOnSurfaceId != "") { + DDT.TraySortOrderModel.move(model.surfaceId, dropOnSurfaceId, dropOnBefore); + dropOnSurfaceId = ""; + } } DragHandler { @@ -182,4 +189,42 @@ AppletItemButton { Qt.callLater(function(){ root.Drag.active = dragHandler.active }) } } + + DropArea { + enabled: dragable + anchors.fill: parent + keys: ["text/x-dde-shell-tray-dnd-surfaceId"] + + onEntered: function(drag) { + let legacyTrayPlugin = drag.source as ActionLegacyTrayPluginDelegate + if (!legacyTrayPlugin) { + return + } + + const sourceSurfaceId = drag.getDataAsString("text/x-dde-shell-tray-dnd-surfaceId") + const sourceSectionType = drag.getDataAsString("text/x-dde-shell-tray-dnd-sectionType") + if (model.sectionType != "stashed" && sourceSectionType == "stashed") { + DDT.TraySortOrderModel.move(sourceSurfaceId, model.surfaceId, false); + return + } + + if (model.sectionType == "stashed" && sourceSectionType != "stashed") { + if (!sourceSurfaceId.startsWith("application-tray::")) { + drag.accepted = false + return + } + DDT.TraySortOrderModel.move(surfaceId, model.surfaceId, false); + return + } + + if (DelegateModel.itemsIndex == legacyTrayPlugin.DelegateModel.itemsIndex) { + return + } + + visualModel.items.move(legacyTrayPlugin.DelegateModel.itemsIndex, DelegateModel.itemsIndex) + + legacyTrayPlugin.dropOnSurfaceId = model.surfaceId + legacyTrayPlugin.dropOnBefore = DelegateModel.itemsIndex > legacyTrayPlugin.DelegateModel.itemsIndex + } + } } diff --git a/panels/dock/tray/package/ActionShowStashDelegate.qml b/panels/dock/tray/package/ActionShowStashDelegate.qml index c46bbb4ff..5b091b7f8 100644 --- a/panels/dock/tray/package/ActionShowStashDelegate.qml +++ b/panels/dock/tray/package/ActionShowStashDelegate.qml @@ -119,4 +119,26 @@ AppletItemButton { } } } + + DropArea { + anchors.fill: parent + keys: ["text/x-dde-shell-tray-dnd-surfaceId"] + + onEntered: function(drag) { + let legacyTrayPlugin = drag.source as ActionLegacyTrayPluginDelegate + if (!legacyTrayPlugin) { + return + } + + let surfaceId = dragEvent.getDataAsString("text/x-dde-shell-tray-dnd-surfaceId") + if (!surfaceId.startsWith("application-tray::")) { + dragEvent.accepted = false + return + } + } + onDropped: function (dropEvent) { + let surfaceId = dropEvent.getDataAsString("text/x-dde-shell-tray-dnd-surfaceId") + DDT.TraySortOrderModel.move(surfaceId, "internal/action-stash-placeholder", true); + } + } } diff --git a/panels/dock/tray/package/ActionToggleCollapseDelegate.qml b/panels/dock/tray/package/ActionToggleCollapseDelegate.qml index 4c30e88a8..622cce41c 100644 --- a/panels/dock/tray/package/ActionToggleCollapseDelegate.qml +++ b/panels/dock/tray/package/ActionToggleCollapseDelegate.qml @@ -79,4 +79,23 @@ AppletItemButton { } } } + + DropArea { + anchors.fill: parent + keys: ["text/x-dde-shell-tray-dnd-surfaceId"] + + onEntered: function(drag) { + let legacyTrayPlugin = drag.source as ActionLegacyTrayPluginDelegate + if (legacyTrayPlugin) { + if (DelegateModel.itemsIndex == legacyTrayPlugin.DelegateModel.itemsIndex) { + return + } + + visualModel.items.move(legacyTrayPlugin.DelegateModel.itemsIndex, DelegateModel.itemsIndex) + + legacyTrayPlugin.dropOnSurfaceId = model.surfaceId + legacyTrayPlugin.dropOnBefore = DelegateModel.itemsIndex > legacyTrayPlugin.DelegateModel.itemsIndex + } + } + } } diff --git a/panels/dock/tray/package/DebugRectangle.qml b/panels/dock/tray/package/DebugRectangle.qml new file mode 100644 index 000000000..ec6396058 --- /dev/null +++ b/panels/dock/tray/package/DebugRectangle.qml @@ -0,0 +1,13 @@ +import QtQuick 2.15 + +Rectangle { + property var toFill: parent // instantiation site "can" (optionally) override + property color customColor: 'yellow' // instantiation site "can" (optionally) override + property int customThickness: 1 // instantiation site "can" (optionally) override + + anchors.fill: toFill + z: 200 + color: 'transparent' + border.color: customColor + border.width: customThickness +} diff --git a/panels/dock/tray/package/StashContainer.qml b/panels/dock/tray/package/StashContainer.qml index 53b6b51f1..c5f64fb60 100644 --- a/panels/dock/tray/package/StashContainer.qml +++ b/panels/dock/tray/package/StashContainer.qml @@ -11,15 +11,12 @@ import org.deepin.ds.dock.tray 1.0 as DDT Item { id: root - property var model: ListModel { - ListElement { - delegateType: "dummy" - surfaceId: "folder-trash" - visualIndex: 0 - } - ListElement { - delegateType: "dummy" - visualIndex: 1 + required property DDT.SortFilterProxyModel model: DDT.SortFilterProxyModel { + sourceModel: DDT.TraySortOrderModel + filterRowCallback: (sourceRow, sourceParent) => { + let index = sourceModel.index(sourceRow, 0, sourceParent) + return sourceModel.data(index, DDT.TraySortOrderModel.SectionTypeRole) === "stashed" && + sourceModel.data(index, DDT.TraySortOrderModel.VisibilityRole) === true } } @@ -59,14 +56,6 @@ Item { } // Delegates - StashedItemDelegateChooser { - columnCount: root.columnCount - rowCount: root.rowCount - itemPadding: root.itemPadding - id: stashedItemDelegateChooser - stashedSurfacePopup: stashSurfacePopup - } - // tooltip and menu DDT.SurfacePopup { id: stashSurfacePopup @@ -101,19 +90,43 @@ Item { } onDropped: function (dropEvent) { let surfaceId = dropEvent.getDataAsString("text/x-dde-shell-tray-dnd-surfaceId") - DDT.TraySortOrderModel.dropToStashTray(surfaceId, 0, false); + DDT.TraySortOrderModel.move(surfaceId, "internal/action-stash-placeholder", true); } } - Item { + Flow { + id: stashedList anchors.fill: parent anchors.margins: 0 + spacing: root.itemSpacing + + add: Transition { + NumberAnimation { + properties: "scale,opacity" + from: 0 + to: 1 + duration: 200 + } + } + move: Transition { + NumberAnimation { + properties: "x,y" + easing.type: Easing.OutQuad + } + } - // Tray items Repeater { - anchors.fill: parent - model: root.model - delegate: stashedItemDelegateChooser + model: DelegateModel { + id: visualModel + model: root.model + delegate: StashedItemDelegateChooser { + columnCount: root.columnCount + rowCount: root.rowCount + itemPadding: root.itemPadding + id: stashedItemDelegateChooser + stashedSurfacePopup: stashSurfacePopup + } + } } } } diff --git a/panels/dock/tray/package/TrayContainer.qml b/panels/dock/tray/package/TrayContainer.qml index 0677a2b8c..84e13a2d4 100644 --- a/panels/dock/tray/package/TrayContainer.qml +++ b/panels/dock/tray/package/TrayContainer.qml @@ -86,30 +86,24 @@ Item { property int trayHeight: 50 property size containerSize: DDT.TrayItemPositionManager.visualSize property bool isDragging: DDT.TraySortOrderModel.actionsAlwaysVisible - property bool animationEnable: true + property bool animationEnable: false // visiualIndex default value is -1 property int dropHoverIndex: -1 required property var surfaceAcceptor readonly property bool isDropping: dropArea.containsDrag - onIsDraggingChanged: { - animationEnable = !isDragging - animationEnableTimer.start() - } - + // 启动时关闭动画,10s 后再启用 Timer { id: animationEnableTimer - interval: 10 + interval: 10000 repeat: false onTriggered: { animationEnable = true } } - implicitWidth: width - width: containerSize.width - implicitHeight: height - height: containerSize.height + implicitWidth: isHorizontal ? trayList.contentItem.childrenRect.width : DDT.TrayItemPositionManager.dockHeight + implicitHeight: isHorizontal ? DDT.TrayItemPositionManager.dockHeight : trayList.contentItem.childrenRect.height Behavior on width { enabled: animationEnable @@ -121,76 +115,68 @@ Item { NumberAnimation { duration: 200; easing.type: collapsed || !DDT.TraySortOrderModel.isCollapsing ? Easing.OutQuad : Easing.InQuad } } - // Delegates - TrayItemDelegateChooser { - id: trayItemDelegateChooser - isHorizontal: root.isHorizontal - collapsed: root.collapsed - itemPadding: root.itemPadding - surfaceAcceptor: root.surfaceAcceptor - disableInputEvents: root.isDropping - } - // debug Rectangle { color: root.color anchors.fill: parent } - DropArea { - id: dropArea + // Tray items + ListView { + id: trayList anchors.fill: parent - keys: ["text/x-dde-shell-tray-dnd-surfaceId"] - onEntered: function (dragEvent) { - console.log(dragEvent.getDataAsString("text/x-dde-shell-tray-dnd-surfaceId")) + interactive: false + orientation: root.isHorizontal ? Qt.Horizontal : Qt.Vertical + spacing: root.itemSpacing + model: DelegateModel { + id: visualModel + + model: DDT.SortFilterProxyModel { + sourceModel: root.model + filterRowCallback: (sourceRow, sourceParent) => { + let index = sourceModel.index(sourceRow, 0, sourceParent) + return sourceModel.data(index, DDT.TraySortOrderModel.SectionTypeRole) !== "stashed" && + sourceModel.data(index, DDT.TraySortOrderModel.VisibilityRole) === true + } + } + delegate: TrayItemDelegateChooser { + id: delegateRoot + isHorizontal: root.isHorizontal + collapsed: root.collapsed + itemPadding: root.itemPadding + surfaceAcceptor: root.surfaceAcceptor + disableInputEvents: root.isDropping + + property int visualIndex: DelegateModel.itemsIndex + } } - onPositionChanged: function (dragEvent) { - let surfaceId = dragEvent.getDataAsString("text/x-dde-shell-tray-dnd-surfaceId") - let pos = root.isHorizontal ? drag.x : drag.y - let currentItemIndex = pos / (root.itemVisualSize + root.itemSpacing) - let currentPosMapToItem = pos % (root.itemVisualSize + root.itemSpacing) - let isBefore = currentPosMapToItem < root.itemVisualSize / 2 - dropHoverIndex = Math.floor(currentItemIndex) - let isStash = dragEvent.getDataAsString("text/x-dde-shell-tray-dnd-sectionType") === "stashed" - // TODO: If this method is used in the stash area, it will cause the drag state to be terminated when dragging to the tray area - if (!isStash) { - if (dropHoverIndex !== 0) { - dropTrayTimer.handleDrop = function() { - DDT.TraySortOrderModel.dropToDockTray(surfaceId, Math.floor(currentItemIndex), isBefore) - } - dropTrayTimer.start() - } else if (!surfaceId.startsWith("application-tray")){ - dragEvent.accepted = false - } + add: Transition { + enabled: animationEnable + NumberAnimation { + properties: "scale,opacity" + from: 0 + to: 1 + duration: 200 } } - onDropped: function (dropEvent) { - let surfaceId = dropEvent.getDataAsString("text/x-dde-shell-tray-dnd-surfaceId") - let dropIdx = DDT.TrayItemPositionManager.itemIndexByPoint(Qt.point(drag.x, drag.y)) - let currentItemIndex = dropIdx.index - let isBefore = dropIdx.isBefore - console.log("dropped", currentItemIndex, isBefore) - DDT.TraySortOrderModel.dropToDockTray(surfaceId, Math.floor(currentItemIndex), isBefore); - DDT.TraySortOrderModel.actionsAlwaysVisible = false + remove: Transition { + enabled: animationEnable + NumberAnimation { + properties: "scale,opacity" + from: 1 + to: 0 + duration: 200 + } } - - Timer { - id: dropTrayTimer - interval: 50 - repeat: false - property var handleDrop - onTriggered: { - handleDrop() + displaced: Transition { + enabled: animationEnable + NumberAnimation { + properties: "x,y" + easing.type: Easing.OutQuad } } - } - - // Tray items - Repeater { - anchors.fill: parent - model: root.model - delegate: trayItemDelegateChooser + move: displaced } Component.onCompleted: { @@ -198,10 +184,12 @@ Item { return root.isHorizontal ? Qt.Horizontal : Qt.Vertical }); DDT.TrayItemPositionManager.visualItemCount = Qt.binding(function() { - return root.model.visualItemCount + return root.model.rowCount }); DDT.TrayItemPositionManager.dockHeight = Qt.binding(function() { return root.trayHeight }); + + animationEnableTimer.start() } } diff --git a/panels/dock/tray/package/TrayItemPositioner.qml b/panels/dock/tray/package/TrayItemPositioner.qml index d067c6868..890e69054 100644 --- a/panels/dock/tray/package/TrayItemPositioner.qml +++ b/panels/dock/tray/package/TrayItemPositioner.qml @@ -8,31 +8,12 @@ import org.deepin.ds.dock.tray 1.0 as DDT Control { id: root - property bool itemVisible: { - if (model.sectionType === "collapsable") return !collapsed && model.visibility - return model.sectionType !== "stashed" && model.visibility - } + property bool itemVisible: true property size visualSize: Qt.size(0, 0) - property point visualPosition: DDT.TrayItemPositionRegister.visualPosition property bool isDragging: DDT.TraySortOrderModel.actionsAlwaysVisible - property bool animationEnable: true - onIsDraggingChanged: { - animationEnable = !isDragging - animationEnableTimer.start() - } - - Timer { - id: animationEnableTimer - interval: 10 - repeat: false - onTriggered: { - animationEnable = true - } - } - - DDT.TrayItemPositionRegister.visualIndex: (model.sectionType !== "stashed") ? model.visualIndex : -1 + DDT.TrayItemPositionRegister.visualIndex: DelegateModel.visualIndex DDT.TrayItemPositionRegister.visualSize: (model.sectionType !== "stashed") ? Qt.size(width, height) : Qt.size(0, 0) DDT.TrayItemPositionRegister.surfaceId: model.surfaceId DDT.TrayItemPositionRegister.sectionType: model.sectionType @@ -40,44 +21,6 @@ Control { width: visualSize.width !== 0 ? visualSize.width : DDT.TrayItemPositionManager.itemVisualSize.width height: visualSize.height !== 0 ? visualSize.height : DDT.TrayItemPositionManager.itemVisualSize.height - x: visualPosition.x - y: visualPosition.y - Behavior on x { - enabled: isHorizontal && animationEnable - NumberAnimation { duration: 200; easing.type: collapsed || !DDT.TraySortOrderModel.isCollapsing ? Easing.OutQuad : Easing.InQuad } - } - Behavior on y { - enabled: !isHorizontal && animationEnable - NumberAnimation { duration: 200; easing.type: collapsed || !DDT.TraySortOrderModel.isCollapsing ? Easing.OutQuad : Easing.InQuad } - } - states: [ - State { - when: root.itemVisible - PropertyChanges { target: root; opacity: 1.0 } - PropertyChanges { target: root; scale: 1.0 } - PropertyChanges { target: root; visible: true } - }, - State { - name: "item-invisible" - when: !root.itemVisible - PropertyChanges { target: root; opacity: 0 } - PropertyChanges { target: root; scale: 0.2 } - } - ] - transitions: [ - Transition { - to: "item-invisible" - SequentialAnimation { - NumberAnimation { properties: "opacity,scale"; easing.type: Easing.OutQuad; duration: 200 } - PropertyAction { target: root; property: "visible"; value: false } - PropertyAction { target: DDT.TraySortOrderModel; property: "isCollapsing"; value: false } - } - }, - Transition { - SequentialAnimation { - NumberAnimation { properties: "opacity,scale"; easing.type: Easing.InQuad; duration: 200 } - PropertyAction { target: DDT.TraySortOrderModel; property: "isCollapsing"; value: false } - } - } - ] + anchors.verticalCenter: isHorizontal ? parent.verticalCenter : undefined + anchors.horizontalCenter: isHorizontal ? undefined : parent.horizontalCenter } diff --git a/panels/dock/tray/package/tray.qml b/panels/dock/tray/package/tray.qml index 49b0b0566..6a380ddc8 100644 --- a/panels/dock/tray/package/tray.qml +++ b/panels/dock/tray/package/tray.qml @@ -55,14 +55,6 @@ AppletItem { contentItem: StashContainer { id: stashContainer color: "transparent" - model: DDT.SortFilterProxyModel { - sourceModel: DDT.TraySortOrderModel - filterRowCallback: (sourceRow, sourceParent) => { - let index = sourceModel.index(sourceRow, 0, sourceParent) - return sourceModel.data(index, DDT.TraySortOrderModel.SectionTypeRole) === "stashed" && - sourceModel.data(index, DDT.TraySortOrderModel.VisibilityRole) === true - } - } anchors.centerIn: parent onRowCountChanged: { if (stashContainer.rowCount === 0 || stashContainer.columnCount === 0) { diff --git a/panels/dock/tray/traysortordermodel.cpp b/panels/dock/tray/traysortordermodel.cpp index 1da0ced7a..ec0ea750a 100644 --- a/panels/dock/tray/traysortordermodel.cpp +++ b/panels/dock/tray/traysortordermodel.cpp @@ -15,34 +15,43 @@ const QString SECTION_COLLAPSABLE = QLatin1String("collapsable"); const QString SECTION_FIXED = QLatin1String("fixed"); const QString SECTION_PINNED = QLatin1String("pinned"); +const QString INTERNAL_PREFIX = QLatin1String("internal/"); +const QString ACTION_STASH_PLACEHOLDER = QLatin1String("action-stash-placeholder"); +const QString ACTION_STASH_PLACEHOLDER_NAME = INTERNAL_PREFIX + ACTION_STASH_PLACEHOLDER; +const QString ACTION_SHOW_STASH = QLatin1String("action-show-stash"); +const QString ACTION_SHOW_STASH_NAME = INTERNAL_PREFIX + ACTION_SHOW_STASH; +const QString ACTION_TOGGLE_COLLAPSE = QLatin1String("action-toggle-collapse"); +const QString ACTION_TOGGLE_COLLAPSE_NAME = INTERNAL_PREFIX + ACTION_TOGGLE_COLLAPSE; +const QString ACTION_TOGGLE_QUICK_SETTINGS = QLatin1String("action-toggle-quick-settings"); +const QString ACTION_TOGGLE_QUICK_SETTINGS_NAME = INTERNAL_PREFIX + ACTION_TOGGLE_QUICK_SETTINGS; + TraySortOrderModel::TraySortOrderModel(QObject *parent) - : QStandardItemModel(parent) - , m_dconfig(Dtk::Core::DConfig::create("org.deepin.dde.shell", "org.deepin.ds.dock.tray")) -{ - QHash defaultRoleNames = roleNames(); - defaultRoleNames.insert({ + : QAbstractItemModel(parent) + , m_defaultRoleNames({ {TraySortOrderModel::SurfaceIdRole, QByteArrayLiteral("surfaceId")}, {TraySortOrderModel::VisibilityRole, QByteArrayLiteral("visibility")}, {TraySortOrderModel::SectionTypeRole, QByteArrayLiteral("sectionType")}, - {TraySortOrderModel::VisualIndexRole, QByteArrayLiteral("visualIndex")}, {TraySortOrderModel::DelegateTypeRole, QByteArrayLiteral("delegateType")}, {TraySortOrderModel::ForbiddenSectionsRole, QByteArrayLiteral("forbiddenSections")} - }); - setItemRoleNames(defaultRoleNames); + }) + , m_dconfig(Dtk::Core::DConfig::create("org.deepin.dde.shell", "org.deepin.ds.dock.tray")) +{ + connect(this, &QAbstractListModel::rowsInserted, this, &TraySortOrderModel::rowCountChanged); + connect(this, &QAbstractListModel::rowsRemoved, this, &TraySortOrderModel::rowCountChanged); // init sort order data and hidden list data loadDataFromDConfig(); // internal tray actions - appendRow(createTrayItem("internal/action-stash-placeholder", SECTION_STASHED, "action-stash-placeholder")); - appendRow(createTrayItem("internal/action-show-stash", SECTION_TRAY_ACTION, "action-show-stash")); - appendRow(createTrayItem("internal/action-toggle-collapse", SECTION_TRAY_ACTION, "action-toggle-collapse")); - appendRow(createTrayItem("internal/action-toggle-quick-settings", SECTION_TRAY_ACTION, "action-toggle-quick-settings")); + createTrayItem(ACTION_STASH_PLACEHOLDER_NAME, SECTION_STASHED, ACTION_STASH_PLACEHOLDER); + createTrayItem(ACTION_SHOW_STASH_NAME, SECTION_TRAY_ACTION, ACTION_SHOW_STASH); + createTrayItem(ACTION_TOGGLE_COLLAPSE_NAME, SECTION_TRAY_ACTION, ACTION_TOGGLE_COLLAPSE); + createTrayItem(ACTION_TOGGLE_QUICK_SETTINGS_NAME, SECTION_TRAY_ACTION, ACTION_TOGGLE_QUICK_SETTINGS); connect(m_dconfig.get(), &Dtk::Core::DConfig::valueChanged, this, [this](const QString &key){ if (key == QLatin1String("hiddenSurfaceIds")) { loadDataFromDConfig(); - updateVisualIndexes(); + updateVisibilities(); } }); @@ -50,14 +59,13 @@ TraySortOrderModel::TraySortOrderModel(QObject *parent) connect(this, &TraySortOrderModel::collapsedChanged, this, [this](){ qDebug() << "collapsedChanged"; - updateVisualIndexes(); saveDataToDConfig(); + updateVisibilities(); }); connect(this, &TraySortOrderModel::actionsAlwaysVisibleChanged, this, [this](){ qDebug() << "actionsAlwaysVisibleChanged"; - updateVisualIndexes(); + updateShowStashActionVisible(); }); - updateVisualIndexes(); } TraySortOrderModel::~TraySortOrderModel() @@ -65,65 +73,128 @@ TraySortOrderModel::~TraySortOrderModel() // dtor } -bool TraySortOrderModel::dropToStashTray(const QString &draggedSurfaceId, int dropVisualIndex, bool isBefore) +QHash TraySortOrderModel::roleNames() const { - // Check if the dragged tray surfaceId exists. Reject if not the case - QList draggedItems = findItems(draggedSurfaceId); - if (draggedItems.isEmpty()) return false; - Q_ASSERT(draggedItems.count() == 1); - QStandardItem * draggedItem = draggedItems[0]; - if (draggedItem->data(ForbiddenSectionsRole).toStringList().contains(SECTION_STASHED)) return false; - QStringList * sourceSection = getSection(draggedItem->data(SectionTypeRole).toString()); + return m_defaultRoleNames; +} - // Ensure position adjustment will be saved at last - auto deferSaveSortOrder = qScopeGuard([this](){saveDataToDConfig();}); +QVariant TraySortOrderModel::data(const QModelIndex &index, int role) const +{ + switch (role) { + case SurfaceIdRole: + return m_list[index.row()].surfaceId; + case VisibilityRole: + return m_list[index.row()].visible; + case SectionTypeRole: + return m_list[index.row()].sectionType; + case DelegateTypeRole: + return m_list[index.row()].delegateType; + case ForbiddenSectionsRole: + return m_list[index.row()].forbiddenSections; + default: + return {}; + } +} + +QModelIndex TraySortOrderModel::index(int row, int column, const QModelIndex &parent) const +{ + if (parent.isValid()) return QModelIndex(); + if (row < 0 || row >= m_list.size()) return QModelIndex(); + if (column < 0 || column > 1) return QModelIndex(); + + return createIndex(row, column); +} + +QModelIndex TraySortOrderModel::parent(const QModelIndex &index) const +{ + return QModelIndex(); +} + +int TraySortOrderModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) return 0; + + return m_list.size(); +} - // drag inside the stashed section - if (sourceSection == &m_stashedIds) { +int TraySortOrderModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) return 0; + + return 1; +} + +// drop existing item to tray, return true if drop attempt is accepted, false if rejected +bool TraySortOrderModel::move(const QString &draggedSurfaceId, const QString &dropOnSurfaceId, bool isBefore) +{ + bool res = false; + QString sectionType; + std::tie(res, sectionType) = moveAux(draggedSurfaceId, dropOnSurfaceId, isBefore); + if (!res) { return false; - } else { - sourceSection->removeOne(draggedSurfaceId); - m_stashedIds.append(draggedSurfaceId); - updateVisualIndexes(); - return true; } + + auto iter = findItem(draggedSurfaceId); + Q_ASSERT(iter != m_list.end()); + + QVector::const_iterator insertIter = findInsertionPos(iter->surfaceId, sectionType); + int sourceRow = std::distance(m_list.begin(), iter); + int destRow = std::distance(m_list.cbegin(), insertIter); + if (sourceRow < destRow) { + destRow--; + } + + QString originSectionType = iter->sectionType; + if (originSectionType != sectionType) { + iter->sectionType = sectionType; + dataChanged(index(sourceRow, 0), index(sourceRow, 0), { SectionTypeRole }); + } + + beginMoveRows(QModelIndex(), sourceRow, sourceRow, QModelIndex(), sourceRow < destRow ? destRow + 1 : destRow); + m_list.move(sourceRow, destRow); + endMoveRows(); + + if (originSectionType == SECTION_STASHED && sectionType != SECTION_STASHED) { + m_visibleStashed--; + updateStashPlaceholderVisible(); + } else if (originSectionType != SECTION_STASHED && sectionType == SECTION_STASHED) { + m_visibleStashed++; + updateStashPlaceholderVisible(); + } + + return true; } -// drop existing item to tray, return true if drop attempt is accepted, false if rejected -bool TraySortOrderModel::dropToDockTray(const QString &draggedSurfaceId, int dropVisualIndex, bool isBefore) +std::tuple TraySortOrderModel::moveAux(const QString &draggedSurfaceId, const QString &dropOnSurfaceId, bool isBefore) { + Q_ASSERT(!m_hiddenIds.contains(draggedSurfaceId)); + + if (draggedSurfaceId == dropOnSurfaceId) { + // same item, don't need to do anything + return {false, ""}; + } + // Check if the dragged tray surfaceId exists. Reject if not the case - QList draggedItems = findItems(draggedSurfaceId); - if (draggedItems.isEmpty()) return false; - Q_ASSERT(draggedItems.count() == 1); - QStringList * sourceSection = getSection(draggedItems[0]->data(SectionTypeRole).toString()); - QStringList forbiddenSections(draggedItems[0]->data(ForbiddenSectionsRole).toStringList()); + auto draggedItemIter = findItem(draggedSurfaceId); + if (draggedItemIter == m_list.end()) return {false, ""}; + QStringList *sourceSection = getSection(draggedItemIter->sectionType); + QStringList forbiddenSections(draggedItemIter->forbiddenSections); - // Find the item attempted to drop on - QStandardItem * dropOnItem = findItemByVisualIndex(dropVisualIndex, DockTraySection); - if (!dropOnItem) return false; - QString dropOnSurfaceId(dropOnItem->data(SurfaceIdRole).toString()); + auto dropOnItemIter = findItem(dropOnSurfaceId); + if (dropOnItemIter == m_list.end()) return {false, ""}; // Ensure position adjustment will be saved at last auto deferSaveSortOrder = qScopeGuard([this](){saveDataToDConfig();}); - // If it's hidden, remove it from the hidden list. updateVisualIndexes() will update the - // item's VisibilityRole property. - // This is mainly for the drag item from quick settings panel feature. - auto deferUpdateVisualIndex = qScopeGuard([this](){updateVisualIndexes();}); - if (m_hiddenIds.contains(draggedSurfaceId)) { - m_hiddenIds.removeOne(draggedSurfaceId); - } - - if (dropOnSurfaceId == QLatin1String("internal/action-show-stash")) { + if (dropOnSurfaceId == ACTION_SHOW_STASH_NAME) { // show stash action is always the first action, drop before it consider as drop into stashed area if (sourceSection != &m_stashedIds) { sourceSection->removeOne(draggedSurfaceId); m_stashedIds.append(draggedSurfaceId); - return true; + return {true, SECTION_STASHED}; } else { // already in the stashed tray - return false; + return {false, ""}; } if (sourceSection == &m_collapsableIds) { // same-section move @@ -133,53 +204,65 @@ bool TraySortOrderModel::dropToDockTray(const QString &draggedSurfaceId, int dro sourceSection->removeOne(draggedSurfaceId); m_collapsableIds.prepend(draggedSurfaceId); } - return true; + return {true, SECTION_COLLAPSABLE}; } - if (dropOnSurfaceId == QLatin1String("internal/action-toggle-collapse")) { + if (dropOnSurfaceId == ACTION_TOGGLE_COLLAPSE_NAME) { QStringList * targetSection = isBefore ? &m_collapsableIds : &m_pinnedIds; if (isBefore) { // move to the end of collapsable section - if (forbiddenSections.contains(SECTION_COLLAPSABLE)) return false; + if (forbiddenSections.contains(SECTION_COLLAPSABLE)) return {false, ""}; if (targetSection == sourceSection) { m_collapsableIds.move(m_collapsableIds.indexOf(draggedSurfaceId), m_collapsableIds.count() - 1); } else { sourceSection->removeOne(draggedSurfaceId); m_collapsableIds.append(draggedSurfaceId); } + return {true, SECTION_COLLAPSABLE}; } else { // move to the beginning of pinned section - if (forbiddenSections.contains(SECTION_PINNED)) return false; + if (forbiddenSections.contains(SECTION_PINNED)) return {false, ""}; if (targetSection == sourceSection) { m_pinnedIds.move(m_pinnedIds.indexOf(draggedSurfaceId), 0); } else { sourceSection->removeOne(draggedSurfaceId); m_pinnedIds.prepend(draggedSurfaceId); } + return {true, SECTION_PINNED}; } - return true; } - if (dropOnSurfaceId == QLatin1String("internal/action-toggle-quick-settings")) { - return false; - } + if (dropOnSurfaceId == ACTION_TOGGLE_QUICK_SETTINGS_NAME) { + if (!isBefore) { + return {false, ""}; + } - if (dropOnSurfaceId == draggedSurfaceId) { - // same item, don't need to do anything - return false; + QStringList *targetSection = &m_pinnedIds; + + // move to the end of pinned section + if (forbiddenSections.contains(SECTION_PINNED)) return {false, ""}; + if (targetSection == sourceSection) { + m_pinnedIds.move(m_pinnedIds.indexOf(draggedSurfaceId), m_pinnedIds.count() - 1); + } else { + sourceSection->removeOne(draggedSurfaceId); + m_pinnedIds.append(draggedSurfaceId); + } + + return {true, SECTION_PINNED}; } - QString targetSectionName(dropOnItem->data(SectionTypeRole).toString()); - if (forbiddenSections.contains(targetSectionName)) return false; + QString targetSectionName(dropOnItemIter->sectionType); + if (forbiddenSections.contains(targetSectionName)) return {false, ""}; QStringList * targetSection = getSection(targetSectionName); if (targetSection == sourceSection) { int sourceIndex = targetSection->indexOf(draggedSurfaceId); int targetIndex = targetSection->indexOf(dropOnSurfaceId); - if (isBefore) targetIndex--; - if (targetIndex < 0) targetIndex = 0; + if (isBefore && sourceIndex < targetIndex) { + targetIndex--; + } if (sourceIndex == targetIndex) { // same item (draggedSurfaceId != dropOnSurfaceId caused by isBefore). - return false; + return {false, ""}; } targetSection->move(sourceIndex, targetIndex); } else { @@ -188,7 +271,7 @@ bool TraySortOrderModel::dropToDockTray(const QString &draggedSurfaceId, int dro sourceSection->removeOne(draggedSurfaceId); targetSection->insert(targetIndex, draggedSurfaceId); } - return true; + return {true, targetSectionName}; } void TraySortOrderModel::setSurfaceVisible(const QString &surfaceId, bool visible) @@ -202,27 +285,23 @@ void TraySortOrderModel::setSurfaceVisible(const QString &surfaceId, bool visibl m_hiddenIds.append(surfaceId); } } - updateVisualIndexes(); + + auto i = findItem(surfaceId); + if (i == m_list.end()) { + return; + } + + i->visible = getSurfaceVisible(i->surfaceId, i->sectionType); + int row = std::distance(m_list.begin(), i); + auto idx = index(row, 0); + dataChanged(idx, idx, { VisibilityRole }); } -QStandardItem *TraySortOrderModel::findItemByVisualIndex(int visualIndex, VisualSections visualSection) const +QVector::iterator TraySortOrderModel::findItem(const QString &surfaceId) { - QStandardItem * result = nullptr; - const QModelIndexList matched = match(index(0, 0), VisualIndexRole, visualIndex, -1, Qt::MatchExactly); - for (const QModelIndex & index : matched) { - QString section(data(index, SectionTypeRole).toString()); - if (visualSection == DockTraySection) { - if (section == SECTION_STASHED) continue; - if (m_collapsed && section == SECTION_COLLAPSABLE) continue; - } else { - if (section != SECTION_STASHED) continue; - } - if (!data(index, VisibilityRole).toBool()) continue; - - result = itemFromIndex(index); - break; - } - return result; + return std::find_if(m_list.begin(), m_list.end(), [surfaceId](const Item & item) { + return item.surfaceId == surfaceId; + }); } QStringList *TraySortOrderModel::getSection(const QString §ionType) @@ -263,7 +342,7 @@ QString TraySortOrderModel::findSection(const QString & surfaceId, const QString if (!found && // 不在列表中 !isForceDock && // 非 forceDock result != SECTION_FIXED && // 非固定位置插件(时间) - !surfaceId.startsWith("internal/") && // 非内置插件 + !surfaceId.startsWith(INTERNAL_PREFIX) && // 非内置插件 !surfaceId.startsWith("application-tray::") // 非托盘图标 ) { if (!m_hiddenIds.contains(surfaceId)) { @@ -291,144 +370,146 @@ void TraySortOrderModel::registerToSection(const QString & surfaceId, const QStr return; } + // stash placeholder 永远在 stash 最后,不保存位置 + if (surfaceId == ACTION_STASH_PLACEHOLDER_NAME) { + return; + } + if (!section->contains(surfaceId)) { section->prepend(surfaceId); } } -QStandardItem * TraySortOrderModel::createTrayItem(const QString & name, const QString & sectionType, - const QString & delegateType, const QStringList &forbiddenSections, - bool isForceDock) +QVector::const_iterator TraySortOrderModel::findInsertionPosInSection(const QString &surfaceId, const QString §ionType, + QVector::const_iterator sectionBegin, QVector::const_iterator sectionEnd) const { - QString actualSectionType = findSection(name, sectionType, forbiddenSections, isForceDock); - registerToSection(name, actualSectionType); + QStringList ids; + if (sectionType == SECTION_STASHED) { + ids = m_stashedIds; + } else if (sectionType == SECTION_COLLAPSABLE) { + ids = m_collapsableIds; + } else if (sectionType == SECTION_PINNED) { + ids = m_pinnedIds; + } else if (sectionType == SECTION_FIXED) { + ids = m_fixedIds; + } - qDebug() << actualSectionType << name << delegateType; + auto ri = std::find(ids.cbegin(), ids.cend(), surfaceId); + if (ri == ids.cend()) { + // 不在列表中,插入开头 + return sectionBegin; + } - QStandardItem * item = new QStandardItem(name); - item->setData(name, TraySortOrderModel::SurfaceIdRole); - item->setData(true, TraySortOrderModel::VisibilityRole); - item->setData(actualSectionType, TraySortOrderModel::SectionTypeRole); - item->setData(delegateType, TraySortOrderModel::DelegateTypeRole); - item->setData(forbiddenSections, TraySortOrderModel::ForbiddenSectionsRole); - item->setData(-1, TraySortOrderModel::VisualIndexRole); - item->setData(isForceDock, TraySortOrderModel::IsForceDockRole); + // 获取下一个的迭代器 + ri++; + for (; ri != ids.cend(); ri++) { + auto res = std::find_if(sectionBegin, sectionEnd, [ri](const Item &item) { + return item.surfaceId == *ri; + }); + if (res != sectionEnd) { + return res; + } + } - return item; + // 找不到下一个的迭代器,说明当前是最后一个 + return sectionEnd; } -void TraySortOrderModel::updateVisualIndexes() +QVector::const_iterator TraySortOrderModel::findInsertionPos(const QString &surfaceId, const QString §ionType) const { - for (int i = 0; i < rowCount(); i++) { - auto results = item(i); - results->setData(-1, TraySortOrderModel::VisualIndexRole); - } - - // stashed action - // "internal/action-stash-placeholder" - QList results = findItems("internal/action-stash-placeholder"); - Q_ASSERT(!results.isEmpty()); - QStandardItem * stashPlaceholder = results[0]; - - // the visual index of stashed items are also for their sort order, but the index - // number is independently from these non-stashed items. - int stashedVisualIndex = 0; - bool showStashActionVisible = m_actionsAlwaysVisible; - for (const QString & id : std::as_const(m_stashedIds)) { - QList results = findItems(id); - if (results.isEmpty()) continue; - if (results[0]->data(TraySortOrderModel::VisualIndexRole).toInt() != -1) continue; - if (stashPlaceholder == results[0]) continue; - bool itemVisible = results[0]->data(TraySortOrderModel::IsForceDockRole).toBool() || !m_hiddenIds.contains(id); - results[0]->setData(SECTION_STASHED, TraySortOrderModel::SectionTypeRole); - if (itemVisible) { - showStashActionVisible = true; - results[0]->setData(stashedVisualIndex, TraySortOrderModel::VisualIndexRole); - stashedVisualIndex++; - } + Q_ASSERT(!m_hiddenIds.contains(surfaceId)); + + auto stashedSectionBegin = m_list.cbegin(); + auto stashedSectionEnd = std::find_if_not(stashedSectionBegin, m_list.cend(), [](const Item &item) { + return item.sectionType == SECTION_STASHED && item.surfaceId != ACTION_STASH_PLACEHOLDER_NAME; + }); + if (sectionType == SECTION_STASHED && surfaceId != ACTION_STASH_PLACEHOLDER_NAME) { + return findInsertionPosInSection(surfaceId, sectionType, stashedSectionBegin, stashedSectionEnd); } - stashPlaceholder->setData(stashedVisualIndex == 0 && showStashActionVisible, TraySortOrderModel::VisibilityRole); - - int currentVisualIndex = 0; - // "internal/action-show-stash" - results = findItems("internal/action-show-stash"); - Q_ASSERT(!results.isEmpty()); - results[0]->setData(showStashActionVisible, TraySortOrderModel::VisibilityRole); - if (showStashActionVisible) { - results[0]->setData(currentVisualIndex, TraySortOrderModel::VisualIndexRole); - currentVisualIndex++; - } - - // collapsable - bool toogleCollapseActionVisible = m_actionsAlwaysVisible; - for (const QString & id : std::as_const(m_collapsableIds)) { - QList results = findItems(id); - if (results.isEmpty()) continue; - if (results[0]->data(TraySortOrderModel::VisualIndexRole).toInt() != -1) continue; - bool itemVisible = results[0]->data(TraySortOrderModel::IsForceDockRole).toBool() || !m_hiddenIds.contains(id); - results[0]->setData(SECTION_COLLAPSABLE, TraySortOrderModel::SectionTypeRole); - results[0]->setData(itemVisible, TraySortOrderModel::VisibilityRole); - if (itemVisible) { - toogleCollapseActionVisible = true; - if (!m_collapsed) { - results[0]->setData(currentVisualIndex++, TraySortOrderModel::VisualIndexRole); - } else { - results[0]->setData(currentVisualIndex-1, TraySortOrderModel::VisualIndexRole); - } - } + auto stashPlaceholderActionBegin = stashedSectionEnd; + auto stashPlaceholderActionEnd = std::find_if_not(stashPlaceholderActionBegin, m_list.cend(), [](const Item &item) { + return item.surfaceId == ACTION_STASH_PLACEHOLDER_NAME; + }); + if (surfaceId == ACTION_SHOW_STASH_NAME) { + return stashPlaceholderActionBegin; } - // "internal/action-toggle-collapse" - results = findItems("internal/action-toggle-collapse"); - Q_ASSERT(!results.isEmpty()); - results[0]->setData(toogleCollapseActionVisible, TraySortOrderModel::VisibilityRole); - if (toogleCollapseActionVisible) { - results[0]->setData(currentVisualIndex, TraySortOrderModel::VisualIndexRole); - currentVisualIndex++; - } - - // pinned - for (const QString & id : std::as_const(m_pinnedIds)) { - QList results = findItems(id); - if (results.isEmpty()) continue; - if (results[0]->data(TraySortOrderModel::VisualIndexRole).toInt() != -1) continue; - bool itemVisible = results[0]->data(TraySortOrderModel::IsForceDockRole).toBool() || !m_hiddenIds.contains(id); - results[0]->setData(SECTION_PINNED, TraySortOrderModel::SectionTypeRole); - results[0]->setData(itemVisible, TraySortOrderModel::VisibilityRole); - if (itemVisible) { - results[0]->setData(currentVisualIndex, TraySortOrderModel::VisualIndexRole); - currentVisualIndex++; - } + auto showStashActionBegin = stashedSectionEnd; + auto showStashActionEnd = std::find_if_not(showStashActionBegin, m_list.cend(), [](const Item &item) { + return item.surfaceId == ACTION_SHOW_STASH_NAME; + }); + if (surfaceId == ACTION_SHOW_STASH_NAME) { + return showStashActionBegin; } - // "internal/action-toggle-quick-settings" - results = findItems("internal/action-toggle-quick-settings"); - Q_ASSERT(!results.isEmpty()); - results[0]->setData(currentVisualIndex, TraySortOrderModel::VisualIndexRole); - currentVisualIndex++; - - // fixed (not actually 'fixed' since it's just a section next to pinned) - // By design, fixed items can be rearranged within the fixed section, but cannot - // move to other sections. We archive that by setting the 'forbiddenSections' property - // to the items in fixed sections. - for (const QString & id : std::as_const(m_fixedIds)) { - QList results = findItems(id); - if (results.isEmpty()) continue; - if (results[0]->data(TraySortOrderModel::VisualIndexRole).toInt() != -1) continue; - bool itemVisible = results[0]->data(TraySortOrderModel::IsForceDockRole).toBool() || !m_hiddenIds.contains(id); - results[0]->setData(SECTION_FIXED, TraySortOrderModel::SectionTypeRole); - results[0]->setData(itemVisible, TraySortOrderModel::VisibilityRole); - if (itemVisible) { - results[0]->setData(currentVisualIndex, TraySortOrderModel::VisualIndexRole); - currentVisualIndex++; - } + auto collapsableSectionBegin = showStashActionEnd; + auto collapsableSectionEnd = std::find_if_not(collapsableSectionBegin, m_list.cend(), [](const Item &item) { + return item.sectionType == SECTION_COLLAPSABLE; + }); + if (sectionType == SECTION_COLLAPSABLE) { + Q_ASSERT(m_collapsableIds.contains(surfaceId)); + return findInsertionPosInSection(surfaceId, sectionType, collapsableSectionBegin, collapsableSectionEnd); + } + + auto toggleCollapseActionBegin = collapsableSectionEnd; + auto toggleCollapseActionEnd = std::find_if_not(toggleCollapseActionBegin, m_list.cend(), [](const Item &item) { + return item.surfaceId == ACTION_TOGGLE_COLLAPSE_NAME; + }); + if (surfaceId == ACTION_TOGGLE_COLLAPSE_NAME) { + return toggleCollapseActionBegin; + } + + auto pinnedSectionBegin = toggleCollapseActionEnd; + auto pinnedSectionEnd = std::find_if_not(pinnedSectionBegin, m_list.cend(), [](const Item &item) { + return item.sectionType == SECTION_PINNED; + }); + if (sectionType == SECTION_PINNED) { + Q_ASSERT(m_pinnedIds.contains(surfaceId)); + return findInsertionPosInSection(surfaceId, sectionType, pinnedSectionBegin, pinnedSectionEnd); + } + + auto toggleQuickSettingsActionBegin = pinnedSectionEnd; + auto toggleQuickSettingsActionEnd = std::find_if_not(toggleQuickSettingsActionBegin, m_list.cend(), [](const Item &item) { + return item.surfaceId == ACTION_TOGGLE_QUICK_SETTINGS_NAME; + }); + if (surfaceId == ACTION_TOGGLE_QUICK_SETTINGS_NAME) { + return toggleQuickSettingsActionBegin; } - // update visible item count property - setProperty("visualItemCount", currentVisualIndex); + auto fixedSectionBegin = toggleQuickSettingsActionEnd; + auto fixedSectionEnd = m_list.cend(); + return findInsertionPosInSection(surfaceId, sectionType, fixedSectionBegin, fixedSectionEnd); +} + +void TraySortOrderModel::createTrayItem(const QString & name, const QString & sectionType, + const QString & delegateType, const QStringList &forbiddenSections, + bool isForceDock) +{ + QString actualSectionType = findSection(name, sectionType, forbiddenSections, isForceDock); + registerToSection(name, actualSectionType); - qDebug() << "update" << m_visualItemCount << currentVisualIndex; + qDebug() << "====createTrayItem" << actualSectionType << name << delegateType; + Item item = { + name, + getSurfaceVisible(name, actualSectionType), + actualSectionType, + delegateType, + forbiddenSections, + isForceDock, + }; + + QVector::const_iterator insertIter = findInsertionPos(name, actualSectionType); + + int row = std::distance(m_list.cbegin(), insertIter); + beginInsertRows(QModelIndex(), row, row); + m_list.insert(insertIter, item); + endInsertRows(); + + if (actualSectionType == SECTION_STASHED && name != ACTION_STASH_PLACEHOLDER_NAME && item.visible) { + m_visibleStashed++; + updateStashPlaceholderVisible(); + } } QString TraySortOrderModel::registerSurfaceId(const QVariantMap & surfaceData) @@ -439,21 +520,58 @@ QString TraySortOrderModel::registerSurfaceId(const QVariantMap & surfaceData) QStringList forbiddenSections(surfaceData.value("forbiddenSections").toStringList()); bool isForceDock(surfaceData.value("isForceDock").toBool()); - QList results = findItems(surfaceId); - if (!results.isEmpty()) { - QStandardItem * result = results[0]; + auto i = findItem(surfaceId); + if (i != m_list.end()) { // check if the item is currently in a forbidden zone - QString currentSection(result->data(SectionTypeRole).toString()); - if (forbiddenSections.contains(currentSection)) { - result->setData(findSection(surfaceId, preferredSection, forbiddenSections, isForceDock), SectionTypeRole); + QString currentSection(i->sectionType); + if (!forbiddenSections.contains(currentSection)) { + return surfaceId; } - return surfaceId; + + int row = std::distance(m_list.begin(), i); + beginRemoveRows(QModelIndex(), row, row); + m_list.erase(i); + endRemoveRows(); } - appendRow(createTrayItem(surfaceId, preferredSection, delegateType, forbiddenSections, isForceDock)); + createTrayItem(surfaceId, preferredSection, delegateType, forbiddenSections, isForceDock); return surfaceId; } +bool TraySortOrderModel::getSurfaceVisible(const QString &surfaceId, const QString §ionType) +{ + if (surfaceId == ACTION_STASH_PLACEHOLDER_NAME) { + return getStashPlaceholderVisible(); + } + + if (surfaceId == ACTION_SHOW_STASH_NAME) { + return getShowStashActionVisible(); + } + + if (sectionType == SECTION_TRAY_ACTION) { + return true; + } + + if ( + m_hiddenIds.contains(surfaceId) || + (m_collapsed && sectionType == SECTION_COLLAPSABLE) + ) { + return false; + } + + return true; +} + +bool TraySortOrderModel::getStashPlaceholderVisible() +{ + return m_visibleStashed == 0; +} + +bool TraySortOrderModel::getShowStashActionVisible() +{ + return m_actionsAlwaysVisible || m_visibleStashed != 0; +} + void TraySortOrderModel::loadDataFromDConfig() { m_stashedIds = m_dconfig->value("stashedSurfaceIds").toStringList(); @@ -483,16 +601,88 @@ void TraySortOrderModel::onAvailableSurfacesChanged() } // check if there are tray items no longer availabled, and remove them. - for (int i = rowCount() - 1; i >= 0; i--) { - const QString surfaceId(data(index(i, 0), TraySortOrderModel::SurfaceIdRole).toString()); - if (availableSurfaceIds.contains(surfaceId)) continue; - if (surfaceId.startsWith("internal/")) continue; - removeRow(i); - } - // finally, update visual index - updateVisualIndexes(); + auto i = m_list.begin(); + while (i != m_list.end()) { + if (availableSurfaceIds.contains(i->surfaceId)) { + ++i; + continue; + } + if (i->surfaceId.startsWith(INTERNAL_PREFIX)) { + ++i; + continue; + } + + int row = std::distance(m_list.begin(), i); + beginRemoveRows(QModelIndex(), row, row); + i = m_list.erase(i); + endRemoveRows(); + } + + updateVisibilities(); + // and also save the current sort order saveDataToDConfig(); } +void TraySortOrderModel::updateStashPlaceholderVisible() +{ + auto i = findItem(ACTION_STASH_PLACEHOLDER_NAME); + if (i == m_list.end()) { + return; + } + + bool visible = getStashPlaceholderVisible(); + if (i->visible == visible) { + return; + } + + i->visible = visible; + QModelIndex idx = index(std::distance(m_list.begin(), i), 0); + dataChanged(idx, idx, { VisibilityRole }); +} + +void TraySortOrderModel::updateShowStashActionVisible() +{ + auto i = findItem(ACTION_SHOW_STASH_NAME); + if (i == m_list.end()) { + return; + } + + bool visible = getShowStashActionVisible(); + if (i->visible == visible) { + return; + } + + i->visible = visible; + QModelIndex idx = index(std::distance(m_list.begin(), i), 0); + dataChanged(idx, idx, { VisibilityRole }); +} + +void TraySortOrderModel::updateVisibilities() +{ + m_visibleStashed = 0; + for (auto i = m_list.begin(); i != m_list.end(); ++i) { + bool visible = getSurfaceVisible(i->surfaceId, i->sectionType); + if (visible && i->sectionType == SECTION_STASHED && i->surfaceId != ACTION_STASH_PLACEHOLDER_NAME) { + m_visibleStashed++; + } + + // placeholder 在 stash 最后 + if (i->surfaceId == ACTION_STASH_PLACEHOLDER_NAME) { + visible = getStashPlaceholderVisible(); + } else if (i->surfaceId == ACTION_SHOW_STASH_NAME) { + visible = getShowStashActionVisible(); + } + + if (i->visible == visible) { + continue; + } + + i->visible = visible; + + QModelIndex idx = index(std::distance(m_list.begin(), i), 0); + dataChanged(idx, idx, { VisibilityRole }); + } +} + } diff --git a/panels/dock/tray/traysortordermodel.h b/panels/dock/tray/traysortordermodel.h index 52f3ba3e3..7f1c71ecc 100644 --- a/panels/dock/tray/traysortordermodel.h +++ b/panels/dock/tray/traysortordermodel.h @@ -4,7 +4,7 @@ #pragma once -#include +#include #include namespace Dtk { @@ -14,13 +14,22 @@ class DConfig; namespace docktray { -class TraySortOrderModel : public QStandardItemModel +struct Item { + QString surfaceId; + bool visible; + QString sectionType; + QString delegateType; + QStringList forbiddenSections; + bool isForceDock; +}; + +class TraySortOrderModel : public QAbstractItemModel { Q_OBJECT QML_ELEMENT QML_SINGLETON - Q_PROPERTY(int visualItemCount MEMBER m_visualItemCount NOTIFY visualItemCountChanged) + Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged) Q_PROPERTY(bool collapsed MEMBER m_collapsed NOTIFY collapsedChanged) Q_PROPERTY(bool isCollapsing MEMBER m_isCollapsing NOTIFY isCollapsingChanged) Q_PROPERTY(bool actionsAlwaysVisible MEMBER m_actionsAlwaysVisible NOTIFY actionsAlwaysVisibleChanged) @@ -39,7 +48,6 @@ class TraySortOrderModel : public QStandardItemModel SurfaceIdRole = Qt::UserRole, // actually "pluginId::itemKey" or an internal one. VisibilityRole, SectionTypeRole, - VisualIndexRole, DelegateTypeRole, // this tray item cannot be drop (or moved in any form) to the given sections ForbiddenSectionsRole, @@ -57,11 +65,18 @@ class TraySortOrderModel : public QStandardItemModel explicit TraySortOrderModel(QObject *parent = nullptr); ~TraySortOrderModel(); - Q_INVOKABLE bool dropToStashTray(const QString & draggedSurfaceId, int dropVisualIndex, bool isBefore); - Q_INVOKABLE bool dropToDockTray(const QString & draggedSurfaceId, int dropVisualIndex, bool isBefore); + QHash roleNames() const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + Q_INVOKABLE bool move(const QString &draggedSurfaceId, const QString &dropOnSurfaceId, bool isBefore); Q_INVOKABLE void setSurfaceVisible(const QString & surfaceId, bool visible); signals: + void rowCountChanged(); void collapsedChanged(bool); void isCollapsingChanged(bool); void actionsAlwaysVisibleChanged(bool); @@ -69,10 +84,12 @@ class TraySortOrderModel : public QStandardItemModel void availableSurfacesChanged(const QList &); private: - int m_visualItemCount = 0; + QHash m_defaultRoleNames; + QVector m_list; bool m_collapsed = false; bool m_isCollapsing = false; bool m_actionsAlwaysVisible = false; + int m_visibleStashed = 0; std::unique_ptr m_dconfig; // this is for the plugins that currently available. QList m_availableSurfaces; @@ -84,19 +101,28 @@ class TraySortOrderModel : public QStandardItemModel // surface IDs that should be invisible/hidden from the tray area. QStringList m_hiddenIds; - QStandardItem * findItemByVisualIndex(int visualIndex, VisualSections visualSection) const; + QVector::iterator findItem(const QString &surfaceId); + QVector::const_iterator findInsertionPosInSection(const QString &surfaceId, const QString §ionType, + QVector::const_iterator sectionBegin, QVector::const_iterator sectionEnd) const; + QVector::const_iterator findInsertionPos(const QString &surfaceId, const QString §ionType) const; QStringList * getSection(const QString & sectionType); QString findSection(const QString & surfaceId, const QString & fallback, const QStringList & forbiddenSections, bool isForceDock); void registerToSection(const QString & surfaceId, const QString & sectionType); - QStandardItem * createTrayItem(const QString & name, const QString & sectionType, + void createTrayItem(const QString & name, const QString & sectionType, const QString & delegateType, const QStringList & forbiddenSections = {}, bool isForceDock = false); - void updateVisualIndexes(); + void updateStashPlaceholderVisible(); + void updateShowStashActionVisible(); + std::tuple moveAux(const QString &draggedSurfaceId, const QString &dropOnSurfaceId, bool isBefore); QString registerSurfaceId(const QVariantMap &surfaceData); + bool getSurfaceVisible(const QString &surfaceId, const QString §ionType); + bool getStashPlaceholderVisible(); + bool getShowStashActionVisible(); void loadDataFromDConfig(); void saveDataToDConfig(); private slots: void onAvailableSurfacesChanged(); + void updateVisibilities(); }; }