]> cloud.milkyroute.net Git - dolphin.git/commitdiff
Improve Touch support
authorSteffen Hartleib <sh033@gmx.de>
Sun, 13 Sep 2020 18:53:32 +0000 (18:53 +0000)
committerElvis Angelaccio <elvis.angelaccio@kde.org>
Sun, 13 Sep 2020 18:53:32 +0000 (18:53 +0000)
With this patch dolphin now supports the following touch gestures:

* Tap gesture to interact/open with directories, files and so on
* TapAndHold and release gesture for access to the context menu (main window, panel folder, places and information)
* TapAndHold and moving gesture for drag and drop action (main windows, panel folder and places)
* pinch gesture for zoom in main window
* kinetic scrolling (QScroller) for main window, panel folder, panel places, panel information, setting preview and service
* two fingers swipe gesture to left, right and up as shortcut to navigate back, forward and up
* two finger tap gesture to toggle item selection, similar to Ctrl and left mouse click

FEATURE: 385066
FIXED-IN: 20.11.80

 You are currently rebasing branch 'touch' on '85241a924'.

20 files changed:
src/CMakeLists.txt
src/dolphinmainwindow.cpp
src/kitemviews/kitemlistcontainer.cpp
src/kitemviews/kitemlistcontainer.h
src/kitemviews/kitemlistcontroller.cpp
src/kitemviews/kitemlistcontroller.h
src/kitemviews/kitemlistview.cpp
src/kitemviews/kitemlistview.h
src/kitemviews/private/kitemlistrubberband.h
src/kitemviews/private/ktwofingerswipe.cpp [new file with mode: 0644]
src/kitemviews/private/ktwofingerswipe.h [new file with mode: 0644]
src/kitemviews/private/ktwofingertap.cpp [new file with mode: 0644]
src/kitemviews/private/ktwofingertap.h [new file with mode: 0644]
src/panels/information/informationpanel.cpp
src/panels/information/informationpanelcontent.cpp
src/panels/information/informationpanelcontent.h
src/settings/general/previewssettingspage.cpp
src/settings/services/servicessettingspage.cpp
src/views/dolphinview.cpp
src/views/dolphinview.h

index 5d1c9d98666f0b8ec83b2a96123fdad783dc8507..a6178841ddbdc72ea7547b81f88d75f11d30179e 100644 (file)
@@ -90,6 +90,8 @@ set(dolphinprivate_LIB_SRCS
     kitemviews/private/kitemlistviewanimation.cpp
     kitemviews/private/kitemlistviewlayouter.cpp
     kitemviews/private/kpixmapmodifier.cpp
+    kitemviews/private/ktwofingerswipe.cpp
+    kitemviews/private/ktwofingertap.cpp
     settings/applyviewpropsjob.cpp
     settings/viewmodes/viewmodesettings.cpp
     settings/viewpropertiesdialog.cpp
index dd86dfd8159c08a43d42dda90869dd86e35299da..7c9a687aa14cf29f7a709a7af7217cb5c83da38f 100644 (file)
@@ -2095,6 +2095,8 @@ void DolphinMainWindow::connectViewSignals(DolphinViewContainer* container)
             this, &DolphinMainWindow::goForward);
     connect(view, &DolphinView::urlActivated,
             this, &DolphinMainWindow::handleUrl);
