Warning, /plasma/plasma-desktop/applets/kickoff/package/contents/ui/FullRepresentation.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: 2012 Marco Martin <mart@kde.org>
0005     SPDX-FileCopyrightText: 2013 2014 David Edmundson <davidedmundson@kde.org>
0006     SPDX-FileCopyrightText: 2014 Sebastian Kügler <sebas@kde.org>
0007     SPDX-FileCopyrightText: 2021 Mikel Johnson <mikel5764@gmail.com>
0008     SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
0009 
0010     SPDX-License-Identifier: GPL-2.0-or-later
0011 */
0012 import QtQuick 2.15
0013 import QtQuick.Templates 2.15 as T
0014 import QtQuick.Layouts 1.15
0015 import QtQml 2.15
0016 import org.kde.plasma.plasmoid 2.0
0017 import org.kde.kirigami 2.20 as Kirigami
0018 import org.kde.plasma.extras 2.0 as PlasmaExtras
0019 
0020 EmptyPage {
0021     id: root
0022 
0023     // kickoff is Kickoff.qml
0024     leftPadding: -kickoff.backgroundMetrics.leftPadding
0025     rightPadding: -kickoff.backgroundMetrics.rightPadding
0026     topPadding: 0
0027     bottomPadding: -kickoff.backgroundMetrics.bottomPadding
0028     readonly property var appletInterface: kickoff
0029 
0030     Layout.minimumWidth: implicitWidth
0031     Layout.maximumWidth: Kirigami.Units.gridUnit * 80
0032     Layout.minimumHeight: implicitHeight
0033     Layout.maximumHeight: Kirigami.Units.gridUnit * 40
0034     Layout.preferredWidth: Math.max(implicitWidth, width)
0035     Layout.preferredHeight: Math.max(implicitHeight, height)
0036 
0037     property alias normalPage: normalPage
0038     property bool blockingHoverFocus: false
0039 
0040     /* NOTE: Important things to know about keyboard input handling:
0041      *
0042      * - Key events are passed up to parent items until the end is reached.
0043      * Be mindful of this when using `Keys.forwardTo`.
0044      *
0045      * - Keys defaults to BeforeItem while KeyNavigation defaults to AfterItem.
0046      *
0047      * - When Keys and KeyNavigation are using the same priority, it seems like
0048      * the one declared first in the QML file gets priority over the other.
0049      *
0050      * - Except for Keys.onPressed, all Keys.on*Pressed signals automatically
0051      * set `event.accepted = true`.
0052      *
0053      * - If you do `item.forceActiveFocus()` and `item` is a focus scope, the
0054      * children of `item` won't necessarily get focus. It seems like
0055      * `forceActiveFocus()` is better for forcing a specific thing to be focused
0056      * while KeyNavigation is better at passing focus down to children of the
0057      * thing you want to focus when dealing with focus scopes.
0058      *
0059      * - KeyNavigation uses BacktabFocusReason (TabFocusReason if mirrored) for left,
0060      * TabFocusReason (BacktabFocusReason if mirrored) for right,
0061      * BacktabFocusReason for up and TabFocusReason for down.
0062      *
0063      * - KeyNavigation does not seem to respect dynamic changes to focus chain
0064      * rules in the reverse direction, which can lead to confusing results.
0065      * It is therefore safer to use Keys for items whose position in the Tab
0066      * order must be changed on demand. (Tested with Qt 5.15.8 on X11.)
0067      */
0068 
0069     header: Header {
0070         id: header
0071         preferredNameAndIconWidth: normalPage.preferredSideBarWidth
0072         Binding {
0073             target: kickoff
0074             property: "header"
0075             value: header
0076             restoreMode: Binding.RestoreBinding
0077         }
0078     }
0079 
0080     contentItem: VerticalStackView {
0081         id: contentItemStackView
0082         focus: true
0083         movementTransitionsEnabled: true
0084         // Not using a component to prevent it from being destroyed
0085         initialItem: NormalPage {
0086             id: normalPage
0087             objectName: "normalPage"
0088         }
0089 
0090         Component {
0091             id: searchViewComponent
0092             KickoffListView {
0093                 id: searchView
0094                 objectName: "searchView"
0095                 mainContentView: true
0096                 // Forces the function be re-run every time runnerModel.count changes.
0097                 // This is absolutely necessary to make the search view work reliably.
0098                 model: kickoff.runnerModel.count ? kickoff.runnerModel.modelForRow(0) : null
0099                 delegate: KickoffListDelegate {
0100                     width: view.availableWidth
0101                     isSearchResult: true
0102                 }
0103                 activeFocusOnTab: true
0104                 property var interceptedPosition: null
0105                 Keys.onTabPressed: event => {
0106                     kickoff.firstHeaderItem.forceActiveFocus(Qt.TabFocusReason);
0107                 }
0108                 Keys.onBacktabPressed: event => {
0109                     kickoff.lastHeaderItem.forceActiveFocus(Qt.BacktabFocusReason);
0110                 }
0111                 T.StackView.onActivated: {
0112                     kickoff.sideBar = null
0113                     kickoff.contentArea = searchView
0114                 }
0115 
0116                 Connections {
0117                     target: blockHoverFocusHandler
0118                     enabled: blockHoverFocusHandler.enabled && !searchView.interceptedPosition
0119                     function onPointChanged() {
0120                         searchView.interceptedPosition = blockHoverFocusHandler.point.position
0121                     }
0122                 }
0123 
0124                 Connections {
0125                     target: blockHoverFocusHandler
0126                     enabled: blockHoverFocusHandler.enabled && searchView.interceptedPosition && root.blockingHoverFocus
0127                     function onPointChanged() {
0128                         if (blockHoverFocusHandler.point.position === searchView.interceptedPosition) {
0129                             return;
0130                         }
0131                         root.blockingHoverFocus = false
0132                     }
0133                 }
0134 
0135                 HoverHandler {
0136                     id: blockHoverFocusHandler
0137                     enabled: !contentItemStackView.busy && (!searchView.interceptedPosition || root.blockingHoverFocus)
0138                 }
0139 
0140                 Loader {
0141                     anchors.centerIn: searchView.view
0142                     width: searchView.view.width - (Kirigami.Units.gridUnit * 4)
0143 
0144                     active: searchView.view.count === 0
0145                     visible: active
0146                     asynchronous: true
0147 
0148                     sourceComponent: PlasmaExtras.PlaceholderMessage {
0149                         id: emptyHint
0150 
0151                         iconName: "edit-none"
0152                         opacity: 0
0153                         text: i18nc("@info:status", "No matches")
0154 
0155                         Connections {
0156                             target: kickoff.runnerModel
0157                             function onQueryFinished() {
0158                                 showAnimation.restart()
0159                             }
0160                         }
0161 
0162                         NumberAnimation {
0163                             id: showAnimation
0164                             duration: Kirigami.Units.longDuration
0165                             easing.type: Easing.OutCubic
0166                             property: "opacity"
0167                             target: emptyHint
0168                             to: 1
0169                         }
0170                     }
0171                 }
0172             }
0173         }
0174 
0175         Keys.priority: Keys.AfterItem
0176         // This is here rather than root because events are implicitly forwarded
0177         // to parent items. Don't want to send multiple events to searchField.
0178         Keys.forwardTo: kickoff.searchField
0179 
0180         Connections {
0181             target: root.header
0182             function onSearchTextChanged() {
0183                 if (root.header.searchText.length === 0 && contentItemStackView.currentItem.objectName !== "normalPage") {
0184                     root.blockingHoverFocus = false
0185                     contentItemStackView.reverseTransitions = true
0186                     contentItemStackView.replace(normalPage)
0187                 } else if (root.header.searchText.length > 0) {
0188                     if (contentItemStackView.currentItem.objectName !== "searchView") {
0189                         contentItemStackView.reverseTransitions = false
0190                         contentItemStackView.replace(searchViewComponent)
0191                     } else {
0192                         root.blockingHoverFocus = true
0193                         contentItemStackView.contentItem.interceptedPosition = null
0194                         contentItemStackView.contentItem.currentIndex = 0
0195                     }
0196                 }
0197             }
0198         }
0199     }
0200 
0201     Component.onCompleted: {
0202         rootModel.refresh();
0203     }
0204 }