/* SPDX-FileCopyrightText: 2013-2014 Eike Hein SPDX-License-Identifier: GPL-2.0-or-later */ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import org.kde.kirigami as Kirigami import org.kde.plasma.components as PlasmaComponents3 import org.kde.plasma.extras as PlasmaExtras import org.kde.plasma.private.kicker as Kicker PlasmaComponents3.ScrollView { id: itemList signal exited signal keyNavigationAtListEnd signal navigateLeftRequested signal navigateRightRequested signal interactionConcluded property Item mainSearchField: null property Kicker.SubMenu dialog: null property Kicker.SubMenu childDialog: null property bool iconsEnabled: false property bool dynamicResize : true property bool showDescriptionInTooltip: false property int innerLeftMargin: 0 property int innerRightMargin: PlasmaComponents3.ScrollBar.vertical.visible ? Kirigami.Units.smallSpacing : 0 property alias currentIndex: listView.currentIndex property alias currentItem: listView.currentItem property alias keyNavigationWraps: listView.keyNavigationWraps property alias model: listView.model property alias count: listView.count property alias resetOnExitDelay: resetIndexTimer.interval implicitWidth: listView.implicitWidth + leftPadding + rightPadding implicitHeight: listView.contentHeight + topPadding + bottomPadding Layout.minimumWidth: Kirigami.Units.gridUnit * 14 Layout.maximumWidth: Math.round(Layout.minimumWidth * 1.5) Layout.maximumHeight: contentHeight PlasmaComponents3.ScrollBar.horizontal.policy: PlasmaComponents3.ScrollBar.AlwaysOff function resetDelegateSizing() { // only needed when submenus are reused, called from ItemListDialog listView.maxDelegateImplicitWidth = 0 } function subMenuForCurrentItem() { if (!kicker.expanded || !itemList.model || itemList.currentIndex === -1 || ActionMenu.opened) { return; } if (itemList && !(itemList.currentItem as ItemListDelegate).hasChildren) { clearChildDialog(); } else if (!itemList.childDialog) { itemList.childDialog = itemListDialogComponent.createObject(itemList, { mainSearchField: mainSearchField, visualParent: listView.currentItem, model: model.modelForRow(listView.currentIndex), visible: Qt.binding(() => itemList.Window.window.visible), dialogMirrored: itemList.LayoutMirroring.enabled }); itemList.childDialog.index = listView.currentIndex; (itemList.childDialog as ItemListDialog).interactionConcluded.connect(itemList.interactionConcluded) itemList.childDialog.mainItem.forceActiveFocus(Qt.TabFocusReason) } else { itemList.childDialog.model = model.modelForRow(itemList.currentIndex); itemList.childDialog.visualParent = listView.currentItem; itemList.childDialog.index = listView.currentIndex; } } function clearChildDialog() { if (childDialog) { childDialog.delayedDestroy() childDialog = null } } Keys.priority: Keys.AfterItem Keys.forwardTo: [itemList.mainSearchField] onHoveredChanged: { const parentItem = itemList.dialog?.visualParent as ItemListDelegate if (hovered && parentItem) { parentItem.ListView.view.currentIndex = parentItem.index } Qt.callLater( () =>{ if (ActionMenu.opened) { return } else if (hovered || (listView.currentItem as ItemListDelegate).dragActive || (listView.currentItem as ItemListDelegate).containsMouse) { resetIndexTimer.stop(); } else { resetIndexTimer.start(); } }) } ListView { id: listView width: itemList.availableWidth implicitHeight: contentHeight implicitWidth: itemList.Layout.minimumWidth anchors { top: parent.top left: parent.left leftMargin: itemList.innerLeftMargin } property int maxDelegateImplicitWidth: 0 // used to set implicitWidth Binding on implicitWidth { value: listView.maxDelegateImplicitWidth when: listView.maxDelegateImplicitWidth > 0 && itemList.dynamicResize } currentIndex: -1 focus: true clip: height < contentHeight + topMargin + bottomMargin boundsBehavior: Flickable.StopAtBounds snapMode: ListView.SnapToItem spacing: 0 keyNavigationEnabled: false Binding on cacheBuffer { when: itemList.dynamicResize value: 10000 // load more delegates, to get their width right } Accessible.name: itemList.Accessible.name Accessible.role: Accessible.List delegate: ItemListDelegate { width: listView.width - itemList.innerRightMargin anchors.left: listView.contentItem.left showIcons: itemList.iconsEnabled showDescriptionInTooltip: itemList.showDescriptionInTooltip dialogDefaultRight: !itemList.LayoutMirroring.enabled onInteractionConcluded: itemList.interactionConcluded() onOpenCategory: keyboardInitiated => { listView.currentIndex = index listView.openOrFocusSubmenu() if (keyboardInitiated) { itemList.childDialog.mainItem.currentIndex = 0; } } onContainsMouseChanged: { if (containsMouse && itemList.hoverEnabled && !isSeparator && !ActionMenu.opened) { listView.currentIndex = index itemList.forceActiveFocus() dialogSpawnTimer.restart() } else if (listView.currentIndex === index) { dialogSpawnTimer.stop() } } onImplicitWidthChanged: { listView.maxDelegateImplicitWidth = Math.max(listView.maxDelegateImplicitWidth, implicitWidth) } } highlight: PlasmaExtras.Highlight { anchors.left: listView.contentItem.left visible: !(listView.currentItem as ItemListDelegate)?.isSeparator pressed: !!((listView.currentItem as ItemListDelegate)?.iconAndLabelsShouldlookSelected) active: !!(listView.currentItem as ItemListDelegate)?.hovered } highlightMoveDuration: 0 onCountChanged: { if (currentIndex == 0 && !itemList.mainSearchField.activeFocus) { currentItem?.forceActiveFocus(); } else { currentIndex = -1; } } onCurrentIndexChanged: { if (currentIndex === itemList.childDialog?.index) { return; } else if (currentIndex === -1 || !(currentItem as ItemListDelegate)?.hasChildren || !kicker.expanded) { dialogSpawnTimer.stop(); itemList.clearChildDialog(); } else if (itemList.childDialog) { dialogSpawnTimer.restart(); } } Connections { target: ActionMenu function onClosed() { resetIndexTimer.restart() } } function openOrFocusSubmenu() : void { if (itemList.childDialog === null) { itemList.subMenuForCurrentItem(); } else { itemList.childDialog.requestActivate() itemList.childDialog.mainItem.forceActiveFocus(Qt.TabFocusReason); } } function handleLeftRightArrow(event: KeyEvent) : void { let backArrowKey = (event.key === Qt.Key_Left && !itemList.LayoutMirroring.enabled) || (event.key === Qt.Key_Right && itemList.LayoutMirroring.enabled) let forwardArrowKey = (event.key === Qt.Key_Right && !itemList.LayoutMirroring.enabled) || (event.key === Qt.Key_Left && itemList.LayoutMirroring.enabled) if (backArrowKey) { if (itemList.dialog != null) { itemList.dialog.destroy(); } else { itemList.navigateLeftRequested(); } } else if (forwardArrowKey) { if (listView.currentItem !== null && (listView.currentItem as ItemListDelegate).hasChildren) { openOrFocusSubmenu() itemList.childDialog.mainItem.currentIndex = 0; } else { itemList.navigateRightRequested(); } } } function handleUpDownArrow(event: KeyEvent) : void { let moveIndex = (event.key === Qt.Key_Up) ? listView.decrementCurrentIndex : listView.incrementCurrentIndex if (!listView.keyNavigationWraps && ((event.key === Qt.Key_Up && listView.currentIndex == 0) || (event.key === Qt.Key_Down && listView.currentIndex == listView.count - 1))) { itemList.keyNavigationAtListEnd(); } else { moveIndex(); if (listView.currentItem !== null) { if ((listView.currentItem as ItemListDelegate).isSeparator) { moveIndex(); } listView.currentItem.forceActiveFocus(Qt.TabFocusReason); } } } Keys.onLeftPressed: event => handleLeftRightArrow(event) Keys.onRightPressed: event => handleLeftRightArrow(event) Keys.onUpPressed: event => handleUpDownArrow(event) Keys.onDownPressed: event => handleUpDownArrow(event) Keys.onEscapePressed: itemList.interactionConcluded() Keys.onPressed: event => { if (event.key !== Qt.Key_Tab && event.text !== "") { itemList.mainSearchField?.forceActiveFocus(Qt.ShortcutFocusReason); } } Timer { id: dialogSpawnTimer interval: 100 repeat: false onTriggered: itemList.subMenuForCurrentItem() } Timer { id: resetIndexTimer interval: (itemList.dialog != null) ? 50 : 150 repeat: false onTriggered: { if (itemList.focus && !(itemList.childDialog?.mainItem as ItemListView)?.hovered) { itemList.currentIndex = -1; itemList.exited(); } } } Connections { target: itemList.childDialog?.mainItem ?? null function onHoveredChanged() { const childListView = itemList.childDialog?.mainItem as ItemListView if (childListView.hovered) { resetIndexTimer.stop() } } } } }