diff --git a/applets/kicker/package/contents/ui/ItemGridView.qml b/applets/kicker/package/contents/ui/ItemGridView.qml index c63effbca..ccc2b08d3 100644 --- a/applets/kicker/package/contents/ui/ItemGridView.qml +++ b/applets/kicker/package/contents/ui/ItemGridView.qml @@ -1,472 +1,470 @@ /*************************************************************************** * Copyright (C) 2015 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 * * 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 2.4 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.kquickcontrolsaddons 2.0 import org.kde.draganddrop 2.0 FocusScope { id: itemGrid signal keyNavLeft signal keyNavRight signal keyNavUp signal keyNavDown signal itemActivated(int index, string actionId, string argument) property bool dragEnabled: true property bool dropEnabled: false property bool showLabels: true property alias usesPlasmaTheme: gridView.usesPlasmaTheme property alias currentIndex: gridView.currentIndex property alias currentItem: gridView.currentItem property alias contentItem: gridView.contentItem property alias count: gridView.count property alias model: gridView.model property alias cellWidth: gridView.cellWidth property alias cellHeight: gridView.cellHeight property alias iconSize: gridView.iconSize property alias horizontalScrollBarPolicy: scrollArea.horizontalScrollBarPolicy property alias verticalScrollBarPolicy: scrollArea.verticalScrollBarPolicy onDropEnabledChanged: { if (!dropEnabled && "dropPlaceHolderIndex" in model) { model.dropPlaceHolderIndex = -1; } } onFocusChanged: { if (!focus && !root.keyEventProxy.activeFocus) { currentIndex = -1; } } function currentRow() { if (currentIndex == -1) { return -1; } return Math.floor(currentIndex / Math.floor(width / cellWidth)); } function currentCol() { if (currentIndex == -1) { return -1; } return currentIndex - (currentRow() * Math.floor(width / cellWidth)); } function lastRow() { var columns = Math.floor(width / cellWidth); return Math.ceil(count / columns) - 1; } function tryActivate(row, col) { if (count) { var columns = Math.floor(width / cellWidth); var rows = Math.ceil(count / columns); row = Math.min(row, rows - 1); col = Math.min(col, columns - 1); currentIndex = Math.min(row ? ((Math.max(1, row) * columns) + col) : col, count - 1); focus = true; } } function forceLayout() { gridView.forceLayout(); } ActionMenu { id: actionMenu onActionClicked: { visualParent.actionTriggered(actionId, actionArgument); } } DropArea { id: dropArea anchors.fill: parent onDragMove: { if (!dropEnabled || gridView.animating || !kicker.dragSource) { return; } var x = Math.max(0, event.x - (width % cellWidth)); var cPos = mapToItem(gridView.contentItem, x, event.y); var item = gridView.itemAt(cPos.x, cPos.y); if (item) { if (kicker.dragSource.parent === gridView.contentItem) { if (item !== kicker.dragSource) { item.GridView.view.model.moveRow(dragSource.itemIndex, item.itemIndex); } } else if (kicker.dragSource.GridView.view.model.favoritesModel === model && !model.isFavorite(kicker.dragSource.favoriteId)) { var hasPlaceholder = (model.dropPlaceholderIndex !== -1); model.dropPlaceholderIndex = item.itemIndex; if (!hasPlaceholder) { gridView.currentIndex = (item.itemIndex - 1); } } } else if (kicker.dragSource.parent !== gridView.contentItem && kicker.dragSource.GridView.view.model.favoritesModel === model && !model.isFavorite(kicker.dragSource.favoriteId)) { var hasPlaceholder = (model.dropPlaceholderIndex !== -1); model.dropPlaceholderIndex = hasPlaceholder ? model.count - 1 : model.count; if (!hasPlaceholder) { gridView.currentIndex = (model.count - 1); } } else { model.dropPlaceholderIndex = -1; gridView.currentIndex = -1; } } onDragLeave: { if ("dropPlaceholderIndex" in model) { model.dropPlaceholderIndex = -1; gridView.currentIndex = -1; } } onDrop: { if (kicker.dragSource && kicker.dragSource.parent !== gridView.contentItem && kicker.dragSource.GridView.view.model.favoritesModel === model) { model.addFavorite(kicker.dragSource.favoriteId, model.dropPlaceholderIndex); gridView.currentIndex = -1; } } Timer { id: resetAnimationDurationTimer interval: 120 repeat: false onTriggered: { gridView.animationDuration = interval - 20; } } PlasmaExtras.ScrollArea { id: scrollArea anchors.fill: parent focus: true horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff GridView { id: gridView signal itemContainsMouseChanged(bool containsMouse) property bool usesPlasmaTheme: false property int iconSize: units.iconSizes.huge property bool animating: false property int animationDuration: dropEnabled ? resetAnimationDurationTimer.interval : 0 focus: true currentIndex: -1 move: Transition { enabled: itemGrid.dropEnabled SequentialAnimation { PropertyAction { target: gridView; property: "animating"; value: true } NumberAnimation { duration: gridView.animationDuration properties: "x, y" easing.type: Easing.OutQuad } PropertyAction { target: gridView; property: "animating"; value: false } } } moveDisplaced: Transition { enabled: itemGrid.dropEnabled SequentialAnimation { PropertyAction { target: gridView; property: "animating"; value: true } NumberAnimation { duration: gridView.animationDuration properties: "x, y" easing.type: Easing.OutQuad } PropertyAction { target: gridView; property: "animating"; value: false } } } keyNavigationWraps: false boundsBehavior: Flickable.StopAtBounds delegate: ItemGridDelegate { showLabel: showLabels } highlight: Item { property bool isDropPlaceHolder: "dropPlaceholderIndex" in model && currentIndex === model.dropPlaceholderIndex PlasmaComponents.Highlight { visible: gridView.currentItem && !isDropPlaceHolder anchors.fill: parent } PlasmaCore.FrameSvgItem { visible: gridView.currentItem && isDropPlaceHolder anchors.fill: parent imagePath: "widgets/viewitem" prefix: "selected" opacity: 0.5 PlasmaCore.IconItem { anchors { right: parent.right rightMargin: parent.margins.right bottom: parent.bottom bottomMargin: parent.margins.bottom } width: units.iconSizes.smallMedium height: width source: "list-add" active: false } } } highlightFollowsCurrentItem: true highlightMoveDuration: 0 onCurrentIndexChanged: { if (currentIndex != -1) { hoverArea.hoverEnabled = false focus = true; } } onCountChanged: { animationDuration = 0; resetAnimationDurationTimer.start(); } onModelChanged: { currentIndex = -1; } Keys.onLeftPressed: { if (currentCol() !== 0) { event.accepted = true; moveCurrentIndexLeft(); } else { itemGrid.keyNavLeft(); } } Keys.onRightPressed: { var columns = Math.floor(width / cellWidth); if (currentCol() !== columns - 1 && currentIndex != count -1) { event.accepted = true; moveCurrentIndexRight(); } else { itemGrid.keyNavRight(); } } Keys.onUpPressed: { if (currentRow() !== 0) { event.accepted = true; moveCurrentIndexUp(); positionViewAtIndex(currentIndex, GridView.Contain); } else { itemGrid.keyNavUp(); } } Keys.onDownPressed: { if (currentRow() < itemGrid.lastRow()) { // Fix moveCurrentIndexDown()'s lack of proper spatial nav down // into partial columns. event.accepted = true; var columns = Math.floor(width / cellWidth); var newIndex = currentIndex + columns; currentIndex = Math.min(newIndex, count - 1); positionViewAtIndex(currentIndex, GridView.Contain); } else { itemGrid.keyNavDown(); } } onItemContainsMouseChanged: { if (!containsMouse) { if (!actionMenu.opened) { gridView.currentIndex = -1; } hoverArea.pressX = -1; hoverArea.pressY = -1; hoverArea.lastX = -1; hoverArea.lastY = -1; hoverArea.pressedItem = null; hoverArea.hoverEnabled = true; } } } } MouseArea { id: hoverArea anchors.fill: parent property int pressX: -1 property int pressY: -1 property int lastX: -1 property int lastY: -1 property Item pressedItem: null acceptedButtons: Qt.LeftButton | Qt.RightButton hoverEnabled: true function updatePositionProperties(x, y) { // Prevent hover event synthesis in QQuickWindow interfering // with keyboard navigation by ignoring repeated events with // identical coordinates. As the work done here would be re- // dundant in any case, these are safe to ignore. if (lastX === x && lastY === y) { return; } lastX = x; lastY = y; var cPos = mapToItem(gridView.contentItem, x, y); var item = gridView.itemAt(cPos.x, cPos.y); if (!item) { gridView.currentIndex = -1; pressedItem = null; } else { gridView.currentIndex = item.itemIndex; itemGrid.focus = (currentIndex != -1) } return item; } onPressed: { mouse.accepted = true; updatePositionProperties(mouse.x, mouse.y); pressX = mouse.x; pressY = mouse.y; if (mouse.button == Qt.RightButton) { if (gridView.currentItem) { if (gridView.currentItem.hasActionList) { var mapped = mapToItem(gridView.currentItem, mouse.x, mouse.y); gridView.currentItem.openActionMenu(mapped.x, mapped.y); } } else { var mapped = mapToItem(rootItem, mouse.x, mouse.y); contextMenu.open(mapped.x, mapped.y); } } else { pressedItem = gridView.currentItem; } } onReleased: { mouse.accepted = true; - var item = updatePositionProperties(mouse.x, mouse.y); - - if (item === undefined) { - if (gridView.currentItem && gridView.currentItem == pressedItem) { - if ("trigger" in gridView.model) { - gridView.model.trigger(pressedItem.itemIndex, "", null); - root.toggle(); - } + updatePositionProperties(mouse.x, mouse.y); - itemGrid.itemActivated(pressedItem.itemIndex, "", null); - } else if (!pressedItem && mouse.button == Qt.LeftButton) { + if (gridView.currentItem && gridView.currentItem == pressedItem) { + if ("trigger" in gridView.model) { + gridView.model.trigger(pressedItem.itemIndex, "", null); root.toggle(); } + + itemGrid.itemActivated(pressedItem.itemIndex, "", null); + } else if (!dragHelper.dragging && !pressedItem && mouse.button == Qt.LeftButton) { + root.toggle(); } pressX = -1; pressY = -1; pressedItem = null; } onPositionChanged: { var item = pressedItem? pressedItem : updatePositionProperties(mouse.x, mouse.y); if (gridView.currentIndex != -1) { if (dragEnabled && pressX != -1 && dragHelper.isDrag(pressX, pressY, mouse.x, mouse.y)) { if ("pluginName" in item.m) { dragHelper.startDrag(kicker, item.url, item.icon, "text/x-plasmoidservicename", item.m.pluginName); } else { dragHelper.startDrag(kicker, item.url, item.icon); } kicker.dragSource = item; pressX = -1; pressY = -1; } } } } } } diff --git a/applets/kicker/plugin/draghelper.cpp b/applets/kicker/plugin/draghelper.cpp index 77240732f..ddc397103 100644 --- a/applets/kicker/plugin/draghelper.cpp +++ b/applets/kicker/plugin/draghelper.cpp @@ -1,97 +1,113 @@ /*************************************************************************** * Copyright (C) 2013 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 * * 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 . * ***************************************************************************/ #include "draghelper.h" #include #include #include #include #include +#include DragHelper::DragHelper(QObject* parent) : QObject(parent) , m_dragIconSize(32) +, m_dragging(false) { } DragHelper::~DragHelper() { } int DragHelper::dragIconSize() const { return m_dragIconSize; } void DragHelper::setDragIconSize(int size) { if (m_dragIconSize != size) { m_dragIconSize = size; emit dragIconSizeChanged(); } } bool DragHelper::isDrag(int oldX, int oldY, int newX, int newY) const { return ((QPoint(oldX, oldY) - QPoint(newX, newY)).manhattanLength() >= QApplication::startDragDistance()); } void DragHelper::startDrag(QQuickItem *item, const QUrl &url, const QIcon &icon, const QString &extraMimeType, const QString &extraMimeData) { // This allows the caller to return, making sure we don't crash if // the caller is destroyed mid-drag (as can happen due to a sycoca // change). QMetaObject::invokeMethod(this, "doDrag", Qt::QueuedConnection, Q_ARG(QQuickItem*, item), Q_ARG(QUrl, url), Q_ARG(QIcon, icon), Q_ARG(QString, extraMimeType), Q_ARG(QString, extraMimeData)); } void DragHelper::doDrag(QQuickItem *item, const QUrl &url, const QIcon &icon, - const QString &extraMimeType, const QString &extraMimeData) const + const QString &extraMimeType, const QString &extraMimeData) { + setDragging(true); + if (item && item->window() && item->window()->mouseGrabberItem()) { item->window()->mouseGrabberItem()->ungrabMouse(); } QDrag *drag = new QDrag(item); QMimeData *mimeData = new QMimeData(); if (!url.isEmpty()) { mimeData->setUrls(QList() << url); } if (!extraMimeType.isEmpty() && !extraMimeData.isEmpty()) { mimeData->setData(extraMimeType, extraMimeData.toLatin1()); } drag->setMimeData(mimeData); if (!icon.isNull()) { drag->setPixmap(icon.pixmap(m_dragIconSize, m_dragIconSize)); } drag->exec(); emit dropped(); + + // Ensure dragging is still true when onRelease is called. + QTimer::singleShot(0, qApp, [this] { + setDragging(false); + }); } +void DragHelper::setDragging(bool dragging) +{ + if (m_dragging == dragging) + return; + m_dragging = dragging; + emit draggingChanged(); +} \ No newline at end of file diff --git a/applets/kicker/plugin/draghelper.h b/applets/kicker/plugin/draghelper.h index 9b6b99845..ec5691f3e 100644 --- a/applets/kicker/plugin/draghelper.h +++ b/applets/kicker/plugin/draghelper.h @@ -1,55 +1,60 @@ /*************************************************************************** * Copyright (C) 2013 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 * * 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 . * ***************************************************************************/ #ifndef DRAGHELPER_H #define DRAGHELPER_H #include #include #include class QQuickItem; class DragHelper : public QObject { Q_OBJECT Q_PROPERTY(int dragIconSize READ dragIconSize WRITE setDragIconSize NOTIFY dragIconSizeChanged) + Q_PROPERTY(bool dragging READ isDragging NOTIFY draggingChanged) public: explicit DragHelper(QObject *parent = nullptr); ~DragHelper() override; int dragIconSize() const; void setDragIconSize(int size); + bool isDragging() const { return m_dragging; } Q_INVOKABLE bool isDrag(int oldX, int oldY, int newX, int newY) const; Q_INVOKABLE void startDrag(QQuickItem* item, const QUrl &url = QUrl(), const QIcon &icon = QIcon(), const QString &extraMimeType = QString(), const QString &extraMimeData = QString()); Q_SIGNALS: void dragIconSizeChanged() const; void dropped() const; + void draggingChanged() const; private: int m_dragIconSize; + bool m_dragging; Q_INVOKABLE void doDrag(QQuickItem* item, const QUrl &url = QUrl(), const QIcon &icon = QIcon(), - const QString &extraMimeType = QString(), const QString &extraMimeData = QString()) const; + const QString &extraMimeType = QString(), const QString &extraMimeData = QString()); + void setDragging(bool dragging); }; #endif