diff --git a/containments/desktop/package/contents/ui/BackButtonItem.qml b/containments/desktop/package/contents/ui/BackButtonItem.qml --- a/containments/desktop/package/contents/ui/BackButtonItem.qml +++ b/containments/desktop/package/contents/ui/BackButtonItem.qml @@ -34,9 +34,20 @@ visible: history.length != 0 property bool ignoreClick: false + property bool containsDrag: false imagePath: "widgets/viewitem" + function handleDragMove() { + containsDrag = true; + hoverActivateTimer.restart(); + } + + function endDragMove() { + containsDrag = false; + hoverActivateTimer.stop(); + } + MouseArea { id: mouseArea @@ -105,10 +116,18 @@ text: i18n("Back") } + Timer { + id: hoverActivateTimer + + interval: root.hoverActivateDelay + + onTriggered: doBack() + } + states: [ State { name: "hover" - when: mouseArea.containsMouse + when: mouseArea.containsMouse || containsDrag PropertyChanges { target: upButton diff --git a/containments/desktop/package/contents/ui/CompactRepresentation.qml b/containments/desktop/package/contents/ui/CompactRepresentation.qml --- a/containments/desktop/package/contents/ui/CompactRepresentation.qml +++ b/containments/desktop/package/contents/ui/CompactRepresentation.qml @@ -26,9 +26,21 @@ DragDrop.DropArea { property Item folderView: null + onContainsDragChanged: { + if (containsDrag) { + hoverActivateTimer.restart(); + } else { + hoverActivateTimer.stop(); + } + } + onDrop: folderView.model.dropCwd(event) preventStealing: true + function toggle() { + plasmoid.expanded = !plasmoid.expanded; + } + PlasmaCore.IconItem { id: icon @@ -47,8 +59,14 @@ hoverEnabled: true - onClicked: { - plasmoid.expanded = !plasmoid.expanded; - } + onClicked: toggle() + } + + Timer { + id: hoverActivateTimer + + interval: root.hoverActivateDelay + + onTriggered: toggle() } } diff --git a/containments/desktop/package/contents/ui/FolderItemDelegate.qml b/containments/desktop/package/contents/ui/FolderItemDelegate.qml --- a/containments/desktop/package/contents/ui/FolderItemDelegate.qml +++ b/containments/desktop/package/contents/ui/FolderItemDelegate.qml @@ -41,6 +41,12 @@ property Item hoverArea: loader.item ? loader.item.hoverArea : null property Item toolTip: loader.item ? loader.item.toolTip : null + function openPopup() { + if (isDir) { + loader.item.openPopup(); + } + } + Loader { id: loader @@ -90,13 +96,13 @@ } onHoveredChanged: { - if (hovered && !main.GridView.view.isRootView && model.isDir) { - openPopupTimer.start(); + if (hovered && (!main.GridView.view.isRootView || root.containsDrag) && model.isDir) { + hoverActivateTimer.restart(); } else if (!hovered) - openPopupTimer.stop(); + hoverActivateTimer.stop(); if (popupDialog != null) { - popupDialog.destroy(); + popupDialog.requestDestroy(); popupDialog = null; } } @@ -111,12 +117,30 @@ } Timer { - id: openPopupTimer + id: hoverActivateTimer - interval: units.longDuration * 3 + interval: root.hoverActivateDelay onTriggered: { - impl.openPopup(); + if (root.useListViewMode) { + doCd(index); + } else { + impl.openPopup(); + } + } + } + + Connections { + target: main.GridView.view + + enabled: hovered + + onContentXChanged: { + hoverActivateTimer.stop(); + } + + onContentYChanged: { + hoverActivateTimer.stop(); } } @@ -314,6 +338,22 @@ Column { id: actions + visible: { + if (main.GridView.view.isRootView && root.containsDrag) { + return false; + } + + if (!main.GridView.view.isRootView && dialog.containsDrag) { + return false; + } + + if (popupDialog) { + return false; + } + + return true; + } + x: units.smallSpacing * 3 y: units.smallSpacing * 3 diff --git a/containments/desktop/package/contents/ui/FolderView.qml b/containments/desktop/package/contents/ui/FolderView.qml --- a/containments/desktop/package/contents/ui/FolderView.qml +++ b/containments/desktop/package/contents/ui/FolderView.qml @@ -42,6 +42,7 @@ property alias url: dir.url property alias positions: positioner.positions property alias errorString: dir.errorString + property alias dragging: dir.dragging property alias locked: dir.locked property alias sortMode: dir.sortMode property alias filterMode: dir.filterMode @@ -56,6 +57,7 @@ property alias scrollRight: gridView.scrollRight property alias scrollUp: gridView.scrollUp property alias scrollDown: gridView.scrollDown + property alias hoveredItem: gridView.hoveredItem property var history: [] property Item backButton: null @@ -70,6 +72,36 @@ dir.linkHere(sourceUrl); } + function handleDragMove(x, y) { + var child = childAt(x, y); + + if (child != null && child == backButton) { + hoveredItem = null; + backButton.handleDragMove(); + } else { + if (backButton && backButton.containsDrag) { + backButton.endDragMove(); + } + + var pos = mapToItem(gridView.contentItem, x, y); + var item = gridView.itemAt(pos.x, pos.y); + + if (item && item.isDir) { + hoveredItem = item; + } else { + hoveredItem = null; + } + } + } + + function endDragMove() { + if (backButton && backButton.active) { + backButton.endDragMove(); + } else if (hoveredItem && !hoveredItem.popupDialog) { + hoveredItem = null; + } + } + function dropItemAt(pos) { var item = gridView.itemAt(pos.x, pos.y); diff --git a/containments/desktop/package/contents/ui/FolderViewDialog.qml b/containments/desktop/package/contents/ui/FolderViewDialog.qml --- a/containments/desktop/package/contents/ui/FolderViewDialog.qml +++ b/containments/desktop/package/contents/ui/FolderViewDialog.qml @@ -29,6 +29,18 @@ visible: false + property bool containsDrag: { + if (folderViewDropArea.containsDrag) { + return true; + } + + if (folderView.hoveredItem && folderView.hoveredItem.popupDialog) { + return folderView.hoveredItem.popupDialog.containsDrag; + } + + return false; + } + property QtObject closeTimer: closeTimer property QtObject childDialog: (folderView.hoveredItem != null) ? folderView.hoveredItem.popupDialog : null property bool containsMouse: folderView.containsMouse || (childDialog != null && childDialog.containsMouse) @@ -46,22 +58,36 @@ } } - mainItem: FolderView { - id: folderView + mainItem: FolderViewDropArea { + id: folderViewDropArea + + width: folderView.cellWidth * 3 + (10 * units.devicePixelRatio) // FIXME HACK: Use actual scrollbar width. + height: folderView.cellHeight * 2 + + folderView: folderView + + FolderView { + id: folderView - width: cellWidth * 3 + (10 * units.devicePixelRatio) // FIXME HACK: Use actual scrollbar width. - height: cellHeight * 2 + anchors.fill: parent - isRootView: false + isRootView: false - locked: true + locked: true - sortMode: ((plasmoid.configuration.sortMode == 0) ? 1 : plasmoid.configuration.sortMode) - filterMode: 0 + sortMode: ((plasmoid.configuration.sortMode == 0) ? 1 : plasmoid.configuration.sortMode) + filterMode: 0 - // TODO: Bidi. - flow: GridView.FlowLeftToRight - layoutDirection: Qt.LeftToRight + // TODO: Bidi. + flow: GridView.FlowLeftToRight + layoutDirection: Qt.LeftToRight + + onDraggingChanged: { + if (!dragging && !dialog.visible) { + dialog.destroy(); + } + } + } } data: [ @@ -82,6 +108,14 @@ } ] + function requestDestroy() { + if (folderView.dragging) { + visible = false; + } else { + destroy(); + } + } + function delayedDestroy() { var timer = Qt.createQmlObject('import QtQuick 2.0; Timer { onTriggered: itemDialog.destroy() }', itemDialog); timer.interval = 0; diff --git a/containments/desktop/package/contents/ui/CompactRepresentation.qml b/containments/desktop/package/contents/ui/FolderViewDropArea.qml copy from containments/desktop/package/contents/ui/CompactRepresentation.qml copy to containments/desktop/package/contents/ui/FolderViewDropArea.qml --- a/containments/desktop/package/contents/ui/CompactRepresentation.qml +++ b/containments/desktop/package/contents/ui/FolderViewDropArea.qml @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2013-2014 by Eike Hein * + * Copyright (C) 2014-2017 by Eike Hein * * * * 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 * @@ -17,38 +17,56 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -import QtQuick 2.0 -import QtQuick.Layouts 1.1 +import QtQuick 2.4 -import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.draganddrop 2.0 as DragDrop DragDrop.DropArea { - property Item folderView: null + id: dropArea - onDrop: folderView.model.dropCwd(event) - preventStealing: true + property Item folderView: null - PlasmaCore.IconItem { - id: icon + function handleDragMove(folderView, pos) { + // Trigger autoscroll. + folderView.scrollLeft = (pos.x < (units.largeSpacing * 3)); + folderView.scrollRight = (pos.x > width - (units.largeSpacing * 3)); + folderView.scrollUp = (pos.y < (units.largeSpacing * 3)); + folderView.scrollDown = (pos.y > height - (units.largeSpacing * 3)); - anchors.fill: parent + folderView.handleDragMove(pos.x, pos.y); + } - active: mouseArea.containsMouse + function handleDragEnd(folderView) { + // Cancel autoscroll. + folderView.scrollLeft = false; + folderView.scrollRight = false; + folderView.scrollUp = false; + folderView.scrollDown = false; - source: plasmoid.configuration.useCustomIcon ? plasmoid.configuration.icon : folderView.model.iconName + folderView.endDragMove(); } - MouseArea - { - id: mouseArea + onDragMove: { + // TODO: We should reject drag moves onto file items that don't accept drops + // (cf. QAbstractItemModel::flags() here, but DeclarativeDropArea currently + // is currently incapable of rejecting drag events. + + if (folderView) { + handleDragMove(folderView, mapToItem(folderView, event.x, event.y)); + } + } - anchors.fill: parent + onDragLeave: { + if (folderView) { + handleDragEnd(folderView); + } + } - hoverEnabled: true + onDrop: { + if (folderView) { + handleDragEnd(folderView); - onClicked: { - plasmoid.expanded = !plasmoid.expanded; + folderView.drop(folderView, event, mapToItem(folderView, event.x, event.y)); } } } diff --git a/containments/desktop/package/contents/ui/main.qml b/containments/desktop/package/contents/ui/main.qml --- a/containments/desktop/package/contents/ui/main.qml +++ b/containments/desktop/package/contents/ui/main.qml @@ -33,7 +33,7 @@ import "LayoutManager.js" as LayoutManager import "FolderTools.js" as FolderTools -DragDrop.DropArea { +FolderViewDropArea { id: root objectName: isFolder ? "folder" : "desktop" @@ -66,6 +66,8 @@ property int iconWidth: iconSize property int iconHeight: iconWidth + readonly property int hoverActivateDelay: 750 // Magic number that matches Dolphin's auto-expand folders delay. + preventStealing: true // Plasmoid.title is set by a Binding {} in FolderViewLayer @@ -255,10 +257,7 @@ // Trigger autoscroll. if (isFolder && FolderTools.isFileDrag(event)) { - folderViewLayer.view.scrollLeft = (event.x < (units.largeSpacing * 3)); - folderViewLayer.view.scrollRight = (event.x > width - (units.largeSpacing * 3)); - folderViewLayer.view.scrollUp = (event.y < (units.largeSpacing * 3)); - folderViewLayer.view.scrollDown = (event.y > height - (units.largeSpacing * 3)); + handleDragMove(folderViewLayer.view, mapToItem(folderViewLayer.view, event.x, event.y)); } else if (isContainment) { placeHolder.width = LayoutManager.defaultAppletSize.width; placeHolder.height = LayoutManager.defaultAppletSize.height; @@ -274,10 +273,7 @@ onDragLeave: { // Cancel autoscroll. if (isFolder) { - folderViewLayer.view.scrollLeft = false; - folderViewLayer.view.scrollRight = false; - folderViewLayer.view.scrollUp = false; - folderViewLayer.view.scrollDown = false; + handleDragEnd(folderViewLayer.view); } if (isContainment) { @@ -287,12 +283,7 @@ onDrop: { if (isFolder && FolderTools.isFileDrag(event)) { - // Cancel autoscroll. - folderViewLayer.view.scrollLeft = false; - folderViewLayer.view.scrollRight = false; - folderViewLayer.view.scrollUp = false; - folderViewLayer.view.scrollDown = false; - + handleDragEnd(folderViewLayer.view); folderViewLayer.view.drop(root, event, mapToItem(folderViewLayer.view, event.x, event.y)); } else if (isContainment) { placeHolderPaint.opacity = 0; diff --git a/containments/desktop/plugins/folder/foldermodel.h b/containments/desktop/plugins/folder/foldermodel.h --- a/containments/desktop/plugins/folder/foldermodel.h +++ b/containments/desktop/plugins/folder/foldermodel.h @@ -73,6 +73,7 @@ Q_PROPERTY(QString iconName READ iconName NOTIFY iconNameChanged) Q_PROPERTY(QUrl resolvedUrl READ resolvedUrl NOTIFY resolvedUrlChanged) Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged) + Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged) Q_PROPERTY(bool usedByContainment READ usedByContainment WRITE setUsedByContainment NOTIFY usedByContainmentChanged) Q_PROPERTY(bool locked READ locked WRITE setLocked NOTIFY lockedChanged) Q_PROPERTY(int sortMode READ sortMode WRITE setSortMode NOTIFY sortModeChanged) @@ -123,6 +124,8 @@ QString errorString() const; + bool dragging() const; + bool usedByContainment() const; void setUsedByContainment(bool used); @@ -212,6 +215,7 @@ void iconNameChanged() const; void resolvedUrlChanged() const; void errorStringChanged() const; + void draggingChanged() const; void usedByContainmentChanged() const; void lockedChanged() const; void sortModeChanged() const; @@ -268,6 +272,7 @@ QHash m_dragImages; QPoint m_dragHotSpotScrollOffset; bool m_dragInProgress; + bool m_urlChangedWhileDragging; QPointer m_previewGenerator; QPointer m_viewAdapter; KActionCollection m_actionCollection; diff --git a/containments/desktop/plugins/folder/foldermodel.cpp b/containments/desktop/plugins/folder/foldermodel.cpp --- a/containments/desktop/plugins/folder/foldermodel.cpp +++ b/containments/desktop/plugins/folder/foldermodel.cpp @@ -94,6 +94,7 @@ FolderModel::FolderModel(QObject *parent) : QSortFilterProxyModel(parent), m_dirWatch(nullptr), m_dragInProgress(false), + m_urlChangedWhileDragging(false), m_previewGenerator(0), m_viewAdapter(0), m_actionCollection(this), @@ -182,6 +183,7 @@ m_isDirCache.clear(); m_dirModel->dirLister()->openUrl(resolvedUrl); clearDragImages(); + m_dragIndexes.clear(); endResetModel(); emit urlChanged(); @@ -201,6 +203,10 @@ m_dirWatch->addFile(resolvedUrl.toLocalFile() + QLatin1String("/.directory")); } + if (m_dragInProgress) { + m_urlChangedWhileDragging = true; + } + emit iconNameChanged(); } @@ -238,6 +244,11 @@ return m_errorString; } +bool FolderModel::dragging() const +{ + return m_dragInProgress; +} + bool FolderModel::usedByContainment() const { return m_usedByContainment; @@ -758,6 +769,8 @@ } m_dragInProgress = true; + emit draggingChanged(); + m_urlChangedWhileDragging = false; // Avoid starting a drag synchronously in a mouse handler or interferes with // child event filtering in parent items (and thus e.g. press-and-hold hand- @@ -771,6 +784,7 @@ { if (!m_viewAdapter || !m_selectionModel->hasSelection()) { m_dragInProgress = false; + emit draggingChanged(); return; } @@ -796,16 +810,28 @@ drag->setMimeData(m_dirModel->mimeData(sourceDragIndexes)); + // Due to spring-loading (aka auto-expand), the URL might change + // while the drag is in-flight - in that case we don't want to + // unnecessarily emit dataChanged() for (possibly invalid) indices + // after it ends. + const QUrl currentUrl(m_dirModel->dirLister()->url()); + item->grabMouse(); drag->exec(supportedDragActions()); - m_dragInProgress = false; + item->ungrabMouse(); - const QModelIndex first(m_dragIndexes.first()); - const QModelIndex last(m_dragIndexes.last()); - m_dragIndexes.clear(); - // TODO: Optimize to emit contiguous groups. - emit dataChanged(first, last, QVector() << BlankRole); + m_dragInProgress = false; + emit draggingChanged(); + m_urlChangedWhileDragging = false; + + if (m_dirModel->dirLister()->url() == currentUrl) { + const QModelIndex first(m_dragIndexes.first()); + const QModelIndex last(m_dragIndexes.last()); + m_dragIndexes.clear(); + // TODO: Optimize to emit contiguous groups. + emit dataChanged(first, last, QVector() << BlankRole); + } } void FolderModel::drop(QQuickItem *target, QObject* dropEvent, int row) @@ -816,7 +842,7 @@ return; } - if (m_dragInProgress && row == -1) { + if (m_dragInProgress && row == -1 && !m_urlChangedWhileDragging) { if (m_locked || mimeData->urls().isEmpty()) { return; } @@ -875,6 +901,11 @@ QUrl dropTargetUrl; + // So we get to run mostLocalUrl() over the current URL. + if (item.isNull()) { + item = m_dirModel->dirLister()->rootItem(); + } + if (item.isNull()) { dropTargetUrl = m_dirModel->dirLister()->url(); } else if (m_parseDesktopFiles && item.isDesktopFile()) {