filemanager/qml/FolderGridView.qml

353 lines
12 KiB
QML

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import Cutefish.FileManager 1.0
import MeuiKit 1.0 as Meui
GridView {
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 itemSize: settings.gridIconSize
property var itemWidth: itemSize + Meui.Units.largeSpacing
property var itemHeight: itemSize + Meui.Units.fontMetrics.height * 2
property variant cachedRectangleSelection: null
property bool scrollLeft: false
property bool scrollRight: false
property bool scrollUp: false
property bool scrollDown: false
signal keyPress(var event)
highlightMoveDuration: 0
keyNavigationEnabled : true
keyNavigationWraps : 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
}
}
cellHeight: {
// var extraHeight = calcExtraSpacing(itemHeight, control.height - topMargin - bottomMargin)
return itemHeight // + extraHeight
}
cellWidth: {
var extraWidth = calcExtraSpacing(itemWidth, control.width - leftMargin - rightMargin)
return itemWidth + extraWidth
}
clip: true
currentIndex: -1
ScrollBar.vertical: ScrollBar { }
onPressXChanged: {
cPress = mapToItem(control.contentItem, pressX, pressY)
}
onPressYChanged: {
cPress = mapToItem(control.contentItem, pressX, pressY)
}
onCachedRectangleSelectionChanged: {
if (cachedRectangleSelection === null)
return
if (cachedRectangleSelection.length)
control.currentIndex[0]
folderModel.updateSelection(cachedRectangleSelection, control.ctrlPressed)
}
MouseArea {
id: _mouseArea
anchors.fill: parent
propagateComposedEvents: true
preventStealing: true
acceptedButtons: Qt.RightButton | Qt.LeftButton
hoverEnabled: true
enabled: true
z: -1
onPressed: {
control.forceActiveFocus()
pressX = mouse.x
pressY = mouse.y
// 如果找到 hoveredItem 则点击后设置成为 pressedItem
if (hoveredItem) {
pressedItem = hoveredItem
// 这里更改 currentIndex 会影响到范围选择
if (!control.shiftPressed)
currentIndex = pressedItem.index
// Shift 处理, 选择区域
if (control.shiftPressed && control.currentIndex !== -1) {
folderModel.setRangeSelected(control.anchorIndex, hoveredItem.index)
} else {
// Ctrl 处理
if (!control.ctrlPressed && !folderModel.isSelected(hoveredItem.index)) {
folderModel.clearSelection()
}
// Item 选择
if (control.ctrlPressed) {
folderModel.toggleSelected(hoveredItem.index)
} else {
folderModel.setSelected(hoveredItem.index)
}
}
// 弹出 Item 菜单
if (mouse.buttons & Qt.RightButton) {
clearPressState()
folderModel.openContextMenu(null, mouse.modifiers)
mouse.accepted = true
}
} else {
// 处理空白区域点击
if (!control.ctrlPressed) {
control.currentIndex = -1
folderModel.clearSelection()
}
// 弹出文件夹菜单
if (mouse.buttons & Qt.RightButton) {
clearPressState()
folderModel.openContextMenu(null, mouse.modifiers)
mouse.accepted = true
}
}
}
onPositionChanged: {
control.ctrlPressed = (mouse.modifiers & Qt.ControlModifier)
control.shiftPressed = (mouse.modifiers & Qt.ShiftModifier)
var index = control.indexAt(mouse.x + control.contentX,
mouse.y + control.contentY)
var indexItem = control.itemAtIndex(index)
// Set hoveredItem.
if (indexItem) {
var iconPos = mapToItem(indexItem.iconArea, mouse.x, mouse.y)
var textPos = mapToItem(indexItem.textArea, mouse.x, mouse.y)
if (iconPos.x < 0 || iconPos.y < 0
|| iconPos.x > indexItem.iconArea.paintedWidth
|| iconPos.y > indexItem.iconArea.paintedHeight + indexItem.textArea.paintedHeight) {
control.hoveredItem = null
} else {
control.hoveredItem = indexItem
}
} else {
control.hoveredItem = null
}
// Update rubberband geometry.
if (control.rubberBand) {
var cPos = mapToItem(control.contentItem, mouse.x, mouse.y)
var leftEdge = Math.min(control.contentX, control.originX)
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
const 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
const ceil = Math.max(control.height, control.contentItem.height)
rB.height = Math.min(ceil - 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
}
if (pressX != -1) {
if (pressedItem != null && folderModel.isSelected(pressedItem.index)) {
control.dragX = mouse.x
control.dragY = mouse.y
control.verticalDropHitscanOffset = pressedItem.y + (pressedItem.height / 2)
folderModel.dragSelected(mouse.x, mouse.y)
control.dragX = -1
control.dragY = -1
clearPressState()
} else {
folderModel.pinSelection()
control.rubberBand = rubberBandObject.createObject(control.contentItem, {x: cPress.x, y: cPress.y})
control.interactive = false
}
}
}
onClicked: {
clearPressState()
if (mouse.buttons & Qt.RightButton) {
folderModel.openContextMenu(null, mouse.modifiers)
}
}
onDoubleClicked: {
if (mouse.button === Qt.LeftButton && control.pressedItem)
folderModel.openSelected()
}
onReleased: pressCanceled()
onCanceled: pressCanceled()
}
function clearPressState() {
pressedItem = null
pressX = -1
pressY = -1
}
function pressCanceled() {
if (control.rubberBand) {
control.rubberBand.close()
control.rubberBand = null
control.interactive = true
// control.cachedRectangleSelection = null
folderModel.unpinSelection()
}
clearPressState()
}
function rectangleSelect(x, y, width, height) {
var rows = (control.flow === GridView.FlowLeftToRight)
var axis = rows ? control.width : control.height
var step = rows ? cellWidth : cellHeight
var perStripe = Math.floor(axis / step)
var stripes = Math.ceil(control.count / perStripe)
var cWidth = control.cellWidth - (2 * Meui.Units.smallSpacing)
var cHeight = control.cellHeight - (2 * Meui.Units.smallSpacing)
var midWidth = control.cellWidth / 2
var midHeight = control.cellHeight / 2
var indices = []
for (var s = 0; s < stripes; s++) {
for (var i = 0; i < perStripe; i++) {
var index = (s * perStripe) + i
if (index >= control.count) {
break
}
if (folderModel.isBlank(index)) {
continue
}
var itemX = ((rows ? i : s) * control.cellWidth)
var itemY = ((rows ? s : i) * control.cellHeight)
if (control.effectiveLayoutDirection === Qt.RightToLeft) {
itemX -= (rows ? control.contentX : control.originX)
itemX += cWidth
itemX = (rows ? control.width : control.contentItem.width) - itemX
}
// Check if the rubberband intersects this cell first to avoid doing more
// expensive work.
if (control.rubberBand.intersects(Qt.rect(itemX + Meui.Units.smallSpacing, itemY + Meui.Units.smallSpacing,
cWidth, cHeight))) {
var item = control.contentItem.childAt(itemX + midWidth, itemY + midHeight)
// If this is a visible item, check for intersection with the actual
// icon or label rects for better feel.
if (item && item.iconArea) {
var iconRect = Qt.rect(itemX + item.iconArea.x, itemY + item.iconArea.y,
item.iconArea.width, item.iconArea.height)
if (control.rubberBand.intersects(iconRect)) {
indices.push(index)
continue
}
var labelRect = Qt.rect(itemX + item.textArea.x, itemY + item.textArea.y,
item.textArea.width, item.textArea.height)
if (control.rubberBand.intersects(labelRect)) {
indices.push(index)
continue
}
} else {
// Otherwise be content with the cell intersection.
indices.push(index)
}
}
}
}
control.cachedRectangleSelection = indices
}
function calcExtraSpacing(cellSize, containerSize) {
var availableColumns = Math.floor(containerSize / cellSize)
var extraSpacing = 0
if (availableColumns > 0) {
var allColumnSize = availableColumns * cellSize
var extraSpace = Math.max(containerSize - allColumnSize, Meui.Units.smallSpacing)
extraSpacing = extraSpace / availableColumns
}
return Math.floor(extraSpacing)
}
}