/*SPDX-FileCopyrightText: 2015 Eike Hein <hein@kde.org>SPDX-FileCopyrightText: 2021 Mikel Johnson <mikel5764@gmail.com>SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>SPDX-License-Identifier: GPL-2.0-or-later*/import QtQuick 2.15import QtQml 2.15import org.kde.plasma.core 2.0 as PlasmaCoreimport org.kde.plasma.components 2.0 as PC2import org.kde.plasma.components 3.0 as PC3import org.kde.kirigami 2.16 as Kirigami// ScrollView makes it difficult to control implicit size using the contentItem.// Using EmptyPage instead.EmptyPage {id: rootproperty alias model: view.modelproperty alias count: view.countproperty alias currentIndex: view.currentIndexproperty alias currentItem: view.currentItemproperty alias delegate: view.delegateproperty alias view: viewclip: view.height < view.contentHeightheader: MouseArea {implicitHeight: KickoffSingleton.listItemMetrics.margins.tophoverEnabled: trueonEntered: {if (containsMouse) {let targetIndex = view.indexAt(mouseX + view.contentX, view.contentY)if (targetIndex >= 0) {view.currentIndex = targetIndexview.forceActiveFocus(Qt.MouseFocusReason)}}}}footer: MouseArea {implicitHeight: KickoffSingleton.listItemMetrics.margins.bottomhoverEnabled: trueonEntered: {if (containsMouse) {let targetIndex = view.indexAt(mouseX + view.contentX, view.height + view.contentY - 1)if (targetIndex >= 0) {view.currentIndex = targetIndexview.forceActiveFocus(Qt.MouseFocusReason)}}}}/* Not setting GridView as the contentItem because GridView has no way to* set horizontal alignment. I don't want to use leftPadding/rightPadding* for that because I'd have to change the implicitWidth formula and use a* more complicated calculation to get the correct padding.*/GridView {id: viewreadonly property real availableWidth: width - leftMargin - rightMarginreadonly property real availableHeight: height - topMargin - bottomMarginreadonly property int columns: Math.floor(availableWidth / cellWidth)readonly property int rows: Math.floor(availableHeight / cellHeight)property bool movedWithKeyboard: false// NOTE: parent is the contentItem that Control subclasses automatically// create when no contentItem is set, but content is added.height: parent.height// There are lots of ways to try to center the content of a GridView// and many of them have bad visual flaws. This way works pretty well.// Not center aligning when there might be a scrollbar to keep click target positions consistent.anchors.horizontalCenter: plasmoid.rootItem.mayHaveGridWithScrollBar ? undefined : parent.horizontalCenteranchors.horizontalCenterOffset: if (plasmoid.rootItem.mayHaveGridWithScrollBar) {if (root.mirrored) {return verticalScrollBar.implicitWidth/2} else {return -verticalScrollBar.implicitWidth/2}} else {return 0}width: Math.min(parent.width, Math.floor((parent.width - leftMargin - rightMargin) / cellWidth) * cellWidth + leftMargin + rightMargin)Accessible.description: i18n("Grid with %1 rows, %2 columns", rows, columns) // can't use i18np hereimplicitWidth: {let w = view.cellWidth * 4 + leftMargin + rightMarginif (plasmoid.rootItem.mayHaveGridWithScrollBar) {w += verticalScrollBar.implicitWidth}return w}implicitHeight: view.cellHeight * 4 + topMargin + bottomMarginleftMargin: plasmoid.rootItem.backgroundMetrics.leftPaddingrightMargin: plasmoid.rootItem.backgroundMetrics.rightPaddingcellHeight: KickoffSingleton.gridCellSizecellWidth: KickoffSingleton.gridCellSizecurrentIndex: count > 0 ? 0 : -1focus: trueinteractive: height < contentHeightpixelAligned: truereuseItems: trueboundsBehavior: Flickable.StopAtBounds// default keyboard navigation doesn't allow focus reasons to be used// and eats up/down key events when at the beginning or end of the list.keyNavigationEnabled: falsekeyNavigationWraps: falsehighlightMoveDuration: 0highlight: PlasmaCore.FrameSvgItem {// The default Z value for delegates is 1. The default Z value for the section delegate is 2.// The highlight gets a value of 3 while the drag is active and then goes back to the default value of 0.z: root.currentItem && root.currentItem.Drag.active ?3 : 0opacity: view.activeFocus|| (plasmoid.rootItem.contentArea === root&& plasmoid.rootItem.searchField.activeFocus) ? 1 : 0.5width: view.cellWidthheight: view.cellHeightimagePath: "widgets/viewitem"prefix: "hover"}delegate: KickoffGridDelegate {id: itemDelegatewidth: view.cellWidthAccessible.role: Accessible.Cell}move: normalTransitionmoveDisplaced: normalTransitionTransition {id: normalTransitionNumberAnimation {duration: PlasmaCore.Units.shortDurationproperties: "x, y"easing.type: Easing.OutCubic}}PC3.ScrollBar.vertical: PC3.ScrollBar {id: verticalScrollBarparent: rootz: 2height: root.heightanchors.right: parent.right}Kirigami.WheelHandler {target: viewfilterMouseEvents: true// `20 * Qt.styleHints.wheelScrollLines` is the default speed.// `* PlasmaCore.Units.devicePixelRatio` is needed on X11// because Plasma doesn't support Qt scaling.horizontalStepSize: 20 * Qt.styleHints.wheelScrollLines * PlasmaCore.Units.devicePixelRatioverticalStepSize: 20 * Qt.styleHints.wheelScrollLines * PlasmaCore.Units.devicePixelRatio}Connections {target: plasmoidfunction onExpandedChanged() {if (plasmoid.expanded) {view.currentIndex = 0view.positionViewAtBeginning()}}}// Used to block hover events temporarily after using keyboard navigation.// If you have one hand on the touch pad or mouse and another hand on the keyboard,// it's easy to accidentally reset the highlight/focus position to the mouse position.Timer {id: movedWithKeyboardTimerinterval: 200onTriggered: view.movedWithKeyboard = false}function focusCurrentItem(event, focusReason) {currentItem.forceActiveFocus(focusReason)event.accepted = true}Keys.onMenuPressed: if (currentItem !== null) {currentItem.forceActiveFocus(Qt.ShortcutFocusReason)currentItem.openActionMenu()}Keys.onPressed: {let targetX = currentItem ? currentItem.x : contentXlet targetY = currentItem ? currentItem.y : contentYlet targetIndex = currentIndex// supports mirroringlet atLeft = currentIndex % columns === 0// at the top of a given column and in the top rowlet atTop = currentIndex < columns// supports mirroringlet atRight = currentIndex % columns === columns - 1// at bottom of a given column, not necessarily in the last rowlet atBottom = currentIndex >= count - columns// Implements the keyboard navigation described in https://www.w3.org/TR/wai-aria-practices-1.2/#gridif (count > 1) {switch (event.key) {case Qt.Key_Left: if (!atLeft && !plasmoid.rootItem.searchField.activeFocus) {moveCurrentIndexLeft()focusCurrentItem(event, Qt.BacktabFocusReason)} breakcase Qt.Key_H: if (!atLeft && !plasmoid.rootItem.searchField.activeFocus && event.modifiers & Qt.ControlModifier) {moveCurrentIndexLeft()focusCurrentItem(event, Qt.BacktabFocusReason)} breakcase Qt.Key_Up: if (!atTop) {moveCurrentIndexUp()focusCurrentItem(event, Qt.BacktabFocusReason)} breakcase Qt.Key_K: if (!atTop && event.modifiers & Qt.ControlModifier) {moveCurrentIndexUp()focusCurrentItem(event, Qt.BacktabFocusReason)} breakcase Qt.Key_Right: if (!atRight && !plasmoid.rootItem.searchField.activeFocus) {moveCurrentIndexRight()focusCurrentItem(event, Qt.TabFocusReason)} breakcase Qt.Key_L: if (!atRight && !plasmoid.rootItem.searchField.activeFocus && event.modifiers & Qt.ControlModifier) {moveCurrentIndexRight()focusCurrentItem(event, Qt.TabFocusReason)} breakcase Qt.Key_Down: if (!atBottom) {moveCurrentIndexDown()focusCurrentItem(event, Qt.TabFocusReason)} breakcase Qt.Key_J: if (!atBottom && event.modifiers & Qt.ControlModifier) {moveCurrentIndexDown()focusCurrentItem(event, Qt.TabFocusReason)} breakcase Qt.Key_Home: if (event.modifiers === Qt.ControlModifier && currentIndex !== 0) {currentIndex = 0focusCurrentItem(event, Qt.BacktabFocusReason)} else if (!atLeft) {targetIndex -= currentIndex % columnscurrentIndex = Math.max(targetIndex, 0)focusCurrentItem(event, Qt.BacktabFocusReason)} breakcase Qt.Key_End: if (event.modifiers === Qt.ControlModifier && currentIndex !== count - 1) {currentIndex = count - 1focusCurrentItem(event, Qt.TabFocusReason)} else if (!atRight) {targetIndex += columns - 1 - (currentIndex % columns)currentIndex = Math.min(targetIndex, count - 1)focusCurrentItem(event, Qt.TabFocusReason)} breakcase Qt.Key_PageUp: if (!atTop) {targetY = targetY - height + 1targetIndex = indexAt(targetX, targetY)// TODO: Find a more efficient, but accurate way to do thiswhile (targetIndex === -1) {targetY += 1targetIndex = indexAt(targetX, targetY)}currentIndex = Math.max(targetIndex, 0)focusCurrentItem(event, Qt.BacktabFocusReason)} breakcase Qt.Key_PageDown: if (!atBottom) {targetY = targetY + height - 1targetIndex = indexAt(targetX, targetY)// TODO: Find a more efficient, but accurate way to do thiswhile (targetIndex === -1) {targetY -= 1targetIndex = indexAt(targetX, targetY)}currentIndex = Math.min(targetIndex, count - 1)focusCurrentItem(event, Qt.TabFocusReason)} break}}movedWithKeyboard = event.acceptedif (movedWithKeyboard) {movedWithKeyboardTimer.restart()}}}}