+    connect(view, &DolphinView::goUpRequested,
+            this, &DolphinMainWindow::goUp);
 
     const KUrlNavigator* navigator = container->urlNavigator();
     connect(navigator, &KUrlNavigator::urlChanged,
index dfd5e8a0483f0e5e2fc6cd9fc036c061668d84f4..77847bcc7967576119898b7432d6179b6c9ab9be 100644 (file)
@@ -17,6 +17,7 @@
 #include <QGraphicsScene>
 #include <QGraphicsView>
 #include <QScrollBar>
+#include <QScroller>
 #include <QStyleOption>
 
 /**
@@ -54,7 +55,8 @@ KItemListContainer::KItemListContainer(KItemListController* controller, QWidget*
     QAbstractScrollArea(parent),
     m_controller(controller),
     m_horizontalSmoothScroller(nullptr),
-    m_verticalSmoothScroller(nullptr)
+    m_verticalSmoothScroller(nullptr),
+    m_scroller(nullptr)
 {
     Q_ASSERT(controller);
     controller->setParent(this);
@@ -76,6 +78,13 @@ KItemListContainer::KItemListContainer(KItemListController* controller, QWidget*
             this, &KItemListContainer::slotModelChanged);
     connect(controller, &KItemListController::viewChanged,
             this, &KItemListContainer::slotViewChanged);
+
+    m_scroller = QScroller::scroller(viewport());
+    m_scroller->grabGesture(viewport());
+    connect(controller, &KItemListController::scrollerStop,
+            this, &KItemListContainer::stopScroller);
+    connect(m_scroller, &QScroller::stateChanged,
+            controller, &KItemListController::slotStateChanged);
 }
 
 KItemListContainer::~KItemListContainer()
@@ -325,6 +334,11 @@ void KItemListContainer::updateItemOffsetScrollBar()
     }
 }
 
+void KItemListContainer::stopScroller()
+{
+    m_scroller->stop();
+}
+
 void KItemListContainer::updateGeometries()
 {
     QRect rect = geometry();
index 317036db005f4e68e400b62a05f1ef5f72effcf3..537bab142048683d7cd217a54ea53c3e60d319f7 100644 (file)
@@ -17,6 +17,7 @@ class KItemListController;
 class KItemListSmoothScroller;
 class KItemListView;
 class KItemModelBase;
+class QScroller;
 
 /**
  * @brief Provides a QWidget based scrolling view for a KItemListController.
@@ -57,6 +58,7 @@ private slots:
     void scrollTo(qreal offset);
     void updateScrollOffsetScrollBar();
     void updateItemOffsetScrollBar();
+    void stopScroller();
 
 private:
     void updateGeometries();
@@ -74,6 +76,7 @@ private:
 
     KItemListSmoothScroller* m_horizontalSmoothScroller;
     KItemListSmoothScroller* m_verticalSmoothScroller;
+    QScroller* m_scroller;
 };
 
 #endif
index ca385899b6db63406dd9832e4d3c34ed4c441b87..650bf628693f54e9cd238f32461fa6bf9144dee9 100644 (file)
 #include "kitemlistview.h"
 #include "private/kitemlistkeyboardsearchmanager.h"
 #include "private/kitemlistrubberband.h"
+#include "private/ktwofingerswipe.h"
+#include "private/ktwofingertap.h"
 #include "views/draganddrophelper.h"
 
 #include <QAccessible>
 #include <QApplication>
 #include <QDrag>
+#include <QGesture>
 #include <QGraphicsScene>
 #include <QGraphicsSceneEvent>
 #include <QGraphicsView>
@@ -29,6 +32,11 @@ KItemListController::KItemListController(KItemModelBase* model, KItemListView* v
     m_singleClickActivationEnforced(false),
     m_selectionTogglePressed(false),
     m_clearSelectionIfItemsAreNotDragged(false),
+    m_isSwipeGesture(false),
+    m_dragActionOrRightClick(false),
+    m_scrollerIsScrolling(false),
+    m_pinchGestureInProgress(false),
+    m_mousePress(false),
     m_selectionBehavior(NoSelection),
     m_autoActivationBehavior(ActivationAndExpansion),
     m_mouseDoubleClickAction(ActivateItemOnly),
@@ -39,6 +47,9 @@ KItemListController::KItemListController(KItemModelBase* model, KItemListView* v
     m_pressedIndex(-1),
     m_pressedMousePos(),
     m_autoActivationTimer(nullptr),
+    m_swipeGesture(Qt::CustomGesture),
+    m_twoFingerTapGesture(Qt::CustomGesture),
+    m_lastSource(Qt::MouseEventNotSynthesized),
     m_oldSelection(),
     m_keyboardAnchorIndex(-1),
     m_keyboardAnchorPos(0)
@@ -57,6 +68,14 @@ KItemListController::KItemListController(KItemModelBase* model, KItemListView* v
 
     setModel(model);
     setView(view);
+
+    m_swipeGesture = QGestureRecognizer::registerRecognizer(new KTwoFingerSwipeRecognizer());
+    m_twoFingerTapGesture = QGestureRecognizer::registerRecognizer(new KTwoFingerTapRecognizer());
+    view->grabGesture(m_swipeGesture);
+    view->grabGesture(m_twoFingerTapGesture);
+    view->grabGesture(Qt::TapGesture);
+    view->grabGesture(Qt::TapAndHoldGesture);
+    view->grabGesture(Qt::PinchGesture);
 }
 
 KItemListController::~KItemListController()
@@ -517,13 +536,19 @@ bool KItemListController::inputMethodEvent(QInputMethodEvent* event)
 
 bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
 {
+    m_mousePress = true;
+    m_lastSource = event->source();
+
+    if (event->source() == Qt::MouseEventSynthesizedByQt) {
+        return false;
+    }
+
     if (!m_view) {
         return false;
     }
 
     m_pressedMousePos = transform.map(event->pos());
     m_pressedIndex = m_view->itemAt(m_pressedMousePos);
-    emit mouseButtonPressed(m_pressedIndex, event->buttons());
 
     if (event->buttons() & (Qt::BackButton | Qt::ForwardButton)) {
         // Do not select items when clicking the back/forward buttons, see
@@ -531,135 +556,12 @@ bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent* event, const
         return true;
     }
 
-    if (m_view->isAboveExpansionToggle(m_pressedIndex, m_pressedMousePos)) {
-        m_selectionManager->endAnchoredSelection();
-        m_selectionManager->setCurrentItem(m_pressedIndex);
-        m_selectionManager->beginAnchoredSelection(m_pressedIndex);
-        return true;
-    }
-
-    m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
-    if (m_selectionTogglePressed) {
-        m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
-        // The previous anchored selection has been finished already in
-        // KItemListSelectionManager::setSelected(). We can safely change
-        // the current item and start a new anchored selection now.
-        m_selectionManager->setCurrentItem(m_pressedIndex);
-        m_selectionManager->beginAnchoredSelection(m_pressedIndex);
-        return true;
-    }
-
-    const bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
-    const bool controlPressed = event->modifiers() & Qt::ControlModifier;
-
-    // The previous selection is cleared if either
-    // 1. The selection mode is SingleSelection, or
-    // 2. the selection mode is MultiSelection, and *none* of the following conditions are met:
-    //    a) Shift or Control are pressed.
-    //    b) The clicked item is selected already. In that case, the user might want to:
-    //       - start dragging multiple items, or
-    //       - open the context menu and perform an action for all selected items.
-    const bool shiftOrControlPressed = shiftPressed || controlPressed;
-    const bool pressedItemAlreadySelected = m_pressedIndex >= 0 && m_selectionManager->isSelected(m_pressedIndex);
-    const bool clearSelection = m_selectionBehavior == SingleSelection ||
-                                (!shiftOrControlPressed && !pressedItemAlreadySelected);
-    if (clearSelection) {
-        m_selectionManager->clearSelection();
-    } else if (pressedItemAlreadySelected && !shiftOrControlPressed && (event->buttons() & Qt::LeftButton)) {
-        // The user might want to start dragging multiple items, but if he clicks the item
-        // in order to trigger it instead, the other selected items must be deselected.
-        // However, we do not know yet what the user is going to do.
-        // -> remember that the user pressed an item which had been selected already and
-        //    clear the selection in mouseReleaseEvent(), unless the items are dragged.
-        m_clearSelectionIfItemsAreNotDragged = true;
-
-        if (m_selectionManager->selectedItems().count() == 1 && m_view->isAboveText(m_pressedIndex, m_pressedMousePos)) {
-            emit selectedItemTextPressed(m_pressedIndex);
-        }
-    }
-
-    if (!shiftPressed) {
-        // Finish the anchored selection before the current index is changed
-        m_selectionManager->endAnchoredSelection();
-    }
-
-    if (event->buttons() & Qt::RightButton) {
-        // Stop rubber band from persisting after right-clicks
-        KItemListRubberBand* rubberBand = m_view->rubberBand();
-        if (rubberBand->isActive()) {
-            disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
-            rubberBand->setActive(false);
-            m_view->setAutoScroll(false);
-        }
-    }
-
-    if (m_pressedIndex >= 0) {
-        m_selectionManager->setCurrentItem(m_pressedIndex);
-
-        switch (m_selectionBehavior) {
-        case NoSelection:
-            break;
-
-        case SingleSelection:
-            m_selectionManager->setSelected(m_pressedIndex);
-            break;
-
-        case MultiSelection:
-            if (controlPressed && !shiftPressed) {
-                m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
-                m_selectionManager->beginAnchoredSelection(m_pressedIndex);
-            } else if (!shiftPressed || !m_selectionManager->isAnchoredSelectionActive()) {
-                // Select the pressed item and start a new anchored selection
-                m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select);
-                m_selectionManager->beginAnchoredSelection(m_pressedIndex);
-            }
-            break;
-
-        default:
-            Q_ASSERT(false);
-            break;
-        }
-
-        if (event->buttons() & Qt::RightButton) {
-            emit itemContextMenuRequested(m_pressedIndex, event->screenPos());
-        }
-
-        return true;
-    }
-
-    if (event->buttons() & Qt::RightButton) {
-        const QRectF headerBounds = m_view->headerBoundaries();
-        if (headerBounds.contains(event->pos())) {
-            emit headerContextMenuRequested(event->screenPos());
-        } else {
-            emit viewContextMenuRequested(event->screenPos());
-        }
-        return true;
-    }
-
-    if (m_selectionBehavior == MultiSelection) {
-        QPointF startPos = m_pressedMousePos;
-        if (m_view->scrollOrientation() == Qt::Vertical) {
-            startPos.ry() += m_view->scrollOffset();
-            if (m_view->itemSize().width() < 0) {
-                // Use a special rubberband for views that have only one column and
-                // expand the rubberband to use the whole width of the view.
-                startPos.setX(0);
-            }
-        } else {
-            startPos.rx() += m_view->scrollOffset();
-        }
-
-        m_oldSelection = m_selectionManager->selectedItems();
-        KItemListRubberBand* rubberBand = m_view->rubberBand();
-        rubberBand->setStartPosition(startPos);
-        rubberBand->setEndPosition(startPos);
-        rubberBand->setActive(true);
-        connect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
-        m_view->setAutoScroll(true);
+    const Qt::MouseButtons buttons = event->buttons();
+    if (!onPress(event->screenPos(), event->pos(), event->modifiers(), buttons)) {
+        startRubberBand();
+        return false;
     }
-
-    return false;
+    return true;
 }
 
 bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
@@ -668,6 +570,14 @@ bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const
         return false;
     }
 
+    if (m_view->m_tapAndHoldIndicator->isActive()) {
+        m_view->m_tapAndHoldIndicator->setActive(false);
+    }
+
+    if (event->source() == Qt::MouseEventSynthesizedByQt && !m_dragActionOrRightClick) {
+        return false;
+    }
+
     if (m_pressedIndex >= 0) {
         // Check whether a dragging should be started
         if (event->buttons() & Qt::LeftButton) {
@@ -683,8 +593,8 @@ bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const
                     // -> the selection should not be cleared when the mouse button is released.
                     m_clearSelectionIfItemsAreNotDragged = false;
                 }
-
                 startDragging();
+                m_mousePress = false;
             }
         }
     } else {
@@ -720,75 +630,24 @@ bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const
 
 bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
 {
+    m_mousePress = false;
+
     if (!m_view) {
         return false;
     }
 
-    emit mouseButtonReleased(m_pressedIndex, event->buttons());
-
-    const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
-    if (isAboveSelectionToggle) {
-        m_selectionTogglePressed = false;
-        return true;
+    if (m_view->m_tapAndHoldIndicator->isActive()) {
+        m_view->m_tapAndHoldIndicator->setActive(false);
     }
 
-    if (!isAboveSelectionToggle && m_selectionTogglePressed) {
-        m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
-        m_selectionTogglePressed = false;
-        return true;
-    }
-
-    const bool shiftOrControlPressed = event->modifiers() & Qt::ShiftModifier ||
-                                       event->modifiers() & Qt::ControlModifier;
-
     KItemListRubberBand* rubberBand = m_view->rubberBand();
-    if (rubberBand->isActive()) {
-        disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
-        rubberBand->setActive(false);
-        m_oldSelection.clear();
-        m_view->setAutoScroll(false);
+    if (event->source() == Qt::MouseEventSynthesizedByQt && !rubberBand->isActive()) {
+        return false;
     }
 
-    const QPointF pos = transform.map(event->pos());
-    const int index = m_view->itemAt(pos);
-
-    if (index >= 0 && index == m_pressedIndex) {
-        // The release event is done above the same item as the press event
-
-        if (m_clearSelectionIfItemsAreNotDragged) {
-            // A selected item has been clicked, but no drag operation has been started
-            // -> clear the rest of the selection.
-            m_selectionManager->clearSelection();
-            m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select);
-            m_selectionManager->beginAnchoredSelection(m_pressedIndex);
-        }
-
-        if (event->button() & Qt::LeftButton) {
-            bool emitItemActivated = true;
-            if (m_view->isAboveExpansionToggle(index, pos)) {
-                const bool expanded = m_model->isExpanded(index);
-                m_model->setExpanded(index, !expanded);
-
-                emit itemExpansionToggleClicked(index);
-                emitItemActivated = false;
-            } else if (shiftOrControlPressed) {
-                // The mouse click should only update the selection, not trigger the item
-                emitItemActivated = false;
-            } else if (!(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced)) {
-                emitItemActivated = false;
-            }
-            if (emitItemActivated) {
-                emit itemActivated(index);
-            }
-        } else if (event->button() & Qt::MiddleButton) {
-            emit itemMiddleClicked(index);
-        }
-    }
+    emit mouseButtonReleased(m_pressedIndex, event->buttons());
 
-    m_pressedMousePos = QPointF();
-    m_pressedIndex = -1;
-    m_clearSelectionIfItemsAreNotDragged = false;
-    return false;
+    return onRelease(transform.map(event->pos()), event->modifiers(), event->button(), false);
 }
 
 bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
@@ -1001,6 +860,8 @@ bool KItemListController::hoverLeaveEvent(QGraphicsSceneHoverEvent* event, const
     Q_UNUSED(event)
     Q_UNUSED(transform)
 
+    m_mousePress = false;
+
     if (!m_model || !m_view) {
         return false;
     }
@@ -1028,6 +889,202 @@ bool KItemListController::resizeEvent(QGraphicsSceneResizeEvent* event, const QT
     return false;
 }
 
+bool KItemListController::gestureEvent(QGestureEvent* event, const QTransform& transform)
+{
+    if (!m_view) {
+        return false;
+    }
+
+    //you can touch on different views at the same time, but only one QWidget gets a mousePressEvent
+    //we use this to get the right QWidget
+    //the only exception is a tap gesture with state GestureStarted, we need to reset some variable
+    if (!m_mousePress) {
+        if (QGesture* tap = event->gesture(Qt::TapGesture)) {
+            QTapGesture* tapGesture = static_cast<QTapGesture*>(tap);
+            if (tapGesture->state() == Qt::GestureStarted) {
+                tapTriggered(tapGesture, transform);
+            }
+        }
+        return false;
+    }
+
+    bool accepted = false;
+
+    if (QGesture* tap = event->gesture(Qt::TapGesture)) {
+        tapTriggered(static_cast<QTapGesture*>(tap), transform);
+        accepted = true;
+    }
+    if (event->gesture(Qt::TapAndHoldGesture)) {
+        tapAndHoldTriggered(event, transform);
+        accepted = true;
+    }
+    if (event->gesture(Qt::PinchGesture)) {
+        pinchTriggered(event, transform);
+        accepted = true;
+    }
+    if (event->gesture(m_swipeGesture)) {
+        swipeTriggered(event, transform);
+        accepted = true;
+    }
+    if (event->gesture(m_twoFingerTapGesture)) {
+        twoFingerTapTriggered(event, transform);
+        accepted = true;
+    }
+    return accepted;
+}
+
+void KItemListController::tapTriggered(QTapGesture* tap, const QTransform& transform)
+{
+    static bool scrollerWasActive = false;
+
+    if (tap->state() == Qt::GestureStarted) {
+        m_dragActionOrRightClick = false;
+        m_isSwipeGesture = false;
+        m_pinchGestureInProgress = false;
+        m_lastSource = Qt::MouseEventSynthesizedByQt;
+        scrollerWasActive = m_scrollerIsScrolling;
+    }
+
+    if (tap->state() == Qt::GestureFinished) {
+        m_mousePress = false;
+
+        //if at the moment of the gesture start the QScroller was active, the user made the tap
+        //to stop the QScroller and not to tap on an item
+        if (scrollerWasActive) {
+            return;
+        }
+
+        if (m_view->m_tapAndHoldIndicator->isActive()) {
+            m_view->m_tapAndHoldIndicator->setActive(false);
+        }
+
+        m_pressedMousePos = transform.map(tap->position());
+        m_pressedIndex = m_view->itemAt(m_pressedMousePos);
+
+        if (m_dragActionOrRightClick) {
+            onPress(tap->hotSpot().toPoint(), tap->position().toPoint(), Qt::NoModifier, Qt::RightButton);
+            onRelease(transform.map(tap->position()), Qt::NoModifier, Qt::RightButton, false);
+            m_dragActionOrRightClick = false;
+        }
+        else {
+            onPress(tap->hotSpot().toPoint(), tap->position().toPoint(), Qt::NoModifier, Qt::LeftButton);
+            onRelease(transform.map(tap->position()), Qt::NoModifier, Qt::LeftButton, true);
+        }
+    }
+}
+
+void KItemListController::tapAndHoldTriggered(QGestureEvent* event, const QTransform& transform)
+{
+
+    //the Qt TabAndHold gesture is triggerable with a mouse click, we don't want this
+    if (m_lastSource == Qt::MouseEventNotSynthesized) {
+        return;
+    }
+
+    const QTapAndHoldGesture* tap = static_cast<QTapAndHoldGesture*>(event->gesture(Qt::TapAndHoldGesture));
+    if (tap->state() == Qt::GestureFinished) {
+        //if a pinch gesture is in progress we don't want a TabAndHold gesture
+        if (m_pinchGestureInProgress) {
+            return;
+        }
+        m_pressedMousePos = transform.map(event->mapToGraphicsScene(tap->position()));
+        m_pressedIndex = m_view->itemAt(m_pressedMousePos);
+
+        if (m_pressedIndex >= 0 && !m_selectionManager->isSelected(m_pressedIndex)) {
+            m_selectionManager->clearSelection();
+            m_selectionManager->setSelected(m_pressedIndex);
+        } else if (m_pressedIndex == -1) {
+            m_selectionManager->clearSelection();
+            startRubberBand();
+        }
+
+        emit scrollerStop();
+
+        m_view->m_tapAndHoldIndicator->setStartPosition(m_pressedMousePos);
+        m_view->m_tapAndHoldIndicator->setActive(true);
+
+        m_dragActionOrRightClick = true;
+    }
+}
+
+void KItemListController::pinchTriggered(QGestureEvent* event, const QTransform& transform)
+{
+    Q_UNUSED(transform)
+
+    const QPinchGesture* pinch = static_cast<QPinchGesture*>(event->gesture(Qt::PinchGesture));
+    const qreal sensitivityModifier = 0.2;
+    static qreal counter = 0;
+
+    if (pinch->state() == Qt::GestureStarted) {
+        m_pinchGestureInProgress = true;
+        counter = 0;
+    }
+    if (pinch->state() == Qt::GestureUpdated) {
+        //if a swipe gesture was recognized or in progress, we don't want a pinch gesture to change the zoom
+        if (m_isSwipeGesture) {
+            return;
+        }
+        counter = counter + (pinch->scaleFactor() - 1);
+        if (counter >= sensitivityModifier) {
+            emit increaseZoom();
+            counter = 0;
+        } else if (counter <= -sensitivityModifier) {
+            emit decreaseZoom();
+            counter = 0;
+        }
+    }
+}
+
+void KItemListController::swipeTriggered(QGestureEvent* event, const QTransform& transform)
+{
+    Q_UNUSED(transform)
+
+    const KTwoFingerSwipe* swipe = static_cast<KTwoFingerSwipe*>(event->gesture(m_swipeGesture));
+
+    if (!swipe) {
+        return;
+    }
+    if (swipe->state() == Qt::GestureStarted) {
+        m_isSwipeGesture = true;
+    }
+
+    if (swipe->state() == Qt::GestureCanceled) {
+        m_isSwipeGesture = false;
+    }
+
+    if (swipe->state() == Qt::GestureFinished) {
+        emit scrollerStop();
+
+        if (swipe->swipeAngle() <= 20 || swipe->swipeAngle() >= 340) {
+            emit mouseButtonPressed(m_pressedIndex, Qt::BackButton);
+        } else if (swipe->swipeAngle() <= 200 && swipe->swipeAngle() >= 160) {
+            emit mouseButtonPressed(m_pressedIndex, Qt::ForwardButton);
+        } else if (swipe->swipeAngle() <= 110 && swipe->swipeAngle() >= 60) {
+            emit swipeUp();
+        }
+        m_isSwipeGesture = true;
+    }
+}
+
+void KItemListController::twoFingerTapTriggered(QGestureEvent* event, const QTransform& transform)
+{
+    const KTwoFingerTap* twoTap = static_cast<KTwoFingerTap*>(event->gesture(m_twoFingerTapGesture));
+
+    if (!twoTap) {
+        return;
+    }
+
+    if (twoTap->state() == Qt::GestureStarted) {
+        m_pressedMousePos = transform.map(twoTap->pos());
+        m_pressedIndex = m_view->itemAt(m_pressedMousePos);
+        if (m_pressedIndex >= 0) {
+            onPress(twoTap->screenPos().toPoint(), twoTap->pos().toPoint(), Qt::ControlModifier, Qt::LeftButton);
+            onRelease(transform.map(twoTap->pos()), Qt::ControlModifier, Qt::LeftButton, false);
+        }
+
+    }
+}
+
 bool KItemListController::processEvent(QEvent* event, const QTransform& transform)
 {
     if (!event) {
@@ -1065,6 +1122,8 @@ bool KItemListController::processEvent(QEvent* event, const QTransform& transfor
         return hoverLeaveEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
     case QEvent::GraphicsSceneResize:
         return resizeEvent(static_cast<QGraphicsSceneResizeEvent*>(event), transform);
+    case QEvent::Gesture:
+        return gestureEvent(static_cast<QGestureEvent*>(event), transform);
     default:
         break;
     }
@@ -1345,3 +1404,219 @@ void KItemListController::updateExtendedSelectionRegion()
     }
 }
 
+bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons)
+{
+    emit mouseButtonPressed(m_pressedIndex, buttons);
+
+    if (m_view->isAboveExpansionToggle(m_pressedIndex, m_pressedMousePos)) {
+        m_selectionManager->endAnchoredSelection();
+        m_selectionManager->setCurrentItem(m_pressedIndex);
+        m_selectionManager->beginAnchoredSelection(m_pressedIndex);
+        return true;
+    }
+
+    m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
+    if (m_selectionTogglePressed) {
+        m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
+        // The previous anchored selection has been finished already in
+        // KItemListSelectionManager::setSelected(). We can safely change
+        // the current item and start a new anchored selection now.
+        m_selectionManager->setCurrentItem(m_pressedIndex);
+        m_selectionManager->beginAnchoredSelection(m_pressedIndex);
+        return true;
+    }
+
+    const bool shiftPressed = modifiers & Qt::ShiftModifier;
+    const bool controlPressed = modifiers & Qt::ControlModifier;
+
+    // The previous selection is cleared if either
+    // 1. The selection mode is SingleSelection, or
+    // 2. the selection mode is MultiSelection, and *none* of the following conditions are met:
+    //    a) Shift or Control are pressed.
+    //    b) The clicked item is selected already. In that case, the user might want to:
+    //       - start dragging multiple items, or
+    //       - open the context menu and perform an action for all selected items.
+    const bool shiftOrControlPressed = shiftPressed || controlPressed;
+    const bool pressedItemAlreadySelected = m_pressedIndex >= 0 && m_selectionManager->isSelected(m_pressedIndex);
+    const bool clearSelection = m_selectionBehavior == SingleSelection ||
+                                (!shiftOrControlPressed && !pressedItemAlreadySelected);
+    if (clearSelection) {
+        m_selectionManager->clearSelection();
+    } else if (pressedItemAlreadySelected && !shiftOrControlPressed && (buttons & Qt::LeftButton)) {
+        // The user might want to start dragging multiple items, but if he clicks the item
+        // in order to trigger it instead, the other selected items must be deselected.
+        // However, we do not know yet what the user is going to do.
+        // -> remember that the user pressed an item which had been selected already and
+        //    clear the selection in mouseReleaseEvent(), unless the items are dragged.
+        m_clearSelectionIfItemsAreNotDragged = true;
+
+        if (m_selectionManager->selectedItems().count() == 1 && m_view->isAboveText(m_pressedIndex, m_pressedMousePos)) {
+            emit selectedItemTextPressed(m_pressedIndex);
+        }
+    }
+
+    if (!shiftPressed) {
+        // Finish the anchored selection before the current index is changed
+        m_selectionManager->endAnchoredSelection();
+    }
+
+    if (buttons & Qt::RightButton) {
+        // Stop rubber band from persisting after right-clicks
+        KItemListRubberBand* rubberBand = m_view->rubberBand();
+        if (rubberBand->isActive()) {
+            disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
+            rubberBand->setActive(false);
+            m_view->setAutoScroll(false);
+        }
+    }
+
+    if (m_pressedIndex >= 0) {
+        m_selectionManager->setCurrentItem(m_pressedIndex);
+
+        switch (m_selectionBehavior) {
+        case NoSelection:
+            break;
+
+        case SingleSelection:
+            m_selectionManager->setSelected(m_pressedIndex);
+            break;
+
+        case MultiSelection:
+            if (controlPressed && !shiftPressed) {
+                m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
+                m_selectionManager->beginAnchoredSelection(m_pressedIndex);
+            } else if (!shiftPressed || !m_selectionManager->isAnchoredSelectionActive()) {
+                // Select the pressed item and start a new anchored selection
+                m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select);
+                m_selectionManager->beginAnchoredSelection(m_pressedIndex);
+            }
+            break;
+
+        default:
+            Q_ASSERT(false);
+            break;
+        }
+
+        if (buttons & Qt::RightButton) {
+            emit itemContextMenuRequested(m_pressedIndex, screenPos);
+        }
+
+        return true;
+    }
+
+    if (buttons & Qt::RightButton) {
+        const QRectF headerBounds = m_view->headerBoundaries();
+        if (headerBounds.contains(pos)) {
+            emit headerContextMenuRequested(screenPos);
+        } else {
+            emit viewContextMenuRequested(screenPos);
+        }
+        return true;
+    }
+
+    return false;
+}
+
+bool KItemListController::onRelease(const QPointF& pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons, bool touch)
+{
+    const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
+    if (isAboveSelectionToggle) {
+        m_selectionTogglePressed = false;
+        return true;
+    }
+
+    if (!isAboveSelectionToggle && m_selectionTogglePressed) {
+        m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
+        m_selectionTogglePressed = false;
+        return true;
+    }
+
+    const bool shiftOrControlPressed = modifiers & Qt::ShiftModifier ||
+                                       modifiers & Qt::ControlModifier;
+
+    KItemListRubberBand* rubberBand = m_view->rubberBand();
+    if (rubberBand->isActive()) {
+        disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
+        rubberBand->setActive(false);
+        m_oldSelection.clear();
+        m_view->setAutoScroll(false);
+    }
+
+    const int index = m_view->itemAt(pos);
+
+    if (index >= 0 && index == m_pressedIndex) {
+        // The release event is done above the same item as the press event
+
+        if (m_clearSelectionIfItemsAreNotDragged) {
+            // A selected item has been clicked, but no drag operation has been started
+            // -> clear the rest of the selection.
+            m_selectionManager->clearSelection();
+            m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select);
+            m_selectionManager->beginAnchoredSelection(m_pressedIndex);
+        }
+
+        if (buttons & Qt::LeftButton) {
+            bool emitItemActivated = true;
+            if (m_view->isAboveExpansionToggle(index, pos)) {
+                const bool expanded = m_model->isExpanded(index);
+                m_model->setExpanded(index, !expanded);
+
+                emit itemExpansionToggleClicked(index);
+                emitItemActivated = false;
+            } else if (shiftOrControlPressed) {
+                // The mouse click should only update the selection, not trigger the item
+                emitItemActivated = false;
+            } else if (!(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced)) {
+                if (touch) {
+                emitItemActivated = true;
+                } else {
+                emitItemActivated = false;
+                }
+            }
+            if (emitItemActivated) {
+                emit itemActivated(index);
+            }
+        } else if (buttons & Qt::MiddleButton) {
+            emit itemMiddleClicked(index);
+        }
+    }
+
+    m_pressedMousePos = QPointF();
+    m_pressedIndex = -1;
+    m_clearSelectionIfItemsAreNotDragged = false;
+    return false;
+}
+
+void KItemListController::startRubberBand()
+{
+    if (m_selectionBehavior == MultiSelection) {
+        QPointF startPos = m_pressedMousePos;
+        if (m_view->scrollOrientation() == Qt::Vertical) {
+            startPos.ry() += m_view->scrollOffset();
+            if (m_view->itemSize().width() < 0) {
+                // Use a special rubberband for views that have only one column and
+                // expand the rubberband to use the whole width of the view.
+                startPos.setX(0);
+            }
+        } else {
+            startPos.rx() += m_view->scrollOffset();
+        }
+
+        m_oldSelection = m_selectionManager->selectedItems();
+        KItemListRubberBand* rubberBand = m_view->rubberBand();
+        rubberBand->setStartPosition(startPos);
+        rubberBand->setEndPosition(startPos);
+        rubberBand->setActive(true);
+        connect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
+        m_view->setAutoScroll(true);
+    }
+}
+
+void KItemListController::slotStateChanged(QScroller::State newState)
+{
+    if (newState == QScroller::Scrolling) {
+        m_scrollerIsScrolling = true;
+    } else if (newState == QScroller::Inactive) {
+        m_scrollerIsScrolling = false;
+    }
+}
index a84d1b8c8be7304bedf9b51cf2191f833226c5a5..d929027b7b364ce35f396b1ed28713fcde6dad93 100644 (file)
@@ -14,6 +14,7 @@
 
 #include <QObject>
 #include <QPointF>
+#include <QScroller>
 
 class QTimer;
 class KItemModelBase;
@@ -21,6 +22,7 @@ class KItemListKeyboardSearchManager;
 class KItemListSelectionManager;
 class KItemListView;
 class KItemListWidget;
+class QGestureEvent;
 class QGraphicsSceneHoverEvent;
 class QGraphicsSceneDragDropEvent;
 class QGraphicsSceneMouseEvent;
@@ -28,6 +30,7 @@ class QGraphicsSceneResizeEvent;
 class QGraphicsSceneWheelEvent;
 class QInputMethodEvent;
 class QKeyEvent;
+class QTapGesture;
 class QTransform;
 
 /**
@@ -208,6 +211,14 @@ signals:
 
     void selectedItemTextPressed(int index);
 
+    void scrollerStop();
+    void increaseZoom();
+    void decreaseZoom();
+    void swipeUp();
+
+public slots:
+    void slotStateChanged(QScroller::State newState);
+
 private slots:
     void slotViewScrollOffsetChanged(qreal current, qreal previous);
 
@@ -289,11 +300,25 @@ private:
     bool hoverLeaveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform);
     bool wheelEvent(QGraphicsSceneWheelEvent* event, const QTransform& transform);
     bool resizeEvent(QGraphicsSceneResizeEvent* event, const QTransform& transform);
+    bool gestureEvent(QGestureEvent* event, const QTransform& transform);
+    void tapTriggered(QTapGesture* tap, const QTransform& transform);
+    void tapAndHoldTriggered(QGestureEvent* event, const QTransform& transform);
+    void pinchTriggered(QGestureEvent* event, const QTransform& transform);
+    void swipeTriggered(QGestureEvent* event, const QTransform& transform);
+    void twoFingerTapTriggered(QGestureEvent* event, const QTransform& transform);
+    bool onPress(const QPoint& screenPos, const QPointF& pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons);
+    bool onRelease(const QPointF& pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons, bool touch);
+    void startRubberBand();
 
 private:
     bool m_singleClickActivationEnforced;
     bool m_selectionTogglePressed;
     bool m_clearSelectionIfItemsAreNotDragged;
+    bool m_isSwipeGesture;
+    bool m_dragActionOrRightClick;
+    bool m_scrollerIsScrolling;
+    bool m_pinchGestureInProgress;
+    bool m_mousePress;
     SelectionBehavior m_selectionBehavior;
     AutoActivationBehavior m_autoActivationBehavior;
     MouseDoubleClickAction m_mouseDoubleClickAction;
@@ -306,6 +331,10 @@ private:
 
     QTimer* m_autoActivationTimer;
 
+    Qt::GestureType m_swipeGesture;
+    Qt::GestureType m_twoFingerTapGesture;
+    Qt::MouseEventSource m_lastSource;
+
     /**
      * When starting a rubberband selection during a Shift- or Control-key has been
      * pressed the current selection should never be deleted. To be able to restore
index 05204a91082aa82cc9e2bfc5ed64ed69bd8b3abd..f14369cbd959f197b34b06ea83ae8e7f2704fe15 100644 (file)
@@ -24,6 +24,7 @@
 #include <QElapsedTimer>
 #include <QGraphicsSceneMouseEvent>
 #include <QGraphicsView>
+#include <QPropertyAnimation>
 #include <QStyleOptionRubberBand>
 #include <QTimer>
 
@@ -80,11 +81,13 @@ KItemListView::KItemListView(QGraphicsWidget* parent) :
     m_oldMaximumItemOffset(0),
     m_skipAutoScrollForRubberBand(false),
     m_rubberBand(nullptr),
+    m_tapAndHoldIndicator(nullptr),
     m_mousePos(),
     m_autoScrollIncrement(0),
     m_autoScrollTimer(nullptr),
     m_header(nullptr),
     m_headerWidget(nullptr),
+    m_indicatorAnimation(nullptr),
     m_dropIndicator()
 {
     setAcceptHoverEvents(true);
@@ -105,6 +108,23 @@ KItemListView::KItemListView(QGraphicsWidget* parent) :
     m_rubberBand = new KItemListRubberBand(this);
     connect(m_rubberBand, &KItemListRubberBand::activationChanged, this, &KItemListView::slotRubberBandActivationChanged);
 
+    m_tapAndHoldIndicator = new KItemListRubberBand(this);
+    m_indicatorAnimation = new QPropertyAnimation(m_tapAndHoldIndicator, "endPosition", this);
+    connect(m_tapAndHoldIndicator, &KItemListRubberBand::activationChanged, this, [this](bool active) {
+        if (active) {
+            m_indicatorAnimation->setDuration(150);
+            m_indicatorAnimation->setStartValue(QPointF(1, 1));
+            m_indicatorAnimation->setEndValue(QPointF(40, 40));
+            m_indicatorAnimation->start();
+        }
+        update();
+    });
+    connect(m_tapAndHoldIndicator, &KItemListRubberBand::endPositionChanged, this, [this]() {
+        if (m_tapAndHoldIndicator->isActive()) {
+            update();
+        }
+    });
+
     m_headerWidget = new KItemListHeaderWidget(this);
     m_headerWidget->setVisible(false);
 
@@ -658,6 +678,18 @@ void KItemListView::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt
         style()->drawControl(QStyle::CE_RubberBand, &opt, painter);
     }
 
+    if (m_tapAndHoldIndicator->isActive()) {
+        const QPointF indicatorSize = m_tapAndHoldIndicator->endPosition();
+        const QRectF rubberBandRect = QRectF(m_tapAndHoldIndicator->startPosition() - indicatorSize,
+                                    (m_tapAndHoldIndicator->startPosition()) + indicatorSize).normalized();
+        QStyleOptionRubberBand opt;
+        initStyleOption(&opt);
+        opt.shape = QRubberBand::Rectangle;
+        opt.opaque = false;
+        opt.rect = rubberBandRect.toRect();
+        style()->drawControl(QStyle::CE_RubberBand, &opt, painter);
+    }
+
     if (!m_dropIndicator.isEmpty()) {
         const QRectF r = m_dropIndicator.toRect();
 
index 26a33f144e69b064830c5e907d0280f0ce298ae0..df582aad05e2648142d812cbcdea3919d3278fe7 100644 (file)
@@ -31,6 +31,7 @@ class KItemListWidget;
 class KItemListWidgetInformant;
 class KItemListWidgetCreatorBase;
 class QTimer;
+class QPropertyAnimation;
 
 /**
  * @brief Represents the view of an item-list.
@@ -727,6 +728,7 @@ private:
 
     bool m_skipAutoScrollForRubberBand;
     KItemListRubberBand* m_rubberBand;
+    KItemListRubberBand* m_tapAndHoldIndicator;
 
     QPointF m_mousePos;
     int m_autoScrollIncrement;
@@ -735,6 +737,8 @@ private:
     KItemListHeader* m_header;
     KItemListHeaderWidget* m_headerWidget;
 
+    QPropertyAnimation* m_indicatorAnimation;
+
     // When dragging items into the view where the sort-role of the model
     // is empty, a visual indicator should be shown during dragging where
     // the dropping will happen. This indicator is specified by an index
index 2e57135a3648ebaf09de22056565a9e5172ed99a..7886b1e846ccd7f49a145fcbd183f5bd0ddad592 100644 (file)
@@ -18,6 +18,7 @@
 class DOLPHIN_EXPORT KItemListRubberBand : public QObject
 {
     Q_OBJECT
+    Q_PROPERTY(QPointF endPosition MEMBER m_endPos READ endPosition WRITE setEndPosition)
 
 public:
     explicit KItemListRubberBand(QObject* parent = nullptr);
diff --git a/src/kitemviews/private/ktwofingerswipe.cpp b/src/kitemviews/private/ktwofingerswipe.cpp
new file mode 100644 (file)
index 0000000..6d0e18e
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * SPDX-FileCopyrightText: 2020 Steffen Hartleib <steffenhartleib@t-online.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+// Self
+#include "ktwofingerswipe.h"
+
+// Qt
+#include <QTouchEvent>
+#include <QLineF>
+
+KTwoFingerSwipeRecognizer::KTwoFingerSwipeRecognizer() :
+    QGestureRecognizer(),
+    m_touchBeginnTimestamp(0),
+    m_gestureAlreadyTriggered(false)
+{
+}
+
+KTwoFingerSwipeRecognizer::~KTwoFingerSwipeRecognizer()
+{
+}
+
+QGesture* KTwoFingerSwipeRecognizer::create(QObject*)
+{
+    return static_cast<QGesture*>(new KTwoFingerSwipe());
+}
+
+QGestureRecognizer::Result KTwoFingerSwipeRecognizer::recognize(QGesture* gesture, QObject* watched, QEvent* event)
+{
+    Q_UNUSED(watched)
+
+    KTwoFingerSwipe* const kTwoFingerSwipe = static_cast<KTwoFingerSwipe*>(gesture);
+    const QTouchEvent* touchEvent = static_cast<const QTouchEvent*>(event);
+
+    const int maxTimeFrameForSwipe = 90;
+    const int minDistanceForSwipe = 30;
+
+    switch (event->type()) {
+    case QEvent::TouchBegin: {
+        m_touchBeginnTimestamp = touchEvent->timestamp();
+        m_gestureAlreadyTriggered = false;
+        kTwoFingerSwipe->setHotSpot(touchEvent->touchPoints().first().startScreenPos());
+        kTwoFingerSwipe->setPos(touchEvent->touchPoints().first().startPos());
+        kTwoFingerSwipe->setScreenPos(touchEvent->touchPoints().first().startScreenPos());
+        kTwoFingerSwipe->setScenePos(touchEvent->touchPoints().first().startScenePos());
+        return MayBeGesture;
+    }
+
+    case QEvent::TouchUpdate: {
+        const qint64 now = touchEvent->timestamp();
+        const qint64 elapsedTime = now - m_touchBeginnTimestamp;
+        const QPointF distance = touchEvent->touchPoints().first().startPos() - touchEvent->touchPoints().first().pos();
+        kTwoFingerSwipe->setHotSpot(touchEvent->touchPoints().first().startScreenPos());
+        kTwoFingerSwipe->setPos(touchEvent->touchPoints().first().startPos());
+        kTwoFingerSwipe->setScreenPos(touchEvent->touchPoints().first().startScreenPos());
+        kTwoFingerSwipe->setScenePos(touchEvent->touchPoints().first().startScenePos());
+        const QLineF ql = QLineF(touchEvent->touchPoints().first().startPos(), touchEvent->touchPoints().first().pos());
+        kTwoFingerSwipe->setSwipeAngle(ql.angle());
+
+        if (touchEvent->touchPoints().size() > 2) {
+            return CancelGesture;
+        }
+
+        if (touchEvent->touchPoints().size() == 2) {
+            if ((elapsedTime) > maxTimeFrameForSwipe) {
+                return CancelGesture;
+            }
+
+            if (distance.manhattanLength() >= minDistanceForSwipe &&
+                    (elapsedTime) <= maxTimeFrameForSwipe && !m_gestureAlreadyTriggered) {
+                m_gestureAlreadyTriggered = true;
+                return FinishGesture;
+            } else if ((elapsedTime) <= maxTimeFrameForSwipe && !m_gestureAlreadyTriggered) {
+                return TriggerGesture;
+            }
+        }
+        break;
+    }
+
+    default:
+        return Ignore;
+    }
+    return Ignore;
+}
+
+KTwoFingerSwipe::KTwoFingerSwipe(QObject* parent) :
+    QGesture(parent),
+    m_pos(QPointF(-1, -1)),
+    m_screenPos(QPointF(-1, -1)),
+    m_scenePos(QPointF(-1, -1)),
+    m_swipeAngle(0.0)
+{
+}
+
+KTwoFingerSwipe::~KTwoFingerSwipe()
+{
+}
+
+QPointF KTwoFingerSwipe::pos() const
+{
+    return m_pos;
+}
+
+void KTwoFingerSwipe::setPos(QPointF _pos)
+{
+    m_pos = _pos;
+}
+
+QPointF KTwoFingerSwipe::screenPos() const
+{
+    return m_screenPos;
+}
+
+void KTwoFingerSwipe::setScreenPos(QPointF _screenPos)
+{
+    m_screenPos = _screenPos;
+}
+
+QPointF KTwoFingerSwipe::scenePos() const
+{
+    return m_scenePos;
+}
+
+void KTwoFingerSwipe::setScenePos(QPointF _scenePos)
+{
+    m_scenePos = _scenePos;
+}
+
+qreal KTwoFingerSwipe::swipeAngle() const
+{
+    return m_swipeAngle;
+}
+ void KTwoFingerSwipe::setSwipeAngle(qreal _swipeAngle)
+{
+    m_swipeAngle = _swipeAngle;
+}
+
diff --git a/src/kitemviews/private/ktwofingerswipe.h b/src/kitemviews/private/ktwofingerswipe.h
new file mode 100644 (file)
index 0000000..27d9d75
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * SPDX-FileCopyrightText: 2020 Steffen Hartleib <steffenhartleib@t-online.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef KTWOFINGERSWIPE_H
+#define KTWOFINGERSWIPE_H
+
+#include "dolphin_export.h"
+// Qt
+#include <QGesture>
+#include <QGestureRecognizer>
+
+class DOLPHIN_EXPORT KTwoFingerSwipe : public QGesture
+{
+    Q_OBJECT
+    Q_PROPERTY(QPointF pos READ pos WRITE setPos)
+    Q_PROPERTY(QPointF screenPos READ screenPos WRITE setScreenPos)
+    Q_PROPERTY(QPointF scenePos READ scenePos WRITE setScenePos)
+    Q_PROPERTY(qreal swipeAngle READ swipeAngle WRITE setSwipeAngle)
+public:
+    explicit KTwoFingerSwipe(QObject* parent = nullptr);
+    ~KTwoFingerSwipe();
+    QPointF pos() const;
+    void setPos(QPointF pos);
+    QPointF screenPos() const;
+    void setScreenPos(QPointF screenPos);
+    QPointF scenePos() const;
+    void setScenePos(QPointF scenePos);
+    qreal swipeAngle() const;
+    void setSwipeAngle(qreal swipeAngle);
+private:
+    QPointF m_pos;
+    QPointF m_screenPos;
+    QPointF m_scenePos;
+    qreal m_swipeAngle;
+};
+
+class DOLPHIN_EXPORT KTwoFingerSwipeRecognizer : public QGestureRecognizer
+{
+public:
+    explicit KTwoFingerSwipeRecognizer();
+    ~KTwoFingerSwipeRecognizer();
+    QGesture* create(QObject*) override;
+    Result recognize(QGesture*, QObject*, QEvent*) override;
+private:
+    Q_DISABLE_COPY( KTwoFingerSwipeRecognizer )
+    qint64 m_touchBeginnTimestamp;
+    bool m_gestureAlreadyTriggered;
+};
+
+#endif /* KTWOFINGERSWIPE_H */
+
diff --git a/src/kitemviews/private/ktwofingertap.cpp b/src/kitemviews/private/ktwofingertap.cpp
new file mode 100644 (file)
index 0000000..9f4521d
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * SPDX-FileCopyrightText: 2020 Steffen Hartleib <steffenhartleib@t-online.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+// Self
+#include "ktwofingertap.h"
+
+// Qt
+#include <QTouchEvent>
+#include <QApplication>
+
+KTwoFingerTapRecognizer::KTwoFingerTapRecognizer() :
+    QGestureRecognizer(),
+    m_gestureTriggered(false)
+{
+}
+
+KTwoFingerTapRecognizer::~KTwoFingerTapRecognizer()
+{
+}
+
+QGesture* KTwoFingerTapRecognizer::create(QObject*)
+{
+    return static_cast<QGesture*>(new KTwoFingerTap());
+}
+
+QGestureRecognizer::Result KTwoFingerTapRecognizer::recognize(QGesture* gesture, QObject* watched, QEvent* event)
+{
+    Q_UNUSED(watched)
+
+    KTwoFingerTap* const kTwoFingerTap = static_cast<KTwoFingerTap*>(gesture);
+    const QTouchEvent* touchEvent = static_cast<const QTouchEvent*>(event);
+
+    switch (event->type()) {
+    case QEvent::TouchBegin: {
+        kTwoFingerTap->setHotSpot(touchEvent->touchPoints().first().startScreenPos());
+        kTwoFingerTap->setPos(touchEvent->touchPoints().first().startPos());
+        kTwoFingerTap->setScreenPos(touchEvent->touchPoints().first().startScreenPos());
+        kTwoFingerTap->setScenePos(touchEvent->touchPoints().first().startScenePos());
+        m_gestureTriggered = false;
+        return MayBeGesture;
+    }
+
+    case QEvent::TouchUpdate: {
+
+        if (touchEvent->touchPoints().size() > 2) {
+            m_gestureTriggered = false;
+            return CancelGesture;
+        }
+
+        if (touchEvent->touchPoints().size() == 2) {
+            if ((touchEvent->touchPoints().first().startPos() - touchEvent->touchPoints().first().pos()).manhattanLength() >= QApplication::startDragDistance()) {
+                m_gestureTriggered = false;
+                return CancelGesture;
+            }
+            if ((touchEvent->touchPoints().at(1).startPos() - touchEvent->touchPoints().at(1).pos()).manhattanLength() >= QApplication::startDragDistance()) {
+                m_gestureTriggered = false;
+                return CancelGesture;
+            }
+            if (touchEvent->touchPointStates() & Qt::TouchPointPressed) {
+                m_gestureTriggered = true;
+            }
+            if (touchEvent->touchPointStates() & Qt::TouchPointReleased && m_gestureTriggered) {
+                m_gestureTriggered = false;
+                return FinishGesture;
+            }
+        }
+        break;
+    }
+
+    default:
+        return Ignore;
+    }
+    return Ignore;
+}
+
+KTwoFingerTap::KTwoFingerTap(QObject* parent) :
+    QGesture(parent),
+    m_pos(QPointF(-1, -1)),
+    m_screenPos(QPointF(-1, -1)),
+    m_scenePos(QPointF(-1, -1))
+{
+}
+
+KTwoFingerTap::~KTwoFingerTap()
+{
+}
+
+QPointF KTwoFingerTap::pos() const
+{
+    return m_pos;
+}
+
+void KTwoFingerTap::setPos(QPointF _pos)
+{
+    m_pos = _pos;
+}
+
+QPointF KTwoFingerTap::screenPos() const
+{
+    return m_screenPos;
+}
+
+void KTwoFingerTap::setScreenPos(QPointF _screenPos)
+{
+    m_screenPos = _screenPos;
+}
+
+QPointF KTwoFingerTap::scenePos() const
+{
+    return m_scenePos;
+}
+
+void KTwoFingerTap::setScenePos(QPointF _scenePos)
+{
+    m_scenePos = _scenePos;
+}
diff --git a/src/kitemviews/private/ktwofingertap.h b/src/kitemviews/private/ktwofingertap.h
new file mode 100644 (file)
index 0000000..614df24
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * SPDX-FileCopyrightText: 2020 Steffen Hartleib <steffenhartleib@t-online.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef KTWOFINGERTAP_H
+#define KTWOFINGERTAP_H
+
+#include <dolphin_export.h>
+// Qt
+#include <QGesture>
+#include <QGestureRecognizer>
+
+class DOLPHIN_EXPORT KTwoFingerTap : public QGesture
+{
+    Q_OBJECT
+    Q_PROPERTY(QPointF pos READ pos WRITE setPos)
+    Q_PROPERTY(QPointF screenPos READ screenPos WRITE setScreenPos)
+    Q_PROPERTY(QPointF scenePos READ scenePos WRITE setScenePos)
+public:
+    explicit KTwoFingerTap(QObject* parent = nullptr);
+    ~KTwoFingerTap();
+    QPointF pos() const;
+    void setPos(QPointF pos);
+    QPointF screenPos() const;
+    void setScreenPos(QPointF screenPos);
+    QPointF scenePos() const;
+    void setScenePos(QPointF scenePos);
+private:
+    QPointF m_pos;
+    QPointF m_screenPos;
+    QPointF m_scenePos;
+};
+
+class DOLPHIN_EXPORT KTwoFingerTapRecognizer : public QGestureRecognizer
+{
+public:
+    explicit KTwoFingerTapRecognizer();
+    ~KTwoFingerTapRecognizer();
+    QGesture* create(QObject*) override; 
+    Result recognize(QGesture*, QObject*, QEvent*) override;
+private:
+    Q_DISABLE_COPY(KTwoFingerTapRecognizer)
+    bool m_gestureTriggered;
+};
+
+#endif /* KTWOFINGERTAP_H */
index 5f4ac84e82dcdd0da51c550ac8185832ebfef548..f843e7f4624016332e4fcd3f432ff64b91172c0f 100644 (file)
@@ -405,6 +405,7 @@ void InformationPanel::init()
     m_content = new InformationPanelContent(this);
     connect(m_content, &InformationPanelContent::urlActivated, this, &InformationPanel::urlActivated);
     connect(m_content, &InformationPanelContent::configurationFinished, this, [this]() { m_inConfigurationMode = false; });
+    connect(m_content, &InformationPanelContent::contextMenuRequested, this, &InformationPanel::showContextMenu);
 
     QVBoxLayout* layout = new QVBoxLayout(this);
     layout->setContentsMargins(0, 0, 0, 0);
index d632cfcd1c956c8b84a707a2199a1d4fea8d8070..ded88bd96785fa305fd1bff5364a9b483418fb33 100644 (file)
 #include <QTextLayout>
 #include <QTimer>
 #include <QVBoxLayout>
+#include <QScroller>
 #include <QStyle>
 #include <QPainter>
 #include <QBitmap>
 #include <QLinearGradient>
 #include <QPolygon>
+#include <QGesture>
 
 #include "dolphin_informationpanelsettings.h"
 #include "phononwidget.h"
@@ -134,6 +136,7 @@ InformationPanelContent::InformationPanelContent(QWidget* parent) :
     m_metaDataArea->setFrameShape(QFrame::NoFrame);
 
     QWidget* viewport = m_metaDataArea->viewport();
+    QScroller::grabGesture(viewport, QScroller::TouchGesture);
     viewport->installEventFilter(this);
 
     layout->addWidget(m_preview);
@@ -144,6 +147,8 @@ InformationPanelContent::InformationPanelContent(QWidget* parent) :
     layout->addWidget(m_metaDataArea);
     layout->addWidget(m_configureButtons);
 
+    grabGesture(Qt::TapAndHoldGesture);
+
     m_placesItemModel = new PlacesItemModel(this);
 }
 
@@ -338,6 +343,33 @@ bool InformationPanelContent::eventFilter(QObject* obj, QEvent* event)
     return QWidget::eventFilter(obj, event);
 }
 
+bool InformationPanelContent::event(QEvent* event)
+{
+    if (event->type() == QEvent::Gesture) {
+        gestureEvent(static_cast<QGestureEvent*>(event));
+        return true;
+    }
+    return QWidget::event(event);
+}
+
+bool InformationPanelContent::gestureEvent(QGestureEvent* event)
+{
+    if (!underMouse()) {
+        return false;
+    }
+
+    QTapAndHoldGesture* tap = static_cast<QTapAndHoldGesture*>(event->gesture(Qt::TapAndHoldGesture));
+
+    if (tap) {
+        if (tap->state() == Qt::GestureFinished) {
+            emit contextMenuRequested(tap->position().toPoint());
+        }
+        event->accept();
+        return true;
+    }
+    return false;
+}
+
 void InformationPanelContent::showIcon(const KFileItem& item)
 {
     m_outdatedPreviewTimer->stop();
index 7b83e5d41f1c9dff75211b1786429196d88a8c03..abdfdeb354453f0b213880ba119725b6f260869b 100644 (file)
@@ -23,6 +23,7 @@ class QDialogButtonBox;
 class QString;
 class QLabel;
 class QScrollArea;
+class QGestureEvent;
 
 namespace KIO {
   class PreviewJob;
@@ -78,6 +79,7 @@ public:
 signals:
     void urlActivated( const QUrl& url );
     void configurationFinished();
+    void contextMenuRequested(const QPoint& pos);
 
 public slots:
     /**
@@ -90,6 +92,8 @@ protected:
     /** @see QObject::eventFilter() */
     bool eventFilter(QObject* obj, QEvent* event) override;
 
+    bool event(QEvent * event) override;
+
 private slots:
     /**
      * Is invoked if no preview is available for the item. In this
@@ -131,6 +135,8 @@ private:
      */
     void refreshPixmapView();
 
+    bool gestureEvent(QGestureEvent* event);
+
 private:
     KFileItem m_item;
 
index c2f21dfab20b0ed2df16ac3ba002a8ff0703fb80..a41515c2532afe61cfc94f3c7654ee2cc7179330 100644 (file)
@@ -19,6 +19,7 @@
 #include <QLabel>
 #include <QListView>
 #include <QPainter>
+#include <QScroller>
 #include <QShowEvent>
 #include <QSortFilterProxyModel>
 #include <QSpinBox>
@@ -42,6 +43,7 @@ PreviewsSettingsPage::PreviewsSettingsPage(QWidget* parent) :
     QLabel* showPreviewsLabel = new QLabel(i18nc("@title:group", "Show previews in the view for:"), this);
 
     m_listView = new QListView(this);
+    QScroller::grabGesture(m_listView->viewport(), QScroller::TouchGesture);
 
     ServiceItemDelegate* delegate = new ServiceItemDelegate(m_listView, m_listView);
     connect(delegate, &ServiceItemDelegate::requestServiceConfiguration,
@@ -56,6 +58,7 @@ PreviewsSettingsPage::PreviewsSettingsPage(QWidget* parent) :
     m_listView->setModel(proxyModel);
     m_listView->setItemDelegate(delegate);
     m_listView->setVerticalScrollMode(QListView::ScrollPerPixel);
+    m_listView->setUniformItemSizes(true);
 
     QLabel* localFileSizeLabel = new QLabel(i18n("Skip previews for local files above:"), this);
 
index 6ce5e1fc95a033382b3ae0987fcf11a9c4f72c38..fa064d8a13bf78fcb3126175157da2b30c6ced02 100644 (file)
@@ -23,6 +23,7 @@
 #include <QGridLayout>
 #include <QLabel>
 #include <QListWidget>
+#include <QScroller>
 #include <QShowEvent>
 #include <QSortFilterProxyModel>
 #include <QLineEdit>
@@ -56,6 +57,8 @@ ServicesSettingsPage::ServicesSettingsPage(QWidget* parent) :
     });
 
     m_listView = new QListView(this);
+    QScroller::grabGesture(m_listView->viewport(), QScroller::TouchGesture);
+
     auto *delegate = new ServiceItemDelegate(m_listView, m_listView);
     m_serviceModel = new ServiceModel(this);
     m_sortModel = new QSortFilterProxyModel(this);
index f102e9acb1a6a9905643a85379715b10415d660e..2b4970eeb9a4baaa02f71e86c598fc304acc90b4 100644 (file)
@@ -134,6 +134,9 @@ DolphinView::DolphinView(const QUrl& url, QWidget* parent) :
     connect(controller, &KItemListController::escapePressed, this, &DolphinView::stopLoading);
     connect(controller, &KItemListController::modelChanged, this, &DolphinView::slotModelChanged);
     connect(controller, &KItemListController::selectedItemTextPressed, this, &DolphinView::slotSelectedItemTextPressed);
+    connect(controller, &KItemListController::increaseZoom, this, &DolphinView::slotIncreaseZoom);
+    connect(controller, &KItemListController::decreaseZoom, this, &DolphinView::slotDecreaseZoom);
+    connect(controller, &KItemListController::swipeUp, this, &DolphinView::slotSwipeUp);
 
     connect(m_model, &KFileItemModel::directoryLoadingStarted,       this, &DolphinView::slotDirectoryLoadingStarted);
     connect(m_model, &KFileItemModel::directoryLoadingCompleted,     this, &DolphinView::slotDirectoryLoadingCompleted);
@@ -1954,3 +1957,18 @@ void DolphinView::copyPathToClipboard()
     }
     clipboard->setText(path);
 }
+
+void DolphinView::slotIncreaseZoom()
+{
+    setZoomLevel(zoomLevel() + 1);
+}
+
+void DolphinView::slotDecreaseZoom()
+{
+    setZoomLevel(zoomLevel() - 1);
+}
+
+void DolphinView::slotSwipeUp()
+{
+    emit goUpRequested();
+}
index 50a88e9367b20249263982c50623b8604749293e..1d0ebe0feb15e56747d90c86579bfee09ccc2868 100644 (file)
@@ -582,6 +582,8 @@ signals:
      */
     void urlActivated(const QUrl& url);
 
+    void goUpRequested();
+
 protected:
     /** Changes the zoom level if Control is pressed during a wheel event. */
     void wheelEvent(QWheelEvent* event) override;
@@ -611,6 +613,9 @@ private slots:
     void slotRenameDialogRenamingFinished(const QList<QUrl>& urls);
     void slotSelectedItemTextPressed(int index);
     void slotCopyingDone(KIO::Job *, const QUrl &, const QUrl &to);
+    void slotIncreaseZoom();
+    void slotDecreaseZoom();
+    void slotSwipeUp();
 
     /*
      * Is called when new items get pasted or dropped.