diff --git a/Modules/Dock/DockMenu.qml b/Modules/Dock/DockMenu.qml index 30de31e4..b3570bc3 100644 --- a/Modules/Dock/DockMenu.qml +++ b/Modules/Dock/DockMenu.qml @@ -18,15 +18,67 @@ PopupWindow { property var onAppClosed: null // Callback function for when an app is closed // Track which menu item is hovered - property int hoveredItem: -1 // -1: none, 0: focus, 1: pin, 2: close + property int hoveredItem: -1 // -1: none, otherwise the index of the item in `items` + + property var items: [] signal requestClose - implicitWidth: 140 * scaling + implicitWidth: 160 * scaling implicitHeight: contextMenuColumn.implicitHeight + (Style.marginM * scaling * 2) color: Color.transparent visible: false + function initItems() { + // Is this a running app? + const isRunning = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel) + + // Is this a pinned app? + const isPinned = root.toplevel && root.isAppPinned(root.toplevel.appId) + + var next = [] + if (isRunning) { + // Focus item + next.push({ + "icon": "eye", + "text": I18n.tr("dock.menu.focus"), + "action": function() { handleFocus() } + }) + } + + // Pin/Unpin item + next.push({ + "icon": !isPinned ? "pin" : "unpin", + "text": !isPinned ? I18n.tr("dock.menu.pin") : I18n.tr("dock.menu.unpin"), + "action": function() { handlePin() } + }) + + if (isRunning) { + // Close item + next.push({ + "icon": "close", + "text": I18n.tr("dock.menu.close"), + "action": function() { handleClose() } + }) + } + + // Create a menu entry for each app-specific action definied in its .desktop file + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.byId) { + const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(appId) : DesktopEntries.byId(appId) + if (entry != null) { + entry.actions.forEach(function(action) { + next.push({ + "icon": "", + "text": action.name, + "action": function() { action.execute() } + }) + }) + } + } + + root.items = next + } + // Helper functions for pin/unpin functionality function isAppPinned(appId) { if (!appId) @@ -66,11 +118,13 @@ PopupWindow { anchorItem = item toplevel = toplevelData + initItems() visible = true } function hide() { visible = false + root.items.length = 0 } // Helper function to determine which menu item is under the mouse @@ -83,44 +137,38 @@ PopupWindow { return -1 const itemIndex = Math.floor(relativeY / itemHeight) - return itemIndex >= 0 && itemIndex < 3 ? itemIndex : -1 + return itemIndex >= 0 && itemIndex < root.items.length ? itemIndex : -1 } - // Handle menu item clicks - function handleItemClick(itemIndex) { - switch (itemIndex) { - case 0: - // Focus - if (root.toplevel?.activate) { - root.toplevel.activate() - } - root.requestClose() - break - case 1: - // Pin/Unpin - if (root.toplevel?.appId) { - root.toggleAppPin(root.toplevel.appId) - } - root.requestClose() - break - case 2: - // Close - // Check if toplevel is still valid before trying to close it - const isValidToplevel = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel) - - if (isValidToplevel && root.toplevel.close) { - root.toplevel.close() - // Trigger immediate dock update callback if provided - if (root.onAppClosed && typeof root.onAppClosed === "function") { - Qt.callLater(root.onAppClosed) - } - } else { - Logger.warn("DockMenu", "Cannot close app - invalid toplevel reference") - } - root.hide() - root.requestClose() - break + function handleFocus() { + if (root.toplevel?.activate) { + root.toplevel.activate() } + root.requestClose() + } + + function handlePin() { + if (root.toplevel?.appId) { + root.toggleAppPin(root.toplevel.appId) + } + root.requestClose() + } + + function handleClose() { + // Check if toplevel is still valid before trying to close it + const isValidToplevel = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel) + + if (isValidToplevel && root.toplevel.close) { + root.toplevel.close() + // Trigger immediate dock update callback if provided + if (root.onAppClosed && typeof root.onAppClosed === "function") { + Qt.callLater(root.onAppClosed) + } + } else { + Logger.warn("DockMenu", "Cannot close app - invalid toplevel reference") + } + root.hide() + root.requestClose() } Timer { @@ -163,7 +211,7 @@ PopupWindow { onClicked: mouse => { const clickedItem = root.getHoveredItem(mouse.y) if (clickedItem >= 0) { - root.handleItemClick(clickedItem) + root.items[clickedItem].action.call() } } } @@ -174,99 +222,37 @@ PopupWindow { anchors.margins: Style.marginM * scaling spacing: 0 - // Focus item - Rectangle { - Layout.fillWidth: true - height: 32 * scaling - color: root.hoveredItem === 0 ? Color.mTertiary : Color.transparent - radius: Style.radiusXS * scaling + Repeater { + model: root.items - RowLayout { - anchors.left: parent.left - anchors.leftMargin: Style.marginS * scaling - anchors.verticalCenter: parent.verticalCenter - spacing: Style.marginS * scaling + Rectangle { + Layout.fillWidth: true + height: 32 * scaling + color: root.hoveredItem === index ? Color.mTertiary : Color.transparent + radius: Style.radiusXS * scaling - NIcon { - icon: "eye" - pointSize: Style.fontSizeL * scaling - color: root.hoveredItem === 0 ? Color.mOnTertiary : Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignVCenter - } + RowLayout { + anchors.left: parent.left + anchors.leftMargin: Style.marginS * scaling + anchors.verticalCenter: parent.verticalCenter + spacing: Style.marginS * scaling - NText { - text: I18n.tr("dock.menu.focus") - pointSize: Style.fontSizeS * scaling - color: root.hoveredItem === 0 ? Color.mOnTertiary : Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignVCenter - } - } - } + NIcon { + icon: modelData.icon + pointSize: Style.fontSizeL * scaling + color: root.hoveredItem === index ? Color.mOnTertiary : Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignVCenter + } - // Pin/Unpin item - Rectangle { - Layout.fillWidth: true - height: 32 * scaling - color: root.hoveredItem === 1 ? Color.mTertiary : Color.transparent - radius: Style.radiusXS * scaling - - RowLayout { - anchors.left: parent.left - anchors.leftMargin: Style.marginS * scaling - anchors.verticalCenter: parent.verticalCenter - spacing: Style.marginS * scaling - - NIcon { - icon: { - if (!root.toplevel) - return "pin" - return root.isAppPinned(root.toplevel.appId) ? "unpin" : "pin" + NText { + text: modelData.text + pointSize: Style.fontSizeS * scaling + color: root.hoveredItem === index ? Color.mOnTertiary : Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignVCenter + elide: Text.ElideRight } - pointSize: Style.fontSizeL * scaling - color: root.hoveredItem === 1 ? Color.mOnTertiary : Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignVCenter - } - - NText { - text: { - if (!root.toplevel) - return I18n.tr("dock.menu.pin") - return root.isAppPinned(root.toplevel.appId) ? I18n.tr("dock.menu.unpin") : I18n.tr("dock.menu.pin") - } - pointSize: Style.fontSizeS * scaling - color: root.hoveredItem === 1 ? Color.mOnTertiary : Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignVCenter - } - } - } - - // Close item - Rectangle { - Layout.fillWidth: true - height: 32 * scaling - color: root.hoveredItem === 2 ? Color.mTertiary : Color.transparent - radius: Style.radiusXS * scaling - - RowLayout { - anchors.left: parent.left - anchors.leftMargin: Style.marginS * scaling - anchors.verticalCenter: parent.verticalCenter - spacing: Style.marginS * scaling - - NIcon { - icon: "close" - pointSize: Style.fontSizeL * scaling - color: root.hoveredItem === 2 ? Color.mOnTertiary : Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignVCenter - } - - NText { - text: I18n.tr("dock.menu.close") - pointSize: Style.fontSizeS * scaling - color: root.hoveredItem === 2 ? Color.mOnTertiary : Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignVCenter - } - } + } + } } } }