Commit d7b33b76 authored by Steffen Hartleib's avatar Steffen Hartleib Committed by Elvis Angelaccio

Improve Touch support

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 '85241a92'.
parent d899c2b4
...@@ -90,6 +90,8 @@ set(dolphinprivate_LIB_SRCS ...@@ -90,6 +90,8 @@ set(dolphinprivate_LIB_SRCS
kitemviews/private/kitemlistviewanimation.cpp kitemviews/private/kitemlistviewanimation.cpp
kitemviews/private/kitemlistviewlayouter.cpp kitemviews/private/kitemlistviewlayouter.cpp
kitemviews/private/kpixmapmodifier.cpp kitemviews/private/kpixmapmodifier.cpp
kitemviews/private/ktwofingerswipe.cpp
kitemviews/private/ktwofingertap.cpp
settings/applyviewpropsjob.cpp settings/applyviewpropsjob.cpp
settings/viewmodes/viewmodesettings.cpp settings/viewmodes/viewmodesettings.cpp
settings/viewpropertiesdialog.cpp settings/viewpropertiesdialog.cpp
......
...@@ -2095,6 +2095,8 @@ void DolphinMainWindow::connectViewSignals(DolphinViewContainer* container) ...@@ -2095,6 +2095,8 @@ void DolphinMainWindow::connectViewSignals(DolphinViewContainer* container)
this, &DolphinMainWindow::goForward); this, &DolphinMainWindow::goForward);
connect(view, &DolphinView::urlActivated, connect(view, &DolphinView::urlActivated,
this, &DolphinMainWindow::handleUrl); this, &DolphinMainWindow::handleUrl);
connect(view, &DolphinView::goUpRequested,
this, &DolphinMainWindow::goUp);
const KUrlNavigator* navigator = container->urlNavigator(); const KUrlNavigator* navigator = container->urlNavigator();
connect(navigator, &KUrlNavigator::urlChanged, connect(navigator, &KUrlNavigator::urlChanged,
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include <QGraphicsScene> #include <QGraphicsScene>
#include <QGraphicsView> #include <QGraphicsView>
#include <QScrollBar> #include <QScrollBar>
#include <QScroller>
#include <QStyleOption> #include <QStyleOption>
/** /**
...@@ -54,7 +55,8 @@ KItemListContainer::KItemListContainer(KItemListController* controller, QWidget* ...@@ -54,7 +55,8 @@ KItemListContainer::KItemListContainer(KItemListController* controller, QWidget*
QAbstractScrollArea(parent), QAbstractScrollArea(parent),
m_controller(controller), m_controller(controller),
m_horizontalSmoothScroller(nullptr), m_horizontalSmoothScroller(nullptr),
m_verticalSmoothScroller(nullptr) m_verticalSmoothScroller(nullptr),
m_scroller(nullptr)
{ {
Q_ASSERT(controller); Q_ASSERT(controller);
controller->setParent(this); controller->setParent(this);
...@@ -76,6 +78,13 @@ KItemListContainer::KItemListContainer(KItemListController* controller, QWidget* ...@@ -76,6 +78,13 @@ KItemListContainer::KItemListContainer(KItemListController* controller, QWidget*
this, &KItemListContainer::slotModelChanged); this, &KItemListContainer::slotModelChanged);
connect(controller, &KItemListController::viewChanged, connect(controller, &KItemListController::viewChanged,
this, &KItemListContainer::slotViewChanged); 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() KItemListContainer::~KItemListContainer()
...@@ -325,6 +334,11 @@ void KItemListContainer::updateItemOffsetScrollBar() ...@@ -325,6 +334,11 @@ void KItemListContainer::updateItemOffsetScrollBar()
} }
} }
void KItemListContainer::stopScroller()
{
m_scroller->stop();
}
void KItemListContainer::updateGeometries() void KItemListContainer::updateGeometries()
{ {
QRect rect = geometry(); QRect rect = geometry();
......
...@@ -17,6 +17,7 @@ class KItemListController; ...@@ -17,6 +17,7 @@ class KItemListController;
class KItemListSmoothScroller; class KItemListSmoothScroller;
class KItemListView; class KItemListView;
class KItemModelBase; class KItemModelBase;
class QScroller;
/** /**
* @brief Provides a QWidget based scrolling view for a KItemListController. * @brief Provides a QWidget based scrolling view for a KItemListController.
...@@ -57,6 +58,7 @@ private slots: ...@@ -57,6 +58,7 @@ private slots:
void scrollTo(qreal offset); void scrollTo(qreal offset);
void updateScrollOffsetScrollBar(); void updateScrollOffsetScrollBar();
void updateItemOffsetScrollBar(); void updateItemOffsetScrollBar();
void stopScroller();
private: private:
void updateGeometries(); void updateGeometries();
...@@ -74,6 +76,7 @@ private: ...@@ -74,6 +76,7 @@ private:
KItemListSmoothScroller* m_horizontalSmoothScroller; KItemListSmoothScroller* m_horizontalSmoothScroller;
KItemListSmoothScroller* m_verticalSmoothScroller; KItemListSmoothScroller* m_verticalSmoothScroller;
QScroller* m_scroller;
}; };
#endif #endif
......
This diff is collapsed.
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include <QObject> #include <QObject>
#include <QPointF> #include <QPointF>
#include <QScroller>
class QTimer; class QTimer;
class KItemModelBase; class KItemModelBase;
...@@ -21,6 +22,7 @@ class KItemListKeyboardSearchManager; ...@@ -21,6 +22,7 @@ class KItemListKeyboardSearchManager;
class KItemListSelectionManager; class KItemListSelectionManager;
class KItemListView; class KItemListView;
class KItemListWidget; class KItemListWidget;
class QGestureEvent;
class QGraphicsSceneHoverEvent; class QGraphicsSceneHoverEvent;
class QGraphicsSceneDragDropEvent; class QGraphicsSceneDragDropEvent;
class QGraphicsSceneMouseEvent; class QGraphicsSceneMouseEvent;
...@@ -28,6 +30,7 @@ class QGraphicsSceneResizeEvent; ...@@ -28,6 +30,7 @@ class QGraphicsSceneResizeEvent;
class QGraphicsSceneWheelEvent; class QGraphicsSceneWheelEvent;
class QInputMethodEvent; class QInputMethodEvent;
class QKeyEvent; class QKeyEvent;
class QTapGesture;
class QTransform; class QTransform;
/** /**
...@@ -208,6 +211,14 @@ signals: ...@@ -208,6 +211,14 @@ signals:
void selectedItemTextPressed(int index); void selectedItemTextPressed(int index);
void scrollerStop();
void increaseZoom();
void decreaseZoom();
void swipeUp();
public slots:
void slotStateChanged(QScroller::State newState);
private slots: private slots:
void slotViewScrollOffsetChanged(qreal current, qreal previous); void slotViewScrollOffsetChanged(qreal current, qreal previous);
...@@ -289,11 +300,25 @@ private: ...@@ -289,11 +300,25 @@ private:
bool hoverLeaveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform); bool hoverLeaveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform);
bool wheelEvent(QGraphicsSceneWheelEvent* event, const QTransform& transform); bool wheelEvent(QGraphicsSceneWheelEvent* event, const QTransform& transform);
bool resizeEvent(QGraphicsSceneResizeEvent* 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: private:
bool m_singleClickActivationEnforced; bool m_singleClickActivationEnforced;
bool m_selectionTogglePressed; bool m_selectionTogglePressed;
bool m_clearSelectionIfItemsAreNotDragged; bool m_clearSelectionIfItemsAreNotDragged;
bool m_isSwipeGesture;
bool m_dragActionOrRightClick;
bool m_scrollerIsScrolling;
bool m_pinchGestureInProgress;
bool m_mousePress;
SelectionBehavior m_selectionBehavior; SelectionBehavior m_selectionBehavior;
AutoActivationBehavior m_autoActivationBehavior; AutoActivationBehavior m_autoActivationBehavior;
MouseDoubleClickAction m_mouseDoubleClickAction; MouseDoubleClickAction m_mouseDoubleClickAction;
...@@ -306,6 +331,10 @@ private: ...@@ -306,6 +331,10 @@ private:
QTimer* m_autoActivationTimer; 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 * 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 * pressed the current selection should never be deleted. To be able to restore
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QGraphicsSceneMouseEvent> #include <QGraphicsSceneMouseEvent>
#include <QGraphicsView> #include <QGraphicsView>
#include <QPropertyAnimation>
#include <QStyleOptionRubberBand> #include <QStyleOptionRubberBand>
#include <QTimer> #include <QTimer>
...@@ -80,11 +81,13 @@ KItemListView::KItemListView(QGraphicsWidget* parent) : ...@@ -80,11 +81,13 @@ KItemListView::KItemListView(QGraphicsWidget* parent) :
m_oldMaximumItemOffset(0), m_oldMaximumItemOffset(0),
m_skipAutoScrollForRubberBand(false), m_skipAutoScrollForRubberBand(false),
m_rubberBand(nullptr), m_rubberBand(nullptr),
m_tapAndHoldIndicator(nullptr),
m_mousePos(), m_mousePos(),
m_autoScrollIncrement(0), m_autoScrollIncrement(0),
m_autoScrollTimer(nullptr), m_autoScrollTimer(nullptr),
m_header(nullptr), m_header(nullptr),
m_headerWidget(nullptr), m_headerWidget(nullptr),
m_indicatorAnimation(nullptr),
m_dropIndicator() m_dropIndicator()
{ {
setAcceptHoverEvents(true); setAcceptHoverEvents(true);
...@@ -105,6 +108,23 @@ KItemListView::KItemListView(QGraphicsWidget* parent) : ...@@ -105,6 +108,23 @@ KItemListView::KItemListView(QGraphicsWidget* parent) :
m_rubberBand = new KItemListRubberBand(this); m_rubberBand = new KItemListRubberBand(this);
connect(m_rubberBand, &KItemListRubberBand::activationChanged, this, &KItemListView::slotRubberBandActivationChanged); 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 = new KItemListHeaderWidget(this);
m_headerWidget->setVisible(false); m_headerWidget->setVisible(false);
...@@ -658,6 +678,18 @@ void KItemListView::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt ...@@ -658,6 +678,18 @@ void KItemListView::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt
style()->drawControl(QStyle::CE_RubberBand, &opt, painter); 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()) { if (!m_dropIndicator.isEmpty()) {
const QRectF r = m_dropIndicator.toRect(); const QRectF r = m_dropIndicator.toRect();
......
...@@ -31,6 +31,7 @@ class KItemListWidget; ...@@ -31,6 +31,7 @@ class KItemListWidget;
class KItemListWidgetInformant; class KItemListWidgetInformant;
class KItemListWidgetCreatorBase; class KItemListWidgetCreatorBase;
class QTimer; class QTimer;
class QPropertyAnimation;
/** /**
* @brief Represents the view of an item-list. * @brief Represents the view of an item-list.
...@@ -727,6 +728,7 @@ private: ...@@ -727,6 +728,7 @@ private:
bool m_skipAutoScrollForRubberBand; bool m_skipAutoScrollForRubberBand;
KItemListRubberBand* m_rubberBand; KItemListRubberBand* m_rubberBand;
KItemListRubberBand* m_tapAndHoldIndicator;
QPointF m_mousePos; QPointF m_mousePos;
int m_autoScrollIncrement; int m_autoScrollIncrement;
...@@ -735,6 +737,8 @@ private: ...@@ -735,6 +737,8 @@ private:
KItemListHeader* m_header; KItemListHeader* m_header;
KItemListHeaderWidget* m_headerWidget; KItemListHeaderWidget* m_headerWidget;
QPropertyAnimation* m_indicatorAnimation;
// When dragging items into the view where the sort-role of the model // When dragging items into the view where the sort-role of the model
// is empty, a visual indicator should be shown during dragging where // is empty, a visual indicator should be shown during dragging where
// the dropping will happen. This indicator is specified by an index // the dropping will happen. This indicator is specified by an index
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
class DOLPHIN_EXPORT KItemListRubberBand : public QObject class DOLPHIN_EXPORT KItemListRubberBand : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QPointF endPosition MEMBER m_endPos READ endPosition WRITE setEndPosition)
public: public:
explicit KItemListRubberBand(QObject* parent = nullptr); explicit KItemListRubberBand(QObject* parent = nullptr);
......
/*
* 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;
}
/*
* 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 */
/*
* 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()) {