/* SPDX-FileCopyrightText: 2011 Martin Gräßlin SPDX-FileCopyrightText: 2012 Gregor Taetzner SPDX-FileCopyrightText: 2014 Sebastian Kügler SPDX-FileCopyrightText: 2015-2018 Eike Hein SPDX-FileCopyrightText: 2021 Mikel Johnson SPDX-FileCopyrightText: 2021 Noah Davis SPDX-FileCopyrightText: 2022 Nate Graham SPDX-License-Identifier: GPL-2.0-or-later */ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Templates as T import org.kde.plasma.core as PlasmaCore import org.kde.plasma.components as PC3 import org.kde.plasma.extras as PlasmaExtras import org.kde.kirigami as Kirigami import "code/tools.js" as Tools import org.kde.plasma.plasmoid T.ItemDelegate { id: root enum AppNameFormat { NameOnly, GenericNameOnly, NameAndGenericName, GenericNameAndName } // model properties required property var model required property int index required property url url required property var decoration required property string description required property bool isMultilineText readonly property Flickable view: ListView.view ?? GridView.view property bool isCategoryListItem: false property bool removalPlaceholderActive: false readonly property bool hasActionList: model && (model.favoriteId !== null || ("hasActionList" in model && model.hasActionList === true)) property bool isSearchResult: false readonly property bool isSeparator: model && (model.isSeparator === true) property int separatorHeight: KickoffSingleton.lineSvg.horLineHeight + (2 * Kirigami.Units.smallSpacing) property int itemHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding) readonly property bool dragEnabled: enabled && !isCategoryListItem && Plasmoid.immutability !== PlasmaCore.Types.SystemImmutable readonly property alias mouseArea: mouseArea readonly property bool iconAndLabelsShouldlookSelected: down && !isCategoryListItem property bool labelTruncated: false property bool descriptionTruncated: false property bool descriptionVisible: true property Item dragIconItem: null down: mouseArea.pressed || dragHandler.active function openActionMenu(x = undefined, y = undefined) { if (!hasActionList) { return; } let actions = Array.from(model.actionList); const favoriteActions = Tools.createFavoriteActions( i18n, //i18n() function callback root.view.model.favoritesModel, model.favoriteId, ); if (favoriteActions) { if (actions && actions.length > 0) { // same logic as in kicker's fillActionMenu() // Insert favoriteActions after the "Add to" actions (called // "addLauncherActions" by libkicker) const firstAddToActionIndex = actions.findIndex((action) => ["addToDesktop", "addToTaskManager", "addToPanel"].includes(action?.actionId), ); if (firstAddToActionIndex >= 0) { actions.splice(firstAddToActionIndex, 0, ...favoriteActions); } else { // Or at the end (with a separator) if they're not found actions.push({ type: "separator" }, ...favoriteActions); } } else { actions = favoriteActions; } } if (actions && actions.length > 0) { ActionMenu.plasmoid = kickoff; ActionMenu.menu.visualParent = root; ActionMenu.actionList = actions; if (x !== undefined && y !== undefined) { ActionMenu.menu.open(x, y); } else { ActionMenu.menu.openRelative(); } } } // The default Z value for delegates is 1. The default Z value for the section delegate is 2. // The highlight gets a value of 3 while the drag is active and then goes back to the default value of 0. z: Drag.active ? 4 : 1 implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding) implicitHeight: isSeparator ? separatorHeight : itemHeight spacing: KickoffSingleton.fontMetrics.descent enabled: !isSeparator && !model.disabled hoverEnabled: false text: model.compactNameWrapped ?? model.compactName ?? model.displayWrapped ?? model.display Accessible.role: Accessible.ListItem Accessible.description: root.description !== root.text ? root.description : "" Accessible.onPressAction: { root.forceActiveFocus() // trigger is focus guarded action.trigger() } // Using an action so that it can be replaced or manually triggered // using `model` () instead of `root.model` leads to errors about // `model` not having the trigger() function action: T.Action { onTriggered: { // Unless we're showing search results, eat the activation if we // don't have focus, to prevent the return/enter key from // inappropriately activating unfocused items // Also block activation while dragging. if ((!root.activeFocus && !root.isSearchResult) || dragHandler.active || touchDragHandler.active) { return; } root.view.currentIndex = root.index // if successfully triggered, close popup if (root.view.model.trigger && root.view.model.trigger(root.index, "", null)) { if (kickoff.hideOnWindowDeactivate) { kickoff.expanded = false; } } } } function performDrag(handler: DragHandler): void { if (!handler.active) { kickoff.dragSource.Drag.active = false; kickoff.dragSource.Drag.imageSource = ""; kickoff.dragSource.sourceItem = null; return; } root.dragIconItem.grabToImage(result => { if (!handler.active) { return; } kickoff.dragSource.sourceItem = root; kickoff.dragSource.Drag.imageSource = result.url; kickoff.dragSource.Drag.mimeData = { "text/uri-list" : [root.url] }; kickoff.dragSource.Drag.active = handler.active; }); } DragHandler { id: dragHandler acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad | PointerDevice.Stylus enabled: root.dragEnabled && root.dragIconItem !== null target: null // Using this Item fixes drag and drop causing delegates to reset to a 0 X position and overlapping each other. onActiveChanged: root.performDrag(this) } DragHandler { id: touchDragHandler acceptedDevices: PointerDevice.TouchScreen enabled: dragHandler.enabled target: null yAxis.enabled: false onActiveChanged: root.performDrag(this) } MouseArea { id: mouseArea parent: root anchors.fill: parent anchors.margins: 1 // Flickable margins are not mirrored, so disable layout mirroring LayoutMirroring.enabled: false // Only for ListView since extending margins for GridView is hard anchors.leftMargin: root.view instanceof ListView ? -root.view.leftMargin : anchors.margins anchors.rightMargin: root.view instanceof ListView ? -root.view.rightMargin : anchors.margins hoverEnabled: root.view // When the movedWithWheel condition is broken, this ensures that // onEntered is called again without moving the mouse. && !root.view.movedWithWheel // Fix VerticalStackView animation causing view currentIndex // to change while delegates are moving under the mouse cursor && kickoff.fullRepresentationItem && !kickoff.fullRepresentationItem.contentItem.busy && !kickoff.fullRepresentationItem.blockingHoverFocus acceptedButtons: Qt.LeftButton | Qt.RightButton onEntered: { // - When the movedWithKeyboard condition is broken, we do not want to // select the hovered item without moving the mouse. // - Don't highlight separators. // - Don't switch category items on hover if the setting isn't enabled if (root.view.movedWithKeyboard || root.isSeparator || (root.isCategoryListItem && !Plasmoid.configuration.switchCategoryOnHover)) { return } // forceActiveFocus() touches multiple items, so check for // activeFocus first to be more efficient. Keep activeFocus // stable if it's on the searchField to avoid unintentional // activation with Space key presses. if (!root.activeFocus && !kickoff.searchField?.activeFocus) { root.forceActiveFocus(Qt.MouseFocusReason) } // No need to check currentIndex first because it's // built into QQuickListView::setCurrentIndex() already root.view.currentIndex = root.index } onPressed: mouse => { // Select and focus on press to improve responsiveness and touch feedback root.view.currentIndex = root.index root.forceActiveFocus(Qt.MouseFocusReason) // We normally try to open right click menus on press like Qt Widgets if (mouse.button === Qt.RightButton) { root.openActionMenu(mouseX, mouseY) } } onClicked: mouse => { if (mouse.button === Qt.LeftButton) { root.action.trigger() } } TapHandler { acceptedDevices: PointerDevice.TouchScreen onLongPressed: root.openActionMenu(point.position.x, point.position.y) } } PC3.ToolTip.text: { if (root.labelTruncated) { return model?.display ?? "" } else if (root.descriptionTruncated || (!root.descriptionVisible && (root.isSearchResult || Plasmoid.configuration.appNameFormat > 1))) { return description } return "" } PC3.ToolTip.visible: mouseArea.containsMouse && PC3.ToolTip.text.length > 0 PC3.ToolTip.delay: Kirigami.Units.toolTipDelay background: Loader { active: (root.GridView?.isCurrentItem || root.ListView?.isCurrentItem) ?? false sourceComponent: PlasmaExtras.Highlight { anchors.fill: parent z: (root.Drag.active ?? false) ? 3 : 0 hovered: true pressed: root.down && !root.isCategoryListItem active: root.view.activeFocus || (kickoff.contentArea === root && kickoff.searchField.activeFocus) } } }