Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/VLC/modules/gui/qt/widgets/qml/   (VLC Media Plyer ©)  Datei vom 23.9.2026 mit Größe 29 kB image not shown  

Quelle  ListViewExt.qml  Sprache: unbekannt

 
Spracherkennung für: .qml vermutete Sprache: Abap {ABAP[88] Delphi[69] Cobol[66]} [Methode: maximale Elemente, drei Dimensionen]

/*****************************************************************************
 * Copyright (C) 2019 VLC authors and VideoLAN
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * ( at your option ) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

import QtQuick
import QtQuick.Templates as T
import QtQuick.Window
import QtQuick.Layouts

import VLC.MainInterface
import VLC.Style
import VLC.Util
import VLC.Widgets

ListView {
    id: root

    // Properties

    // Optional
    property ListSelectionModel selectionModel

    // Useful for batch rendering when the delegate draws things outside the cell, which
    // is the case with shadow effect. Note that this change affects how the view looks,
    // if the overlapping parts of delegate are not using commutative blending (which
    // is the case by default with source-over blending in scene graph), but it is not
    // really obvious with the shadows. Note that item delegate must emit view signal
    // `onDelegateInstantiated` with a parameter of itself when instantiated for this
    // to work.
    property bool delegateZTiling: true

    // Optional property for drop indicator placement and auto scroll feature:
    property var itemContainsDrag: undefined

    // Optional functions for the optional drag accessory footer:
    property var isDropAcceptableFunc
    property var acceptDropFunc

    property Component defaultScrollBar: Component {
        ScrollBarExt { }
    }

    // Private

    property bool _keyPressed: false

    // Aliases

    // TODO: Qt 7 try to assign the item inline if it is possible
    //       to set it to null, so that the item is not created
    //       if the effect is not wanted.
    property alias fadingEdge: fadingEdge

    property alias autoScrollDirection: viewDragAutoScrollHandlerLoader.scrollingDirection

    // Signals
    signal actionAtIndex(int index)

    signal showContextMenu(point globalPos)

    signal delegateInstantiated(Item instance)

    onDelegateInstantiated: (instance /*: Item*/) => {
        console.assert(instance)
        delegateZTilingBindingComponent.incubateObject(instance,
                                                       {'targetItem': instance},
                                                       1 /* QQmlIncubator::AsynchronousIfNested */)
    }

    Component {
        id: delegateZTilingBindingComponent

        Binding {
            required property Item targetItem

            when: root.delegateZTiling

            target: targetItem

            // We can do this, as the view we can manage the `z` of delegate.
            property: "z"

            value: {
                if (!targetItem)
                    return

                const delegateIndex = targetItem.index
                console.assert(Number.isInteger(delegateIndex))

                // Divide by 10 to create a z group (1.0 to 1.3) for this purpose, so
                // that non-delegate items can remain stacked properly with regard
                // to their z. Note that `z` does not need to be integer for items:
                return (root.move?.running || root.moveDisplaced?.running) ? 1 : ((delegateIndex % 4) / 10.0 + 1)
            }
        }
    }

    // Private

    property int _currentFocusReason: Qt.OtherFocusReason

    // Settings

    pixelAligned: (MainCtx.qtVersion() >= MainCtx.qtVersionCheck(6, 2, 5)) // QTBUG-103996
                  && (Screen.pixelDensity >= VLCStyle.highPixelDensityThreshold) // no need for sub-pixel alignment with high pixel density

    focus: true

    activeFocusOnTab: true

    //key navigation is reimplemented for item selection
    keyNavigationEnabled: false
    keyNavigationWraps: false

    T.ScrollBar.vertical: {
        // By default vertical scroll bar is only used when the orientation is vertical.
        if (root.defaultScrollBar && (root.orientation === ListView.Vertical))
            return root.defaultScrollBar.createObject() // rely on JS/QML engine's garbage collection
        return null
    }
    T.ScrollBar.horizontal: {
        // By default horizontal scroll bar is only used when the orientation is horizontal.
        if (root.defaultScrollBar && (root.orientation === ListView.Horizontal))
            return root.defaultScrollBar.createObject() // rely on JS/QML engine's garbage collection
        return null
    }

    flickableDirection: Flickable.AutoFlickIfNeeded

    highlightMoveDuration: 300 //ms
    highlightMoveVelocity: 1000 //px/s

    boundsBehavior: Flickable.StopAtBounds

    reuseItems: true

    section.property: ""
    section.criteria: ViewSection.FullString
    section.delegate: sectionHeading

    // Content size is set to the size by default
    // If the delegate does not obey it, calculate
    // the content size appropriately.
    contentWidth: (orientation === ListView.Vertical) ? width - (leftMargin + rightMargin) : -1
    contentHeight: (orientation === ListView.Horizontal) ? height - (topMargin + bottomMargin) : -1

    footer: !!root.acceptDropFunc ? footerDragAccessoryComponent : null

    onItemContainsDragChanged: {
        if (!dropIndicatorItem && dropIndicator)
            dropIndicatorItem = dropIndicator.createObject(this)
    }

    Binding on maximumFlickVelocity {
        id: maximumFlickVelocitySuppressor

        when: false
        value: -1
    }

    component VerticalDropAreaLayout : ColumnLayout {
        id: verticalDropAreaLayout

        spacing: 0

        required property int index

        property alias higherDropArea: higherDropArea
        property alias lowerDropArea: lowerDropArea

        property var isDropAcceptable
        property var acceptDrop

        property Item view

        readonly property point dragPosition: {
            let area = null

            if (higherDropArea.containsDrag)
                area = higherDropArea
            else if (lowerDropArea.containsDrag)
                area = lowerDropArea
            else
                return Qt.point(0, 0)

            const drag = area.drag
            return Qt.point(drag.x, drag.y)
        }

        function commonDrop(targetIndex, drop) {
            const promise = acceptDrop(targetIndex, drop)
            if (view) {
                MainCtx.setCursor(view, Qt.BusyCursor)
                promise.then(() => {
                    // NOTE: check view again for the unlikely case it is
                    //       gone by the time the promise is resolved:
                    if (view)
                        MainCtx.unsetCursor(view)
                })
            }
        }

        function mappedIndex() {
            if (view?.model instanceof AbstractProxyModel)
                return view.model.mapToSource(view.model.index(index, 0)).row
            else
                return index
        }

        // NOTE: Nested inline components are not supported in QML as of Qt 6.8

        DropArea {
            id: higherDropArea

            Layout.fillWidth: true
            Layout.fillHeight: true

            onEntered: (drag) => {
                if (!acceptDrop) {
                    drag.accepted = false
                    return
                }

                if (isDropAcceptable && !isDropAcceptable(drag, verticalDropAreaLayout.mappedIndex())) {
                    drag.accepted = false
                    return
                }
            }

            onDropped: (drop) => {
                console.assert(acceptDrop)
                commonDrop(verticalDropAreaLayout.mappedIndex(), drop)
            }
        }

        DropArea {
            id: lowerDropArea

            Layout.fillWidth: true
            Layout.fillHeight: true

            onEntered: (drag) =>  {
                if (!acceptDrop) {
                    drag.accepted = false
                    return
                }

                if (isDropAcceptable && !isDropAcceptable(drag, verticalDropAreaLayout.mappedIndex() + 1)) {
                    drag.accepted = false
                    return
                }
            }

            onDropped: (drop) => {
                console.assert(acceptDrop)
                commonDrop(verticalDropAreaLayout.mappedIndex() + 1, drop)
            }
        }
    }

    Component {
        id: footerDragAccessoryComponent

        Item {
            id: footerItem

            implicitWidth: root.contentWidth
            implicitHeight: Math.max(VLCStyle.icon_normal, root.height - y - (root.headerItem?.height ?? 0))

            property alias firstItemIndicatorVisible: firstItemIndicator.visible

            readonly property bool containsDrag: dropArea.containsDrag
            readonly property bool topContainsDrag: containsDrag
            readonly property bool bottomContainsDrag: false

            onContainsDragChanged: {
                if (root.count > 0) {
                    root.updateItemContainsDrag(this, containsDrag)
                } else if (!containsDrag && root.itemContainsDrag === this) {
                    // In case model count is changed somehow while
                    // containsDrag is set
                    root.updateItemContainsDrag(this, false)
                }
            }

            property alias dragPosition: dropArea.drag

            Rectangle {
                id: firstItemIndicator

                anchors.fill: parent
                anchors.margins: VLCStyle.margin_small

                border.width: VLCStyle.dp(2, VLCStyle.scale)
                border.color: theme.accent
                border.pixelAligned: (radius < Number.EPSILON)

                color: "transparent"

                visible: (root.count === 0 && (dropArea.containsDrag || dropArea.dropOperationOngoing))

                opacity: 0.8

                IconLabel {
                    anchors.centerIn: parent

                    text: VLCIcons.add

                    font.pixelSize: VLCStyle.fontHeight_xxxlarge

                    color: theme.accent
                }
            }

            DropArea {
                id: dropArea

                anchors.fill: parent

                property bool dropOperationOngoing: false

                onDropOperationOngoingChanged: {
                    if (dropOperationOngoing)
                        MainCtx.setCursor(root, Qt.BusyCursor)
                    else
                        MainCtx.unsetCursor(root)
                }

                onEntered: function(drag) {
                    if (!root.isDropAcceptableFunc || !root.isDropAcceptableFunc(drag, root.model.rowCount())
                            || !root.acceptDropFunc) {
                        drag.accepted = false
                        return
                    }

                    drag.accepted = true
                }

                onDropped: function(drop) {
                    console.assert(!!root.acceptDropFunc)
                    dropOperationOngoing = true
                    let targetIndex
                    if (root.model instanceof AbstractProxyModel)
                        targetIndex = (root.model.mapToSource(root.model.index(root.count - 1, 0))?.row ?? 0) + 1
                    else
                        targetIndex = root.count
                    root.acceptDropFunc(targetIndex, drop)
                        .then(() => { dropOperationOngoing = false })
                }
            }
        }
    }

    Accessible.role: Accessible.List

    add: Transition {
        // Transition is relevant when drag and drop is feasible.
        // Component approach can not be used here because `Transition`
        // does not have a "target" property, and wants a valid parent.
        enabled: !!root.acceptDropFunc

        // WARNING: Do not use `OpacityAnimator` here, it is bugged.
        NumberAnimation {
            property: "opacity"
            duration: VLCStyle.duration_long
            easing.type: Easing.OutSine
        }

        onRunningChanged: {
            if (!running) {
                // This intends to clear the artifact (QTBUG-110969).
                // Note that this does not help if items are not
                // positioned correctly, for that x/y animation should
                // not be used for add transitions.
                const epsilon = 0.001
                if (root.orientation === ListView.Vertical) {
                    root.contentY -= epsilon
                    root.contentY += epsilon
                } else if (root.orientation === ListView.Horizontal) {
                    root.contentX -= epsilon
                    root.contentX += epsilon
                }
            }
        }
    }

    // WARNING: Add displaced transition is disabled, because it is
    //          often not executed properly, and causes items to have
    //          incorrect positions. It is currently not possible to
    //          recover from that situation. Fortunately move displaced 
    //          seemingly is not affected from that
    //          issue. See QTBUG-131106, QTBUG-89158, ...

    component DefaultDisplacedTransition : Transition {
        // Transition is relevant when drag and drop is feasible.
        // Component approach can not be used here because `Transition`
        // does not have a "target" property, and wants a valid parent.
        enabled: !!view.acceptDropFunc

        required property ListViewExt view

        NumberAnimation {
            // TODO: Use YAnimator >= Qt 6.0 (QTBUG-66475)
            property: (view.orientation === ListView.Vertical) ? "y" : "x"
            duration: VLCStyle.duration_long
            easing.type: Easing.OutSine
        }
    }

    moveDisplaced: DefaultDisplacedTransition {
        view: root
    }

    // NOTE: removeDisplaced uses a 0ms transition as a workaround for QTBUG-131106.
    // Disabling the transition (or setting it to null) breaks item recycling (reuseItems: true)
    // during multi-slice deletions, causing delegate position overlap.
    // The 0ms transition preserves the Transition lifecycle while avoiding displacement coordinate desync.
    removeDisplaced: Transition {
        enabled: !!root.acceptDropFunc

        NumberAnimation {
            property: (root.orientation === ListView.Vertical) ? "y" : "x"
            duration: 0
        }
    }

    // Events

    Component.onCompleted: {
        if (!usingTouch) {
            // Flickable filters child mouse events for flicking (even when
            // the delegate is grabbed). However, this is not a useful
            // feature for non-touch cases, so disable it here and enable
            // it if touch is detected through the hover handler:
            MainCtx.setFiltersChildMouseEvents(root, false)
        }
    }

    readonly property bool usingTouch: MainCtx.usingTouch

    onUsingTouchChanged: {
        if (usingTouch)
            MainCtx.setFiltersChildMouseEvents(root, true)
        // We do not disable filtering child mouse events
        // because Qt currently has a bug that the flickable
        // jumps when it is enabled again later on.
    }

    // NOTE: We always want a valid 'currentIndex' by default.
    onCountChanged: if (count && currentIndex === -1) currentIndex = 0

    onCurrentItemChanged: {
        if (_currentFocusReason === Qt.OtherFocusReason)
            return;

        // NOTE: We make sure the view has active focus before enforcing it on the item.
        if (root.activeFocus && currentItem)
            Helpers.enforceFocus(currentItem, _currentFocusReason);

        _currentFocusReason = Qt.OtherFocusReason;
    }

    // Functions

    // NOTE: This function is useful to set the currentItem without losing the visual focus.
    function setCurrentItem(index) {
        if (currentIndex === index)
            return

        let reason

        if (currentItem)
            reason = currentItem.focusReason
        else
            reason = _currentFocusReason

        currentIndex = index

        if (reason !== Qt.OtherFocusReason) {
            if (currentItem)
                Helpers.enforceFocus(currentItem, reason)
            else
                setCurrentItemFocus(reason)
        }
    }

    function setCurrentItemFocus(reason) {
        if (!model || model.count === 0) {
            // NOTE: By default we want the focus on the flickable.
            root.forceActiveFocus(reason);

            // NOTE: Saving the focus reason for later.
            _currentFocusReason = reason;

            return;
        }

        if (currentIndex === -1)
            currentIndex = 0;

        positionViewAtIndex(currentIndex, ItemView.Contain);

        Helpers.enforceFocus(currentItem, reason);
    }

    // Qt does not allow having multiple behavior on a single
    // property. Having this behavior for a single purpose can
    // be problematic for the cases where a different behavior
    // wanted to be used. However, I have not found a nice
    // solution for that, so we have this behavior here for now.
    Behavior on contentX {
        id: horizontalPageAnimationBehavior

        enabled: false

        // NOTE: Usage of `SmoothedAnimation` is intentional here.
        SmoothedAnimation {
            duration: VLCStyle.duration_veryLong
            easing.type: Easing.InOutSine
        }
    }

    function animatePage(func) {
        // One might think, what is the purpose of this if `highlightFollowsCurrentItem` (default
        // true) causes the view to smoothly follow the current item. The thing is that, not in
        // all cases the current index is changed. With the horizontal page buttons, for example,
        // it is not conventional to change the current index.
        console.assert(func === root.nextPage || func === root.prevPage)
        horizontalPageAnimationBehavior.enabled = true
        func()
        horizontalPageAnimationBehavior.enabled = false
    }

    function positionViewAtBeginningAnimated() {
        if (!root.flickTo) { // flickTo was added in Qt 6.11
            positionViewAtBeginning()
            return
        }

        // flickTo() velocity is capped by maximumFlickVelocity, meaning it won't reach
        // the top in a big list. Suppress the cap only for the flickTo() call
        maximumFlickVelocitySuppressor.when = true
        root.flickTo(Qt.point(root.originX, root.originY))
        maximumFlickVelocitySuppressor.when = false
    }

    function nextPage() {
        root.contentX = Helpers.getFlickableBoundedContentX(root, root.contentX + root.width)
    }

    function prevPage() {
        root.contentX = Helpers.getFlickableBoundedContentX(root, root.contentX - root.width)
    }

    // Add an indirection here because additional control
    // might be necessary as in Playqueue.
    // Derived views may override this function.
    function updateSelection(modifiers, oldIndex, newIndex) {
        if (selectionModel)
            selectionModel.updateSelection(modifiers, oldIndex, newIndex)
    }

    function updateItemContainsDrag(item, set) {
        if (set) {
            if (itemContainsDrag)
                console.debug(item + " set itemContainsDrag before it was released!")
            itemContainsDrag = item
        } else {
            if (itemContainsDrag !== item)
                console.debug(item + " released itemContainsDrag that is not owned!")
            itemContainsDrag = null
        }
    }

    Keys.onPressed: (event) => {
        let newIndex = currentIndex

        let target = undefined
        let suppressPageModifier = false

        while (target === undefined || !target.visible || !target.enabled) {
            if (orientation === ListView.Vertical) {
                if ( suppressPageModifier || KeyHelper.matchDown(event) ) {
                    if (currentIndex !== count - 1 )
                        newIndex += 1
                    else if ( root.keyNavigationWraps )
                        newIndex = 0
                } else if ( !suppressPageModifier && KeyHelper.matchPageDown(event) ) {
                    newIndex = Math.min(count - 1, newIndex + 10)
                } else if ( suppressPageModifier || KeyHelper.matchUp(event) ) {
                    if ( newIndex !== 0 )
                        newIndex -= 1
                    else if ( root.keyNavigationWraps )
                        newIndex = count - 1
                } else if ( !suppressPageModifier && KeyHelper.matchPageUp(event) ) {
                    newIndex = Math.max(0, newIndex - 10)
                }
            } else {
                if ( suppressPageModifier || KeyHelper.matchRight(event) ) {
                    if (newIndex !== count - 1 )
                        newIndex += 1
                    else if ( root.keyNavigationWraps )
                        newIndex = 0
                }
                else if ( !suppressPageModifier && KeyHelper.matchPageDown(event) ) {
                    newIndex = Math.min(count - 1, newIndex + 10)
                } else if ( suppressPageModifier || KeyHelper.matchLeft(event) ) {
                    if ( newIndex !== 0 )
                        newIndex -= 1
                    else if ( root.keyNavigationWraps )
                        newIndex = count - 1
                } else if ( !suppressPageModifier && KeyHelper.matchPageUp(event) ) {
                    newIndex = Math.max(0, newIndex - 10)
                }
            }

            const newTarget = root.itemAtIndex(newIndex)

            if (newTarget === null || newTarget === target) {
                root.Navigation.defaultKeyAction(event)
                return
            } else if (!newTarget.visible || !newTarget.enabled) {
                if (KeyHelper.matchPageDown(event) || KeyHelper.matchPageUp(event)) {
                    suppressPageModifier = true
                }
            }

            target = newTarget
        }

        if (event.matches(StandardKey.SelectAll)) {
            event.accepted = true
            if (selectionModel)
                selectionModel.selectAll()
        } else if (KeyHelper.matchOk(event)) {
            // Keep activation handling on release to avoid double interpretation.
            event.accepted = true
            _keyPressed = true
        }

        const oldIndex = currentIndex
        if (newIndex >= 0 && newIndex < count && newIndex !== oldIndex) {
            event.accepted = true;

            currentIndex = newIndex;

            root.updateSelection(event.modifiers, oldIndex, newIndex);

            // NOTE: If we skip this call the item might end up under the header.
            if (root.highlightFollowsCurrentItem) {
                // FIXME: Items can go beneath `OverlayHeader` or `OverlayFooter`.
                //        We should move the header and footer outside of the view,
                //        if we do not want that behavior instead of having this
                //        workaround, as Qt does not seem to offer that configuration
                //        as a built-in mode in its views.

                if (root.headerItem && (root.headerPositioning !== ListView.InlineHeader)) {
                    if (root.currentItem.y < (root.headerItem.y + root.headerItem.height))
                        positionViewAtIndex(currentIndex, ItemView.Contain);
                }

                if (root.footerItem && (root.footerPositioning !== ListView.InlineFooter)) {
                    if (root.currentItem.y > root.footerItem.y)
                        positionViewAtIndex(currentIndex, ItemView.Contain);
                }
            }

            // NOTE: We make sure we have the proper visual focus on components.
            if (oldIndex < currentIndex)
                Helpers.enforceFocus(currentItem, Qt.TabFocusReason);
            else
                Helpers.enforceFocus(currentItem, Qt.BacktabFocusReason);
        }

        if (!event.accepted) {
            root.Navigation.defaultKeyAction(event)
        }
    }

    Keys.onReleased: (event) => {
        if (_keyPressed === false)
            return

        _keyPressed = false

        if (KeyHelper.matchOk(event)) { //enter/return/space
            event.accepted = true
            actionAtIndex(currentIndex)
        }
    }

    readonly property ColorContext colorContext: ColorContext {
        id: theme
        colorSet: ColorContext.View
    }

    FadingEdgeForItemView {
        id: fadingEdge

        anchors.fill: parent

        itemView: root

        backgroundColor: theme.bg.primary

        Binding on enableBeginningFade {
            when: (root.autoScrollDirection === ViewDragAutoScrollHandler.Direction.Backward)
            value: false
        }

        Binding on enableEndFade {
            when: (root.autoScrollDirection === ViewDragAutoScrollHandler.Direction.Forward)
            value: false
        }
    }

    Loader {
        id: viewDragAutoScrollHandlerLoader

        active: root.itemContainsDrag !== undefined

        readonly property int scrollingDirection: item ? item.scrollingDirection : -1

        sourceComponent: ViewDragAutoScrollHandler {
            view: root
            dragging: root.itemContainsDrag !== null
            dragPosProvider: function () {
                const source = root.itemContainsDrag
                const point = source.dragPosition
                return root.mapFromItem(source, point.x, point.y)
            }
        }
    }

    Component {
        id: sectionHeading

        Column {
            width: parent.width

            Text {
                text: section
                font.pixelSize: VLCStyle.fontSize_xlarge
                color: theme.accent
            }

            Rectangle {
                width: parent.width
                height: 1
                color: theme.border
            }
        }
    }

    property Component implicitFlickableScrollHandler: DefaultFlickableScrollHandler { }

    // NOTE: This property can be set to null to prevent using a scroll handler:
    property FlickableScrollHandler scrollHandler: {
        if (interactive) {
            // JS ownership:
            return implicitFlickableScrollHandler.createObject(null, { target: root })
        } else {
            return null
        }
    }

    // FIXME: This is probably not useful anymore.
    Connections {
        target: root.headerItem
        function onFocusChanged() {
            if (!headerItem.focus) {
                currentItem.focus = true
            }
        }
    }

    TapHandler {
        acceptedDevices: PointerDevice.AllDevices & ~(PointerDevice.TouchScreen)

        acceptedButtons: Qt.LeftButton | Qt.RightButton

        grabPermissions: PointerHandler.TakeOverForbidden

        gesturePolicy: TapHandler.ReleaseWithinBounds

        onTapped: (eventPoint, button) => {
            initialAction()

            if (button === Qt.RightButton) {
                root.showContextMenu(eventPoint.globalPosition)
            }
        }

        Component.onCompleted: {
            canceled.connect(initialAction)
        }

        function initialAction() {
            if (root.currentItem)
                root.currentItem.focus = false // Grab the focus from delegate
            root.forceActiveFocus(Qt.MouseFocusReason) // Re-focus the list

            if (!(point.modifiers & (Qt.ShiftModifier | Qt.ControlModifier))) {
                if (selectionModel)
                    selectionModel.clearSelection()
            }
        }
    }

    TapHandler {
        acceptedDevices: PointerDevice.TouchScreen

        grabPermissions: PointerHandler.TakeOverForbidden

        gesturePolicy: TapHandler.ReleaseWithinBounds

        property bool pendingContextMenu: false

        onSingleTapped: (eventPoint, button) => {
            initialAction()
        }

        onLongPressed: {
            initialAction()

            pendingContextMenu = true
        }

        onPressedChanged: {
            if (!pressed) {
                if (pendingContextMenu) {
                    root.showContextMenu(parent.mapToGlobal(point.position.x, point.position.y))
                    pendingContextMenu = false
                }
            }
        }

        onCanceled: {
            pendingContextMenu = false
        }

        function initialAction() {
            if (root.currentItem)
                root.currentItem.focus = false // Grab the focus from delegate
            root.forceActiveFocus(Qt.MouseFocusReason) // Re-focus the list

            root.currentIndex = -1
        }
    }

    property Component dropIndicator: Rectangle {
        parent: {
            const item = root.itemContainsDrag
            if (!item || item.topContainsDrag === undefined || item.bottomContainsDrag === undefined)
              return null
            return item
        }

        z: 99

        anchors {
            left: !!parent ? parent.left : undefined
            right: !!parent ? parent.right : undefined
            top: {
                if (parent === null)
                    return undefined
                else if (parent.topContainsDrag === true)
                    return parent.top
                else if (parent.bottomContainsDrag === true)
                    return parent.bottom
                else
                    return undefined
            }
        }

        implicitHeight: VLCStyle.dp(1, VLCStyle.scale)

        visible: !!parent
        color: theme.accent
    }

    property Item dropIndicatorItem
}

[Dauer der Verarbeitung: 0.7 Sekunden, vorverarbeitet 2026-10-24]