Warning, /plasma/plasma-desktop/containments/desktop/package/contents/ui/FolderView.qml is written in an unsupported language. File is not indexed.
0001 /* 0002 SPDX-FileCopyrightText: 2014-2015 Eike Hein <hein@kde.org> 0003 SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 import QtQuick 2.15 0009 import QtQuick.Layouts 1.15 0010 import QtQml 2.15 0011 0012 import org.kde.plasma.plasmoid 2.0 0013 import org.kde.kirigami 2.20 as Kirigami 0014 import org.kde.plasma.components 3.0 as PlasmaComponents 0015 import org.kde.kquickcontrolsaddons 2.0 0016 0017 import org.kde.private.desktopcontainment.folder 0.1 as Folder 0018 import "code/FolderTools.js" as FolderTools 0019 0020 FocusScope { 0021 id: main 0022 0023 signal pressed() 0024 0025 property QtObject model: dir 0026 property Item rubberBand: null 0027 0028 property alias view: gridView 0029 property alias isRootView: gridView.isRootView 0030 property alias currentIndex: gridView.currentIndex 0031 property alias url: dir.url 0032 property alias status: dir.status 0033 property alias perStripe: positioner.perStripe 0034 property alias positions: positioner.positions 0035 property alias errorString: dir.errorString 0036 property alias dragging: dir.dragging 0037 property alias dragInProgressAnywhere: dir.dragInProgressAnywhere 0038 property alias locked: dir.locked 0039 property alias sortMode: dir.sortMode 0040 property alias filterMode: dir.filterMode 0041 property alias filterPattern: dir.filterPattern 0042 property alias filterMimeTypes: dir.filterMimeTypes 0043 property alias showHiddenFiles: dir.showHiddenFiles 0044 property alias flow: gridView.flow 0045 property alias layoutDirection: gridView.layoutDirection 0046 property alias cellWidth: gridView.cellWidth 0047 property alias cellHeight: gridView.cellHeight 0048 property alias overflowing: gridView.overflowing 0049 property alias scrollLeft: gridView.scrollLeft 0050 property alias scrollRight: gridView.scrollRight 0051 property alias scrollUp: gridView.scrollUp 0052 property alias scrollDown: gridView.scrollDown 0053 property alias hoveredItem: gridView.hoveredItem 0054 property var history: [] 0055 property var lastPosition: null 0056 property bool goingBack: false 0057 property Item backButton: null 0058 property var dialog: null 0059 property Item editor: null 0060 0061 property int previouslySelectedItemIndex: -1 0062 0063 function positionViewAtBeginning() { 0064 gridView.positionViewAtBeginning(); 0065 } 0066 0067 function rename() { 0068 if (gridView.currentIndex !== -1) { 0069 var renameAction = folderView.model.action("rename"); 0070 if (renameAction && !renameAction.enabled) { 0071 return; 0072 } 0073 0074 if (!editor) { 0075 editor = editorComponent.createObject(listener); 0076 } 0077 0078 editor.targetItem = gridView.currentItem; 0079 } 0080 } 0081 0082 function cancelRename() { 0083 if (editor) { 0084 editor.targetItem = null; 0085 } 0086 } 0087 0088 function linkHere(sourceUrl) { 0089 dir.linkHere(sourceUrl); 0090 } 0091 0092 function handleDragMove(x, y) { 0093 var child = childAt(x, y); 0094 0095 if (child !== null && child === backButton) { 0096 hoveredItem = null; 0097 backButton.handleDragMove(); 0098 } else { 0099 if (backButton && backButton.containsDrag) { 0100 backButton.endDragMove(); 0101 } 0102 0103 var pos = mapToItem(gridView.contentItem, x, y); 0104 var item = gridView.itemAt(pos.x, pos.y); 0105 0106 if (item && item.isDir) { 0107 hoveredItem = item; 0108 } else { 0109 hoveredItem = null; 0110 } 0111 } 0112 } 0113 0114 function endDragMove() { 0115 if (backButton && backButton.active) { 0116 backButton.endDragMove(); 0117 } else if (hoveredItem && !hoveredItem.popupDialog) { 0118 hoveredItem = null; 0119 } 0120 } 0121 0122 function dropItemAt(pos) { 0123 var item = gridView.itemAt(pos.x, pos.y); 0124 0125 if (item) { 0126 if (item.blank) { 0127 return -1; 0128 } 0129 0130 var hOffset = Math.abs(Math.min(gridView.contentX, gridView.originX)); 0131 var hPos = mapToItem(item.hoverArea, pos.x + hOffset, pos.y); 0132 0133 if ((hPos.x < 0 || hPos.y < 0 || hPos.x > item.hoverArea.width || hPos.y > item.hoverArea.height)) { 0134 return -1; 0135 } else { 0136 return positioner.map(item.index); 0137 } 0138 } 0139 0140 return -1; 0141 } 0142 0143 function drop(target, event, pos) { 0144 var dropPos = mapToItem(gridView.contentItem, pos.x, pos.y); 0145 var dropIndex = gridView.indexAt(dropPos.x, dropPos.y); 0146 var dragPos = mapToItem(gridView.contentItem, listener.dragX, listener.dragY); 0147 var dragIndex = gridView.indexAt(dragPos.x, dragPos.y); 0148 0149 if (listener.dragX === -1 || dragIndex !== dropIndex) { 0150 dir.drop(target, event, dropItemAt(dropPos), root.isContainment && !Plasmoid.immutable); 0151 } 0152 } 0153 0154 Connections { 0155 target: dir 0156 function onPopupMenuAboutToShow(dropJob, mimeData, x, y) { 0157 if (root.isContainment && !Plasmoid.immutable) { 0158 root.processMimeData(mimeData, x, y, dropJob); 0159 } 0160 } 0161 } 0162 0163 Connections { 0164 target: root 0165 function onExpandedChanged() { 0166 if (root.expanded && dir.status === Folder.FolderModel.Ready && !gridView.model) { 0167 gridView.model = positioner; 0168 } 0169 } 0170 } 0171 0172 Binding { 0173 target: Plasmoid 0174 property: "busy" 0175 value: !gridView.model && dir.status === Folder.FolderModel.Listing 0176 restoreMode: Binding.RestoreBinding 0177 } 0178 0179 function makeBackButton() { 0180 return Qt.createQmlObject("BackButtonItem {}", main); 0181 } 0182 0183 function doCd(row) { 0184 history.push({ url: url, index: gridView.currentIndex, yPosition: gridView.visibleArea.yPosition}); 0185 updateHistory(); 0186 dir.cd(row); 0187 gridView.currentIndex = -1; 0188 } 0189 0190 function doBack() { 0191 goingBack = true; 0192 gridView.currentIndex = -1; 0193 lastPosition = history.pop(); 0194 url = lastPosition.url; 0195 updateHistory(); 0196 } 0197 0198 // QML doesn't detect change in the array(history) property, so update it explicitly. 0199 function updateHistory() { 0200 history = history; 0201 } 0202 0203 Connections { 0204 target: root 0205 0206 function onIsPopupChanged() { 0207 if (backButton === null && root.useListViewMode) { 0208 backButton = makeBackButton(); 0209 } else if (backButton !== null) { 0210 backButton.destroy(); 0211 } 0212 } 0213 } 0214 0215 Folder.EventGenerator { 0216 id: eventGenerator 0217 } 0218 0219 MouseEventListener { 0220 id: listener 0221 0222 anchors { 0223 topMargin: backButton !== null ? backButton.height : undefined 0224 fill: parent 0225 } 0226 0227 property alias hoveredItem: gridView.hoveredItem 0228 0229 property Item pressedItem: null 0230 property int pressX: -1 0231 property int pressY: -1 0232 property int dragX: -1 0233 property int dragY: -1 0234 property var cPress: null 0235 property bool doubleClickInProgress: false 0236 0237 acceptedButtons: { 0238 if (hoveredItem === null && main.isRootView) { 0239 return root.isPopup ? (Qt.LeftButton | Qt.MiddleButton | Qt.BackButton) : Qt.LeftButton; 0240 } 0241 0242 return root.isPopup ? (Qt.LeftButton | Qt.MiddleButton | Qt.RightButton | Qt.BackButton) 0243 : (Qt.LeftButton | Qt.RightButton); 0244 } 0245 0246 hoverEnabled: true 0247 0248 onPressXChanged: { 0249 cPress = mapToItem(gridView.contentItem, pressX, pressY); 0250 } 0251 0252 onPressYChanged: { 0253 cPress = mapToItem(gridView.contentItem, pressX, pressY); 0254 } 0255 0256 onPressed: mouse => { 0257 // Ignore press events outside the viewport (i.e. on scrollbars). 0258 if (!scrollArea.viewport.contains(Qt.point(mouse.x, mouse.y))) { 0259 return; 0260 } 0261 0262 scrollArea.focus = true; 0263 0264 if (mouse.buttons & Qt.BackButton) { 0265 if (root.isPopup && dir.resolvedUrl !== dir.resolve(Plasmoid.configuration.url)) { 0266 doBack(); 0267 mouse.accepted = true; 0268 } 0269 0270 return; 0271 } 0272 0273 if (editor && childAt(mouse.x, mouse.y) !== editor) { 0274 editor.commit(); 0275 } 0276 0277 if (mouse.source === Qt.MouseEventSynthesizedByQt) { 0278 var index = gridView.indexAt(mouse.x, mouse.y); 0279 var indexItem = gridView.itemAtIndex(index); 0280 if (indexItem && indexItem.iconArea) { 0281 gridView.currentIndex = index; 0282 hoveredItem = indexItem; 0283 } else { 0284 hoveredItem = null; 0285 } 0286 if (gridView.hoveredItem && gridView.hoveredItem.toolTip.active) { 0287 gridView.hoveredItem.toolTip.hideToolTip(); 0288 } 0289 } 0290 0291 pressX = mouse.x; 0292 pressY = mouse.y; 0293 0294 if (!hoveredItem || hoveredItem.blank) { 0295 if (!gridView.ctrlPressed) { 0296 gridView.currentIndex = -1; 0297 previouslySelectedItemIndex = -1; 0298 dir.clearSelection(); 0299 } 0300 0301 if (mouse.buttons & Qt.RightButton) { 0302 clearPressState(); 0303 dir.openContextMenu(main, mouse.modifiers); 0304 mouse.accepted = true; 0305 } 0306 } else { 0307 pressedItem = hoveredItem; 0308 0309 var pos = mapToItem(hoveredItem.actionsOverlay, mouse.x, mouse.y); 0310 0311 if (!(pos.x <= hoveredItem.actionsOverlay.width && pos.y <= hoveredItem.actionsOverlay.height)) { 0312 if (gridView.shiftPressed && gridView.currentIndex !== -1) { 0313 positioner.setRangeSelected(gridView.anchorIndex, hoveredItem.index); 0314 } else { 0315 // Deselecting everything else when one item is clicked is handled in onReleased in order to distinguish between drag and click 0316 if (!gridView.ctrlPressed && !dir.isSelected(positioner.map(hoveredItem.index))) { 0317 previouslySelectedItemIndex = -1; 0318 dir.clearSelection(); 0319 } 0320 0321 if (gridView.ctrlPressed) { 0322 dir.toggleSelected(positioner.map(hoveredItem.index)); 0323 } else { 0324 dir.setSelected(positioner.map(hoveredItem.index)); 0325 } 0326 } 0327 0328 gridView.currentIndex = hoveredItem.index; 0329 0330 if (mouse.buttons & Qt.RightButton) { 0331 if (pressedItem.toolTip && pressedItem.toolTip.active) { 0332 pressedItem.toolTip.hideToolTip(); 0333 } 0334 0335 clearPressState(); 0336 0337 dir.openContextMenu(hoveredItem, mouse.modifiers); 0338 mouse.accepted = true; 0339 } 0340 } 0341 } 0342 0343 main.pressed(); 0344 } 0345 0346 onCanceled: pressCanceled() 0347 0348 onReleased: mouse => { 0349 if (hoveredItem && !hoveredItem.blank && mouse.button !== Qt.RightButton) { 0350 var pos = mapToItem(hoveredItem.actionsOverlay, mouse.x, mouse.y); 0351 if (!(pos.x <= hoveredItem.actionsOverlay.width && pos.y <= hoveredItem.actionsOverlay.height) 0352 && (!(gridView.shiftPressed && gridView.currentIndex !== -1) && !gridView.ctrlPressed)) { 0353 dir.clearSelection(); 0354 dir.setSelected(positioner.map(hoveredItem.index)); 0355 } 0356 } 0357 pressCanceled(); 0358 } 0359 0360 onPressAndHold: mouse => { 0361 if (mouse.source === Qt.MouseEventSynthesizedByQt) { 0362 if (pressedItem) { 0363 if (pressedItem.toolTip && pressedItem.toolTip.active) { 0364 pressedItem.toolTip.hideToolTip(); 0365 } 0366 } 0367 clearPressState(); 0368 if (hoveredItem) { 0369 dir.openContextMenu(hoveredItem, mouse.modifiers); 0370 } 0371 } 0372 } 0373 0374 onClicked: mouse => { 0375 clearPressState(); 0376 0377 if (mouse.button === Qt.RightButton || 0378 (editor && childAt(mouse.x, mouse.y) === editor)) { 0379 return; 0380 } 0381 0382 if (!hoveredItem || hoveredItem.blank || gridView.currentIndex === -1 || gridView.ctrlPressed || gridView.shiftPressed) { 0383 // Bug 357367: Replay mouse event, so containment actions assigned to left mouse button work. 0384 eventGenerator.sendMouseEvent(root, Folder.EventGenerator.MouseButtonPress, mouse.x, mouse.y, mouse.button, mouse.buttons, mouse.modifiers); 0385 return; 0386 } 0387 0388 var pos = mapToItem(hoveredItem, mouse.x, mouse.y); 0389 0390 // Moving from an item to its preview popup dialog doesn't unset hoveredItem 0391 // even though the cursor has left it, so we need to check whether the click 0392 // actually occurred inside the item we expect it in before going ahead. If it 0393 // didn't, clean up (e.g. dismissing the dialog as a side-effect of unsetting 0394 // hoveredItem) and abort. 0395 if (pos.x < 0 || pos.x > hoveredItem.width || pos.y < 0 || pos.y > hoveredItem.height) { 0396 hoveredItem = null; 0397 previouslySelectedItemIndex = -1; 0398 dir.clearSelection(); 0399 0400 return; 0401 // If the hoveredItem is clicked while having a preview popup dialog open, 0402 // only dismiss the dialog and abort. 0403 } else if (hoveredItem.popupDialog) { 0404 hoveredItem.closePopup(); 0405 0406 return; 0407 } 0408 0409 pos = mapToItem(hoveredItem.actionsOverlay, mouse.x, mouse.y); 0410 0411 if (!(pos.x <= hoveredItem.actionsOverlay.width && pos.y <= hoveredItem.actionsOverlay.height)) { 0412 0413 // Clicked on the label of an already-selected item: rename it 0414 if (pos.x > hoveredItem.labelArea.x 0415 && pos.x <= hoveredItem.labelArea.x + hoveredItem.labelArea.width 0416 && pos.y > hoveredItem.labelArea.y 0417 && pos.y <= hoveredItem.labelArea.y + hoveredItem.labelArea.height 0418 && previouslySelectedItemIndex === gridView.currentIndex 0419 && gridView.currentIndex !== -1 0420 && !Qt.styleHints.singleClickActivation 0421 && Plasmoid.configuration.renameInline 0422 && !doubleClickInProgress 0423 ) { 0424 rename(); 0425 return; 0426 } 0427 0428 // Single-click mode or list view and single-clicked on the item or 0429 // double-click mode and double-clicked on the item: open it 0430 if (Qt.styleHints.singleClickActivation || root.useListViewMode || doubleClickInProgress || mouse.source === Qt.MouseEventSynthesizedByQt) { 0431 var func = root.useListViewMode && mouse.button === Qt.LeftButton && hoveredItem.isDir ? doCd : dir.run; 0432 func(positioner.map(gridView.currentIndex)); 0433 previouslySelectedItemIndex = gridView.currentIndex; 0434 hoveredItem = null; 0435 } else { 0436 // None of the above: select it 0437 doubleClickInProgress = true; 0438 doubleClickTimer.interval = Qt.styleHints.mouseDoubleClickInterval; 0439 doubleClickTimer.start(); 0440 previouslySelectedItemIndex = gridView.currentIndex; 0441 } 0442 } 0443 } 0444 0445 onPositionChanged: mouse => { 0446 gridView.ctrlPressed = (mouse.modifiers & Qt.ControlModifier); 0447 gridView.shiftPressed = (mouse.modifiers & Qt.ShiftModifier); 0448 0449 var cPos = mapToItem(gridView.contentItem, mouse.x, mouse.y); 0450 var item = gridView.itemAt(cPos.x, cPos.y); 0451 var leftEdge = Math.min(gridView.contentX, gridView.originX); 0452 0453 if (!item || item.blank) { 0454 if (gridView.hoveredItem && !root.containsDrag && (!dialog || !dialog.containsDrag) && !gridView.hoveredItem.popupDialog) { 0455 gridView.hoveredItem = null; 0456 } 0457 } else { 0458 var fPos = mapToItem(item.frame, mouse.x, mouse.y); 0459 0460 if (fPos.x < 0 || fPos.y < 0 || fPos.x > item.frame.width || fPos.y > item.frame.height) { 0461 gridView.hoveredItem = null; 0462 } 0463 } 0464 0465 // Trigger autoscroll. 0466 if (pressX !== -1) { 0467 gridView.scrollLeft = (mouse.x <= 0 && gridView.contentX > leftEdge); 0468 gridView.scrollRight = (mouse.x >= gridView.width 0469 && gridView.contentX < gridView.contentItem.width - gridView.width); 0470 gridView.scrollUp = (mouse.y <= 0 && gridView.contentY > 0); 0471 gridView.scrollDown = (mouse.y >= gridView.height 0472 && gridView.contentY < gridView.contentItem.height - gridView.height); 0473 } 0474 0475 // Update rubberband geometry. 0476 if (main.rubberBand) { 0477 var rB = main.rubberBand; 0478 0479 if (cPos.x < cPress.x) { 0480 rB.x = Math.max(leftEdge, cPos.x); 0481 rB.width = Math.abs(rB.x - cPress.x); 0482 } else { 0483 rB.x = cPress.x; 0484 var ceil = Math.max(gridView.width, gridView.contentItem.width) + leftEdge; 0485 rB.width = Math.min(ceil - rB.x, Math.abs(rB.x - cPos.x)); 0486 } 0487 0488 if (cPos.y < cPress.y) { 0489 rB.y = Math.max(0, cPos.y); 0490 rB.height = Math.abs(rB.y - cPress.y); 0491 } else { 0492 rB.y = cPress.y; 0493 var ceil = Math.max(gridView.height, gridView.contentItem.height); 0494 rB.height = Math.min(ceil - rB.y, Math.abs(rB.y - cPos.y)); 0495 } 0496 0497 // Ensure rubberband is at least 1px in size or else it will become 0498 // invisible and not match any items. 0499 rB.width = Math.max(1, rB.width); 0500 rB.height = Math.max(1, rB.height); 0501 0502 gridView.rectangleSelect(rB.x, rB.y, rB.width, rB.height); 0503 0504 return; 0505 } 0506 0507 // Drag initiation. 0508 if (pressX !== -1 && root.isDrag(pressX, pressY, mouse.x, mouse.y)) { 0509 if (pressedItem !== null && dir.isSelected(positioner.map(pressedItem.index))) { 0510 pressedItem.toolTip.hideToolTip(); 0511 dragX = mouse.x; 0512 dragY = mouse.y; 0513 gridView.verticalDropHitscanOffset = pressedItem.iconArea.y + (pressedItem.iconArea.height / 2); 0514 dir.dragSelected(mouse.x, mouse.y); 0515 dragX = -1; 0516 dragY = -1; 0517 clearPressState(); 0518 } else { 0519 // Disable rubberband in popup list view mode or while renaming 0520 if (root.useListViewMode || (editor && editor.targetItem)) { 0521 return; 0522 } 0523 0524 dir.pinSelection(); 0525 main.rubberBand = rubberBandObject.createObject(gridView.contentItem, {x: cPress.x, y: cPress.y}); 0526 gridView.interactive = false; 0527 } 0528 } 0529 } 0530 0531 Component { 0532 id: rubberBandObject 0533 0534 Folder.RubberBand { 0535 id: rubberBand 0536 0537 width: 0 0538 height: 0 0539 z: 99999 0540 0541 function close() { 0542 opacityAnimation.restart(); 0543 } 0544 0545 OpacityAnimator { 0546 id: opacityAnimation 0547 target: rubberBand 0548 to: 0 0549 from: 1 0550 duration: Kirigami.Units.shortDuration 0551 0552 // This easing curve has an elognated start, which works 0553 // better than a standard easing curve for the rubberband 0554 // animation, which fades out fast and is generally of a 0555 // small area. 0556 easing { 0557 bezierCurve: [0.4, 0.0, 1, 1] 0558 type: Easing.Bezier 0559 } 0560 0561 onFinished: { 0562 rubberBand.visible = false; 0563 rubberBand.enabled = false; 0564 rubberBand.destroy(); 0565 } 0566 } 0567 } 0568 } 0569 0570 onContainsMouseChanged: { 0571 if (!containsMouse && !main.rubberBand) { 0572 clearPressState(); 0573 0574 if (gridView.hoveredItem && !gridView.hoveredItem.popupDialog) { 0575 gridView.hoveredItem = null; 0576 } 0577 } 0578 } 0579 0580 onHoveredItemChanged: { 0581 doubleClickInProgress = false; 0582 0583 if (!hoveredItem) { 0584 hoverActivateTimer.stop(); 0585 } 0586 } 0587 0588 function pressCanceled() { 0589 if (main.rubberBand) { 0590 main.rubberBand.close(); 0591 main.rubberBand = null; 0592 0593 gridView.interactive = true; 0594 gridView.cachedRectangleSelection = null; 0595 dir.unpinSelection(); 0596 } 0597 0598 clearPressState(); 0599 gridView.cancelAutoscroll(); 0600 } 0601 0602 function clearPressState() { 0603 pressedItem = null; 0604 pressX = -1; 0605 pressY = -1; 0606 } 0607 0608 Timer { 0609 id: doubleClickTimer 0610 0611 onTriggered: { 0612 listener.doubleClickInProgress = false; 0613 } 0614 } 0615 0616 Timer { 0617 id: hoverActivateTimer 0618 0619 interval: root.hoverActivateDelay 0620 0621 onTriggered: { 0622 if (!hoveredItem) { 0623 return; 0624 } 0625 0626 if (root.useListViewMode) { 0627 doCd(index); 0628 } else if (Plasmoid.configuration.popups) { 0629 hoveredItem.openPopup(); 0630 } 0631 } 0632 } 0633 0634 FocusScope { 0635 id: scrollArea 0636 0637 anchors.fill: parent 0638 0639 focus: true 0640 0641 property bool ready: false 0642 readonly property int viewportWidth: scrollArea.ready && viewport ? Math.ceil(viewport.width) : 0 0643 readonly property int viewportHeight: scrollArea.ready && viewport ? Math.ceil(viewport.height) : 0 0644 readonly property Flickable viewport: gridView 0645 0646 Component.onCompleted: { 0647 scrollArea.ready = true; 0648 } 0649 0650 GridView { 0651 id: gridView 0652 clip: true 0653 anchors.fill: parent 0654 0655 property bool isRootView: false 0656 0657 property int iconSize: makeIconSize() 0658 property int verticalDropHitscanOffset: 0 0659 0660 property Item hoveredItem: null 0661 0662 property int anchorIndex: 0 0663 property bool ctrlPressed: false 0664 property bool shiftPressed: false 0665 0666 property bool overflowing: { 0667 // widthRatio or heightRatio may be 0 when it's not actually 0668 // overflowing, so account for that. 0669 let widthOverflow = visibleArea.widthRatio > 0.0 && visibleArea.widthRatio < 1.0 0670 let heightOverflow = visibleArea.heightRatio > 0.0 && visibleArea.heightRatio < 1.0 0671 return widthOverflow || heightOverflow 0672 } 0673 0674 property bool scrollLeft: false 0675 property bool scrollRight: false 0676 property bool scrollUp: false 0677 property bool scrollDown: false 0678 0679 property var cachedRectangleSelection: null 0680 0681 currentIndex: -1 0682 0683 keyNavigationWraps: false 0684 boundsBehavior: Flickable.StopAtBounds 0685 focus: true 0686 0687 PlasmaComponents.ScrollBar.vertical: PlasmaComponents.ScrollBar { 0688 id: verticalScrollBar 0689 } 0690 PlasmaComponents.ScrollBar.horizontal: PlasmaComponents.ScrollBar {} 0691 0692 function calcExtraSpacing(cellSize, containerSize) { 0693 var availableColumns = Math.floor(containerSize / cellSize); 0694 var extraSpacing = 0; 0695 if (availableColumns > 0) { 0696 var allColumnSize = availableColumns * cellSize; 0697 var extraSpace = Math.max(containerSize - allColumnSize, 0); 0698 extraSpacing = extraSpace / availableColumns; 0699 } 0700 return Math.floor(extraSpacing); 0701 } 0702 0703 cellWidth: { 0704 if (root.useListViewMode) { 0705 return gridView.width - (verticalScrollBar.visible ? verticalScrollBar.width : 0); 0706 } else { 0707 var iconWidth = iconSize + (2 * Kirigami.Units.gridUnit) + (2 * Kirigami.Units.smallSpacing); 0708 if (root.isContainment && isRootView && scrollArea.viewportWidth > 0) { 0709 var minIconWidth = Math.max(iconWidth, Kirigami.Units.iconSizes.small * ((Plasmoid.configuration.labelWidth * 2) + 4)); 0710 var extraWidth = calcExtraSpacing(minIconWidth, scrollArea.viewportWidth); 0711 return minIconWidth + extraWidth; 0712 } else { 0713 return iconWidth; 0714 } 0715 } 0716 } 0717 0718 cellHeight: { 0719 if (root.useListViewMode) { 0720 return Math.ceil((Math.max(Kirigami.Units.iconSizes.sizeForLabels, iconSize) 0721 + Math.max(highlightItemSvg.margins.top + highlightItemSvg.margins.bottom, 0722 listItemSvg.margins.top + listItemSvg.margins.bottom)) / 2) * 2; 0723 } else { 0724 // the smallSpacings are for padding 0725 var iconHeight = iconSize + (Kirigami.Units.gridUnit * Plasmoid.configuration.textLines) + (Kirigami.Units.smallSpacing * 3); 0726 if (root.isContainment && isRootView && scrollArea.viewportHeight > 0) { 0727 var extraHeight = calcExtraSpacing(iconHeight, scrollArea.viewportHeight); 0728 return iconHeight + extraHeight; 0729 } else { 0730 return iconHeight; 0731 } 0732 } 0733 } 0734 0735 delegate: FolderItemDelegate { 0736 width: gridView.cellWidth 0737 height: gridView.cellHeight 0738 } 0739 0740 onContentXChanged: { 0741 if (hoveredItem) { 0742 hoverActivateTimer.stop(); 0743 } 0744 0745 cancelRename(); 0746 0747 dir.setDragHotSpotScrollOffset(contentX, contentY); 0748 0749 if (contentX === 0) { 0750 scrollLeft = false; 0751 } 0752 0753 if (contentX === contentItem.width - width) { 0754 scrollRight = false; 0755 } 0756 0757 // Update rubberband geometry. 0758 if (main.rubberBand) { 0759 var rB = main.rubberBand; 0760 0761 if (scrollLeft) { 0762 rB.x = Math.min(gridView.contentX, gridView.originX); 0763 rB.width = listener.cPress.x; 0764 } 0765 0766 if (scrollRight) { 0767 var lastCol = gridView.contentX + gridView.width; 0768 rB.width = lastCol - rB.x; 0769 } 0770 0771 gridView.rectangleSelect(rB.x, rB.y, rB.width, rB.height); 0772 } 0773 } 0774 0775 onContentYChanged: { 0776 if (hoveredItem) { 0777 hoverActivateTimer.stop(); 0778 } 0779 0780 cancelRename(); 0781 0782 dir.setDragHotSpotScrollOffset(contentX, contentY); 0783 0784 if (contentY === 0) { 0785 scrollUp = false; 0786 } 0787 0788 if (contentY === contentItem.height - height) { 0789 scrollDown = false; 0790 } 0791 0792 // Update rubberband geometry. 0793 if (main.rubberBand) { 0794 var rB = main.rubberBand; 0795 0796 if (scrollUp) { 0797 rB.y = 0; 0798 rB.height = listener.cPress.y; 0799 } 0800 0801 if (scrollDown) { 0802 var lastRow = gridView.contentY + gridView.height; 0803 rB.height = lastRow - rB.y; 0804 } 0805 0806 gridView.rectangleSelect(rB.x, rB.y, rB.width, rB.height); 0807 } 0808 } 0809 0810 onScrollLeftChanged: { 0811 if (scrollLeft && gridView.visibleArea.widthRatio < 1.0) { 0812 smoothX.enabled = true; 0813 contentX = (gridView.flow === GridView.FlowLeftToRight) ? gridView.contentX : gridView.originX; 0814 } else { 0815 contentX = contentX; 0816 smoothX.enabled = false; 0817 } 0818 } 0819 0820 onScrollRightChanged: { 0821 if (scrollRight && gridView.visibleArea.widthRatio < 1.0) { 0822 smoothX.enabled = true; 0823 contentX = ((gridView.flow === GridView.FlowLeftToRight) ? gridView.contentX : gridView.originX) 0824 + (contentItem.width - width); 0825 } else { 0826 contentX = contentX; 0827 smoothX.enabled = false; 0828 } 0829 } 0830 0831 onScrollUpChanged: { 0832 if (scrollUp && gridView.visibleArea.heightRatio < 1.0) { 0833 smoothY.enabled = true; 0834 contentY = 0; 0835 } else { 0836 contentY = contentY; 0837 smoothY.enabled = false; 0838 } 0839 } 0840 0841 onScrollDownChanged: { 0842 if (scrollDown && gridView.visibleArea.heightRatio < 1.0) { 0843 smoothY.enabled = true; 0844 contentY = contentItem.height - height; 0845 } else { 0846 contentY = contentY; 0847 smoothY.enabled = false; 0848 } 0849 } 0850 0851 onCurrentIndexChanged: { 0852 positionViewAtIndex(currentIndex, GridView.Contain); 0853 } 0854 0855 onCachedRectangleSelectionChanged: { 0856 if (cachedRectangleSelection === null) { 0857 return; 0858 } 0859 0860 if (cachedRectangleSelection.length) { 0861 // Set current index to start of selection. 0862 // cachedRectangleSelection is pre-sorted. 0863 currentIndex = cachedRectangleSelection[0]; 0864 } 0865 0866 dir.updateSelection(cachedRectangleSelection.map(row => positioner.map(row)), 0867 gridView.ctrlPressed); 0868 } 0869 0870 function makeIconSize() { 0871 if (root.useListViewMode) { 0872 return Kirigami.Units.iconSizes.small; 0873 } 0874 0875 return FolderTools.iconSizeFromTheme(Plasmoid.configuration.iconSize); 0876 } 0877 0878 function updateSelection(modifier) { 0879 if (modifier & Qt.ShiftModifier) { 0880 positioner.setRangeSelected(anchorIndex, currentIndex); 0881 } else { 0882 dir.clearSelection(); 0883 dir.setSelected(positioner.map(currentIndex)); 0884 if (currentIndex === -1) { 0885 previouslySelectedItemIndex = -1; 0886 } 0887 previouslySelectedItemIndex = currentIndex; 0888 } 0889 } 0890 0891 function cancelAutoscroll() { 0892 scrollLeft = false; 0893 scrollRight = false; 0894 scrollUp = false; 0895 scrollDown = false; 0896 } 0897 0898 function rectangleSelect(x, y, width, height) { 0899 var rows = (gridView.flow === GridView.FlowLeftToRight); 0900 var axis = rows ? gridView.width : gridView.height; 0901 var step = rows ? cellWidth : cellHeight; 0902 var perStripe = Math.floor(axis / step); 0903 var stripes = Math.ceil(gridView.count / perStripe); 0904 var cWidth = gridView.cellWidth - (2 * Kirigami.Units.smallSpacing); 0905 var cHeight = gridView.cellHeight - (2 * Kirigami.Units.smallSpacing); 0906 var midWidth = gridView.cellWidth / 2; 0907 var midHeight = gridView.cellHeight / 2; 0908 var indices = []; 0909 0910 for (var s = 0; s < stripes; s++) { 0911 for (var i = 0; i < perStripe; i++) { 0912 var index = (s * perStripe) + i; 0913 0914 if (index >= gridView.count) { 0915 break; 0916 } 0917 0918 if (positioner.isBlank(index)) { 0919 continue; 0920 } 0921 0922 var itemX = ((rows ? i : s) * gridView.cellWidth); 0923 var itemY = ((rows ? s : i) * gridView.cellHeight); 0924 0925 if (gridView.effectiveLayoutDirection === Qt.RightToLeft) { 0926 itemX -= (rows ? gridView.contentX : gridView.originX); 0927 itemX += cWidth; 0928 itemX = (rows ? gridView.width : gridView.contentItem.width) - itemX; 0929 } 0930 0931 // Check if the rubberband intersects this cell first to avoid doing more 0932 // expensive work. 0933 if (main.rubberBand.intersects(Qt.rect(itemX + Kirigami.Units.smallSpacing, itemY + Kirigami.Units.smallSpacing, 0934 cWidth, cHeight))) { 0935 var item = gridView.contentItem.childAt(itemX + midWidth, itemY + midHeight); 0936 0937 // If this is a visible item, check for intersection with the actual 0938 // icon or label rects for better feel. 0939 if (item && item.iconArea) { 0940 var iconRect = Qt.rect(itemX + item.iconArea.x, itemY + item.iconArea.y, 0941 item.iconArea.width, item.iconArea.height); 0942 0943 if (main.rubberBand.intersects(iconRect)) { 0944 indices.push(index); 0945 continue; 0946 } 0947 0948 var labelRect = Qt.rect(itemX + item.labelArea.x, itemY + item.labelArea.y, 0949 item.labelArea.width, item.labelArea.height); 0950 0951 if (main.rubberBand.intersects(labelRect)) { 0952 indices.push(index); 0953 continue; 0954 } 0955 } else { 0956 // Otherwise be content with the cell intersection. 0957 indices.push(index); 0958 } 0959 } 0960 } 0961 } 0962 0963 gridView.cachedRectangleSelection = indices; 0964 } 0965 0966 function runOrCdSelected() { 0967 if (currentIndex !== -1 && dir.hasSelection()) { 0968 if (root.useListViewMode && currentItem.isDir) { 0969 doCd(positioner.map(currentIndex)); 0970 } else { 0971 dir.runSelected(); 0972 } 0973 } 0974 } 0975 0976 Behavior on contentX { id: smoothX; enabled: false; SmoothedAnimation { velocity: 700 } } 0977 Behavior on contentY { id: smoothY; enabled: false; SmoothedAnimation { velocity: 700 } } 0978 0979 Keys.onReturnPressed: event => { 0980 if (event.modifiers === Qt.AltModifier) { 0981 dir.openPropertiesDialog(); 0982 } else { 0983 runOrCdSelected(); 0984 } 0985 } 0986 0987 Keys.onEnterPressed: event => Keys.returnPressed(event) 0988 0989 Keys.onMenuPressed: event => { 0990 if (currentIndex !== -1 && dir.hasSelection() && currentItem) { 0991 dir.setSelected(positioner.map(currentIndex)); 0992 dir.openContextMenu(currentItem.frame, event.modifiers); 0993 } else { 0994 // Otherwise let the containment handle it. 0995 event.accepted = false; 0996 } 0997 } 0998 0999 Keys.onEscapePressed: event => { 1000 if (!editor || !editor.targetItem) { 1001 previouslySelectedItemIndex = -1; 1002 dir.clearSelection(); 1003 event.accepted = false; 1004 } 1005 } 1006 1007 Folder.ShortCut { 1008 Component.onCompleted: { 1009 installAsEventFilterFor(gridView); 1010 } 1011 1012 onDeleteFile: { 1013 dir.deleteSelected(); 1014 } 1015 1016 onRenameFile: { 1017 rename(); 1018 } 1019 1020 onMoveToTrash: { 1021 const action = dir.action("trash"); 1022 if (action && action.enabled) { 1023 action.trigger(); 1024 } 1025 } 1026 1027 onCreateFolder: { 1028 model.createFolder(); 1029 } 1030 } 1031 1032 Keys.onPressed: event => { 1033 event.accepted = true; 1034 1035 if (event.key === Qt.Key_Control) { 1036 ctrlPressed = true; 1037 } else if (event.key === Qt.Key_Shift) { 1038 shiftPressed = true; 1039 1040 if (currentIndex !== -1) { 1041 anchorIndex = currentIndex; 1042 } 1043 } else if (event.key === Qt.Key_Home) { 1044 currentIndex = 0; 1045 updateSelection(event.modifiers); 1046 } else if (event.key === Qt.Key_End) { 1047 currentIndex = count - 1; 1048 updateSelection(event.modifiers); 1049 } else if (event.matches(StandardKey.Copy)) { 1050 dir.copy(); 1051 } else if (event.matches(StandardKey.Paste)) { 1052 dir.paste(); 1053 } else if (event.matches(StandardKey.Cut)) { 1054 dir.cut(); 1055 } else if (event.matches(StandardKey.Undo)) { 1056 dir.undo(); 1057 } else if (event.matches(StandardKey.Refresh)) { 1058 dir.refresh(); 1059 } else if (event.matches(StandardKey.SelectAll)) { 1060 positioner.setRangeSelected(0, count - 1); 1061 } else { 1062 event.accepted = false; 1063 } 1064 } 1065 1066 Keys.onReleased: event => { 1067 if (event.key === Qt.Key_Control) { 1068 ctrlPressed = false; 1069 } else if (event.key === Qt.Key_Shift) { 1070 shiftPressed = false; 1071 anchorIndex = 0; 1072 } 1073 } 1074 1075 Keys.onLeftPressed: event => { 1076 if (root.isPopup && root.useListViewMode) { 1077 if (dir.resolvedUrl !== dir.resolve(Plasmoid.configuration.url)) { 1078 doBack(); 1079 } 1080 } else if (positioner.enabled) { 1081 var newIndex = positioner.nearestItem(currentIndex, 1082 FolderTools.effectiveNavDirection(gridView.flow, gridView.effectiveLayoutDirection, Qt.LeftArrow)); 1083 1084 if (newIndex !== -1) { 1085 currentIndex = newIndex; 1086 updateSelection(event.modifiers); 1087 } 1088 } else { 1089 var oldIndex = currentIndex; 1090 1091 moveCurrentIndexLeft(); 1092 1093 if (oldIndex === currentIndex) { 1094 return; 1095 } 1096 1097 updateSelection(event.modifiers); 1098 } 1099 } 1100 1101 Keys.onRightPressed: event => { 1102 if (root.isPopup && root.useListViewMode) { 1103 if (currentIndex !== -1 && dir.hasSelection() && currentItem.isDir) { 1104 doCd(positioner.map(currentIndex)); 1105 } 1106 } else if (positioner.enabled) { 1107 var newIndex = positioner.nearestItem(currentIndex, 1108 FolderTools.effectiveNavDirection(gridView.flow, gridView.effectiveLayoutDirection, Qt.RightArrow)); 1109 1110 if (newIndex !== -1) { 1111 currentIndex = newIndex; 1112 updateSelection(event.modifiers); 1113 } 1114 } else { 1115 var oldIndex = currentIndex; 1116 1117 moveCurrentIndexRight(); 1118 1119 if (oldIndex === currentIndex) { 1120 return; 1121 } 1122 1123 updateSelection(event.modifiers); 1124 } 1125 } 1126 1127 Keys.onUpPressed: event => { 1128 if (positioner.enabled) { 1129 var newIndex = positioner.nearestItem(currentIndex, 1130 FolderTools.effectiveNavDirection(gridView.flow, gridView.effectiveLayoutDirection, Qt.UpArrow)); 1131 1132 if (newIndex !== -1) { 1133 currentIndex = newIndex; 1134 updateSelection(event.modifiers); 1135 } 1136 } else { 1137 var oldIndex = currentIndex; 1138 1139 moveCurrentIndexUp(); 1140 1141 if (oldIndex === currentIndex) { 1142 return; 1143 } 1144 1145 updateSelection(event.modifiers); 1146 } 1147 } 1148 1149 Keys.onDownPressed: event => { 1150 if (positioner.enabled) { 1151 var newIndex = positioner.nearestItem(currentIndex, 1152 FolderTools.effectiveNavDirection(gridView.flow, gridView.effectiveLayoutDirection, Qt.DownArrow)); 1153 1154 if (newIndex !== -1) { 1155 currentIndex = newIndex; 1156 updateSelection(event.modifiers); 1157 } 1158 } else { 1159 var oldIndex = currentIndex; 1160 1161 moveCurrentIndexDown(); 1162 1163 if (oldIndex === currentIndex) { 1164 return; 1165 } 1166 1167 updateSelection(event.modifiers); 1168 } 1169 } 1170 1171 Keys.onBackPressed: event => { 1172 if (root.isPopup && dir.resolvedUrl !== dir.resolve(Plasmoid.configuration.url)) { 1173 doBack(); 1174 } 1175 } 1176 1177 Connections { 1178 target: Plasmoid.configuration 1179 1180 function onIconSizeChanged() { 1181 gridView.iconSize = gridView.makeIconSize(); 1182 } 1183 1184 function onViewModeChanged() { 1185 gridView.iconSize = gridView.makeIconSize(); 1186 } 1187 1188 function onUrlChanged() { 1189 history = []; 1190 updateHistory(); 1191 } 1192 } 1193 } 1194 1195 Kirigami.InlineMessage { 1196 width: parent.width / 2.0 1197 anchors.horizontalCenter: parent.horizontalCenter 1198 type: Kirigami.MessageType.Warning 1199 text: i18nc("@info", 1200 "There are a lot of files and folders on the desktop. This can cause bugs and performance issues. Please consider moving some of them elsewhere.") 1201 // Note: the trigger amount is intentionally lower than the screen mapping cap. We want to warn ahead of hitting our caps. 1202 visible: isRootView && gridView.count > 2048 1203 } 1204 } 1205 1206 Folder.WheelInterceptor { 1207 anchors.fill: parent 1208 1209 enabled: root.isContainment && !gridView.overflowing 1210 destination: root 1211 } 1212 1213 Folder.FolderModel { 1214 id: dir 1215 1216 usedByContainment: root.isContainment && main.isRootView 1217 sortDesc: Plasmoid.configuration.sortDesc 1218 sortDirsFirst: Plasmoid.configuration.sortDirsFirst 1219 parseDesktopFiles: (Plasmoid.configuration.url === "desktop:/") 1220 previews: Plasmoid.configuration.previews 1221 previewPlugins: Plasmoid.configuration.previewPlugins 1222 applet: Plasmoid 1223 1224 onListingCompleted: { 1225 if (!gridView.model && root.expanded) { 1226 gridView.model = positioner; 1227 gridView.currentIndex = isPopup ? 0 : -1; 1228 } else if (goingBack) { 1229 goingBack = false; 1230 gridView.currentIndex = Math.min(lastPosition.index, gridView.count - 1); 1231 setSelected(positioner.map(gridView.currentIndex)); 1232 gridView.contentY = lastPosition.yPosition * gridView.contentHeight; 1233 } 1234 } 1235 1236 onMove: (x, y, urls) => { 1237 var rows = (gridView.flow === GridView.FlowLeftToRight); 1238 var axis = rows ? gridView.width : gridView.height; 1239 var step = rows ? cellWidth : cellHeight; 1240 var perStripe = Math.floor(axis / step); 1241 var dropPos = mapToItem(gridView.contentItem, x, y); 1242 var leftEdge = Math.min(gridView.contentX, gridView.originX); 1243 1244 var moves = [] 1245 var itemX = -1; 1246 var itemY = -1; 1247 var col = -1; 1248 var row = -1; 1249 var from = -1; 1250 var to = -1; 1251 1252 for (var i = 0; i < urls.length; i++) { 1253 from = positioner.indexForUrl(urls[i]); 1254 to = -1; 1255 1256 if (from === -1) { 1257 continue; 1258 } 1259 1260 var offset = dir.dragCursorOffset(positioner.map(from)); 1261 1262 if (offset.x === -1) { 1263 continue; 1264 } 1265 1266 itemX = dropPos.x + offset.x + (listener.dragX % cellWidth) + (cellWidth / 2); 1267 itemY = dropPos.y + offset.y + (listener.dragY % cellHeight) + gridView.verticalDropHitscanOffset; 1268 1269 if (gridView.effectiveLayoutDirection === Qt.RightToLeft) { 1270 itemX -= (rows ? gridView.contentX : gridView.originX); 1271 itemX = (rows ? gridView.width : gridView.contentItem.width) - itemX; 1272 } 1273 1274 col = Math.floor(itemX / gridView.cellWidth); 1275 row = Math.floor(itemY / gridView.cellHeight); 1276 1277 if ((rows ? col : row) < perStripe) { 1278 to = ((rows ? row : col) * perStripe) + (rows ? col : row); 1279 1280 if (to < 0) { 1281 return; 1282 } 1283 } 1284 1285 if (from !== to) { 1286 moves.push(from); 1287 moves.push(to); 1288 } 1289 } 1290 1291 if (moves.length) { 1292 // Update also the currentIndex, otherwise it 1293 // is not set properly. 1294 gridView.currentIndex = positioner.move(moves); 1295 gridView.forceLayout(); 1296 } 1297 1298 previouslySelectedItemIndex = -1; 1299 } 1300 } 1301 1302 Folder.Positioner { 1303 id: positioner 1304 1305 enabled: isContainment && sortMode === -1 1306 1307 folderModel: dir 1308 1309 perStripe: Math.floor((gridView.flow === GridView.FlowLeftToRight) 1310 ? (gridView.width / gridView.cellWidth) 1311 : (gridView.height / gridView.cellHeight)) 1312 } 1313 1314 Folder.ItemViewAdapter { 1315 id: viewAdapter 1316 1317 adapterView: gridView 1318 adapterModel: positioner 1319 adapterIconSize: gridView.iconSize * 2 1320 adapterVisibleArea: Qt.rect(gridView.contentX, gridView.contentY, gridView.contentWidth, gridView.contentHeight) 1321 1322 Component.onCompleted: { 1323 gridView.movementStarted.connect(viewAdapter.viewScrolled); 1324 dir.viewAdapter = viewAdapter; 1325 } 1326 } 1327 1328 Component { 1329 id: editorComponent 1330 1331 RenameEditor { 1332 id: editor 1333 1334 visible: false 1335 1336 onCommit: { 1337 if (targetItem) { 1338 dir.rename(positioner.map(targetItem.index), text); 1339 targetItem = null; 1340 } 1341 } 1342 1343 onVisibleChanged: { 1344 if (root.visible) { 1345 focus = true; 1346 } else { 1347 scrollArea.focus = true; 1348 } 1349 } 1350 } 1351 } 1352 1353 Component.onCompleted: { 1354 dir.requestRename.connect(rename); 1355 } 1356 } 1357 1358 Component.onCompleted: { 1359 if (backButton === null && root.useListViewMode) { 1360 backButton = makeBackButton(); 1361 } 1362 } 1363 }