import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import FishUI 1.0 as FishUI import Cutefish.FileManager 1.0 ListView { id: control property Item rubberBand: null property Item hoveredItem: null property Item pressedItem: null property int verticalDropHitscanOffset: 0 property int pressX: -1 property int pressY: -1 property int dragX: -1 property int dragY: -1 property bool ctrlPressed: false property bool shiftPressed: false property int previouslySelectedItemIndex: -1 property variant cPress: null property Item editor: null property int anchorIndex: 0 property var itemHeight: FishUI.Units.fontMetrics.height * 2 + FishUI.Units.smallSpacing property variant cachedRectangleSelection: null signal keyPress(var event) currentIndex: -1 clip: true ScrollBar.vertical: ScrollBar { } function rename() { if (currentIndex !== -1) { var renameAction = control.model.action("rename") if (renameAction && !renameAction.enabled) return if (!control.editor) control.editor = editorComponent.createObject(control) control.editor.targetItem = control.currentItem } } function cancelRename() { if (control.editor) { control.editor.cancel() control.editor.destroy() control.editor = null } } function reset() { currentIndex = -1 anchorIndex = 0 previouslySelectedItemIndex = -1 cancelRename() hoveredItem = null pressedItem = null cPress = null } highlightMoveDuration: 0 Keys.enabled: true Keys.onPressed: { if (event.key === Qt.Key_Control) { ctrlPressed = true } else if (event.key === Qt.Key_Shift) { shiftPressed = true if (currentIndex != -1) anchorIndex = currentIndex } control.keyPress(event) } Keys.onReleased: { if (event.key === Qt.Key_Control) { ctrlPressed = false } else if (event.key === Qt.Key_Shift) { shiftPressed = false anchorIndex = 0 } } Keys.onEscapePressed: { if (!editor || !editor.targetItem) { previouslySelectedItemIndex = -1 dirModel.clearSelection() event.accepted = false } } Keys.onUpPressed: { if (!editor || !editor.targetItem) { var newIndex = currentIndex newIndex--; if (newIndex < 0) newIndex = 0 currentIndex = newIndex updateSelection(event.modifiers) } } Keys.onDownPressed: { if (!editor || !editor.targetItem) { var newIndex = currentIndex newIndex++ if (newIndex >= control.count) return currentIndex = newIndex updateSelection(event.modifiers) } } onCachedRectangleSelectionChanged: { if (cachedRectangleSelection === null) return if (cachedRectangleSelection.length) control.currentIndex[0] dirModel.updateSelection(cachedRectangleSelection, control.ctrlPressed) } onContentXChanged: { cancelRename() } onContentYChanged: { cancelRename() } onPressXChanged: { cPress = mapToItem(control.contentItem, pressX, pressY) } onPressYChanged: { cPress = mapToItem(control.contentItem, pressX, pressY) } MouseArea { id: _mouseArea anchors.fill: parent propagateComposedEvents: true preventStealing: true acceptedButtons: Qt.RightButton | Qt.LeftButton hoverEnabled: true enabled: true z: -1 onDoubleClicked: { if (mouse.button === Qt.LeftButton && control.pressedItem) dirModel.openSelected() } onPressed: { control.forceActiveFocus() if (control.editor && childAt(mouse.x, mouse.y) !== control.editor) control.editor.commit() if (mouse.source === Qt.MouseEventSynthesizedByQt) { var index = control.indexAt(mouse.x, mouse.y + control.contentY) var indexItem = control.itemAtIndex(index) if (indexItem && indexItem.iconArea) { control.currentIndex = index hoveredItem = indexItem } else { hoveredItem = null } } pressX = mouse.x pressY = mouse.y if (!hoveredItem || hoveredItem.blank) { if (!control.ctrlPressed) { control.currentIndex = -1 control.previouslySelectedItemIndex = -1 dirModel.clearSelection() } if (mouse.buttons & Qt.RightButton) { clearPressState() dirModel.openContextMenu(null, mouse.modifiers) mouse.accepted = true } } else { pressedItem = hoveredItem if (control.shiftPressed && control.currentIndex !== -1) { dirModel.setRangeSelected(control.anchorIndex, hoveredItem.index) } else { if (!control.ctrlPressed && !dirModel.isSelected(hoveredItem.index)) { previouslySelectedItemIndex = -1 dirModel.clearSelection() } if (control.ctrlPressed) { dirModel.toggleSelected(hoveredItem.index) } else { if (mouse.button == Qt.LeftButton) dirModel.clearSelection() dirModel.setSelected(hoveredItem.index) } } control.currentIndex = hoveredItem.index if (mouse.buttons & Qt.RightButton) { clearPressState() dirModel.openContextMenu(null, mouse.modifiers) mouse.accepted = true } } } onClicked: { clearPressState() if (!hoveredItem || hoveredItem.blank || control.currentIndex === -1 || control.ctrlPressed || control.shiftPressed) { return } // TODO: rename } onPositionChanged: { control.ctrlPressed = (mouse.modifiers & Qt.ControlModifier) control.shiftPressed = (mouse.modifiers & Qt.ShiftModifier) var cPos = mapToItem(control.contentItem, mouse.x, mouse.y) var item = control.itemAt(mouse.x - control.leftMargin, mouse.y + control.contentY) var leftEdge = Math.min(control.contentX, control.originX) if (!item || item.blank) { if (control.hoveredItem) { control.hoveredItem = null } } else { control.hoveredItem = item } // TODO: autoscroll if (control.rubberBand) { var rB = control.rubberBand if (cPos.x < cPress.x) { rB.x = Math.max(leftEdge, cPos.x) rB.width = Math.abs(rB.x - cPress.x) } else { rB.x = cPress.x var ceil = Math.max(control.width, control.contentItem.width) + leftEdge rB.width = Math.min(ceil - rB.x, Math.abs(rB.x - cPos.x)) } if (cPos.y < cPress.y) { rB.y = Math.max(0, cPos.y) rB.height = Math.abs(rB.y - cPress.y) } else { rB.y = cPress.y var ceilValue = Math.max(control.height, control.contentItem.height) rB.height = Math.min(ceilValue - rB.y, Math.abs(rB.y - cPos.y)) } // Ensure rubberband is at least 1px in size or else it will become // invisible and not match any items. rB.width = Math.max(1, rB.width) rB.height = Math.max(1, rB.height) control.rectangleSelect(rB.x, rB.y, rB.width, rB.height) return } // Drag if (pressX != -1) { if (pressedItem != null && dirModel.isSelected(pressedItem.index)) { control.dragX = mouse.x control.dragY = mouse.y control.verticalDropHitscanOffset = pressedItem.y + (pressedItem.height / 2) dirModel.dragSelected(mouse.x, mouse.y) control.dragX = -1 control.dragY = -1 clearPressState() } else { if (control.editor && control.editor.targetItem) return; dirModel.pinSelection() control.rubberBand = rubberBandObject.createObject(control.contentItem, {x: cPress.x, y: cPress.y}) control.interactive = false } } } onContainsMouseChanged: { if (!containsMouse && !control.rubberBand) { clearPressState() if (control.hoveredItem) { control.hoveredItem = null } } } onReleased: pressCanceled() onCanceled: pressCanceled() } function pressCanceled() { if (control.rubberBand) { control.rubberBand.close() control.rubberBand = null control.interactive = true control.cachedRectangleSelection = null dirModel.unpinSelection() } clearPressState() // control.cancelAutoscroll() } function clearPressState() { pressedItem = null pressX = -1 pressY = -1 } function rectangleSelect(x, y, width, height) { var indexes = [] for (var i = y; i <= y + height; i += 10) { const index = control.indexAt(control.leftMargin, i) if(!indexes.includes(index) && index > -1 && index < control.count) indexes.push(index) } cachedRectangleSelection = indexes } function updateSelection(modifier) { if (modifier & Qt.ShiftModifier) { dirModel.setRangeSelected(anchorIndex, currentIndex) } else { dirModel.clearSelection() dirModel.setSelected(currentIndex) if (currentIndex == -1) previouslySelectedItemIndex = -1 previouslySelectedItemIndex = currentIndex } } Component { id: editorComponent TextField { id: _editor visible: false wrapMode: Text.NoWrap verticalAlignment: TextEdit.AlignVCenter z: 999 property Item targetItem: null onTargetItemChanged: { if (targetItem != null) { var pos = control.mapFromItem(targetItem, targetItem.labelArea.x, targetItem.labelArea.y) width = targetItem.labelArea.width height = FishUI.Units.fontMetrics.height + FishUI.Units.largeSpacing * 2 x = control.mapFromItem(targetItem.labelArea, 0, 0).x y = pos.y + (targetItem.height - height) / 2 text = targetItem.labelArea.text targetItem.labelArea.visible = false targetItem.labelArea2.visible = false _editor.select(0, dirModel.fileExtensionBoundary(targetItem.index)) visible = true control.interactive = false } else { x: 0 y: 0 visible = false control.interactive = true } } onVisibleChanged: { if (visible) _editor.forceActiveFocus() else control.forceActiveFocus() } Keys.onPressed: { switch (event.key) { case Qt.Key_Return: case Qt.Key_Enter: commit() event.accepted = true break case Qt.Key_Escape: cancel() event.accepted = true break } } function commit() { if (targetItem) { targetItem.labelArea.visible = true targetItem.labelArea2.visible = true dirModel.rename(targetItem.index, text) control.currentIndex = targetItem.index targetItem = null control.editor.destroy() } } function cancel() { if (targetItem) { targetItem.labelArea.visible = true targetItem.labelArea2.visible = true control.currentIndex = targetItem.index targetItem = null control.editor.destroy() } } } } }