Warning, /plasma/plasma-desktop/applets/kicker/package/contents/ui/DashboardRepresentation.qml is written in an unsupported language. File is not indexed.

0001 /*
0002     SPDX-FileCopyrightText: 2015 Eike Hein <hein@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 import QtQuick 2.15
0008 import Qt5Compat.GraphicalEffects
0009 // Deliberately imported after QtQuick to avoid missing restoreMode property in Binding. Fix in Qt 6.
0010 import QtQml 2.15
0011 
0012 import org.kde.kquickcontrolsaddons 2.0
0013 import org.kde.kwindowsystem 1.0
0014 import org.kde.plasma.components as PlasmaComponents
0015 import org.kde.plasma.extras 2.0 as PlasmaExtras
0016 import org.kde.plasma.core as PlasmaCore
0017 import org.kde.ksvg 1.0 as KSvg
0018 import org.kde.plasma.private.shell 2.0
0019 import org.kde.kirigami 2.20 as Kirigami
0020 
0021 import org.kde.plasma.plasmoid 2.0
0022 import org.kde.plasma.private.kicker 0.1 as Kicker
0023 
0024 import "code/tools.js" as Tools
0025 
0026 /* TODO
0027  * Reverse middleRow layout + keyboard nav + filter list text alignment in rtl locales.
0028  * Keep cursor column when arrow'ing down past non-full trailing rows into a lower grid.
0029  * Make DND transitions cleaner by performing an item swap instead of index reinsertion.
0030 */
0031 
0032 Kicker.DashboardWindow {
0033     id: root
0034 
0035     property bool smallScreen: ((Math.floor(width / Kirigami.Units.iconSizes.huge) <= 22) || (Math.floor(height / Kirigami.Units.iconSizes.huge) <= 14))
0036 
0037     property int iconSize: smallScreen ? Kirigami.Units.iconSizes.large : Kirigami.Units.iconSizes.huge
0038     property int cellSize: iconSize + (2 * Kirigami.Units.iconSizes.sizeForLabels)
0039         + (2 * Kirigami.Units.smallSpacing)
0040         + (2 * Math.max(highlightItemSvg.margins.top + highlightItemSvg.margins.bottom,
0041                         highlightItemSvg.margins.left + highlightItemSvg.margins.right))
0042     property int columns: Math.floor(((smallScreen ? 85 : 80)/100) * Math.ceil(width / cellSize))
0043     property bool searching: searchField.text !== ""
0044 
0045     keyEventProxy: searchField
0046     backgroundColor: Qt.rgba(0, 0, 0, 0.737)
0047 
0048     onKeyEscapePressed: {
0049         if (searching) {
0050             searchField.clear();
0051         } else {
0052             root.toggle();
0053         }
0054     }
0055 
0056     onVisibleChanged: {
0057         reset();
0058 
0059         if (visible) {
0060             preloadAllAppsTimer.restart();
0061         }
0062     }
0063 
0064     onSearchingChanged: {
0065         if (!searching) {
0066             reset();
0067         } else {
0068             filterList.currentIndex = -1;
0069         }
0070     }
0071 
0072     function reset() {
0073         searchField.clear();
0074         globalFavoritesGrid.currentIndex = -1;
0075         systemFavoritesGrid.currentIndex = -1;
0076         filterList.currentIndex = 0;
0077         funnelModel.sourceModel = rootModel.modelForRow(0);
0078         mainGrid.model = funnelModel;
0079         mainGrid.currentIndex = -1;
0080         filterListScrollArea.focus = true;
0081         filterList.model = rootModel;
0082     }
0083 
0084     mainItem: MouseArea {
0085         id: rootItem
0086 
0087         anchors.fill: parent
0088 
0089         acceptedButtons: Qt.LeftButton | Qt.RightButton
0090 
0091         LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
0092         LayoutMirroring.childrenInherit: true
0093 
0094         Connections {
0095             target: kicker
0096 
0097             function onReset() {
0098                 if (!root.searching) {
0099                     filterList.applyFilter();
0100                     funnelModel.reset();
0101                 }
0102             }
0103 
0104             function onDragSourceChanged() {
0105                 if (!kicker.dragSource) {
0106                     // FIXME TODO HACK: Reset all views post-DND to work around
0107                     // mouse grab bug despite QQuickWindow::mouseGrabberItem==0x0.
0108                     // Needs a more involved hunt through Qt Quick sources later since
0109                     // it's not happening with near-identical code in the menu repr.
0110                     rootModel.refresh();
0111                 }
0112             }
0113         }
0114 
0115         Connections {
0116             target: Plasmoid
0117             function onUserConfiguringChanged() {
0118                 if (Plasmoid.userConfiguring) {
0119                     root.hide()
0120                 }
0121             }
0122         }
0123 
0124         PlasmaExtras.Menu {
0125             id: contextMenu
0126 
0127             PlasmaExtras.MenuItem {
0128                 action: Plasmoid.internalAction("configure")
0129             }
0130         }
0131 
0132         Kirigami.Heading {
0133             id: dummyHeading
0134 
0135             visible: false
0136 
0137             width: 0
0138 
0139             level: 1
0140             textFormat: Text.PlainText
0141         }
0142 
0143         TextMetrics {
0144             id: headingMetrics
0145 
0146             font: dummyHeading.font
0147         }
0148 
0149         Kicker.FunnelModel {
0150             id: funnelModel
0151 
0152             onSourceModelChanged: {
0153                 if (mainColumn.visible) {
0154                     mainGrid.currentIndex = -1;
0155                     mainGrid.forceLayout();
0156                 }
0157             }
0158         }
0159 
0160         Timer {
0161             id: preloadAllAppsTimer
0162 
0163             property bool done: false
0164 
0165             interval: 1000
0166             repeat: false
0167 
0168             onTriggered: {
0169                 if (done || root.searching) {
0170                     return;
0171                 }
0172 
0173                 for (var i = 0; i < rootModel.count; ++i) {
0174                     var model = rootModel.modelForRow(i);
0175 
0176                     if (model.description === "KICKER_ALL_MODEL") {
0177                         allAppsGrid.model = model;
0178                         done = true;
0179                         break;
0180                     }
0181                 }
0182             }
0183 
0184             function defer() {
0185                 if (running && !done) {
0186                     restart();
0187                 }
0188             }
0189         }
0190 
0191         Kicker.ContainmentInterface {
0192             id: containmentInterface
0193         }
0194 
0195         TextEdit {
0196             id: searchField
0197 
0198             width: 0
0199             height: 0
0200 
0201             visible: false
0202 
0203             persistentSelection: true
0204 
0205             onTextChanged: {
0206                 runnerModel.query = searchField.text;
0207             }
0208 
0209             function clear() {
0210                 text = "";
0211             }
0212 
0213             onSelectionStartChanged: Qt.callLater(searchHeading.updateSelection)
0214             onSelectionEndChanged: Qt.callLater(searchHeading.updateSelection)
0215         }
0216 
0217         TextEdit {
0218             id: searchHeading
0219 
0220             anchors {
0221                 horizontalCenter: parent.horizontalCenter
0222             }
0223 
0224             y: (middleRow.anchors.topMargin / 2) - (root.smallScreen ? (height/10) : 0)
0225 
0226             font.pointSize: dummyHeading.font.pointSize * 1.5
0227             wrapMode: Text.NoWrap
0228             opacity: 1.0
0229 
0230             selectByMouse: false
0231             cursorVisible: false
0232 
0233             color: "white"
0234 
0235             text: root.searching ? i18n("Searching for '%1'", searchField.text) : i18nc("@info:placeholder as in, 'start typing to initiate a search'", "Type to search…")
0236 
0237             function updateSelection() {
0238                 if (!searchField.selectedText) {
0239                     return;
0240                 }
0241 
0242                 var delta = text.lastIndexOf(searchField.text, text.length - 2);
0243                 searchHeading.select(searchField.selectionStart + delta, searchField.selectionEnd + delta);
0244             }
0245         }
0246 
0247         PlasmaComponents.ToolButton {
0248             id: cancelSearchButton
0249 
0250             anchors {
0251                 left: searchHeading.right
0252                 leftMargin: Kirigami.Units.gridUnit
0253                 verticalCenter: searchHeading.verticalCenter
0254             }
0255 
0256             width: Kirigami.Units.iconSizes.large
0257             height: width
0258 
0259             visible: (searchField.text !== "")
0260 
0261             icon.name: "edit-clear"
0262             flat: false
0263 
0264             onClicked: searchField.clear();
0265 
0266             Keys.onPressed: event => {
0267                 if (event.key === Qt.Key_Tab) {
0268                     event.accepted = true;
0269 
0270                     if (runnerModel.count) {
0271                         mainColumn.tryActivate(0, 0);
0272                     } else {
0273                         systemFavoritesGrid.tryActivate(0, 0);
0274                     }
0275                 } else if (event.key === Qt.Key_Backtab) {
0276                     event.accepted = true;
0277 
0278                     if (globalFavoritesGrid.enabled) {
0279                         globalFavoritesGrid.tryActivate(0, 0);
0280                     } else {
0281                         systemFavoritesGrid.tryActivate(0, 0);
0282                     }
0283                 }
0284             }
0285         }
0286 
0287         Row {
0288             id: middleRow
0289 
0290             anchors {
0291                 top: parent.top
0292                 topMargin: Kirigami.Units.gridUnit * (smallScreen ? 8 : 10)
0293                 bottom: parent.bottom
0294                 bottomMargin: (Kirigami.Units.gridUnit * 2)
0295                 horizontalCenter: parent.horizontalCenter
0296             }
0297 
0298             width: (root.columns * root.cellSize) + (2 * spacing)
0299 
0300             spacing: Kirigami.Units.gridUnit * 2
0301 
0302             Item {
0303                 id: favoritesColumn
0304 
0305                 anchors {
0306                     top: parent.top
0307                     bottom: parent.bottom
0308                 }
0309 
0310                 width: (columns * root.cellSize) + Kirigami.Units.gridUnit
0311 
0312                 property int columns: 3
0313 
0314                 Kirigami.Heading {
0315                     id: favoritesColumnLabel
0316 
0317                     anchors {
0318                         top: parent.top
0319                     }
0320 
0321                     x: Kirigami.Units.smallSpacing
0322                     width: parent.width - x
0323 
0324                     elide: Text.ElideRight
0325                     wrapMode: Text.NoWrap
0326 
0327                     color: "white"
0328 
0329                     level: 1
0330 
0331                     text: i18n("Favorites")
0332                     textFormat: Text.PlainText
0333 
0334                     opacity: enabled ? 1.0 : 0.3
0335 
0336                     Behavior on opacity { SmoothedAnimation { duration: Kirigami.Units.longDuration; velocity: 0.01 } }
0337                 }
0338 
0339                 KSvg.SvgItem {
0340                     id: favoritesColumnLabelUnderline
0341 
0342                     anchors {
0343                         top: favoritesColumnLabel.bottom
0344                     }
0345 
0346                     width: parent.width - Kirigami.Units.gridUnit
0347                     height: lineSvg.horLineHeight
0348 
0349                     svg: lineSvg
0350                     elementId: "horizontal-line"
0351 
0352                     opacity: enabled ? 1.0 : 0.3
0353 
0354                     Behavior on opacity { SmoothedAnimation { duration: Kirigami.Units.longDuration; velocity: 0.01 } }
0355                 }
0356 
0357                 ItemGridView {
0358                     id: globalFavoritesGrid
0359 
0360                     anchors {
0361                         top: favoritesColumnLabelUnderline.bottom
0362                         topMargin: Kirigami.Units.gridUnit
0363                     }
0364 
0365                     Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
0366                     Kirigami.Theme.inherit: false
0367 
0368                     property int rows: (Math.floor((parent.height - favoritesColumnLabel.height
0369                         - favoritesColumnLabelUnderline.height - Kirigami.Units.gridUnit) / root.cellSize)
0370                         - systemFavoritesGrid.rows)
0371 
0372                     width: parent.width
0373                     height: rows * root.cellSize
0374 
0375                     cellWidth: root.cellSize
0376                     cellHeight: root.cellSize
0377                     iconSize: root.iconSize
0378 
0379                     model: globalFavorites
0380 
0381                     dropEnabled: true
0382 
0383                     opacity: enabled ? 1.0 : 0.3
0384 
0385                     Behavior on opacity { SmoothedAnimation { duration: Kirigami.Units.longDuration; velocity: 0.01 } }
0386 
0387                     onCurrentIndexChanged: {
0388                         preloadAllAppsTimer.defer();
0389                     }
0390 
0391                     onKeyNavRight: {
0392                         mainColumn.tryActivate(currentRow(), 0);
0393                     }
0394 
0395                     onKeyNavDown: {
0396                         systemFavoritesGrid.tryActivate(0, currentCol());
0397                     }
0398 
0399                     Keys.onPressed: event => {
0400                         if (event.key === Qt.Key_Tab) {
0401                             event.accepted = true;
0402 
0403                             if (root.searching) {
0404                                 cancelSearchButton.focus = true;
0405                             } else {
0406                                 mainColumn.tryActivate(0, 0);
0407                             }
0408                         } else if (event.key === Qt.Key_Backtab) {
0409                             event.accepted = true;
0410                             systemFavoritesGrid.tryActivate(0, 0);
0411                         }
0412                     }
0413 
0414                     Binding {
0415                         target: globalFavorites
0416                         property: "iconSize"
0417                         value: root.iconSize
0418                         restoreMode: Binding.RestoreBinding
0419                     }
0420                 }
0421 
0422                 ItemGridView {
0423                     id: systemFavoritesGrid
0424                     Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
0425                     Kirigami.Theme.inherit: false
0426                     anchors {
0427                         top: globalFavoritesGrid.bottom
0428                     }
0429 
0430                     property int rows: Math.ceil(count / Math.floor(width / root.cellSize))
0431 
0432                     width: parent.width
0433                     height: rows * root.cellSize
0434 
0435                     cellWidth: root.cellSize
0436                     cellHeight: root.cellSize
0437                     iconSize: root.iconSize
0438 
0439                     model: systemFavorites
0440 
0441                     dropEnabled: true
0442 
0443                     onCurrentIndexChanged: {
0444                         preloadAllAppsTimer.defer();
0445                     }
0446 
0447                     onKeyNavRight: {
0448                         mainColumn.tryActivate(globalFavoritesGrid.rows + currentRow(), 0);
0449                     }
0450 
0451                     onKeyNavUp: {
0452                         globalFavoritesGrid.tryActivate(globalFavoritesGrid.rows - 1, currentCol());
0453                     }
0454 
0455                     Keys.onPressed: event => {
0456                         if (event.key === Qt.Key_Tab) {
0457                             event.accepted = true;
0458 
0459                             if (globalFavoritesGrid.enabled) {
0460                                 globalFavoritesGrid.tryActivate(0, 0);
0461                             } else if (root.searching && !runnerModel.count) {
0462                                 cancelSearchButton.focus = true;
0463                             } else {
0464                                 mainColumn.tryActivate(0, 0);
0465                             }
0466                         } else if (event.key === Qt.Key_Backtab) {
0467                             event.accepted = true;
0468 
0469                             if (filterList.enabled) {
0470                                 filterList.forceActiveFocus();
0471                             } else if (root.searching && !runnerModel.count) {
0472                                 cancelSearchButton.focus = true;
0473                             } else {
0474                                 mainColumn.tryActivate(0, 0);
0475                             }
0476                         }
0477                     }
0478                 }
0479             }
0480 
0481             Item {
0482                 id: mainColumn
0483 
0484                 anchors.top: parent.top
0485 
0486                 width: (columns * root.cellSize) + Kirigami.Units.gridUnit
0487                 height: Math.floor(parent.height / root.cellSize) * root.cellSize + mainGridContainer.headerHeight
0488 
0489                 Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
0490                     Kirigami.Theme.inherit: false
0491 
0492                 property int columns: root.columns - favoritesColumn.columns - filterListColumn.columns
0493                 property Item visibleGrid: mainGrid
0494 
0495                 function tryActivate(row, col) {
0496                     if (visibleGrid) {
0497                         visibleGrid.tryActivate(row, col);
0498                     }
0499                 }
0500 
0501                 Item {
0502                     id: mainGridContainer
0503 
0504                     anchors.fill: parent
0505                     z: (opacity === 1.0) ? 1 : 0
0506 
0507                     visible: opacity !== 0.0
0508 
0509                     property int headerHeight: mainColumnLabel.height + mainColumnLabelUnderline.height + Kirigami.Units.gridUnit
0510 
0511                     opacity: {
0512                         if (root.searching) {
0513                             return 0.0;
0514                         }
0515 
0516                         if (filterList.allApps) {
0517                             return 0.0;
0518                         }
0519 
0520                         return 1.0;
0521                     }
0522 
0523                     onOpacityChanged: {
0524                         if (opacity === 1.0) {
0525                             mainColumn.visibleGrid = mainGrid;
0526                         }
0527                     }
0528 
0529                     Kirigami.Heading {
0530                         id: mainColumnLabel
0531 
0532                         anchors {
0533                             top: parent.top
0534                         }
0535 
0536                         x: Kirigami.Units.smallSpacing
0537                         width: parent.width - x
0538 
0539                         elide: Text.ElideRight
0540                         wrapMode: Text.NoWrap
0541                         opacity: 1.0
0542 
0543                         color: "white"
0544 
0545                         level: 1
0546 
0547                         text: funnelModel.description
0548                         textFormat: Text.PlainText
0549                     }
0550 
0551                     KSvg.SvgItem {
0552                         id: mainColumnLabelUnderline
0553 
0554                         visible: mainGrid.count
0555 
0556                         anchors {
0557                             top: mainColumnLabel.bottom
0558                         }
0559 
0560                         width: parent.width - Kirigami.Units.gridUnit
0561                         height: lineSvg.horLineHeight
0562 
0563                         svg: lineSvg
0564                         elementId: "horizontal-line"
0565                     }
0566 
0567                     ItemGridView {
0568                         id: mainGrid
0569 
0570                         anchors {
0571                             top: mainColumnLabelUnderline.bottom
0572                             topMargin: Kirigami.Units.gridUnit
0573                         }
0574 
0575                         width: parent.width
0576                         height: systemFavoritesGrid.y + systemFavoritesGrid.height - mainGridContainer.headerHeight
0577 
0578                         cellWidth: root.cellSize
0579                         cellHeight: cellWidth
0580                         iconSize: root.iconSize
0581 
0582                         model: funnelModel
0583 
0584                         onCurrentIndexChanged: {
0585                             preloadAllAppsTimer.defer();
0586                         }
0587 
0588                         onKeyNavLeft: {
0589                             var row = currentRow();
0590                             var target = row + 1 > globalFavoritesGrid.rows ? systemFavoritesGrid : globalFavoritesGrid;
0591                             var targetRow = row + 1 > globalFavoritesGrid.rows ? row - globalFavoritesGrid.rows : row;
0592                             target.tryActivate(targetRow, favoritesColumn.columns - 1);
0593                         }
0594 
0595                         onKeyNavRight: {
0596                             filterListScrollArea.focus = true;
0597                         }
0598                     }
0599                 }
0600 
0601                 ItemMultiGridView {
0602                     id: allAppsGrid
0603 
0604                     anchors {
0605                         top: parent.top
0606                     }
0607 
0608                     z: (opacity === 1.0) ? 1 : 0
0609                     width: parent.width
0610                     height: systemFavoritesGrid.y + systemFavoritesGrid.height
0611 
0612                     visible: opacity !== 0.0
0613 
0614                     opacity: filterList.allApps ? 1.0 : 0.0
0615 
0616                     onOpacityChanged: {
0617                         if (opacity === 1.0) {
0618                             allAppsGrid.flickableItem.contentY = 0;
0619                             mainColumn.visibleGrid = allAppsGrid;
0620                         }
0621                     }
0622 
0623                     onKeyNavLeft: {
0624                         var row = 0;
0625 
0626                         for (var i = 0; i < subGridIndex; i++) {
0627                             row += subGridAt(i).lastRow() + 2; // Header counts as one.
0628                         }
0629 
0630                         row += subGridAt(subGridIndex).currentRow();
0631 
0632                         var target = row + 1 > globalFavoritesGrid.rows ? systemFavoritesGrid : globalFavoritesGrid;
0633                         var targetRow = row + 1 > globalFavoritesGrid.rows ? row - globalFavoritesGrid.rows : row;
0634                         target.tryActivate(targetRow, favoritesColumn.columns - 1);
0635                     }
0636 
0637                     onKeyNavRight: {
0638                         filterListScrollArea.focus = true;
0639                     }
0640                 }
0641 
0642                 ItemMultiGridView {
0643                     id: runnerGrid
0644 
0645                     anchors {
0646                         top: parent.top
0647                     }
0648 
0649                     z: (opacity === 1.0) ? 1 : 0
0650                     width: parent.width
0651                     height: Math.min(implicitHeight, systemFavoritesGrid.y + systemFavoritesGrid.height)
0652 
0653                     visible: opacity !== 0.0
0654 
0655                     model: runnerModel
0656 
0657                     grabFocus: true
0658 
0659                     opacity: root.searching ? 1.0 : 0.0
0660 
0661                     onOpacityChanged: {
0662                         if (opacity === 1.0) {
0663                             mainColumn.visibleGrid = runnerGrid;
0664                         }
0665                     }
0666 
0667                     onKeyNavLeft: {
0668                         var row = 0;
0669 
0670                         for (var i = 0; i < subGridIndex; i++) {
0671                             row += subGridAt(i).lastRow() + 2; // Header counts as one.
0672                         }
0673 
0674                         row += subGridAt(subGridIndex).currentRow();
0675 
0676                         var target = row + 1 > globalFavoritesGrid.rows ? systemFavoritesGrid : globalFavoritesGrid;
0677                         var targetRow = row + 1 > globalFavoritesGrid.rows ? row - globalFavoritesGrid.rows : row;
0678                         target.tryActivate(targetRow, favoritesColumn.columns - 1);
0679                     }
0680                 }
0681 
0682                 Keys.onPressed: event => {
0683                     if (event.key === Qt.Key_Tab) {
0684                         event.accepted = true;
0685 
0686                         if (filterList.enabled) {
0687                             filterList.forceActiveFocus();
0688                         } else {
0689                             systemFavoritesGrid.tryActivate(0, 0);
0690                         }
0691                     } else if (event.key === Qt.Key_Backtab) {
0692                         event.accepted = true;
0693 
0694                         if (root.searching) {
0695                             cancelSearchButton.focus = true;
0696                         } else if (globalFavoritesGrid.enabled) {
0697                             globalFavoritesGrid.tryActivate(0, 0);
0698                         } else {
0699                             systemFavoritesGrid.tryActivate(0, 0);
0700                         }
0701                     }
0702                 }
0703             }
0704 
0705             Item {
0706                 id: filterListColumn
0707 
0708                 anchors {
0709                     top: parent.top
0710                     topMargin: mainColumnLabelUnderline.y + mainColumnLabelUnderline.height + Kirigami.Units.gridUnit
0711                     bottom: parent.bottom
0712                 }
0713 
0714                 width: columns * root.cellSize
0715 
0716                 property int columns: 3
0717 
0718                 PlasmaComponents.ScrollView {
0719                     id: filterListScrollArea
0720 
0721                     x: root.visible ? 0 : Kirigami.Units.gridUnit
0722 
0723                     Behavior on x { SmoothedAnimation { duration: Kirigami.Units.longDuration; velocity: 0.01 } }
0724 
0725                     width: parent.width
0726                     height: mainGrid.height
0727 
0728                     enabled: !root.searching
0729 
0730                     property alias currentIndex: filterList.currentIndex
0731 
0732                     opacity: root.visible ? (root.searching ? 0.30 : 1.0) : 0.3
0733 
0734                     Behavior on opacity { SmoothedAnimation { duration: Kirigami.Units.longDuration; velocity: 0.01 } }
0735 
0736                     PlasmaComponents.ScrollBar.vertical.policy: (opacity === 1.0) ? PlasmaComponents.ScrollBar.AsNeeded : PlasmaComponents.ScrollBar.AlwaysOff
0737 
0738                     onEnabledChanged: {
0739                         if (!enabled) {
0740                             filterList.currentIndex = -1;
0741                         }
0742                     }
0743 
0744                     onCurrentIndexChanged: {
0745                         focus = (currentIndex !== -1);
0746                     }
0747 
0748                     ListView {
0749                         id: filterList
0750 
0751                         focus: true
0752 
0753                         property bool allApps: false
0754                         property int eligibleWidth: width
0755                         property int hItemMargins: Math.max(highlightItemSvg.margins.left + highlightItemSvg.margins.right,
0756                             listItemSvg.margins.left + listItemSvg.margins.right)
0757 
0758                         model: rootModel
0759 
0760                         boundsBehavior: Flickable.StopAtBounds
0761                         snapMode: ListView.SnapToItem
0762                         spacing: 0
0763                         keyNavigationWraps: true
0764 
0765                         delegate: MouseArea {
0766                             id: item
0767 
0768                             signal actionTriggered(string actionId, variant actionArgument)
0769                             signal aboutToShowActionMenu(variant actionMenu)
0770 
0771                             property var m: model
0772                             property int textWidth: label.contentWidth
0773                             property int mouseCol
0774                             property bool hasActionList: ((model.favoriteId !== null)
0775                                 || (("hasActionList" in model) && (model.hasActionList === true)))
0776                             property Item menu: actionMenu
0777 
0778                             width: ListView.view.width
0779                             height: Math.ceil((label.paintedHeight
0780                                 + Math.max(highlightItemSvg.margins.top + highlightItemSvg.margins.bottom,
0781                                 listItemSvg.margins.top + listItemSvg.margins.bottom)) / 2) * 2
0782 
0783                             Accessible.role: Accessible.MenuItem
0784                             Accessible.name: model.display
0785 
0786                             acceptedButtons: Qt.LeftButton | Qt.RightButton
0787 
0788                             hoverEnabled: true
0789 
0790                             onContainsMouseChanged: {
0791                                 if (!containsMouse) {
0792                                     updateCurrentItemTimer.stop();
0793                                 }
0794                             }
0795 
0796                             onPositionChanged: mouse => { // Lazy menu implementation.
0797                                 mouseCol = mouse.x;
0798 
0799                                 if (justOpenedTimer.running || ListView.view.currentIndex === 0 || index === ListView.view.currentIndex) {
0800                                     updateCurrentItem();
0801                                 } else if ((index === ListView.view.currentIndex - 1) && mouse.y < (height - 6)
0802                                     || (index === ListView.view.currentIndex + 1) && mouse.y > 5) {
0803 
0804                                     if (mouse.x > ListView.view.eligibleWidth - 5) {
0805                                         updateCurrentItem();
0806                                     }
0807                                 } else if (mouse.x > ListView.view.eligibleWidth) {
0808                                     updateCurrentItem();
0809                                 }
0810 
0811                                 updateCurrentItemTimer.restart();
0812                             }
0813 
0814                             onPressed: mouse => {
0815                                 if (mouse.buttons & Qt.RightButton) {
0816                                     if (hasActionList) {
0817                                         openActionMenu(item, mouse.x, mouse.y);
0818                                     }
0819                                 }
0820                             }
0821 
0822                             onClicked: mouse => {
0823                                 if (mouse.button === Qt.LeftButton) {
0824                                     updateCurrentItem();
0825                                 }
0826                             }
0827 
0828                             onAboutToShowActionMenu: {
0829                                 var actionList = hasActionList ? model.actionList : [];
0830                                 Tools.fillActionMenu(i18n, actionMenu, actionList, ListView.view.model.favoritesModel, model.favoriteId);
0831                             }
0832 
0833                             onActionTriggered: {
0834                                 if (Tools.triggerAction(ListView.view.model, model.index, actionId, actionArgument) === true) {
0835                                     kicker.expanded = false;
0836                                 }
0837                             }
0838 
0839                             function openActionMenu(visualParent, x, y) {
0840                                 aboutToShowActionMenu(actionMenu);
0841                                 actionMenu.visualParent = visualParent;
0842                                 actionMenu.open(x, y);
0843                             }
0844 
0845                             function updateCurrentItem() {
0846                                 ListView.view.currentIndex = index;
0847                                 ListView.view.eligibleWidth = Math.min(width, mouseCol);
0848                             }
0849 
0850                             ActionMenu {
0851                                 id: actionMenu
0852 
0853                                 onActionClicked: {
0854                                     actionTriggered(actionId, actionArgument);
0855                                 }
0856                             }
0857 
0858                             Timer {
0859                                 id: updateCurrentItemTimer
0860 
0861                                 interval: 50
0862                                 repeat: false
0863 
0864                                 onTriggered: parent.updateCurrentItem()
0865                             }
0866 
0867                             Kirigami.Heading {
0868                                 id: label
0869 
0870                                 anchors {
0871                                     fill: parent
0872                                     leftMargin: highlightItemSvg.margins.left
0873                                     rightMargin: highlightItemSvg.margins.right
0874                                 }
0875 
0876                                 elide: Text.ElideRight
0877                                 wrapMode: Text.NoWrap
0878                                 opacity: 1.0
0879 
0880                                 color: "white"
0881 
0882                                 level: 1
0883 
0884                                 text: model.display
0885                                 textFormat: Text.PlainText
0886                             }
0887                         }
0888 
0889                         highlight: PlasmaExtras.Highlight {
0890                             x: filterList.currentItem ? filterList.currentItem.x : 0
0891                             y: filterList.currentItem ? filterList.currentItem.y : 0
0892                             height: filterList.currentItem ? filterList.currentItem.height : 0
0893                             width: (highlightItemSvg.margins.left
0894                                 + (filterList.currentItem ? filterList.currentItem.textWidth : 0)
0895                                 + highlightItemSvg.margins.right
0896                                 + Kirigami.Units.smallSpacing)
0897 
0898                             visible: filterList.currentItem
0899                             active: filterListScrollArea.focus
0900                             pressed: filterList.currentItem && filterList.currentItem.pressed
0901                         }
0902 
0903                         highlightFollowsCurrentItem: false
0904                         highlightMoveDuration: 0
0905                         highlightResizeDuration: 0
0906 
0907                         onCurrentIndexChanged: applyFilter()
0908 
0909                         onCountChanged: {
0910                             var width = 0;
0911 
0912                             for (var i = 0; i < rootModel.count; ++i) {
0913                                 headingMetrics.text = rootModel.labelForRow(i);
0914 
0915                                 if (headingMetrics.width > width) {
0916                                     width = headingMetrics.width;
0917                                 }
0918                             }
0919 
0920                             filterListColumn.columns = Math.ceil(width / root.cellSize);
0921                             filterListScrollArea.width = width + hItemMargins + (Kirigami.Units.gridUnit * 2);
0922                         }
0923 
0924                         function applyFilter() {
0925                             if (!root.searching && currentIndex >= 0) {
0926                                 if (preloadAllAppsTimer.running) {
0927                                     preloadAllAppsTimer.stop();
0928                                 }
0929 
0930                                 var model = rootModel.modelForRow(currentIndex);
0931 
0932                                 if (model.description === "KICKER_ALL_MODEL") {
0933                                     allAppsGrid.model = model;
0934                                     allApps = true;
0935                                     funnelModel.sourceModel = null;
0936                                     preloadAllAppsTimer.done = true;
0937                                 } else {
0938                                     funnelModel.sourceModel = model;
0939                                     allApps = false;
0940                                 }
0941                             } else {
0942                                 funnelModel.sourceModel = null;
0943                                 allApps = false;
0944                             }
0945                         }
0946 
0947                         Keys.onPressed: event => {
0948                             if (event.key === Qt.Key_Left) {
0949                                 event.accepted = true;
0950 
0951                                 const currentRow = Math.max(0, Math.ceil(currentItem.y / mainGrid.cellHeight) - 1);
0952                                 mainColumn.tryActivate(currentRow, mainColumn.columns - 1);
0953                             } else if (event.key === Qt.Key_Tab) {
0954                                 event.accepted = true;
0955                                 systemFavoritesGrid.tryActivate(0, 0);
0956                             } else if (event.key === Qt.Key_Backtab) {
0957                                 event.accepted = true;
0958                                 mainColumn.tryActivate(0, 0);
0959                             }
0960                         }
0961                     }
0962                 }
0963             }
0964         }
0965 
0966         onPressed: mouse => {
0967             if (mouse.button === Qt.RightButton) {
0968                 contextMenu.open(mouse.x, mouse.y);
0969             }
0970         }
0971 
0972         onClicked: mouse => {
0973             if (mouse.button === Qt.LeftButton) {
0974                 root.toggle();
0975             }
0976         }
0977     }
0978 
0979     Component.onCompleted: {
0980         rootModel.refresh();
0981     }
0982 }