Warning, /plasma/plasma-desktop/applets/kickoff/package/contents/ui/KickoffListView.qml is written in an unsupported language. File is not indexed.
0001 /*
0002 SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org>
0003 SPDX-FileCopyrightText: 2012 Gregor Taetzner <gregor@freenet.de>
0004 SPDX-FileCopyrightText: 2015-2018 Eike Hein <hein@kde.org>
0005 SPDX-FileCopyrightText: 2021 Mikel Johnson <mikel5764@gmail.com>
0006 SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
0007
0008 SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 import QtQuick 2.15
0011 import QtQml 2.15
0012
0013 import org.kde.plasma.plasmoid 2.0
0014 import org.kde.plasma.components 3.0 as PC3
0015 import org.kde.plasma.extras as PlasmaExtras
0016
0017 import org.kde.ksvg 1.0 as KSvg
0018 import org.kde.kirigami 2.20 as Kirigami
0019
0020 // ScrollView makes it difficult to control implicit size using the contentItem.
0021 // Using EmptyPage instead.
0022 EmptyPage {
0023 id: root
0024 property alias model: view.model
0025 property alias count: view.count
0026 property alias currentIndex: view.currentIndex
0027 property alias currentItem: view.currentItem
0028 property alias delegate: view.delegate
0029 property alias section: view.section
0030 property alias highlight: view.highlight
0031 property alias view: view
0032
0033 property bool mainContentView: false
0034 property bool hasSectionView: false
0035
0036 /**
0037 * Request showing the section view
0038 */
0039 signal showSectionViewRequested(string sectionName)
0040
0041 clip: view.height < view.contentHeight
0042
0043 header: MouseArea {
0044 implicitHeight: KickoffSingleton.listItemMetrics.margins.top
0045 hoverEnabled: true
0046 onEntered: {
0047 if (containsMouse) {
0048 let targetIndex = view.indexAt(mouseX + view.contentX, view.contentY)
0049 if (targetIndex >= 0) {
0050 view.currentIndex = targetIndex
0051 view.forceActiveFocus(Qt.MouseFocusReason)
0052 }
0053 }
0054 }
0055 }
0056
0057 footer: MouseArea {
0058 implicitHeight: KickoffSingleton.listItemMetrics.margins.bottom
0059 hoverEnabled: true
0060 onEntered: {
0061 if (containsMouse) {
0062 let targetIndex = view.indexAt(mouseX + view.contentX, view.height + view.contentY - 1)
0063 if (targetIndex >= 0) {
0064 view.currentIndex = targetIndex
0065 view.forceActiveFocus(Qt.MouseFocusReason)
0066 }
0067 }
0068 }
0069 }
0070
0071 implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
0072 contentWidth, // exclude padding to avoid scrollbars automatically affecting implicitWidth
0073 implicitHeaderWidth2,
0074 implicitFooterWidth2)
0075
0076 leftPadding: verticalScrollBar.visible && root.mirrored ? verticalScrollBar.implicitWidth : 0
0077 rightPadding: verticalScrollBar.visible && !root.mirrored ? verticalScrollBar.implicitWidth : 0
0078
0079 contentItem: ListView {
0080 id: view
0081
0082 readonly property real availableWidth: width - leftMargin - rightMargin
0083 readonly property real availableHeight: height - topMargin - bottomMargin
0084 property bool movedWithKeyboard: false
0085 property bool movedWithWheel: false
0086
0087 Accessible.role: Accessible.List
0088
0089 implicitWidth: {
0090 let totalMargins = leftMargin + rightMargin
0091 if (mainContentView) {
0092 if (kickoff.mayHaveGridWithScrollBar) {
0093 totalMargins += verticalScrollBar.implicitWidth
0094 }
0095 return KickoffSingleton.gridCellSize * kickoff.minimumGridRowCount + totalMargins
0096 }
0097 return contentWidth + totalMargins
0098 }
0099 implicitHeight: {
0100 // use grid cells to determine size
0101 let h = KickoffSingleton.gridCellSize * kickoff.minimumGridRowCount
0102 // If no grids are used, use the number of items that would fit in the grid height
0103 if (Plasmoid.configuration.favoritesDisplay !== 0 && Plasmoid.configuration.applicationsDisplay !== 0) {
0104 h = Math.floor(h / kickoff.listDelegateHeight) * kickoff.listDelegateHeight
0105 }
0106 return h + topMargin + bottomMargin
0107 }
0108
0109 leftMargin: kickoff.backgroundMetrics.leftPadding
0110 rightMargin: kickoff.backgroundMetrics.rightPadding
0111
0112 currentIndex: count > 0 ? 0 : -1
0113 focus: true
0114 interactive: height < contentHeight
0115 pixelAligned: true
0116 reuseItems: true
0117 boundsBehavior: Flickable.StopAtBounds
0118 // default keyboard navigation doesn't allow focus reasons to be used
0119 // and eats up/down key events when at the beginning or end of the list.
0120 keyNavigationEnabled: false
0121 keyNavigationWraps: false
0122
0123 // This is actually needed. The highlight will animate from thin to wide otherwise.
0124 highlightResizeDuration: 0
0125 highlightMoveDuration: 0
0126 highlight: PlasmaExtras.Highlight {
0127 // The default Z value for delegates is 1. The default Z value for the section delegate is 2.
0128 // The highlight gets a value of 3 while the drag is active and then goes back to the default value of 0.
0129 z: root.currentItem && root.currentItem.Drag.active ?
0130 3 : 0
0131 pressed: view.currentItem && view.currentItem.isPressed && !view.currentItem.isCategoryListItem
0132 active: view.activeFocus
0133 || (kickoff.contentArea === root
0134 && kickoff.searchField.activeFocus)
0135 }
0136
0137 delegate: KickoffListDelegate {
0138 width: view.availableWidth
0139 }
0140
0141 section {
0142 property: "group"
0143 criteria: ViewSection.FullString
0144 delegate: PC3.AbstractButton {
0145 width: view.availableWidth
0146 height: KickoffSingleton.compactListDelegateHeight
0147
0148 PC3.Label {
0149 id: contentLabel
0150 anchors.left: parent.left
0151 width: section.length === 1
0152 ? KickoffSingleton.compactListDelegateContentHeight + leftPadding + rightPadding
0153 : parent.width
0154 height: parent.height
0155 leftPadding: view.effectiveLayoutDirection === Qt.LeftToRight
0156 ? KickoffSingleton.listItemMetrics.margins.left : 0
0157 rightPadding: view.effectiveLayoutDirection === Qt.RightToLeft
0158 ? KickoffSingleton.listItemMetrics.margins.right : 0
0159 horizontalAlignment: section.length === 1 ? Text.AlignHCenter : Text.AlignLeft
0160 verticalAlignment: Text.AlignVCenter
0161 maximumLineCount: 1
0162 elide: Text.ElideRight
0163 font.pixelSize: KickoffSingleton.compactListDelegateContentHeight
0164 enabled: hoverHandler.hovered
0165 text: section.length === 1 ? section.toUpperCase() : section
0166 textFormat: Text.PlainText
0167 }
0168
0169 HoverHandler {
0170 id: hoverHandler
0171 enabled: root.hasSectionView
0172 cursorShape: enabled ? Qt.PointingHandCursor : undefined
0173 }
0174
0175 onClicked: root.showSectionViewRequested(contentLabel.text)
0176 }
0177 }
0178
0179 move: normalTransition
0180 moveDisplaced: normalTransition
0181
0182 Transition {
0183 id: normalTransition
0184 NumberAnimation {
0185 duration: Kirigami.Units.shortDuration
0186 properties: "x, y"
0187 easing.type: Easing.OutCubic
0188 }
0189 }
0190
0191 PC3.ScrollBar.vertical: PC3.ScrollBar {
0192 id: verticalScrollBar
0193 parent: root
0194 z: 2
0195 height: root.height
0196 anchors.right: parent.right
0197 }
0198
0199 Kirigami.WheelHandler {
0200 target: view
0201 filterMouseEvents: true
0202 // `20 * Qt.styleHints.wheelScrollLines` is the default speed.
0203 horizontalStepSize: 20 * Qt.styleHints.wheelScrollLines
0204 verticalStepSize: 20 * Qt.styleHints.wheelScrollLines
0205
0206 onWheel: wheel => {
0207 view.movedWithWheel = true
0208 view.movedWithKeyboard = false
0209 movedWithWheelTimer.restart()
0210 }
0211 }
0212
0213 Connections {
0214 target: kickoff
0215 function onExpandedChanged() {
0216 if (kickoff.expanded) {
0217 view.currentIndex = 0
0218 view.positionViewAtBeginning()
0219 }
0220 }
0221 }
0222
0223 // Used to block hover events temporarily after using keyboard navigation.
0224 // If you have one hand on the touch pad or mouse and another hand on the keyboard,
0225 // it's easy to accidentally reset the highlight/focus position to the mouse position.
0226 Timer {
0227 id: movedWithKeyboardTimer
0228 interval: 200
0229 onTriggered: view.movedWithKeyboard = false
0230 }
0231
0232 Timer {
0233 id: movedWithWheelTimer
0234 interval: 200
0235 onTriggered: view.movedWithWheel = false
0236 }
0237
0238 function focusCurrentItem(event, focusReason) {
0239 currentItem.forceActiveFocus(focusReason)
0240 event.accepted = true
0241 }
0242
0243 Keys.onMenuPressed: event => {
0244 if (currentItem !== null) {
0245 currentItem.forceActiveFocus(Qt.ShortcutFocusReason)
0246 currentItem.openActionMenu()
0247 }
0248 }
0249 Keys.onPressed: event => {
0250 let targetX = currentItem ? currentItem.x : contentX
0251 let targetY = currentItem ? currentItem.y : contentY
0252 let targetIndex = currentIndex
0253 let atFirst = currentIndex === 0
0254 let atLast = currentIndex === count - 1
0255 if (count > 1) {
0256 switch (event.key) {
0257 case Qt.Key_Up: if (!atFirst) {
0258 decrementCurrentIndex()
0259
0260 if (currentItem.isSeparator) {
0261 decrementCurrentIndex()
0262 }
0263
0264 focusCurrentItem(event, Qt.BacktabFocusReason)
0265 } break
0266 case Qt.Key_K: if (!atFirst && event.modifiers & Qt.ControlModifier) {
0267 decrementCurrentIndex()
0268 focusCurrentItem(event, Qt.BacktabFocusReason)
0269 } break
0270 case Qt.Key_Down: if (!atLast) {
0271 incrementCurrentIndex()
0272
0273 if (currentItem.isSeparator) {
0274 incrementCurrentIndex()
0275 }
0276
0277 focusCurrentItem(event, Qt.TabFocusReason)
0278 } break
0279 case Qt.Key_J: if (!atLast && event.modifiers & Qt.ControlModifier) {
0280 incrementCurrentIndex()
0281 focusCurrentItem(event, Qt.TabFocusReason)
0282 } break
0283 case Qt.Key_Home: if (!atFirst) {
0284 currentIndex = 0
0285 focusCurrentItem(event, Qt.BacktabFocusReason)
0286 } break
0287 case Qt.Key_End: if (!atLast) {
0288 currentIndex = count - 1
0289 focusCurrentItem(event, Qt.TabFocusReason)
0290 } break
0291 case Qt.Key_PageUp: if (!atFirst) {
0292 targetY = targetY - height + 1
0293 targetIndex = indexAt(targetX, targetY)
0294 // TODO: Find a more efficient, but accurate way to do this
0295 while (targetIndex === -1) {
0296 targetY += 1
0297 targetIndex = indexAt(targetX, targetY)
0298 }
0299 currentIndex = Math.max(targetIndex, 0)
0300 focusCurrentItem(event, Qt.BacktabFocusReason)
0301 } break
0302 case Qt.Key_PageDown: if (!atLast) {
0303 targetY = targetY + height - 1
0304 targetIndex = indexAt(targetX, targetY)
0305 // TODO: Find a more efficient, but accurate way to do this
0306 while (targetIndex === -1) {
0307 targetY -= 1
0308 targetIndex = indexAt(targetX, targetY)
0309 }
0310 currentIndex = Math.min(targetIndex, count - 1)
0311 focusCurrentItem(event, Qt.TabFocusReason)
0312 } break
0313 }
0314 }
0315 movedWithKeyboard = event.accepted
0316 if (movedWithKeyboard) {
0317 movedWithKeyboardTimer.restart()
0318 }
0319 }
0320 }
0321 }