/*SPDX-FileCopyrightText: 2013-2014 Eike Hein <hein@kde.org>SPDX-License-Identifier: GPL-2.0-or-later*/pragma ComponentBehavior: Boundimport QtQuickimport QtQuick.Layoutsimport org.kde.kirigami as Kirigamiimport org.kde.plasma.components as PlasmaComponents3import org.kde.plasma.extras as PlasmaExtrasimport org.kde.plasma.private.kicker as KickerPlasmaComponents3.ScrollView {id: itemListsignal exitedsignal keyNavigationAtListEndsignal navigateLeftRequestedsignal navigateRightRequestedsignal interactionConcluded// can't use effectiveScrollBarWidth, it causes binding loopsreadonly property int actualScrollBarWidth: scrollBarVisible ? PlasmaComponents3.ScrollBar.vertical.width : 0property bool scrollBarVisibleBinding on scrollBarVisible {value: itemList.contentHeight > itemList.heightdelayed: true // this needs to be delayed or it can get stuck in a resize loop}property Item mainSearchField: nullproperty Kicker.SubMenu dialog: nullproperty Kicker.SubMenu childDialog: nullproperty bool iconsEnabled: falseproperty bool dynamicResize : trueproperty alias currentIndex: listView.currentIndexproperty alias currentItem: listView.currentItemproperty alias keyNavigationWraps: listView.keyNavigationWrapsproperty alias model: listView.modelproperty alias count: listView.countproperty alias resetOnExitDelay: resetIndexTimer.intervalproperty alias showSeparators: listView.showSeparatorsimplicitWidth: listView.implicitWidth + actualScrollBarWidthimplicitHeight: listView.contentHeightLayout.minimumWidth: Kirigami.Units.gridUnit * 14Layout.maximumWidth: Math.round(Layout.minimumWidth * 1.5)Layout.maximumHeight: contentHeightPlasmaComponents3.ScrollBar.horizontal.policy: PlasmaComponents3.ScrollBar.AlwaysOfffunction resetDelegateSizing() { // only needed when submenus are reused, called from ItemListDialoglistView.maxDelegateImplicitWidth = 0}function subMenuForCurrentItem(focusOnSpawn=false) {if (!kicker.expanded || !itemList.model || itemList.currentIndex === -1) {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: true,dialogMirrored: itemList.LayoutMirroring.enabled});itemList.childDialog.index = listView.currentIndex;(itemList.childDialog as ItemListDialog).interactionConcluded.connect(itemList.interactionConcluded)itemList.childDialog.mainItem.forceActiveFocus(Qt.TabFocusReason)windowSystem.forceActive(itemList.childDialog.mainItem); // only for X11; TODO Plasma 6.8: removeif (focusOnSpawn) {itemList.childDialog.mainItem.currentIndex = 0;}} 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.AfterItemKeys.forwardTo: [itemList.mainSearchField]onHoveredChanged: {Qt.callLater( () =>{if (hovered) {resetIndexTimer.stop();} else if (itemList.childDialog && listView.currentIndex != itemList.childDialog?.index) {listView.currentIndex = childDialog.index} else if ((!itemList.childDialog || !itemList.dialog)&& (!itemList.currentItem || !(itemList.currentItem as ItemListDelegate).menu.opened)) {resetIndexTimer.start();}})}ListView {id: listViewwidth: itemList.availableWidthimplicitHeight: contentHeightimplicitWidth: itemList.Layout.minimumWidthproperty int maxDelegateImplicitWidth: 0 // used to set implicitWidthproperty bool showSeparators: !model.sorted // separators are mostly useless when sortedBinding on implicitWidth {value: listView.maxDelegateImplicitWidthdelayed: true // only resize once all delegates are loadedwhen: listView.maxDelegateImplicitWidth > 0 && itemList.dynamicResize}currentIndex: -1focus: trueclip: height < contentHeight + topMargin + bottomMarginboundsBehavior: Flickable.StopAtBoundssnapMode: ListView.SnapToItemspacing: 0keyNavigationEnabled: falseBinding on cacheBuffer {when: itemList.dynamicResizevalue: 10000 // load more delegates, to get their width right}Accessible.name: itemList.Accessible.nameAccessible.role: Accessible.Listfunction updateImplicitWidth () {implicitWidth = maxDelegateImplicitWidth}delegate: ItemListDelegate {showSeparators: listView.showSeparatorsshowIcons: itemList.iconsEnableddialogDefaultRight: !itemList.LayoutMirroring.enabledhoverEnabled: itemList.hoverEnabledonInteractionConcluded: itemList.interactionConcluded()onHoveredChanged: {if (hovered & !isSeparator) {listView.currentIndex = indexitemList.forceActiveFocus()dialogSpawnTimer.restart()} else if (listView.currentIndex === index) {dialogSpawnTimer.stop()}}onImplicitWidthChanged: {listView.maxDelegateImplicitWidth = Math.max(listView.maxDelegateImplicitWidth, implicitWidth)}}highlight: PlasmaExtras.Highlight {width: listView.widthvisible: !(listView.currentItem as ItemListDelegate)?.isSeparatorpressed: !!((listView.currentItem as ItemListDelegate)?.iconAndLabelsShouldlookSelected)active: !!(listView.currentItem as ItemListDelegate)?.hovered}highlightMoveDuration: 0onCountChanged: {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: (listView.currentItem as ItemListDelegate)?.menu ?? nullfunction onClosed() {resetIndexTimer.restart()}}function handleLeftRightArrowEnter(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 || event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {if (listView.currentItem !== null && (listView.currentItem as ItemListDelegate).hasChildren) {if (itemList.childDialog === null) {itemList.subMenuForCurrentItem(true);} else {windowSystem.forceActive(itemList.childDialog.mainItem); // only for X11; TODO Plasma 6.8: removeconst childListView = itemList.childDialog.mainItem as ItemListViewchildListView.forceActiveFocus(Qt.TabFocusReason);childListView.currentIndex = 0;}} else if (forwardArrowKey) {itemList.navigateRightRequested();} else {event.accepted = false;}}}function handleUpDownArrow(event: KeyEvent) : void {let moveIndex = (event.key === Qt.Key_Up) ? listView.decrementCurrentIndex : listView.incrementCurrentIndexif (!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 => handleLeftRightArrowEnter(event)Keys.onRightPressed: event => handleLeftRightArrowEnter(event)Keys.onEnterPressed: event => handleLeftRightArrowEnter(event)Keys.onReturnPressed: event => handleLeftRightArrowEnter(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: dialogSpawnTimerinterval: 100repeat: falseonTriggered: itemList.subMenuForCurrentItem()}Timer {id: resetIndexTimerinterval: (itemList.dialog != null) ? 50 : 150repeat: falseonTriggered: {if (itemList.focus && !(itemList.childDialog?.mainItem as ItemListView)?.hovered) {itemList.currentIndex = -1;itemList.exited();}}}}}