/*SPDX-FileCopyrightText: 2015 Eike Hein <hein@kde.org>SPDX-License-Identifier: GPL-2.0-or-later*/pragma ComponentBehavior: Boundimport QtQuickimport org.kde.kirigami as Kirigamiimport org.kde.ksvg as KSvgimport org.kde.plasma.components as PlasmaComponentsimport org.kde.plasma.extras as PlasmaExtrasimport org.kde.plasma.plasmoidimport org.kde.plasma.private.kicker as Kicker/* TODO* Reverse middleRow layout + keyboard nav + filter list text alignment in rtl locales.* Keep cursor column when arrow'ing down past non-full trailing rows into a lower grid.* Make DND transitions cleaner by performing an item swap instead of index reinsertion.*/Kicker.DashboardWindow {id: rootrequired property Kicker.KAStatsFavoritesModel globalFavoritesrequired property Kicker.SimpleFavoritesModel systemFavoritesrequired property Kicker.RootModel rootModelrequired property Kicker.RunnerModel runnerModelreadonly property bool smallScreen: ((Math.floor(width / Kirigami.Units.iconSizes.huge) <= 22) || (Math.floor(height / Kirigami.Units.iconSizes.huge) <= 14))readonly property int iconSize: smallScreen ? Kirigami.Units.iconSizes.large : Kirigami.Units.iconSizes.hugereadonly property int cellSize: iconSize + (2 * Kirigami.Units.iconSizes.sizeForLabels)+ (2 * Kirigami.Units.smallSpacing)+ (2 * Math.max(highlightItemSvg.margins.top + highlightItemSvg.margins.bottom,highlightItemSvg.margins.left + highlightItemSvg.margins.right))readonly property int columns: Math.floor(((smallScreen ? 85 : 80)/100) * Math.ceil(width / cellSize))readonly property bool searching: searchField.text !== ""signal interactionConcluded()keyEventProxy: searchFieldbackgroundColor: "transparent"onKeyEscapePressed: {if (searching) {searchField.clear();} else {root.interactionConcluded()}}onActiveChanged: {if (!active && visible) {root.interactionConcluded()}}onVisibleChanged: {reset();if (visible) {preloadAllAppsTimer.restart();}}onSearchingChanged: {if (!searching) {reset();} else {filterList.currentIndex = -1;}}function reset() {searchField.clear();searchField.forceActiveFocus(Qt.OtherFocusReason)globalFavoritesGrid.currentIndex = -1;systemFavoritesGrid.currentIndex = -1;filterList.currentIndex = 0;filterList.applyFilter();mainGrid.currentIndex = -1;filterList.model = rootModel;hoverBlock.reset();}mainItem: MouseArea {id: rootItemanchors.fill: parentKirigami.Theme.colorSet: Plasmoid.configuration.forceDarkMode ? Kirigami.Theme.Complementary : Kirigami.Theme.WindowKirigami.Theme.inherit: falseacceptedButtons: Qt.LeftButton | Qt.RightButtonLayoutMirroring.enabled: Application.layoutDirection === Qt.RightToLeftLayoutMirroring.childrenInherit: true// We draw the background color here instead of in the// DashboardWindow as transparent background window// color do not play well with non-black backgrounds,// as the color spills in the blur effect even with// complete transparency.Rectangle {anchors.fill: parent// Intentionally hardcoded to black in dark mode because any other color// looks terrible here. Any bug reports about illegible text or icons// should be considered a color scheme error and sent back to the user// or their distro.color: Plasmoid.configuration.forceDarkMode ? "black" : Kirigami.Theme.backgroundColoropacity: 0.737}Connections {target: kickerfunction onReset() {if (!root.searching) {filterList.applyFilter();funnelModel.reset();}}}Connections {target: Plasmoidfunction onUserConfiguringChanged() {if (Plasmoid.userConfiguring) {root.hide()}}}PlasmaExtras.Menu {id: contextMenuPlasmaExtras.MenuItem {action: Plasmoid.internalAction("configure")}}Kirigami.Heading {id: dummyHeadingvisible: falsewidth: 0level: 1textFormat: Text.PlainText}TextMetrics {id: headingMetricsfont: dummyHeading.font}KSvg.FrameSvgItem {id: highlightItemSvgvisible: falseimagePath: "widgets/viewitem"prefix: "hover"}KSvg.FrameSvgItem {id: listItemSvgvisible: falseimagePath: "widgets/listitem"prefix: "normal"}Kicker.FunnelModel {id: funnelModelonSourceModelChanged: {if (mainColumn.visible) {mainGrid.currentIndex = -1;mainGrid.forceLayout();}}}DropArea {anchors.fill: parentkeys: ["favoritedrag"]onDropped: drop => {let draggedItem = drag.source as ItemGridDelegateif (draggedItem && draggedItem.favoritesModel.isFavorite(draggedItem.favoriteId)) {draggedItem.showUnfavoritePlaceholder = truedraggedItem.favoritesModel.removeFavorite(draggedItem.favoriteId)drop.accept(Qt.MoveAction)}}}Timer {id: preloadAllAppsTimerproperty bool done: falseinterval: 1000repeat: falseonTriggered: {if (done || root.searching) {return;}for (var i = 0; i < root.rootModel.count; ++i) {var model = root.rootModel.modelForRow(i);if (model.description === "KICKER_ALL_MODEL") {allAppsGrid.model = model;done = true;break;}}}function defer() {if (running && !done) {restart();}}}PlasmaExtras.SearchField {id: searchField// actions should ostly apply to the runner grid, but there are a few ways to// have focus on the search field and have something visually selected, so// fall back to the other possible grids if they're showingreadonly property ItemGridView targetGrid: runnerGrid.visible ? runnerGrid.firstGrid: allAppsGrid.visible ? allAppsGrid.firstGrid: mainGridanchors {horizontalCenter: parent.horizontalCenter}y: (middleRow.anchors.topMargin / 2) - (root.smallScreen ? (height/10) : 0)width: Kirigami.Units.gridUnit * 24font.pointSize: dummyHeading.font.pointSize * 1.5focus: trueonTextChanged: {root.runnerModel.query = searchField.textthis.forceActiveFocus()}onFocusChanged: {if (!focus && runnerGrid?.firstGrid) {runnerGrid.firstGrid.currentIndex = -1}}function clear() {text = "";}Keys.priority: Keys.AfterItemKeys.onTabPressed: {if (root.runnerModel.count) {focus = falsemainColumn.tryActivate(0, 0);} else if (mainGrid.visible && mainGrid.count) {mainGrid.tryActivate(0, 0);} else if (allAppsGrid.visible && allAppsGrid.count) {allAppsGrid.tryActivate(0, 0);} else {systemFavoritesGrid.tryActivate(0, 0);}}Keys.onBacktabPressed: {if (globalFavoritesGrid.enabled) {globalFavoritesGrid.tryActivate(0, 0);} else {systemFavoritesGrid.tryActivate(0, 0);}}Keys.onDownPressed: event => {if (mainGrid.visible) {mainGrid.tryActivate(0, 0);} else if (allAppsGrid.visible) {allAppsGrid.tryActivate(0, 0);} else if (runnerGrid.visible) {if (runnerGrid.firstGrid.currentIndex != -1) {runnerGrid.firstGrid.view.moveCurrentIndexDown()let currentRow = runnerGrid.firstGrid.currentRow()let currentCol = runnerGrid.firstGrid.currentCol()focus = falserunnerGrid.tryActivate(currentRow, currentCol)} else {focus = falserunnerGrid.tryActivate(0,0)}} else {event.accepted = false}}Keys.onRightPressed: event => {if (runnerGrid.visible && runnerGrid.firstGrid.currentIndex != -1 &&(Application.layoutDirection == Qt.LeftToRight || runnerGrid.firstGrid.currentIndex != 0)) {runnerGrid.firstGrid.view.moveCurrentIndexRight()let currentRow = runnerGrid.firstGrid.currentRow()let currentCol = runnerGrid.firstGrid.currentCol()focus = falserunnerGrid.tryActivate(currentRow, currentCol)} else {event.accepted = false}}Keys.onLeftPressed: event => {if (runnerGrid.visible && runnerGrid.firstGrid.currentIndex != -1 &&(Application.layoutDirection == Qt.RightToLeft || runnerGrid.firstGrid.currentIndex != 0)) {runnerGrid.firstGrid.view.moveCurrentIndexLeft()let currentRow = runnerGrid.firstGrid.currentRow()let currentCol = runnerGrid.firstGrid.currentCol()focus = falserunnerGrid.tryActivate(currentRow, currentCol)} else {event.accepted = false}}Keys.onReturnPressed: event => {if (launchMatchTimer.running) {launchMatchTimer.stop()launchMatchTimer.triggered()return}let currentDelegate = searchField.targetGrid?.currentItem as ItemAbstractDelegateif (!root.runnerModel.querying || currentDelegate?.text.toLowerCase().includes(root.runnerModel.query.toLowerCase())) {launchMatchTimer.triggered()return}launchMatchTimer.start()}Keys.onEnterPressed: event => Keys.returnPressed(event)Timer {id: launchMatchTimerinterval: 750onTriggered: {let currentDelegate = searchField.targetGrid?.currentItem as ItemAbstractDelegateif (currentDelegate) {currentDelegate.action.trigger()}}}Connections {target: searchField.targetGridenabled: launchMatchTimer.runningfunction onCurrentItemChanged() : void {const targetItem = searchField.targetGrid.currentItem as ItemGridDelegateif (targetItem?.text.toLowerCase().includes(root.runnerModel.query.toLowerCase())) {launchMatchTimer.stop()launchMatchTimer.triggered()}}}Connections {target: root.runnerModelenabled: launchMatchTimer.runningfunction onQueryFinished() : void {launchMatchTimer.stop()Qt.callLater(launchMatchTimer.triggered) // callLater to give bindings time to update}}}Row {id: middleRowanchors {top: parent.toptopMargin: Kirigami.Units.gridUnit * (root.smallScreen ? 8 : 10)bottom: parent.bottombottomMargin: (Kirigami.Units.gridUnit * 2)horizontalCenter: parent.horizontalCenter}width: (root.columns * root.cellSize) + (2 * spacing)spacing: Kirigami.Units.gridUnit * 2Item {id: favoritesColumnanchors {top: parent.topbottom: parent.bottom}width: (columns * root.cellSize) + Kirigami.Units.gridUnitproperty int columns: Plasmoid.configuration.favoritesColumnsKirigami.Heading {id: favoritesColumnLabelanchors {top: parent.top}x: Kirigami.Units.smallSpacingwidth: parent.width - xelide: Text.ElideRightwrapMode: Text.NoWrapcolor: Kirigami.Theme.textColorlevel: 1text: i18nc("@title:group for column with app and system action grids", "Favorites")textFormat: Text.PlainTextopacity: enabled ? 1.0 : 0.3Behavior on opacity { SmoothedAnimation { duration: Kirigami.Units.longDuration; velocity: 0.01 } }}KSvg.SvgItem {id: favoritesColumnLabelUnderlineanchors {top: favoritesColumnLabel.bottom}width: parent.width - Kirigami.Units.gridUnitimagePath: "widgets/line"elementId: "horizontal-line"opacity: enabled ? 1.0 : 0.3Behavior on opacity { SmoothedAnimation { duration: Kirigami.Units.longDuration; velocity: 0.01 } }}ItemGridView {id: globalFavoritesGridanchors {top: favoritesColumnLabelUnderline.bottomtopMargin: Kirigami.Units.gridUnit}readonly property int rows: (Math.floor((parent.height - favoritesColumnLabel.height- favoritesColumnLabelUnderline.height - Kirigami.Units.gridUnit) / root.cellSize)- systemFavoritesGrid.rows)width: parent.widthheight: rows * root.cellSizecellWidth: root.cellSizecellHeight: root.cellSizeiconSize: root.iconSizemodel: root.globalFavoriteshoverEnabled: !hoverBlock.enableddropEnabled: trueopacity: enabled ? 1.0 : 0.3Behavior on opacity { SmoothedAnimation { duration: Kirigami.Units.longDuration; velocity: 0.01 } }onInteractionConcluded: root.interactionConcluded()onCurrentIndexChanged: {preloadAllAppsTimer.defer();}onKeyNavRight: {mainColumn.tryActivate(currentRow(), 0);}onKeyNavDown: {systemFavoritesGrid.tryActivate(0, currentCol());}Keys.onTabPressed: {currentIndex = -1searchField.forceActiveFocus(Qt.TabFocusReason)}Keys.onBacktabPressed: systemFavoritesGrid.tryActivate(0, 0);Binding {target: root.globalFavoritesproperty: "iconSize"value: root.iconSizerestoreMode: Binding.RestoreBinding}}ItemGridView {id: systemFavoritesGridanchors {top: globalFavoritesGrid.bottom}readonly property int rows: Math.ceil(count / Math.floor(width / root.cellSize))width: parent.widthheight: rows * root.cellSizecellWidth: root.cellSizecellHeight: root.cellSizeiconSize: root.iconSizemodel: root.systemFavoriteshoverEnabled: !hoverBlock.enableddropEnabled: trueonInteractionConcluded: root.interactionConcluded()onCurrentIndexChanged: {preloadAllAppsTimer.defer();}onKeyNavRight: {mainColumn.tryActivate(globalFavoritesGrid.rows + currentRow(), 0);}onKeyNavUp: {globalFavoritesGrid.tryActivate(globalFavoritesGrid.rows - 1, currentCol());}Keys.onTabPressed: {if (globalFavoritesGrid.enabled) {globalFavoritesGrid.tryActivate(0, 0);} else {searchField.forceActiveFocus(Qt.TabFocusReason)}}Keys.onBacktabPressed: {if (filterList.enabled) {filterList.forceActiveFocus();} else {mainColumn.tryActivate(0, 0);}}}}Item {id: splitterwidth: Math.round(Kirigami.Units.gridUnit / 2)anchors.top: parent.topanchors.bottom: parent.bottomMouseArea {anchors.fill: parentanchors.leftMargin: -middleRow.spacinganchors.rightMargin: -middleRow.spacingcursorShape: Qt.SizeHorCursordrag.target: parentdrag.axis: Drag.XAxisproperty int startXproperty int startFavColsonPressed: {startX = splitter.xstartFavCols = favoritesColumn.columns}onPositionChanged: {let deltaCols = Math.round((splitter.x - startX) / root.cellSize)let newFavCols = startFavCols + deltaColsif (newFavCols >= 1 && newFavCols < root.columns - filterListColumn.columns) {favoritesColumn.columns = newFavColsPlasmoid.configuration.favoritesColumns = newFavCols}}}}Item {id: mainColumnanchors.top: parent.topwidth: (columns * root.cellSize) + Kirigami.Units.gridUnitheight: Math.floor(parent.height / root.cellSize) * root.cellSize + mainGridContainer.headerHeightreadonly property int columns: root.columns - favoritesColumn.columns - filterListColumn.columnsproperty Item visibleGrid: mainGridfunction tryActivate(row, col) {if (visibleGrid) {visibleGrid.tryActivate(row, col);}}Item {id: mainGridContaineranchors.fill: parentz: (opacity === 1.0) ? 1 : 0visible: opacity !== 0.0readonly property int headerHeight: mainColumnLabel.height + mainColumnLabelUnderline.height + Kirigami.Units.gridUnitopacity: {if (root.searching) {return 0.0;}if (filterList.allApps) {return 0.0;}return 1.0;}onOpacityChanged: {if (opacity === 1.0) {mainColumn.visibleGrid = mainGrid;}}Kirigami.Heading {id: mainColumnLabelanchors {top: parent.top}x: Kirigami.Units.smallSpacingwidth: parent.width - xelide: Text.ElideRightwrapMode: Text.NoWrapopacity: 1.0color: Kirigami.Theme.textColorlevel: 1text: funnelModel.descriptiontextFormat: Text.PlainText}KSvg.SvgItem {id: mainColumnLabelUnderlinevisible: mainGrid.countanchors {top: mainColumnLabel.bottom}width: parent.width - Kirigami.Units.gridUnitimagePath: "widgets/line"elementId: "horizontal-line"}ItemGridView {id: mainGridanchors {top: mainColumnLabelUnderline.bottomtopMargin: Kirigami.Units.gridUnit}width: parent.widthheight: systemFavoritesGrid.y + systemFavoritesGrid.height - mainGridContainer.headerHeightcellWidth: root.cellSizecellHeight: cellWidthiconSize: root.iconSizehoverEnabled: !hoverBlock.enabledmodel: funnelModelonInteractionConcluded: root.interactionConcluded()onCurrentIndexChanged: {preloadAllAppsTimer.defer();}onKeyNavLeft: {var row = currentRow();var target = row + 1 > globalFavoritesGrid.rows ? systemFavoritesGrid : globalFavoritesGrid;var targetRow = row + 1 > globalFavoritesGrid.rows ? row - globalFavoritesGrid.rows : row;target.tryActivate(targetRow, favoritesColumn.columns - 1);}onKeyNavRight: {filterListScrollArea.focus = true;}onKeyNavUp: {currentIndex = -1searchField.forceActiveFocus(Qt.TabFocusReason)}Keys.onBacktabPressed: event => {currentIndex = -1event.accepted = false // pass to mainColumn handler}}}ItemMultiGridView {id: allAppsGridanchors {top: parent.top}z: (opacity === 1.0) ? 1 : 0width: parent.widthheight: systemFavoritesGrid.y + systemFavoritesGrid.heightcellSize: root.cellSizeiconSize: root.iconSizehoverEnabled: !hoverBlock.enabledvisible: opacity !== 0.0opacity: filterList.allApps && !root.searching ? 1.0 : 0.0onInteractionConcluded: root.interactionConcluded()onOpacityChanged: {if (opacity === 1.0) {allAppsGrid.flickableItem.contentY = 0;mainColumn.visibleGrid = allAppsGrid;}}onKeyNavLeft: subGridIndex => {var row = 0;for (var i = 0; i < subGridIndex; i++) {row += subGridAt(i).lastRow() + 2; // Header counts as one.}row += subGridAt(subGridIndex).currentRow();var target = row + 1 > globalFavoritesGrid.rows ? systemFavoritesGrid : globalFavoritesGrid;var targetRow = row + 1 > globalFavoritesGrid.rows ? row - globalFavoritesGrid.rows : row;target.tryActivate(targetRow, favoritesColumn.columns - 1);}onKeyNavRight: subGridIndex => {filterListScrollArea.focus = true;}onKeyNavUp: {firstGrid.currentIndex = -1searchField.forceActiveFocus(Qt.TabFocusReason)}}ItemMultiGridView {id: runnerGridanchors {top: parent.top}z: (opacity === 1.0) ? 1 : 0width: parent.widthheight: Math.min(implicitHeight, systemFavoritesGrid.y + systemFavoritesGrid.height)cellSize: root.cellSizeiconSize: root.iconSizevisible: opacity !== 0.0model: root.runnerModelhoverEnabled: !hoverBlock.enabledopacity: root.searching ? 1.0 : 0.0onInteractionConcluded: root.interactionConcluded()onOpacityChanged: {if (opacity === 1.0) {mainColumn.visibleGrid = runnerGrid;}}onKeyNavLeft: subGridIndex => {var row = 0;for (var i = 0; i < subGridIndex; i++) {row += subGridAt(i).lastRow() + 2; // Header counts as one.}row += subGridAt(subGridIndex).currentRow();var target = row + 1 > globalFavoritesGrid.rows ? systemFavoritesGrid : globalFavoritesGrid;var targetRow = row + 1 > globalFavoritesGrid.rows ? row - globalFavoritesGrid.rows : row;target.tryActivate(targetRow, favoritesColumn.columns - 1);}onKeyNavUp: {firstGrid.currentIndex = -1searchField.forceActiveFocus(Qt.TabFocusReason)}}Keys.onTabPressed: {if (filterList.enabled) {filterList.forceActiveFocus();} else {systemFavoritesGrid.tryActivate(0, 0);}}Keys.onBacktabPressed: {searchField.forceActiveFocus(Qt.BacktabFocusReason)}}Item {id: filterListColumnanchors {top: parent.toptopMargin: mainColumnLabelUnderline.y + mainColumnLabelUnderline.height + Kirigami.Units.gridUnitbottom: parent.bottom}width: columns * root.cellSizeproperty int columns: 3PlasmaComponents.ScrollView {id: filterListScrollAreax: root.visible ? 0 : Kirigami.Units.gridUnitBehavior on x { SmoothedAnimation { duration: Kirigami.Units.longDuration; velocity: 0.01 } }width: parent.widthheight: mainGrid.heightenabled: !root.searchinghoverEnabled: !hoverBlock.enabledproperty alias currentIndex: filterList.currentIndexopacity: root.visible ? (root.searching ? 0.30 : 1.0) : 0.3Behavior on opacity { SmoothedAnimation { duration: Kirigami.Units.longDuration; velocity: 0.01 } }PlasmaComponents.ScrollBar.vertical.policy: (opacity === 1.0) ? PlasmaComponents.ScrollBar.AsNeeded : PlasmaComponents.ScrollBar.AlwaysOffonEnabledChanged: {if (!enabled) {filterList.currentIndex = -1;}}onHoveredChanged: {if (!hovered) {filterList.currentIndex = filterList.activeIndexswitchFilterTimer.stop()}}ListView {id: filterListfocus: trueproperty bool allApps: falseproperty int activeIndex: -1property int hItemMargins: Math.max(highlightItemSvg.margins.left + highlightItemSvg.margins.right,listItemSvg.margins.left + listItemSvg.margins.right)model: root.rootModelclip: height < contentHeight + topMargin + bottomMarginboundsBehavior: Flickable.StopAtBoundssnapMode: ListView.SnapToItemspacing: 0keyNavigationWraps: truedelegate: ItemAbstractDelegate {id: filterDelegateproperty var m: modelproperty int textWidth: label.contentWidthwidth: filterList.widthheight: implicitHeighthoverEnabled: !hoverBlock.enabledbaseModel: filterList.modelfavoritesModel: baseModel.favoritesModelonClicked: filterList.applyFilter()onHoveredChanged: {if (hovered && !isSeparator && !ActionMenu.opened) {filterList.currentIndex = indexfilterList.forceActiveFocus()switchFilterTimer.restart()} else if (!hovered && filterList.currentIndex === index) {switchFilterTimer.stop()}}contentItem: Item {implicitHeight: Math.max(label.implicitHeight, badgeLoader.implicitHeight)implicitWidth: label.implicitWidth + badgeLoader.implicitWidthKirigami.Heading {id: labelanchors {left: parent.leftright: badgeLoader.left}elide: Text.ElideRightwrapMode: Text.NoWrapopacity: 1.0color: Kirigami.Theme.textColorlevel: 1text: filterDelegate.model.displaytextFormat: Text.PlainText}Loader {id: badgeLoaderanchors.right: parent.rightanchors.verticalCenter: parent.verticalCentervisible: activeactive: filterDelegate.isNewlyInstalled ?? falsesourceComponent: Kirigami.Badge {text: ""type: Kirigami.Badge.Type.PositiveAccessible.name: i18nc("Newly-installed app, badge, keep short", "New!")Accessible.description: i18nc("@info:whatsthis Accessible description for badge", "There is a newly-installed application in this category")}}}}highlight: PlasmaExtras.Highlight {visible: filterList.currentItemactive: filterListScrollArea.focuspressed: filterList.currentItem && (filterList.currentItem as ItemAbstractDelegate).pressed}highlightMoveDuration: 0highlightResizeDuration: 0onCountChanged: {var width = 0;for (var i = 0; i < root.rootModel.count; ++i) {headingMetrics.text = root.rootModel.labelForRow(i);if (headingMetrics.width > width) {width = headingMetrics.width;}}filterListColumn.columns = Math.ceil(width / root.cellSize);filterListScrollArea.width = width + hItemMargins + (Kirigami.Units.gridUnit * 2);}function applyFilter() {if (!root.searching && currentIndex >= 0) {if (preloadAllAppsTimer.running) {preloadAllAppsTimer.stop();}const model = root.rootModel.modelForRow(currentIndex);if (model.description === "KICKER_ALL_MODEL") {allAppsGrid.model = model;allApps = true;funnelModel.sourceModel = null;preloadAllAppsTimer.done = true;} else {funnelModel.sourceModel = model;allApps = false;}} else {funnelModel.sourceModel = null;allApps = false;}filterList.activeIndex = currentIndex;}function handleLeftRightArrow(event: KeyEvent) : void {let backArrowKey = (event.key === Qt.Key_Left && Application.layoutDirection === Qt.LeftToRight) ||(event.key === Qt.Key_Right && Application.layoutDirection === Qt.RightToLeft)if (backArrowKey) {const currentRow = Math.max(0, Math.ceil(currentItem.y / mainGrid.cellHeight) - 1);mainColumn.tryActivate(currentRow, mainColumn.columns - 1);} else {event.accepted = false}}Keys.onTabPressed: systemFavoritesGrid.tryActivate(0, 0);Keys.onBacktabPressed: mainColumn.tryActivate(0, 0);Keys.onLeftPressed: event => handleLeftRightArrow(event)Keys.onRightPressed: event => handleLeftRightArrow(event)Timer {id: switchFilterTimerinterval: 150repeat: falseonTriggered: filterList.applyFilter()}}}}}onPressed: mouse => {if (mouse.button === Qt.RightButton) {contextMenu.open(mouse.x, mouse.y);}}onClicked: mouse => {if (mouse.button === Qt.LeftButton) {root.interactionConcluded()}}HoverBlocker {id: hoverBlockanchors.fill: parentConnections {target: root.runnerModelfunction onQueryChanged() : void {hoverBlock.reset()}}}}Component.onCompleted: {rootModel.refresh();}}