/*SPDX-FileCopyrightText: 2013-2015 Eike Hein <hein@kde.org>SPDX-License-Identifier: GPL-2.0-or-later*/import QtQuickimport QtQuick.Layoutsimport org.kde.kirigami as Kirigamiimport org.kde.ksvg as KSvgimport org.kde.plasma.components as PlasmaComponents3import org.kde.plasma.core as PlasmaCoreimport org.kde.plasma.extras as PlasmaExtrasimport org.kde.plasma.plasmoidPlasmaComponents3.ScrollView {id: rootfocus: trueLayout.minimumWidth: Math.min(mainRow.width, Screen.width - Kirigami.Units.largeSpacing * 4)Layout.maximumWidth: Layout.minimumWidthcontentWidth: mainRow.widthLayout.minimumHeight: Math.max(((rootModel.count - rootModel.separatorCount) * rootList.itemHeight)+ (rootModel.separatorCount * rootList.separatorHeight)+ searchField.height + (2 * Kirigami.Units.smallSpacing), sideBar.margins.top + sideBar.margins.bottom+ favoriteApps.contentHeight + favoriteSystemActions.contentHeight + sidebarSeparator.height+ (4 * Kirigami.Units.smallSpacing))Layout.maximumHeight: Layout.minimumHeightfunction ensureVisible(item: Item) : void {var actualItemX = item.xvar viewXPosition = (item.width <= contentItem.width)? Math.round(actualItemX + item.width / 2 - contentItem.width / 2): actualItemXif (actualItemX < contentItem.contentX) {contentItem.contentX = Math.max(0, viewXPosition)} else if ((actualItemX + item.width) > (contentItem.contentX + contentItem.width)) {contentItem.contentX = Math.min(contentItem.contentWidth - contentItem.width, viewXPosition)}contentItem.returnToBounds()}function focusRunnerColumn(column, focusTopElement = true) : void {if (runnerColumns.visibleChildren.length <= column + 1) { // visibleChildren includes repeaterreturn}const targetList = runnerColumns.visibleChildren[column]targetList.currentIndex = focusTopElement ? 0 : (targetList.count - 1)targetList.forceActiveFocus(Qt.TabFocusReason)root.ensureVisible(targetList)}function focusRootList(focusTopElement = true) : void {rootList.showChildDialogs = false;rootList.currentIndex = focusTopElement ? 0 : (rootList.model.count - 1)rootList.forceActiveFocus(Qt.TabFocusReason)rootList.showChildDialogs = true;}function focusSideBar() : void {sideBar.forceActiveFocus(Qt.TabFocusReason)root.ensureVisible(sideBar)}function reset() {kicker.hideOnWindowDeactivate = true;rootList.currentIndex = -1;hoverBlock.reset();searchField.text = "";searchField.focus = true;}Row {id: mainRowheight: parent.heightwidth: sideBar.width + (sideBar.width ? mainRow.spacing : 0) +Math.max((runnerColumns.visible ? runnerColumns.width : rootList.width), searchField.defaultWidth)spacing: Kirigami.Units.smallSpacingLayoutMirroring.enabled: ((Plasmoid.location === PlasmaCore.Types.RightEdge)|| (Qt.application.layoutDirection === Qt.RightToLeft && Plasmoid.location !== PlasmaCore.Types.LeftEdge))KSvg.FrameSvgItem {id: sideBarproperty bool onTopPanel: Plasmoid.location === PlasmaCore.Types.TopEdgevisible: width > 0width: (globalFavorites && systemFavorites&& (globalFavorites.count + systemFavorites.count)? Kirigami.Units.iconSizes.medium + margins.left + margins.right : 0)height: parent.heightimagePath: "widgets/frame"prefix: "plain"activeFocusOnTab: trueonActiveFocusChanged: if (activeFocus) {let target = (onTopPanel && favoriteSystemActions.model.count) ? favoriteSystemActions : favoriteAppstarget.forceActiveFocus(Qt.TabFocusReason)}KeyNavigation.right: rootListKeys.onPressed: event => {let backArrowKey = (event.key === Qt.Key_Left && !mainRow.LayoutMirroring.enabled) ||(event.key === Qt.Key_Right && mainRow.LayoutMirroring.enabled)let forwardArrowKey = (event.key === Qt.Key_Right && !mainRow.LayoutMirroring.enabled) ||(event.key === Qt.Key_Left && mainRow.LayoutMirroring.enabled)if (backArrowKey & runnerColumns.visibleChildren.length > 1) {root.focusRunnerColumn(runnerColumns.visibleChildren.length - 2, true)event.accepted = true}if (forwardArrowKey) {if (runnerColumns.visibleChildren.length > 1) {root.focusRunnerColumn(0, true)} else if (rootList.visible) {root.focusRootList(true)} else {searchField.forceActiveFocus(Qt.TabFocusReason)}event.accepted = true}}ColumnLayout {anchors.top: parent.topanchors.horizontalCenter: parent.horizontalCenterheight: Math.max(implicitHeight, parent.height)LayoutItemProxy {target: sideBar.onTopPanel ? favoriteSystemActions : favoriteApps}KSvg.SvgItem {id: sidebarSeparatorLayout.fillWidth: trueLayout.leftMargin: Kirigami.Units.smallSpacingLayout.rightMargin: Layout.leftMarginLayout.alignment: Qt.AlignHCentervisible: (favoriteApps.model && favoriteApps.model.count&& favoriteSystemActions.model && favoriteSystemActions.model.count)imagePath: "widgets/line"elementId: "horizontal-line"}LayoutItemProxy {target: sideBar.onTopPanel ? favoriteApps : favoriteSystemActions}SideBarSection {id: favoriteAppsLayout.fillHeight: trueKeyNavigation.up: favoriteSystemActions.bottomSideBarItemKeyNavigation.down: favoriteSystemActionsmodel: globalFavoritesBinding {target: globalFavoritesproperty: "iconSize"value: Kirigami.Units.iconSizes.mediumrestoreMode: Binding.RestoreBinding}}SideBarSection {id: favoriteSystemActionsmodel: systemFavoritesKeyNavigation.up: favoriteApps.bottomSideBarItemKeyNavigation.down: favoriteApps}}}ItemListView {id: rootListanchors.top: parent.topminimumWidth: searchField.defaultWidthheight: ((rootModel.count - rootModel.separatorCount) * itemHeight) + (rootModel.separatorCount * separatorHeight)visible: searchField.text === ""iconsEnabled: Plasmoid.configuration.showIconsRootLevelhoverEnabled: !hoverBlock.enabledmainSearchField: searchFieldmodel: rootModelLayoutMirroring.enabled: mainRow.LayoutMirroring.enabledshowSeparators: true // keep even if sorted, the one between recents and categories worksonKeyNavigationAtListEnd: {searchField.focus = true;}onNavigateLeftRequested: {if (!sideBar.visible) {return}currentIndex = -1root.focusSideBar()}states: State {name: "top"when: Plasmoid.location === PlasmaCore.Types.TopEdgeAnchorChanges {target: rootListanchors.top: parent.top}}Component.onCompleted: {rootList.exited.connect(root.reset);}}Row {id: runnerColumnsheight: parent.heightvisible: searchField.text !== "" && runnerModel.count > 0spacing: Kirigami.Units.smallSpacingLayoutMirroring.enabled: mainRow.LayoutMirroring.enabledRepeater {id: runnerColumnsRepeatermodel: runnerModeldelegate: RunnerResultsList {id: runnerMatchesvisible: runnerModel.modelForRow(index).count > 0mainSearchField: searchFieldLayoutMirroring.enabled: runnerColumns.LayoutMirroring.enabledfunction navigateToAdjacentColumn(forward = true) {let currentColumnIndex = runnerColumns.visibleChildren.indexOf(runnerMatches)let targetColumnIndex = currentColumnIndex + (forward ? 1 : -1)if (currentColumnIndex < 0 || runnerColumns.visibleChildren.length <= 2) {return}currentIndex = -1if (targetColumnIndex >= 0 && targetColumnIndex < (runnerColumns.visibleChildren.length - 1)) {root.focusRunnerColumn(targetColumnIndex)} else if (sideBar.visible) {root.focusSideBar()} else {root.focusRunnerColumn(forward ? 0 : runnerColumns.visibleChildren.length - 2)}}onContainsMouseChanged: {if (containsMouse) {focus = true;}}onFocusChanged: {if (!focus) {currentIndex = -1;}}onNavigateLeftRequested: navigateToAdjacentColumn(false)onNavigateRightRequested: navigateToAdjacentColumn(true)}}}Item {height: parent.heightwidth: searchField.defaultWidthPlasmaExtras.PlaceholderMessage {id: noMatchesPlaceholderproperty bool searchRunning: falseanchors.centerIn: parentvisible: searchField.text !== "" && runnerColumns.width < 1 && (!searchRunning || visible)iconName: "edit-none"text: i18nc("@info:status", "No matches")Connections {target: runnerModelfunction onQueryFinished() {noMatchesPlaceholder.searchRunning = false}}Connections {target: searchFieldfunction onTextChanged() {noMatchesPlaceholder.searchRunning = searchField.text !== ""}}}}}PlasmaExtras.SearchField {id: searchFieldreadonly property int spacing: sideBar.width ? mainRow.spacing : 0anchors.bottom: mainRow.bottomanchors.left: parent.leftanchors.leftMargin: sideBar.width + spacingreadonly property real defaultWidth: Kirigami.Units.gridUnit * 14width: runnerColumns.visibleChildren.length > 1? runnerColumns.visibleChildren[0].width: (rootList.visible ? rootList.width : defaultWidth)focus: !Kirigami.InputMethod.willShowOnActiveonTextChanged: {runnerModel.query = text;}onFocusChanged: {if (focus) {// FIXME: Cleanup arbitration between rootList/runnerCols here and in Keys.if (rootList.visible) {rootList.currentIndex = -1;}if (runnerColumns.visible) {runnerColumnsRepeater.itemAt(0).currentIndex = -1;}}}states: [State {name: "top"when: Plasmoid.location === PlasmaCore.Types.TopEdgeAnchorChanges {target: searchFieldanchors.top: undefinedanchors.bottom: mainRow.bottomanchors.left: parent.leftanchors.right: undefined}PropertyChanges {target: searchFieldanchors.leftMargin: sideBar.width + spacinganchors.rightMargin: undefined}},State {name: "right"when: (Plasmoid.location === PlasmaCore.Types.RightEdge && Qt.application.layoutDirection === Qt.LeftToRight)|| (Plasmoid.location === PlasmaCore.Types.LeftEdge && Qt.application.layoutDirection === Qt.RightToLeft)AnchorChanges {target: searchFieldanchors.top: undefinedanchors.bottom: mainRow.bottomanchors.left: undefinedanchors.right: parent.right}PropertyChanges {target: searchFieldanchors.leftMargin: undefinedanchors.rightMargin: sideBar.width + spacing}}]Keys.priority: Keys.AfterItem // arrow keys should move cursor firstKeys.onPressed: event => {let backArrowKey = (event.key === Qt.Key_Left && !mainRow.LayoutMirroring.enabled) ||(event.key === Qt.Key_Right && mainRow.LayoutMirroring.enabled)let forwardArrowKey = (event.key === Qt.Key_Right && !mainRow.LayoutMirroring.enabled) ||(event.key === Qt.Key_Left && mainRow.LayoutMirroring.enabled)if (event.key === Qt.Key_Up || event.key === Qt.Key_Down) {if (rootList.visible) {root.focusRootList(event.key === Qt.Key_Down);} else if (runnerColumns.visible) {let index = runnerColumns.visibleChildren[0].currentIndexroot.focusRunnerColumn(0, event.key === Qt.Key_Down)// First column, first entry is initially selected even when focus is on the search// field, as Return will activate it. Down should immediately go to the second entry.if (index === 0 && event.key === Qt.Key_Down) {runnerColumns.visibleChildren[0].currentIndex = Math.min(index +1, runnerColumns.visibleChildren[0].length - 1)}}event.accepted = true;} else if (backArrowKey) {if (!sideBar.visible && !runnerColumns.visible) {return;}if (runnerColumns.visibleChildren[0].length > 1) {runnerColumns.visibleChildren[0].currentIndex = -1;}if (sideBar.visible) {root.focusSideBar(true)event.accepted = true;} else {root.focusRunnerColumn(runnerColumns.visibleChildren.length - 2, true)}} else if (forwardArrowKey && runnerColumns.visibleChildren.length > 2) {runnerColumns.visibleChildren[0].currentIndex = -1;root.focusRunnerColumn(1, true)event.accepted = true;} else if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {if (runnerColumns.visible) {for (let i = 0; i < runnerModel.count; ++i) {if (runnerModel.modelForRow(i).count) {runnerModel.modelForRow(i).trigger(0, "", null);kicker.expanded = false;break;}}}event.accepted = true;}}function appendText(newText) {focus = true;text = text + newText;}}Keys.onPressed: event => {if (event.key === Qt.Key_Escape) {kicker.expanded = false;}}Component.onCompleted: {kicker.modelRefreshed.connect(() => {const searchTerm = searchField.textroot.reset()runnerModel.clear() // runnerModel is on a timer, clearing and setting query is too fast to worksearchField.text = searchTerm});kicker.reset.connect(reset)windowSystem.hidden.connect(reset);rootModel.refresh();}MouseArea {id: hoverBlock // don't hover-activate until mouse is moved to not interfere with keyboard useanchors.fill: parenthoverEnabled: truepropagateComposedEvents: true // clicking should still work if hovering is blockedproperty bool mouseMoved: falsefunction reset() {mouseMoved = falseenabled = true}onPositionChanged: if (!mouseMoved) {mouseMoved = true} else {enabled = false // this immediately triggers other hover events when bound to their hoverEnabled}}}