/*SPDX-FileCopyrightText: 2011 Martin GrΓ€Γlin <mgraesslin@kde.org>SPDX-FileCopyrightText: 2012 Gregor Taetzner <gregor@freenet.de>SPDX-FileCopyrightText: 2015-2018 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*/pragma ComponentBehavior: Boundimport QtQuickimport QtQuick.Templates as Timport org.kde.plasma.plasmoidimport org.kde.plasma.components as PC3import org.kde.plasma.extras as PlasmaExtrasimport org.kde.kirigami 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 section: view.sectionproperty alias highlight: view.highlightproperty alias view: viewproperty bool isSidebar: falseproperty bool mainContentView: falseproperty bool hasSectionView: false/*** Request showing the section view*/signal showSectionViewRequested(string sectionName)clip: view.height < view.contentHeightheader: MouseArea {implicitHeight: KickoffSingleton.listItemMetrics.margins.tophoverEnabled: root.mainContentView || Plasmoid.configuration.switchCategoryOnHoveronEntered: {if (containsMouse) {const targetIndex = view.indexAt(mouseX + view.contentX, view.contentY)if (targetIndex >= 0) {view.currentIndex = targetIndexview.forceActiveFocus(Qt.MouseFocusReason)}}}}footer: MouseArea {implicitHeight: KickoffSingleton.listItemMetrics.margins.bottomhoverEnabled: root.mainContentView || Plasmoid.configuration.switchCategoryOnHoveronEntered: {if (containsMouse) {const targetIndex = view.indexAt(mouseX + view.contentX, view.height + view.contentY - 1)if (targetIndex >= 0) {view.currentIndex = targetIndexview.forceActiveFocus(Qt.MouseFocusReason)}}}}implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,contentWidth, // exclude padding to avoid scrollbars automatically affecting implicitWidthimplicitHeaderWidth2,implicitFooterWidth2)leftPadding: verticalScrollBar.visible && root.mirrored ? verticalScrollBar.implicitWidth : 0rightPadding: verticalScrollBar.visible && !root.mirrored ? verticalScrollBar.implicitWidth : 0contentItem: ListView {id: viewreadonly property real availableWidth: width - leftMargin - rightMarginreadonly property real availableHeight: height - topMargin - bottomMarginproperty bool movedWithKeyboard: falseproperty bool movedWithWheel: falseAccessible.role: Accessible.ListimplicitWidth: {let totalMargins = leftMargin + rightMarginif (root.mainContentView) {if (kickoff.mayHaveGridWithScrollBar) {totalMargins += verticalScrollBar.implicitWidth}return KickoffSingleton.gridCellSize * kickoff.minimumGridRowCount + totalMargins}return contentWidth + totalMargins}implicitHeight: {// use grid cells to determine sizelet h = KickoffSingleton.gridCellSize * kickoff.minimumGridRowCount// If no grids are used, use the number of items that would fit in the grid heightif (Plasmoid.configuration.favoritesDisplay !== 0 && Plasmoid.configuration.applicationsDisplay !== 0) {h = Math.floor(h / kickoff.listDelegateHeight) * kickoff.listDelegateHeight}return h + topMargin + bottomMargin}leftMargin: kickoff.backgroundMetrics.leftPaddingrightMargin: kickoff.backgroundMetrics.rightPaddingcurrentIndex: count > 0 ? 0 : -1focus: trueinteractive: height < contentHeightpixelAligned: truereuseItems: false // explicitly disabled because it doesn't work correctly with switching models like we doboundsBehavior: 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: false// This is actually needed. The highlight will animate from thin to wide otherwise.highlightResizeDuration: 0highlightFollowsCurrentItem: falseonCountChanged: {if (!activeFocus) {currentIndex = (count > 0 ? 0 : -1)} else if (count > 0 && currentIndex !== -1) {positionViewAtIndex(currentIndex, ListView.Contain)}}delegate: KickoffListDelegate {width: view.availableWidth}// The selected category and the hovered category can be adjacent, and their highlights touch and// look ugly without some artificial spacing added. The compact style trades this off for density.spacing: root.isSidebar && !Plasmoid.configuration.compactMode ? Kirigami.Units.smallSpacing : 0section {property: "group"criteria: ViewSection.FullStringdelegate: PlasmaExtras.ListSectionHeader {required property string sectionwidth: view.availableWidthheight: KickoffSingleton.compactListDelegateHeighttext: section.length === 1 ? section.toUpperCase() : sectionHoverHandler {enabled: root.hasSectionViewcursorShape: enabled ? Qt.PointingHandCursor : undefined}onClicked: root.showSectionViewRequested(text)}}move: normalTransitionmoveDisplaced: normalTransitionTransition {id: normalTransitionNumberAnimation {duration: Kirigami.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.horizontalStepSize: 20 * Qt.styleHints.wheelScrollLinesverticalStepSize: 20 * Qt.styleHints.wheelScrollLinesonWheel: wheel => {view.movedWithWheel = trueview.movedWithKeyboard = falsemovedWithWheelTimer.restart()}}Connections {target: kickofffunction onExpandedChanged() {if (!kickoff.expanded) {view.currentIndex = 0view.positionViewAtBeginning()}}}Connections {target: kickoff.runnerModelenabled: launchMatchTimer.runningfunction onQueryFinished() : void {launchMatchTimer.stop()Qt.callLater(launchMatchTimer.triggered) // callLater to give bindings time to update}}// 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}Timer {id: movedWithWheelTimerinterval: 200onTriggered: view.movedWithWheel = false}Timer {id: launchMatchTimerinterval: 750onTriggered: {view.currentItem?.action.trigger();currentItem.forceActiveFocus(Qt.ShortcutFocusReason);}}onCurrentItemChanged: {if (launchMatchTimer.running && view.currentItem?.text.toLowerCase().includes(kickoff.runnerModel.query.toLowerCase())) {launchMatchTimer.stop()launchMatchTimer.triggered()}}function focusCurrentItem(event, focusReason) {currentItem.forceActiveFocus(focusReason)positionViewAtIndex(currentIndex, ListView.Contain)event.accepted = true}Keys.onMenuPressed: event => {const delegate = currentItem as AbstractKickoffItemDelegate;if (delegate !== null) {delegate.forceActiveFocus(Qt.ShortcutFocusReason)delegate.openActionMenu()}}Keys.onPressed: event => {const targetX = currentItem ? currentItem.x : contentXlet targetY = currentItem ? currentItem.y : contentYlet targetIndex = currentIndexconst atFirst = currentIndex === 0const atLast = currentIndex === count - 1if (count >= 1 || (kickoff.runnerModel.querying && [Qt.Key_Return, Qt.Key_Enter].includes(event.key))) {switch (event.key) {case Qt.Key_Up: if (!atFirst) {decrementCurrentIndex()if ((currentItem as AbstractKickoffItemDelegate)?.isSeparator) {decrementCurrentIndex()}focusCurrentItem(event, Qt.BacktabFocusReason)} breakcase Qt.Key_K: if (!atFirst && event.modifiers & Qt.ControlModifier) {decrementCurrentIndex()focusCurrentItem(event, Qt.BacktabFocusReason)} breakcase Qt.Key_Down: if (!atLast) {incrementCurrentIndex()if ((currentItem as AbstractKickoffItemDelegate)?.isSeparator) {incrementCurrentIndex()}focusCurrentItem(event, Qt.TabFocusReason)} breakcase Qt.Key_J: if (!atLast && event.modifiers & Qt.ControlModifier) {incrementCurrentIndex()focusCurrentItem(event, Qt.TabFocusReason)} breakcase Qt.Key_Home: if (!atFirst) {currentIndex = 0focusCurrentItem(event, Qt.BacktabFocusReason)} breakcase Qt.Key_End: if (!atLast) {currentIndex = count - 1focusCurrentItem(event, Qt.TabFocusReason)} breakcase Qt.Key_PageUp: if (!atFirst) {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 (!atLast) {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)} breakcase Qt.Key_Return:/* Fall through*/case Qt.Key_Enter:if (launchMatchTimer.running) {launchMatchTimer.stop()launchMatchTimer.triggered()} else if (!kickoff.runnerModel.querying || view.currentItem?.text.toLowerCase().includes(kickoff.runnerModel.query.toLowerCase())) {launchMatchTimer.triggered()} else {launchMatchTimer.start()}event.accepted = true;break;}}movedWithKeyboard = event.acceptedif (movedWithKeyboard) {movedWithKeyboardTimer.restart()}}}}