/*************************************************************************** * Copyright (C) 2014 by Weng Xuetian * Copyright (C) 2013-2017 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ import QtQuick import QtQuick.Layouts import QtQuick.Controls import org.kde.plasma.plasmoid import org.kde.plasma.core as PlasmaCore import org.kde.plasma.components as PC3 import org.kde.plasma.extras as PlasmaExtras import org.kde.plasma.private.kicker as Kicker import org.kde.kquickcontrolsaddons import org.kde.plasma.private.quicklaunch import org.kde.kirigami as Kirigami import org.kde.plasma.plasma5support as Plasma5Support PlasmaCore.Dialog { id: root objectName: "popupWindow" flags: Qt.WindowStaysOnTopHint location: { if (Plasmoid.configuration.displayPosition === 1) return PlasmaCore.Types.Floating; else if (Plasmoid.configuration.displayPosition === 2) return PlasmaCore.Types.BottomEdge; else return Plasmoid.location; } hideOnWindowDeactivate: true // Ensure minimum dimensions to prevent empty dialog error width: Math.max(rootItem.width, 300) height: Math.max(rootItem.height, 200) property int iconSize: { switch (Plasmoid.configuration.appsIconSize) { case 0: return Kirigami.Units.iconSizes.smallMedium; case 1: return Kirigami.Units.iconSizes.medium; case 2: return Kirigami.Units.iconSizes.large; case 3: return Kirigami.Units.iconSizes.huge; default: return 64; } } property int docsIconSize: { switch (Plasmoid.configuration.docsIconSize) { case 0: return Kirigami.Units.iconSizes.smallMedium; case 1: return Kirigami.Units.iconSizes.medium; case 2: return Kirigami.Units.iconSizes.large; case 3: return Kirigami.Units.iconSizes.huge; default: return Kirigami.Units.iconSizes.medium; } } property int cellSizeHeight: iconSize + Kirigami.Units.gridUnit * 2 + (2 * Math.max(highlightItemSvg.margins.top + highlightItemSvg.margins.bottom, highlightItemSvg.margins.left + highlightItemSvg.margins.right)) property int cellSizeWidth: cellSizeHeight + Kirigami.Units.gridUnit property bool searching: (searchField.text != "") onSearchingChanged: { if (searching) { view.currentIndex = 2; } else { view.currentIndex = 0; } } onVisibleChanged: { if (visible) { var pos = popupPosition(width, height); x = pos.x; y = pos.y; reset(); } else { view.currentIndex = 0; } } onHeightChanged: { if (visible) { var pos = popupPosition(width, height); x = pos.x; y = pos.y; } } onWidthChanged: { if (visible) { var pos = popupPosition(width, height); x = pos.x; y = pos.y; } } function toggle() { root.visible = !root.visible; } function reset() { searchField.text = ""; searchField.focus = true; view.currentIndex = 0; globalFavoritesGrid.currentIndex = -1; documentsGrid.currentIndex = -1; allAppsGrid.currentIndex = -1; } function popupPosition(width, height) { var screenAvail = kicker.availableScreenRect; var screen = kicker.screenGeometry; var panelH = kicker.height var panelW = kicker.width var horizMidPoint = screen.x + (screen.width / 2); var vertMidPoint = screen.y + (screen.height / 2); var appletTopLeft = parent.mapToGlobal(0, 0); var offset = Kirigami.Units.smallSpacing * 2; if (Plasmoid.configuration.displayPosition === 1) { horizMidPoint = screen.x + (screen.width / 2); vertMidPoint = screen.y + (screen.height / 2); x = horizMidPoint - width / 2; y = vertMidPoint - height / 2; } else if (Plasmoid.configuration.displayPosition === 2) { horizMidPoint = screen.x + (screen.width / 2); vertMidPoint = screen.y + (screen.height / 2); x = horizMidPoint - width / 2; // y = screen.y + screen.height - height - offset - panelH - Kirigami.Units.gridUnit; // y = screen.y + screen.height - height - offset - panelH - Kirigami.Units.gridUnit / 2; y = screen.y + screen.height - height - offset - panelH - Kirigami.Units.largeSpacing * 1.5; } else { switch (plasmoid.location) { case PlasmaCore.Types.BottomEdge: var y = appletTopLeft.y - height - offset var x = appletTopLeft.x break; case PlasmaCore.Types.TopEdge: x = appletTopLeft.x < screen.width - width ? appletTopLeft.x + panelW - Kirigami.Units.gridUnit / 3 : screen.width - width; y = appletTopLeft.y + kicker.height + Kirigami.Units.gridUnit break; case PlasmaCore.Types.LeftEdge: x = appletTopLeft.x + panelW + Kirigami.Units.gridUnit / 2; y = appletTopLeft.y < screen.height - height ? appletTopLeft.y : appletTopLeft.y - height + iconUser.height / 2; break; case PlasmaCore.Types.RightEdge: x = appletTopLeft.x - width - Kirigami.Units.gridUnit / 2; y = appletTopLeft.y < screen.height - height ? appletTopLeft.y : screen.height - height - Kirigami.Units.gridUnit / 5; break; default: return; } } return Qt.point(x, y); } function colorWithAlpha(color: color, alpha: real): color { return Qt.rgba(color.r, color.g, color.b, alpha); } mainItem: FocusScope { id: rootItem property int widthComputed: root.cellSizeWidth * Plasmoid.configuration.numberColumns + Kirigami.Units.gridUnit * 2 width: Math.max(widthComputed + Kirigami.Units.gridUnit * 2, 300) height: view.height + searchField.height + footer.height + Kirigami.Units.gridUnit * 3 Layout.minimumWidth: width Layout.maximumWidth: width Layout.minimumHeight: view.height + searchField.height + footer.height + Kirigami.Units.gridUnit * 3 Layout.maximumHeight: view.height + searchField.height + footer.height + Kirigami.Units.gridUnit * 3 focus: true onFocusChanged: searchField.focus = true Plasma5Support.DataSource { id: executable engine: "executable" connectedSources: [] property bool dolphinRunning: false onNewData: function (source, data) { if (source.includes("pgrep")) { dolphinRunning = data["exit code"] === 0; } disconnectSource(source); } function exec(cmd) { connectSource(cmd); } function checkDolphin() { connectSource("pgrep dolphin"); } } Kirigami.Heading { id: dummyHeading visible: false width: 0 level: 5 } TextMetrics { id: headingMetrics font: dummyHeading.font } PC3.TextField { id: searchField anchors { top: parent.top topMargin: Kirigami.Units.gridUnit left: parent.left leftMargin: Kirigami.Units.gridUnit right: parent.right rightMargin: Kirigami.Units.gridUnit } focus: true placeholderText: i18n("Type here to search ...") topPadding: 10 bottomPadding: 10 leftPadding: Kirigami.Units.gridUnit + Kirigami.Units.iconSizes.small text: "" font.pointSize: Kirigami.Theme.defaultFont.pointSize background: Rectangle { color: Kirigami.Theme.backgroundColor radius: 20 border.width: 1 border.color: colorWithAlpha(Kirigami.Theme.textColor, 0.05) } onTextChanged: runnerModel.query = text; Keys.onPressed: (event) => { if (event.key === Qt.Key_Escape) { event.accepted = true; if (root.searching) { searchField.clear(); } else { root.toggle(); } } // Forward Up/Down keys directly to runnerGrid when on search page if (view.currentIndex === 2 && (event.key === Qt.Key_Up || event.key === Qt.Key_Down)) { event.accepted = true; // Make sure runnerGrid can receive the key event runnerGrid.focus = true; // Forward the key event to runnerGrid if (event.key === Qt.Key_Down) { console.log("Down key pressed"); // Simulate a down key press on the first item if nothing is selected if (!runnerGrid.focus || runnerGrid.currentIndex === -1) { runnerGrid.tryActivate(0, 0); } else { // Forward the down key to the currently focused grid var currentGrid = runnerGrid.subGridAt(0); // or find the currently active grid if (currentGrid) { currentGrid.forceActiveFocus(); // Trigger the key navigation currentGrid.keyNavDown(); } } } else if (event.key === Qt.Key_Up) { // For up key, go to the last item or handle appropriately console.log("Up key pressed"); var lastGridIndex = runnerGrid.count - 1; if (lastGridIndex >= 0) { var lastGrid = runnerGrid.subGridAt(lastGridIndex); if (lastGrid && lastGrid.count > 0) { lastGrid.tryActivate(lastGrid.lastRow(), 0); lastGrid.focus = true; } } } return; } if (event.key === Qt.Key_Down || event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab) { event.accepted = true; if (view.currentIndex === 2) { // For search results page runnerGrid.focus = true; runnerGrid.tryActivate(0, 0); } else { // For other pages view.currentItem.forceActiveFocus(); view.currentItem.tryActivate(0, 0); } } } Keys.onReturnPressed: { if (view.currentIndex === 2) { // On search results page, activate the first available item runnerGrid.focus = true; // Find the first grid with items and activate its first item for (var i = 0; i < runnerGrid.count; i++) { var grid = runnerGrid.subGridAt(i); if (grid && grid.count > 0) { grid.currentIndex = 0; grid.focus = true; grid.itemActivated(0, "", null); if ("trigger" in grid.model) { //console.log("Calling grid.model.trigger(0)"); grid.model.trigger(0, "", null); root.toggle(); } return; } } } else { // Original logic for other pages for (var i = 0; i < runnerGrid.count; i++) { var grid = runnerGrid.subGridAt(i) if (grid && grid.count > 0) { grid.currentIndex = 0 grid.focus = true grid.itemActivated(0, "", null) return } } } } function backspace() { if (!root.visible) { return; } focus = true; text = text.slice(0, -1); } function appendText(newText) { if (!root.visible) { return; } focus = true; text = text + newText; } Kirigami.Icon { source: 'search' anchors { left: searchField.left verticalCenter: searchField.verticalCenter leftMargin: Kirigami.Units.smallSpacing * 2 } height: Kirigami.Units.iconSizes.small width: height } } SwipeView { id: view interactive: false currentIndex: 0 clip: true anchors { top: searchField.bottom topMargin: Kirigami.Units.gridUnit left: parent.left leftMargin: Kirigami.Units.gridUnit right: parent.right rightMargin: Kirigami.Units.gridUnit } onCurrentIndexChanged: { globalFavoritesGrid.currentIndex = -1; documentsGrid.currentIndex = -1; } width: rootItem.widthComputed / 0.1 height: (root.cellSizeHeight * Plasmoid.configuration.numberRows) + (topRow.height * 2) + ((docsIconSize + Kirigami.Units.largeSpacing) * 5) + (3 * Kirigami.Units.largeSpacing) // // PAGE 1 // Column { width: rootItem.widthComputed height: view.height spacing: Kirigami.Units.largeSpacing * 2 function tryActivate(row, col) { globalFavoritesGrid.tryActivate(row, col); } RowLayout { id: topRow width: parent.width height: butttonActionAllApps.implicitHeight Kirigami.Icon { source: 'favorite' implicitHeight: Kirigami.Units.iconSizes.smallMedium implicitWidth: Kirigami.Units.iconSizes.smallMedium } PlasmaExtras.Heading { id: headLabelFavorites color: colorWithAlpha(Kirigami.Theme.textColor, 0.8) level: 5 text: i18n("Pinned") font.weight: Font.Bold } Item { Layout.fillWidth: true } AToolButton { id: butttonActionAllApps flat: false iconName: "go-next" text: i18n("All apps") buttonHeight: 25 onClicked: { view.currentIndex = 1; } } } ItemGridView { id: globalFavoritesGrid width: parent.width height: root.cellSizeHeight * Plasmoid.configuration.numberRows itemColumns: 1 dragEnabled: true dropEnabled: true cellWidth: parent.width / Plasmoid.configuration.numberColumns cellHeight: root.cellSizeHeight iconSize: root.iconSize onKeyNavUp: { globalFavoritesGrid.focus = false; searchField.focus = true; } onKeyNavDown: { globalFavoritesGrid.focus = false; documentsGrid.tryActivate(0, 0); } Keys.onPressed: event => { if (event.key === Qt.Key_Tab) { event.accepted = true; searchField.focus = true; globalFavoritesGrid.focus = false; } } } RowLayout { width: parent.width height: butttonActionAllApps.implicitHeight Kirigami.Icon { source: 'tag-recents' implicitHeight: Kirigami.Units.iconSizes.smallMedium implicitWidth: Kirigami.Units.iconSizes.smallMedium } PlasmaExtras.Heading { color: colorWithAlpha(Kirigami.Theme.textColor, 0.8) level: 5 text: i18n("Recent documents") Layout.leftMargin: Kirigami.Units.smallSpacing font.weight: Font.Bold } Item { Layout.fillWidth: true } AToolButton { id: butttonActionRecentMore flat: false iconName: "go-next" text: i18n("Show more") buttonHeight: 25 onClicked: { executable.checkDolphin(); if (executable.dolphinRunning) { console.log("dolphin is running"); executable.exec("dolphin 'recentlyused:/files/'"); } else { console.log("dolphin is not running"); executable.exec("dolphin --new-window 'recentlyused:/files/'"); } root.toggle(); } } } ItemGridView { id: documentsGrid width: rootItem.widthComputed height: cellHeight * 3 itemColumns: 2 dragEnabled: true dropEnabled: true cellWidth: rootItem.widthComputed * 0.48 cellHeight: docsIconSize + Kirigami.Units.largeSpacing * 2 iconSize: docsIconSize clip: true onKeyNavUp: { globalFavoritesGrid.tryActivate(0, 0); documentsGrid.focus = false; } Keys.onPressed: (event) => { if (event.key === Qt.Key_Tab) { event.accepted = true; searchField.focus = true; documentsGrid.focus = false; } } } } // // PAGE 2 // Column { width: rootItem.widthComputed height: view.height spacing: Kirigami.Units.largeSpacing * 2 function tryActivate(row, col) { allAppsGrid.tryActivate(row, col); } RowLayout { width: parent.width height: butttonActionAllApps.implicitHeight Kirigami.Icon { source: 'application-menu' implicitHeight: Kirigami.Units.iconSizes.smallMedium implicitWidth: Kirigami.Units.iconSizes.smallMedium } PlasmaExtras.Heading { color: colorWithAlpha(Kirigami.Theme.textColor, 0.8) level: 5 text: i18n("All apps") Layout.leftMargin: Kirigami.Units.smallSpacing font.weight: Font.Bold } Item { Layout.fillWidth: true } AToolButton { flat: false iconName: 'go-previous' text: i18n("Pinned") buttonHeight: 25 mirror: true onClicked: { view.currentIndex = 0; } } } ItemGridView { id: allAppsGrid width: rootItem.widthComputed height: Math.floor((view.height - topRow.height - Kirigami.Units.largeSpacing) / cellHeight) * cellHeight itemColumns: 2 dragEnabled: false dropEnabled: false cellWidth: rootItem.widthComputed - Kirigami.Units.gridUnit * 2 cellHeight: root.iconSize + Kirigami.Units.largeSpacing iconSize: root.iconSize clip: true onKeyNavUp: { searchField.focus = true; allAppsGrid.focus = false; } Keys.onPressed: (event) => { if (event.key === Qt.Key_Tab) { event.accepted = true; searchField.focus = true; allAppsGrid.focus = false; } } } } // // PAGE 3 // ItemMultiGridView { id: runnerGrid width: rootItem.widthComputed height: view.height itemColumns: 3 cellWidth: rootItem.widthComputed - Kirigami.Units.gridUnit * 2 cellHeight: root.iconSize + Kirigami.Units.smallSpacing * 4 model: runnerModel grabFocus: true // Change this to true focus: view.currentIndex === 2 // Add this line onKeyNavUp: { runnerGrid.focus = false; searchField.focus = true; } } } PlasmaExtras.PlasmoidHeading { id: footer contentWidth: parent.width contentHeight: Kirigami.Units.gridUnit * 3 anchors.bottom: parent.bottom position: PC3.ToolBar.Footer Footer { anchors.fill: parent anchors.leftMargin: Kirigami.Units.gridUnit anchors.rightMargin: Kirigami.Units.gridUnit } } Keys.onPressed: (event) => { if (event.modifiers & Qt.ControlModifier || event.modifiers & Qt.ShiftModifier) { searchField.focus = true; return; } if (event.key === Qt.Key_Escape) { event.accepted = true; if (root.searching) { reset(); } else { root.visible = false; } return; } if (searchField.focus) { return; } if (event.key === Qt.Key_Backspace) { event.accepted = true; searchField.backspace(); } else if (event.text !== "") { event.accepted = true; searchField.appendText(event.text); } } } function setModels() { globalFavoritesGrid.model = globalFavorites; allAppsGrid.model = rootModel.modelForRow(2); documentsGrid.model = rootModel.modelForRow(1); } Component.onCompleted: { rootModel.refreshed.connect(setModels); reset(); rootModel.refresh(); } }