/* SPDX-FileCopyrightText: 2015 Eike Hein SPDX-License-Identifier: GPL-2.0-or-later */ import QtQuick 2.15 import Qt5Compat.GraphicalEffects // Deliberately imported after QtQuick to avoid missing restoreMode property in Binding. Fix in Qt 6. import QtQml 2.15 import org.kde.kquickcontrolsaddons 2.0 import org.kde.kwindowsystem 1.0 import org.kde.plasma.components as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.plasma.core as PlasmaCore import org.kde.ksvg 1.0 as KSvg import org.kde.plasma.private.shell 2.0 import org.kde.kirigami 2.20 as Kirigami import QtQuick.Layouts 1.1 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.private.kicker 0.1 as Kicker import QtQuick.Controls import QtQuick import Qt5Compat.GraphicalEffects import "code/tools.js" as Tools Kicker.DashboardWindow { id: root property bool smallScreen: ((Math.floor(width / Kirigami.Units.iconSizes.huge) <= 22) || (Math.floor(height / Kirigami.Units.iconSizes.huge) <= 14)) 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; case 4: return Kirigami.Units.iconSizes.large * 2; case 5: return Kirigami.Units.iconSizes.enormous; default: return 64 } } property int favsIconSize:{ switch(Plasmoid.configuration.favsIconSize){ 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; case 4: return Kirigami.Units.iconSizes.enormous; default: return 64 } } property int systemIconSize:{ switch(Plasmoid.configuration.systemIconSize){ 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; case 4: return Kirigami.Units.iconSizes.enormous; default: return 64 } } property int cellSize: iconSize + (2 * Kirigami.Units.iconSizes.sizeForLabels) + (2 * Kirigami.Units.largeSpacing) + (2 * Math.max(highlightItemSvg.margins.top + highlightItemSvg.margins.bottom, highlightItemSvg.margins.left + highlightItemSvg.margins.right)) //property int columns: Math.floor(width*0.7/root.cellSize)// Math.floor(((smallScreen ? 85 : 80)/100) * Math.ceil(width / cellSize)) property int columns: Math.floor(((smallScreen ? 85 : 80)/100) * Math.ceil(width / cellSize)) property bool searching: searchField.text !== "" property int favoritesRows: Math.floor(height*0.4/cellSize) //keyEventProxy: searchField backgroundColor: "transparent" onKeyEscapePressed: { if (searching) { searchField.clear(); } else { root.toggle(); } } onVisibleChanged: { if(visible){ animatorMainColumn.start() }else{ rootItem.opacity = 0 } reset(); } onSearchingChanged: { if (!searching) { //mainView.currentIndex = 1 mainView.pop() reset(); } else { mainView.push(runnerComponent) //mainView.currentIndex = 0 } } function colorWithAlpha(color: color, alpha: real): color { return Qt.rgba(color.r, color.g, color.b, alpha) } function reset() { allAppsGrid.model = rootModel.modelForRow(2) globalFavoritesGrid.model = globalFavorites allAppsGrid.currentIndex = -1 globalFavoritesGrid.currentIndex = -1; systemFavoritesGrid.currentIndex = -1; allAppsGrid.forceLayout() searchField.clear(); searchField.focus = true } mainItem: MouseArea { id: rootItem anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft LayoutMirroring.childrenInherit: true Rectangle{ anchors.fill: parent color: Kirigami.Theme.backgroundColor opacity: 0.4 } Connections { target: kicker function onReset() { if (!root.searching) { //filterList.applyFilter(); //funnelModel.reset(); } } function onDragSourceChanged() { if (!kicker.dragSource) { // FIXME TODO HACK: Reset all views post-DND to work around // mouse grab bug despite QQuickWindow::mouseGrabberItem==0x0. // Needs a more involved hunt through Qt Quick sources later since // it's not happening with near-identical code in the menu repr. rootModel.refresh(); } } } Connections { target: Plasmoid function onUserConfiguringChanged() { if (Plasmoid.userConfiguring) { root.hide() } } } PlasmaExtras.Menu { id: contextMenu PlasmaExtras.MenuItem { action: Plasmoid.internalAction("configure") } } Kirigami.Heading { id: dummyHeading visible: false width: 0 level: 1 } TextMetrics { id: headingMetrics font: dummyHeading.font } Kicker.FunnelModel { id: funnelModel onSourceModelChanged: { if (mainColumn.visible) { mainGrid.currentIndex = -1; mainGrid.forceLayout(); } } } Kicker.ContainmentInterface { id: containmentInterface } TextField{ id: searchField anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: mainView.top anchors.bottomMargin: Kirigami.Units.largeSpacing * 6 //focus: true width: Kirigami.Units.gridUnit * 13 topPadding: Kirigami.Units.largeSpacing bottomPadding: Kirigami.Units.largeSpacing placeholderText: i18nc("@info:placeholder as in, 'start typing to initiate a search'", "Search") horizontalAlignment: TextInput.AlignHCenter wrapMode: Text.NoWrap font.pointSize: Kirigami.Theme.defaultFont.pointSize + 0.5 onTextChanged: { runnerModel.query = searchField.text; } function clear() { text = ""; } background: Rectangle { color: colorWithAlpha(Kirigami.Theme.backgroundColor,0.7) radius: 100 border.width: 1 border.color: colorWithAlpha(Kirigami.Theme.textColor,0.05) } function appendText(newText) { if (!root.visible) { return; } focus = true; text = text + newText; } function backspace() { if (!root.visible) { return; } focus = true; text = text.slice(0, -1); } function updateSelection() { if (!searchField.selectedText) { return; } var delta = text.lastIndexOf(searchField.text, text.length - 2); searchHeading.select(searchField.selectionStart + delta, searchField.selectionEnd + delta); } Keys.onPressed: event => { if (event.key === Qt.Key_Down || event.key === Qt.Key_Tab) { event.accepted = true; if(root.searching){ mainView.currentItem.tryActivate(0,0) mainView.currentItem.forceActiveFocus() } else{ allAppsGrid.tryActivate(0,0) allAppsGrid.forceActiveFocus() } } } } OpacityAnimator{ id: animatorMainColumn ;from: 0; to: 1 ; target: rootItem;} StackView { id: mainView width: (root.columns * root.cellSize) + Kirigami.Units.gridUnit height: Math.ceil(root.height*0.7/root.cellSize) * root.cellSize anchors{ horizontalCenter: parent.horizontalCenter verticalCenter: parent.verticalCenter } initialItem: Column { id: allAppsColumn clip: true spacing: Kirigami.Units.largeSpacing * 2 ItemGridView { id: allAppsGrid width: parent.width height: Math.ceil(root.height*0.7/cellHeight) * cellHeight cellWidth: root.cellSize cellHeight: root.cellSize iconSize: root.iconSize dropEnabled: false onKeyNavDown: { allAppsGrid.focus = false globalFavoritesGrid.tryActivate(0,0) globalFavoritesGrid.forceActiveFocus() } onKeyNavUp: { allAppsGrid.focus = false searchField.focus = true } Keys.onPressed: event => { if (event.key === Qt.Key_Tab) { event.accepted = true; allAppsGrid.focus = false globalFavoritesGrid.tryActivate(0,0) globalFavoritesGrid.forceActiveFocus() } } } } pushEnter: Transition { PropertyAnimation { property: "opacity" from: 0 to:1 duration: 200 } } pushExit: Transition { PropertyAnimation { property: "opacity" from: 1 to:0 duration: 200 } } popEnter: Transition { PropertyAnimation { property: "opacity" from: 0 to:1 duration: 200 } } popExit: Transition { PropertyAnimation { property: "opacity" from: 1 to:0 duration: 200 } } } Component { id: runnerComponent ItemGridView { id: runnerGrid anchors.horizontalCenter: mainView.horizontalCenter width: mainView.width/2 clip: true height: mainView.height grabFocus: true cellWidth: root.cellSize cellHeight: root.cellSize iconSize: root.iconSize model: runnerModel.count > 0 ? runnerModel.modelForRow(0) : undefined onKeyNavDown: { runnerGrid.focus = false globalFavoritesGrid.tryActivate(0,0) globalFavoritesGrid.forceActiveFocus() } onKeyNavUp: { runnerGrid.focus = false searchField.focus = true } Keys.onPressed: event => { if (event.key === Qt.Key_Tab) { event.accepted = true; runnerGrid.focus = false globalFavoritesGrid.tryActivate(0,0) globalFavoritesGrid.forceActiveFocus() } } } } Rectangle{ anchors.centerIn: globalFavoritesGrid height: globalFavoritesGrid.height + Kirigami.Units.largeSpacing width: globalFavoritesGrid.width + Kirigami.Units.largeSpacing color: Kirigami.Theme.backgroundColor radius: 10 opacity: 0.6 z:1 } ItemGridView { id: globalFavoritesGrid width: globalFavoritesGrid.model ? Math.min(Math.floor((root.width*0.85)/cellWidth)*cellWidth,globalFavoritesGrid.model.count*cellWidth) : 0 iconSize: root.favsIconSize cellHeight: iconSize + Kirigami.Units.largeSpacing cellWidth: cellHeight + Kirigami.Units.largeSpacing clip: true z:2 verticalScrollBarPolicy: PlasmaComponents.ScrollBar.AlwaysOff height: cellHeight anchors.bottom: parent.bottom anchors.bottomMargin: Kirigami.Units.largeSpacing anchors.horizontalCenter: parent.horizontalCenter showLabels: false dropEnabled: true dragEnabled: true onKeyNavUp: { if(root.searching){ globalFavoritesGrid.focus = false //runnerGrid.tryActivate(0,0) //runnerGrid.forceActiveFocus() mainView.currentItem.tryActivate(0,0) mainView.currentItem.forceActiveFocus() } else{ globalFavoritesGrid.focus = false allAppsGrid.tryActivate(0,0) allAppsGrid.forceActiveFocus() } } Keys.onPressed: event => { if (event.key === Qt.Key_Tab) { event.accepted = true; globalFavoritesGrid.focus = false systemFavoritesGrid.tryActivate(0,0) systemFavoritesGrid.forceActiveFocus() } } } ItemGridView { id: systemFavoritesGrid anchors.top: parent.top anchors.right: parent.right clip: true width: cellWidth height: systemFavoritesGrid.model ? systemFavoritesGrid.model.count * cellHeight : 0 cellWidth: iconSize + Kirigami.Units.largeSpacing * 2 cellHeight: cellWidth iconSize: root.systemIconSize z:5 showLabels: false model: systemFavorites dragEnabled: false dropEnabled: false Keys.onPressed: event => { if (event.key === Qt.Key_Tab) { event.accepted = true; systemFavoritesGrid.focus = false searchField.focus = true } } } onPressed: mouse => { if (mouse.button === Qt.RightButton) { contextMenu.open(mouse.x, mouse.y); } } onClicked: mouse => { if (mouse.button === Qt.LeftButton) { root.toggle(); } } 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); } //searchField.focus = true } } Component.onCompleted: { rootModel.refresh(); searchField.forceActiveFocus() } }