diff --git a/app/folderviewcontextmanageritem.cpp b/app/folderviewcontextmanageritem.cpp --- a/app/folderviewcontextmanageritem.cpp +++ b/app/folderviewcontextmanageritem.cpp @@ -28,6 +28,8 @@ #include #include #include +#include +#include // KDE #include @@ -37,6 +39,8 @@ #include #include "sidebar.h" #include "fileoperations.h" +#include +#include "lib/touch/touch_helper.h" namespace Gwenview { @@ -92,6 +96,19 @@ FileOperations::showMenuForDroppedUrls(this, urlList, destUrl); } + bool viewportEvent(QEvent* event) override + { + if (event->type() == QEvent::TouchBegin) { + return true; + } + const QPoint pos = Touch_Helper::simpleTapPosition(event); + if (pos != QPoint(-1, -1)) { + expand(indexAt(pos)); + emit activated(indexAt(pos)); + } + + return QTreeView::viewportEvent(event); + } private: QRect mDropRect; }; @@ -209,6 +226,8 @@ mView->header()->setStretchLastSection(false); mView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + ScrollerUtils::setQScroller(mView->viewport()); + setWidget(mView); QObject::connect(mView, &QTreeView::activated, this, &FolderViewContextManagerItem::slotActivated); EventWatcher::install(mView, QEvent::Show, this, SLOT(expandToSelectedUrl())); diff --git a/app/startmainpage.h b/app/startmainpage.h --- a/app/startmainpage.h +++ b/app/startmainpage.h @@ -31,6 +31,7 @@ class QModelIndex; class QPalette; class QShowEvent; +class QEvent; class QUrl; @@ -61,6 +62,7 @@ protected: void showEvent(QShowEvent*) override; + bool eventFilter(QObject*, QEvent*) override; private Q_SLOTS: void slotListViewActivated(const QModelIndex& index); diff --git a/app/startmainpage.cpp b/app/startmainpage.cpp --- a/app/startmainpage.cpp +++ b/app/startmainpage.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE #include @@ -189,14 +190,28 @@ connect(GwenviewConfig::self(), &GwenviewConfig::configChanged, this, &StartMainPage::loadConfig); d->mRecentFoldersView->setFocus(); + + ScrollerUtils::setQScroller(d->mBookmarksView->viewport()); + d->mBookmarksView->viewport()->installEventFilter(this); } StartMainPage::~StartMainPage() { delete d->mRecentFilesThumbnailProvider; delete d; } +bool StartMainPage::eventFilter(QObject*, QEvent* event) +{ + if (event->type() == QEvent::MouseMove) { + QMouseEvent* mouseEvent = static_cast(event); + if (mouseEvent->source() == Qt::MouseEventSynthesizedByQt) { + return true; + } + } + return false; +} + void StartMainPage::slotTagViewClicked(const QModelIndex& index) { #ifdef GWENVIEW_SEMANTICINFO_BACKEND_BALOO diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -170,6 +170,14 @@ widgetfloater.cpp zoomslider.cpp zoomwidget.cpp + scrollerutils.cpp + touch/touch.cpp + touch/tapholdandmoving.cpp + touch/twofingerpan.cpp + touch/oneandtwofingerswipe.cpp + touch/doubletap.cpp + touch/twofingertap.cpp + touch/touch_helper.cpp ${GV_JPEG_DIR}/transupp.c ) diff --git a/lib/documentview/documentview.h b/lib/documentview/documentview.h --- a/lib/documentview/documentview.h +++ b/lib/documentview/documentview.h @@ -33,6 +33,10 @@ class QPropertyAnimation; class QUrl; +class QGestureEvent; +class QTapGesture; +class QPinchGesture; +class QGesture; namespace Gwenview { @@ -233,6 +237,13 @@ void dragThumbnailLoaded(const KFileItem&, const QPixmap&); void dragThumbnailLoadingFailed(const KFileItem&); + void setPinchParameter(); + void zoomGesture(qreal newZoom, const QPoint& pos); + void rotationsGesture(qreal); + void swipeRight(); + void swipeLeft(); + void panGesture(const QPointF& delta); + void startDragFromTouch(const QPoint& pos); private: friend struct DocumentViewPrivate; diff --git a/lib/documentview/documentview.cpp b/lib/documentview/documentview.cpp --- a/lib/documentview/documentview.cpp +++ b/lib/documentview/documentview.cpp @@ -42,6 +42,7 @@ #include #include #include +#include // KDE #include @@ -68,6 +69,8 @@ #include #include #include +#include +#include namespace Gwenview { @@ -115,6 +118,8 @@ QPointer mDragThumbnailProvider; QPointer mDrag; + Touch* mTouch; + void setCurrentAdapter(AbstractDocumentViewAdapter* adapter) { Q_ASSERT(adapter); @@ -448,6 +453,18 @@ d->mDragStartPosition = QPointF(0, 0); d->mDrag = nullptr; + d->mTouch = new Touch(this); + setAcceptTouchEvents (true); + connect(d->mTouch, &Touch::doubleTapTriggered, this, &DocumentView::toggleFullScreenRequested); + connect(d->mTouch, &Touch::twoFingerTapTriggered, this, &DocumentView::contextMenuRequested); + connect(d->mTouch, &Touch::pinchGestureStarted, this, &DocumentView::setPinchParameter); + connect(d->mTouch, &Touch::pinchZoomTriggered, this, &DocumentView::zoomGesture); + connect(d->mTouch, &Touch::pinchRotateTriggered, this, &DocumentView::rotationsGesture); + connect(d->mTouch, &Touch::swipeRightTriggered, this, &DocumentView::swipeRight); + connect(d->mTouch, &Touch::swipeLeftTriggered, this, &DocumentView::swipeLeft); + connect(d->mTouch, &Touch::PanTriggered, this, &DocumentView::panGesture); + connect(d->mTouch, &Touch::tapHoldAndMovingTriggered, this, &DocumentView::startDragFromTouch); + // We use an opacity effect instead of using the opacity property directly, because the latter operates at // the painter level, which means if you draw multiple layers in paint(), all layers get the specified // opacity, resulting in all layers being visible when 0 < opacity < 1. @@ -471,6 +488,7 @@ DocumentView::~DocumentView() { + delete d->mTouch; delete d->mDragThumbnailProvider; delete d->mDrag; delete d; @@ -710,6 +728,61 @@ return d->mAdapter->zoom(); } +void DocumentView::setPinchParameter() +{ + const qreal sensitivityModifier = 0.85; + const qreal rotationThreshold = 40; + d->mTouch->setZoomParameter(sensitivityModifier, zoom()); + d->mTouch->setRotationThreshold (rotationThreshold); +} + +void DocumentView::zoomGesture(qreal zoom, const QPoint& zoomCenter) +{ + if (zoom >= 0.0 && d->mAdapter->canZoom()) { + d->setZoom(zoom, zoomCenter); + } +} + +void DocumentView::rotationsGesture(qreal rotation) +{ + if (rotation > 0.0) { + TransformImageOperation* op = new TransformImageOperation(ROT_90); + op->applyToDocument(d->mDocument); + } else if (rotation < 0.0) { + TransformImageOperation* op = new TransformImageOperation(ROT_270); + op->applyToDocument(d->mDocument); + } +} + +void DocumentView::swipeRight() +{ + const QPoint scrollPos = d->mAdapter->scrollPos().toPoint(); + if (scrollPos.x() <= 1) { + emit d->mAdapter->previousImageRequested(); + } +} + +void DocumentView::swipeLeft() +{ + const QPoint scrollPos = d->mAdapter->scrollPos().toPoint(); + const int width = d->mAdapter->document()->width() * d->mAdapter->zoom(); + const QRect visibleRect = d->mAdapter->visibleDocumentRect().toRect(); + const int x = scrollPos.x() + visibleRect.width(); + if (x >= (width - 1)) { + emit d->mAdapter->nextImageRequested(); + } +} + +void DocumentView::panGesture(const QPointF& delta) +{ + d->mAdapter->setScrollPos(d->mAdapter->scrollPos() + delta); +} + +void DocumentView::startDragFromTouch(const QPoint&) +{ + d->startDragIfSensible(); +} + void DocumentView::resizeEvent(QGraphicsSceneResizeEvent *event) { d->resizeAdapterWidget(); @@ -946,6 +1019,12 @@ } } else if (event->type() == QEvent::GraphicsSceneMouseMove) { const QGraphicsSceneMouseEvent* mouseEvent = static_cast(event); + //in some older version of Qt, Qt synthesize a mouse event from the touch event + //we need to suppress this. + //I need this for my working system (OpenSUSE Leap 15.0, Qt 5.9.4) + if (mouseEvent->source() == Qt::MouseEventSynthesizedByQt) { + return true; + } const qreal dragDistance = (mouseEvent->pos() - d->mDragStartPosition).manhattanLength(); const qreal minDistanceToStartDrag = QGuiApplication::styleHints()->startDragDistance(); if (!d->canPan() && dragDistance >= minDistanceToStartDrag) { diff --git a/lib/scrollerutils.h b/lib/scrollerutils.h new file mode 100644 --- /dev/null +++ b/lib/scrollerutils.h @@ -0,0 +1,38 @@ +/* +Gwenview: an image viewer +Copyright 2019 Steffen Hartleib + +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 SCROLLERUTILS_H +#define SCROLLERUTILS_H + +#include + +class QScroller; +class QObject; + +namespace Gwenview +{ +namespace ScrollerUtils +{ + +GWENVIEWLIB_EXPORT QScroller* setQScroller(QObject*); + +} // namespace +} // namespace + +#endif /* SCROLLERUTILS_H */ diff --git a/lib/scrollerutils.cpp b/lib/scrollerutils.cpp new file mode 100644 --- /dev/null +++ b/lib/scrollerutils.cpp @@ -0,0 +1,44 @@ +/* +Gwenview: an image viewer +Copyright 2019 Steffen Hartleib + +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 "scrollerutils.h" + +// Qt +#include + + +namespace Gwenview +{ +namespace ScrollerUtils +{ + +QScroller* setQScroller (QObject* viewport) +{ + + QScroller* scroller = QScroller::scroller(viewport); + QScrollerProperties scrollerProperties = scroller->scrollerProperties(); + scrollerProperties.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff); + scrollerProperties.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff); + scroller->setScrollerProperties(scrollerProperties); + scroller->grabGesture(viewport); + return scroller; +} + +} // namespace +} // namespace diff --git a/lib/thumbnailview/thumbnailview.h b/lib/thumbnailview/thumbnailview.h --- a/lib/thumbnailview/thumbnailview.h +++ b/lib/thumbnailview/thumbnailview.h @@ -33,6 +33,10 @@ class QDragMoveEvent; class QDropEvent; class QPixmap; +class QGestureEvent; +class QTapGesture; +class QPinchGesture; +class QGesture; namespace Gwenview { @@ -186,6 +190,10 @@ const QVector &roles = QVector()) override; private Q_SLOTS: + void startDragFromTouch(const QPoint& pos); + void tapGesture(const QPoint& pos); + void setZoomParameter(); + void zoomGesture(qreal newZoom, const QPoint& pos); void showContextMenu(); void emitIndexActivatedIfNoModifiers(const QModelIndex&); void setThumbnail(const KFileItem&, const QPixmap&, const QSize&, qulonglong fileSize); diff --git a/lib/thumbnailview/thumbnailview.cpp b/lib/thumbnailview/thumbnailview.cpp --- a/lib/thumbnailview/thumbnailview.cpp +++ b/lib/thumbnailview/thumbnailview.cpp @@ -36,6 +36,8 @@ #include #include #include +#include +#include // KDE #include @@ -52,6 +54,8 @@ #include "urlutils.h" #include #include +#include +#include namespace Gwenview { @@ -187,6 +191,9 @@ bool mCreateThumbnailsForRemoteUrls; + QScroller* mScroller; + Touch* mTouch; + void setupBusyAnimation() { mBusySequence = KIconLoader::global()->loadPixmapSequence(QStringLiteral("process-working"), 22); @@ -317,10 +324,19 @@ connect(this, &ThumbnailView::customContextMenuRequested, this, &ThumbnailView::showContextMenu); connect(this, &ThumbnailView::activated, this, &ThumbnailView::emitIndexActivatedIfNoModifiers); + + d->mScroller = ScrollerUtils::setQScroller(this->viewport()); + d->mTouch = new Touch(viewport()); + connect(d->mTouch, &Touch::twoFingerTapTriggered, this, &ThumbnailView::showContextMenu); + connect(d->mTouch, &Touch::pinchZoomTriggered, this, &ThumbnailView::zoomGesture); + connect(d->mTouch, &Touch::pinchGestureStarted, this, &ThumbnailView::setZoomParameter); + connect(d->mTouch, &Touch::tapTriggered, this, &ThumbnailView::tapGesture); + connect(d->mTouch, &Touch::tapHoldAndMovingTriggered, this, &ThumbnailView::startDragFromTouch); } ThumbnailView::~ThumbnailView() { + delete d->mTouch; delete d; } @@ -693,6 +709,37 @@ drag->exec(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction, Qt::CopyAction); } +void ThumbnailView::setZoomParameter() +{ + const qreal sensitivityModifier = 0.25; + d->mTouch->setZoomParameter(sensitivityModifier, d->mThumbnailSize.width()); +} + +void ThumbnailView::zoomGesture(qreal newZoom, const QPoint&) +{ + if (newZoom >= 0.0) { + int width = qBound (int(MinThumbnailSize), static_cast(newZoom), int(MaxThumbnailSize)); + setThumbnailWidth(width); + } +} + +void ThumbnailView::tapGesture(const QPoint& pos) +{ + const QRect rect = QRect(pos, QSize(1, 1)); + setSelection(rect, QItemSelectionModel::ClearAndSelect); + emit activated(indexAt(pos)); +} + +void ThumbnailView::startDragFromTouch(const QPoint& pos) +{ + QModelIndex index = indexAt(pos); + if (index.isValid()) { + setCurrentIndex(index); + d->mScroller->stop(); + startDrag(Qt::CopyAction); + } +} + void ThumbnailView::dragEnterEvent(QDragEnterEvent* event) { QAbstractItemView::dragEnterEvent(event); diff --git a/lib/touch/doubletap.h b/lib/touch/doubletap.h new file mode 100644 --- /dev/null +++ b/lib/touch/doubletap.h @@ -0,0 +1,59 @@ +/* +Gwenview: an image viewer +Copyright 2019 Steffen Hartleib + +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, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef DOUBLETAP_H +#define DOUBLETAP_H + +#include +// Qt +#include +#include + +// KDE + +// Local + +namespace Gwenview +{ +struct DoubleTapRecognizerPrivate; + +class GWENVIEWLIB_EXPORT DoubleTap : public QGesture +{ + Q_PROPERTY(QPointF pos READ pos WRITE pos) +public: + explicit DoubleTap(QObject* parent = 0); +private: + QPointF pos; +}; + +class GWENVIEWLIB_EXPORT DoubleTapRecognizer : public QGestureRecognizer +{ +public: + explicit DoubleTapRecognizer(); + ~DoubleTapRecognizer(); +private: + DoubleTapRecognizerPrivate* d; + + virtual QGesture* create(QObject*) override; + virtual Result recognize(QGesture*, QObject*, QEvent*) override; + +}; + +} // namespace +#endif /* DOUBLETAP_H */ diff --git a/lib/touch/doubletap.cpp b/lib/touch/doubletap.cpp new file mode 100644 --- /dev/null +++ b/lib/touch/doubletap.cpp @@ -0,0 +1,127 @@ +/* +Gwenview: an image viewer +Copyright 2019 Steffen Hartleib + +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, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "doubletap.h" + +// Qt +#include +#include +#include +#include + +// KDE + +// Local +#include "lib/touch/touch_helper.h" + + +namespace Gwenview +{ + +struct DoubleTapRecognizerPrivate +{ + DoubleTapRecognizer* q; + bool mTargetIsGrapicsWidget = false; + qint64 mTouchBeginnTimestamp; + bool mIsOnlyTap; + qint64 mLastTapTimestamp = 0; + qint64 mLastDoupleTapTimestamp = 0; +}; + +DoubleTapRecognizer::DoubleTapRecognizer() : QGestureRecognizer() +, d (new DoubleTapRecognizerPrivate) +{ + d->q = this; +} + +DoubleTapRecognizer::~DoubleTapRecognizer() +{ + delete d; +} + +QGesture* DoubleTapRecognizer::create(QObject*) +{ + return static_cast(new DoubleTap()); +} + +QGestureRecognizer::Result DoubleTapRecognizer::recognize(QGesture* state, QObject* watched, QEvent* event) +{ + //Because of a bug in Qt in a gesture event in a graphicsview, all gestures are trigger twice + //https://bugreports.qt.io/browse/QTBUG-13103 + if (qobject_cast(watched)) d->mTargetIsGrapicsWidget = true; + if (d->mTargetIsGrapicsWidget && watched->isWidgetType()) return Ignore; + + switch (event->type()) { + case QEvent::TouchBegin: { + QTouchEvent* touchEvent = static_cast(event); + d->mTouchBeginnTimestamp = touchEvent->timestamp(); + d->mIsOnlyTap = true; + if (d->mLastDoupleTapTimestamp == 0) d->mLastDoupleTapTimestamp = touchEvent->timestamp() - Touch_Helper::Touch::doubleTapInterval; + state->setHotSpot(touchEvent->touchPoints().first().screenPos()); + return MayBeGesture; + } + + case QEvent::TouchUpdate: { + QTouchEvent* touchEvent = static_cast(event); + const qint64 now = touchEvent->timestamp(); + state->setHotSpot(touchEvent->touchPoints().first().screenPos()); + + if (d->mIsOnlyTap && now - d->mTouchBeginnTimestamp < Touch_Helper::Touch::maxTimeForTap && Touch_Helper::touchStationary(event)) { + d->mIsOnlyTap = true; + return MayBeGesture; + } else { + d->mIsOnlyTap = false; + return CancelGesture; + } + break; + } + + case QEvent::TouchEnd: { + QTouchEvent* touchEvent = static_cast(event); + const qint64 now = touchEvent->timestamp(); + + if (now - d->mLastTapTimestamp <= Touch_Helper::Touch::doubleTapInterval && d->mIsOnlyTap) { + //Interval between two double tap gesture need to be bigger than Touch_Helper::Touch::doupleTapIntervall, + //to suppress fast successively double tap gestures + if (now - d->mLastDoupleTapTimestamp > Touch_Helper::Touch::doubleTapInterval) { + d->mLastTapTimestamp = 0; + state->setHotSpot(touchEvent->touchPoints().first().screenPos()); + d->mLastDoupleTapTimestamp = now; + return FinishGesture; + } + } + + if (d->mIsOnlyTap) d->mLastTapTimestamp = now; + + break; + } + + default: + return Ignore; + } + return Ignore; +} + +DoubleTap::DoubleTap(QObject* parent) +: QGesture(parent) +{ +} + +} // namespace diff --git a/lib/touch/oneandtwofingerswipe.h b/lib/touch/oneandtwofingerswipe.h new file mode 100644 --- /dev/null +++ b/lib/touch/oneandtwofingerswipe.h @@ -0,0 +1,62 @@ +/* +Gwenview: an image viewer +Copyright 2019 Steffen Hartleib + +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, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef ONEANDTWOFINGERSWIPE_H +#define ONEANDTWOFINGERSWIPE_H + +#include +// Qt +#include +#include + +// KDE + +// Local + +namespace Gwenview +{ +struct OneAndTwoFingerSwipeRecognizerPrivate; + +class GWENVIEWLIB_EXPORT OneAndTwoFingerSwipe : public QGesture +{ + Q_PROPERTY(bool left READ left WRITE left) + Q_PROPERTY(bool right READ right WRITE right) + +public: + explicit OneAndTwoFingerSwipe(QObject* parent = 0); + +private: + bool left; + bool right; +}; + +class GWENVIEWLIB_EXPORT OneAndTwoFingerSwipeRecognizer : public QGestureRecognizer +{ +public: + explicit OneAndTwoFingerSwipeRecognizer(); + ~OneAndTwoFingerSwipeRecognizer(); +private: + OneAndTwoFingerSwipeRecognizerPrivate* d; + + virtual QGesture* create(QObject*) override; + virtual Result recognize(QGesture*, QObject*, QEvent*) override; +}; + +} // namespace +#endif /* ONEANDTWOFINGERSWIPE_H */ diff --git a/lib/touch/oneandtwofingerswipe.cpp b/lib/touch/oneandtwofingerswipe.cpp new file mode 100644 --- /dev/null +++ b/lib/touch/oneandtwofingerswipe.cpp @@ -0,0 +1,123 @@ +/* +Gwenview: an image viewer +Copyright 2019 Steffen Hartleib + +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, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "oneandtwofingerswipe.h" + +// Qt +#include +#include +#include + +// KDE + +// Local +#include "lib/touch/touch_helper.h" + +namespace Gwenview +{ + +struct OneAndTwoFingerSwipeRecognizerPrivate +{ + OneAndTwoFingerSwipeRecognizer* q; + bool mTargetIsGrapicsWidget = false; + qint64 mTouchBeginnTimestamp; + bool mGestureAlreadyTriggered; +}; + +OneAndTwoFingerSwipeRecognizer::OneAndTwoFingerSwipeRecognizer() : QGestureRecognizer() +, d (new OneAndTwoFingerSwipeRecognizerPrivate) +{ + d->q = this; +} + +OneAndTwoFingerSwipeRecognizer::~OneAndTwoFingerSwipeRecognizer() +{ + delete d; +} + +QGesture* OneAndTwoFingerSwipeRecognizer::create(QObject*) +{ + return static_cast(new OneAndTwoFingerSwipe()); +} + +QGestureRecognizer::Result OneAndTwoFingerSwipeRecognizer::recognize(QGesture* state, QObject* watched, QEvent* event) +{ + //Because of a bug in Qt in a gesture event in a graphicsview, all gestures are trigger twice + //https://bugreports.qt.io/browse/QTBUG-13103 + if (qobject_cast(watched)) d->mTargetIsGrapicsWidget = true; + if (d->mTargetIsGrapicsWidget && watched->isWidgetType()) return Ignore; + + switch (event->type()) { + case QEvent::TouchBegin: { + QTouchEvent* touchEvent = static_cast(event); + d->mTouchBeginnTimestamp = touchEvent->timestamp(); + d->mGestureAlreadyTriggered = false; + state->setHotSpot(touchEvent->touchPoints().first().screenPos()); + return MayBeGesture; + } + + case QEvent::TouchUpdate: { + QTouchEvent* touchEvent = static_cast(event); + const qint64 now = touchEvent->timestamp(); + const QPointF distance = touchEvent->touchPoints().first().startPos() - touchEvent->touchPoints().first().pos(); + state->setHotSpot(touchEvent->touchPoints().first().screenPos()); + + if (touchEvent->touchPoints().size() >> 2) { + d->mGestureAlreadyTriggered = false; + return CancelGesture; + } + + if (distance.manhattanLength() >= Touch_Helper::Touch::minDistanceForSwipe && + (now - d->mTouchBeginnTimestamp) <= Touch_Helper::Touch::maxTimeFrameForSwipe && + !d->mGestureAlreadyTriggered) { + if (distance.x() < 0 && abs(distance.x()) >= abs(distance.y()) * 2) { + state->setProperty("right", true); + state->setProperty("left", false); + d->mGestureAlreadyTriggered = true; + return FinishGesture; + } + if (distance.x() > 0 && abs(distance.x()) >= abs(distance.y()) * 2) { + state->setProperty("right", false); + state->setProperty("left", true); + d->mGestureAlreadyTriggered = true; + return FinishGesture; + } + if ((now - d->mTouchBeginnTimestamp) <= Touch_Helper::Touch::maxTimeFrameForSwipe && !d->mGestureAlreadyTriggered) { + return MayBeGesture; + } else { + d->mGestureAlreadyTriggered = false; + return CancelGesture; + } + } + break; + } + + default: + return Ignore; + } + return Ignore; +} + +OneAndTwoFingerSwipe::OneAndTwoFingerSwipe(QObject* parent) +: QGesture(parent) +{ +} + +} // namespace diff --git a/lib/touch/tapholdandmoving.h b/lib/touch/tapholdandmoving.h new file mode 100644 --- /dev/null +++ b/lib/touch/tapholdandmoving.h @@ -0,0 +1,63 @@ +/* +Gwenview: an image viewer +Copyright 2019 Steffen Hartleib + +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, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef TAPHOLDANDMOVING_H +#define TAPHOLDANDMOVING_H + +#include +// Qt +#include +#include + +// KDE + +// Local + +namespace Gwenview +{ +struct TapHoldAndMovingRecognizerPrivate; + +class GWENVIEWLIB_EXPORT TapHoldAndMoving : public QGesture +{ + Q_PROPERTY(QPoint pos READ getPos WRITE setPos) + +public: + explicit TapHoldAndMoving(QObject* parent = 0); + QPoint getPos() {return pos;}; + void setPos(QPoint _pos) {pos = _pos;}; + +private: + QPoint pos; +}; + +class GWENVIEWLIB_EXPORT TapHoldAndMovingRecognizer : public QGestureRecognizer +{ +public: + explicit TapHoldAndMovingRecognizer(); + ~TapHoldAndMovingRecognizer(); +private: + TapHoldAndMovingRecognizerPrivate* d; + + virtual QGesture* create(QObject* target) override; + virtual Result recognize(QGesture* state, QObject* watched, QEvent* event) override; + +}; + +} // namespace +#endif /* TAPHOLDANDMOVING_H */ diff --git a/lib/touch/tapholdandmoving.cpp b/lib/touch/tapholdandmoving.cpp new file mode 100644 --- /dev/null +++ b/lib/touch/tapholdandmoving.cpp @@ -0,0 +1,129 @@ +/* +Gwenview: an image viewer +Copyright 2019 Steffen Hartleib + +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, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "tapholdandmoving.h" + +// Qt +#include +#include +#include + +// KDE + +// Local +#include "lib/touch/touch_helper.h" + +namespace Gwenview +{ + +struct TapHoldAndMovingRecognizerPrivate +{ + TapHoldAndMovingRecognizer* q; + bool mTargetIsGrapicsWidget = false; + qint64 mTouchBeginnTimestamp; + bool mTouchPointStationary; + bool mGestureTriggered; + Qt::GestureState mLastGestureState = Qt::NoGesture; +}; + +TapHoldAndMovingRecognizer::TapHoldAndMovingRecognizer() : QGestureRecognizer() +, d (new TapHoldAndMovingRecognizerPrivate) +{ + d->q = this; +} + +TapHoldAndMovingRecognizer::~TapHoldAndMovingRecognizer() +{ + delete d; +} + +QGesture* TapHoldAndMovingRecognizer::create(QObject*) +{ + return static_cast(new TapHoldAndMoving()); +} + +QGestureRecognizer::Result TapHoldAndMovingRecognizer::recognize(QGesture* state, QObject* watched, QEvent* event) +{ + //Because of a bug in Qt in a gesture event in a graphicsview, all gestures are trigger twice + //https://bugreports.qt.io/browse/QTBUG-13103 + if (qobject_cast(watched)) d->mTargetIsGrapicsWidget = true; + if (d->mTargetIsGrapicsWidget && watched->isWidgetType()) return Ignore; + + switch (event->type()) { + case QEvent::TouchBegin: { + QTouchEvent* touchEvent = static_cast(event); + d->mTouchBeginnTimestamp = touchEvent->timestamp(); + d->mGestureTriggered = false; + d->mTouchPointStationary = true; + state->setHotSpot(touchEvent->touchPoints().first().screenPos()); + d->mLastGestureState = Qt::NoGesture; + return MayBeGesture; + } + + case QEvent::TouchUpdate: { + QTouchEvent* touchEvent = static_cast(event); + const qint64 now = touchEvent->timestamp(); + const QPoint pos = touchEvent->touchPoints().first().pos().toPoint(); + state->setHotSpot(touchEvent->touchPoints().first().screenPos()); + + if (touchEvent->touchPoints().size() >> 1) { + d->mGestureTriggered = false; + d->mLastGestureState = Qt::GestureCanceled; + return CancelGesture; + } + + if (touchEvent->touchPoints().size() == 1 && d->mLastGestureState != Qt::GestureCanceled) { + if (!d->mGestureTriggered && + d->mTouchPointStationary && + now - d->mTouchBeginnTimestamp >= Touch_Helper::Touch::durationForTapHold) { + d->mGestureTriggered = true; + } + } + d->mTouchPointStationary = Touch_Helper::touchStationary(event); + + if (d->mGestureTriggered && d->mLastGestureState != Qt::GestureCanceled) { + state->setProperty("pos", pos); + d->mLastGestureState = Qt::GestureStarted; + return TriggerGesture; + } + break; + } + + case QEvent::TouchEnd: { + QTouchEvent* touchEvent = static_cast(event); + state->setHotSpot(touchEvent->touchPoints().first().screenPos()); + if (d->mGestureTriggered) { + d->mLastGestureState = Qt::GestureFinished; + return FinishGesture; + } + break; + } + default: + return Ignore; + } + return Ignore; +} + +TapHoldAndMoving::TapHoldAndMoving(QObject* parent) +: QGesture(parent) +{ +} + +} // namespace diff --git a/lib/touch/touch.h b/lib/touch/touch.h new file mode 100644 --- /dev/null +++ b/lib/touch/touch.h @@ -0,0 +1,99 @@ +/* +Gwenview: an image viewer +Copyright 2019 Steffen Hartleib + +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, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef TOUCH_H +#define TOUCH_H + +#include +// Qt +#include + +// KDE + +// Local +#include "lib/touch/tapholdandmoving.h" +#include "lib/touch/twofingerpan.h" +#include "lib/touch/oneandtwofingerswipe.h" +#include "lib/touch/doubletap.h" +#include "lib/touch/twofingertap.h" + +namespace Gwenview +{ + +struct TouchPrivate; +class GWENVIEWLIB_EXPORT Touch : public QObject +{ + Q_OBJECT +public: + Touch(QObject* target); + ~Touch() override; + void setZoomParameter (qreal, qreal); + void setRotationThreshold (qreal); + qreal getRotationFromPinchGesture(QGestureEvent*); + qreal getZoomFromPinchGesture(QGestureEvent*); + QPoint positionGesture(QGestureEvent*); + bool checkTwoFingerPanGesture(QGestureEvent*); + bool checkOneAndTwoFingerSwipeGesture(QGestureEvent*); + bool checkTapGesture(QGestureEvent*); + bool checkDoubleTapGesture(QGestureEvent*); + bool checkTwoFingerTapGesture(QGestureEvent*); + bool checkTapHoldAndMovingGesture(QGestureEvent*, QObject*); + bool checkPinchGesture(QGestureEvent*); + void touchToMouseRelease(QPoint, QObject*); + void touchToMouseMove(QPoint, QEvent*, Qt::MouseButton); + void touchToMouseMove(QPoint, QObject*, Qt::MouseButton); + void touchToMouseClick(QPoint, QObject*); + void setPanGestureState ( QGestureEvent* event ); + Qt::GestureState getLastPanGestureState(); + QPointF getLastTapPos(); + void setLastTapPos(QPointF); + bool getTapHoldandMovingGestureActive(); + void setTapHoldandMovingGestureActive(bool); + + Qt::GestureType getTapHoldandMovingGesture(); + Qt::GestureType getTwoFingerPanGesture(); + Qt::GestureType getOneAndTwoFingerSwipeGesture(); + Qt::GestureType getDoubleTapGesture(); + Qt::GestureType getTwoFingerTapGesture(); + +protected: + bool eventFilter(QObject*, QEvent*) override; + + +signals: + void PanTriggered(const QPointF&); + void swipeLeftTriggered(); + void swipeRightTriggered(); + void doubleTapTriggered(); + void tapHoldAndMovingTriggered(const QPoint&); + void tapTriggered(const QPoint&); + void pinchGestureStarted(); + void twoFingerTapTriggered(); + void pinchZoomTriggered(qreal, const QPoint&); + void pinchRotateTriggered(qreal); + +private: + qreal calculateZoom (qreal, qreal); + void touchToMouseEvent(QPoint, QObject*, QEvent::Type, Qt::MouseButton, Qt::MouseButtons); + bool gestureEvent(QGestureEvent*); + TouchPrivate* const d; +}; + +} // namespace +#endif /* TOUCH_H */ diff --git a/lib/touch/touch.cpp b/lib/touch/touch.cpp new file mode 100644 --- /dev/null +++ b/lib/touch/touch.cpp @@ -0,0 +1,464 @@ +/* +Gwenview: an image viewer +Copyright 2019 Steffen Hartleib + +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, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "touch.h" + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include "touch_helper.h" + + +// KDE + +// Local + +namespace Gwenview +{ + +struct TouchPrivate +{ + Touch* q; + QObject* mTarget; + Qt::GestureState mLastPanGestureState; + QPointF mLastTapPos; + bool mTabHoldandMovingGestureActive; + qreal mStartZoom; + qreal mZoomModifier; + qreal mRotationThreshold; + + TapHoldAndMovingRecognizer* mTapHoldAndMovingRecognizer; + Qt::GestureType mTapHoldAndMoving; + TwoFingerPanRecognizer* mTwoFingerPanRecognizer; + Qt::GestureType mTwoFingerPan; + OneAndTwoFingerSwipeRecognizer* mOneAndTwoFingerSwipeRecognizer; + Qt::GestureType mOneAndTwoFingerSwipe; + DoubleTapRecognizer* mDoubleTapRecognizer; + Qt::GestureType mDoubleTap; + TwoFingerTapRecognizer* mTwoFingerTapRecognizer; + Qt::GestureType mTwoFingerTap; +}; + +Touch::Touch(QObject* target) +: QObject () +, d(new TouchPrivate) +{ + d->q = this; + d->mTarget = target; + + d->mTapHoldAndMovingRecognizer = new TapHoldAndMovingRecognizer(); + d->mTapHoldAndMoving = QGestureRecognizer::registerRecognizer(d->mTapHoldAndMovingRecognizer); + + d->mTwoFingerPanRecognizer = new TwoFingerPanRecognizer(); + d->mTwoFingerPan = QGestureRecognizer::registerRecognizer(d->mTwoFingerPanRecognizer); + + d->mTwoFingerTapRecognizer = new TwoFingerTapRecognizer(); + d->mTwoFingerTap = QGestureRecognizer::registerRecognizer(d->mTwoFingerTapRecognizer); + + d->mOneAndTwoFingerSwipeRecognizer = new OneAndTwoFingerSwipeRecognizer(); + d->mOneAndTwoFingerSwipe = QGestureRecognizer::registerRecognizer(d->mOneAndTwoFingerSwipeRecognizer); + + d->mDoubleTapRecognizer = new DoubleTapRecognizer(); + d->mDoubleTap = QGestureRecognizer::registerRecognizer(d->mDoubleTapRecognizer); + + if (qobject_cast(target)) { + QGraphicsWidget* widgetTarget = qobject_cast(target); + widgetTarget->grabGesture(d->mOneAndTwoFingerSwipe); + widgetTarget->grabGesture(d->mDoubleTap); + widgetTarget->grabGesture(Qt::TapGesture); + widgetTarget->grabGesture(Qt::PinchGesture); + widgetTarget->grabGesture(d->mTwoFingerTap); + widgetTarget->grabGesture(d->mTwoFingerPan); + widgetTarget->grabGesture(d->mTapHoldAndMoving); + } else if (qobject_cast(target)) { + QWidget* widgetTarget = qobject_cast(target); + widgetTarget->grabGesture(Qt::TapGesture); + widgetTarget->grabGesture(Qt::PinchGesture); + widgetTarget->grabGesture(d->mTwoFingerTap); + widgetTarget->grabGesture(d->mTwoFingerPan); + widgetTarget->grabGesture(d->mTapHoldAndMoving); + } + target->installEventFilter(this); +} + +Touch::~Touch() +{ + delete d; +} + +bool Touch::eventFilter(QObject*, QEvent* event) +{ + if (event->type() == QEvent::TouchBegin) { + //move mouse cursor to touchpoint + const QPoint pos = Touch_Helper::simpleTouchPosition(event); + touchToMouseMove(pos, event, Qt::NoButton); + return true; + } + if (event->type() == QEvent::TouchUpdate) { + QTouchEvent* touchEvent = static_cast(event); + //because we suppress the making of mouse event through Qt, we need to make our own one finger panning + //but only if no TapHoldandMovingGesture is active (Drag and Drop action) + if (touchEvent->touchPoints().size() == 1 && !getTapHoldandMovingGestureActive()) { + const QPointF delta = touchEvent->touchPoints().first().lastPos() - touchEvent->touchPoints().first().pos(); + emit PanTriggered(delta); + } + return true; + } + if (event->type() == QEvent::Gesture) { + gestureEvent(static_cast(event)); + } + return false; +} + +bool Touch::gestureEvent(QGestureEvent* event) +{ + bool ret = false; + + if (checkTwoFingerTapGesture(event)) { + ret = true; + } + + if (checkPinchGesture(event)) { + ret = true; + emit pinchZoomTriggered(getZoomFromPinchGesture(event), positionGesture(event)); + emit pinchRotateTriggered(getRotationFromPinchGesture(event)); + } + + if (checkTapGesture(event)) { + ret = true; + if (event->widget()) { + touchToMouseClick(positionGesture(event), event->widget()); + } + emit tapTriggered(positionGesture(event)); + } + + if (checkTapHoldAndMovingGesture(event, d->mTarget)) { + ret = true; + emit tapHoldAndMovingTriggered(positionGesture(event)); + } + + if (checkDoubleTapGesture(event)) { + ret = true; + } + + if (checkOneAndTwoFingerSwipeGesture(event)) { + ret = true; + } + + checkTwoFingerPanGesture(event); + + return ret; +} + +void Touch::setZoomParameter(qreal modifier, qreal startZoom) +{ + d->mZoomModifier = modifier; + d->mStartZoom = startZoom; +} + +void Touch::setRotationThreshold(qreal rotationThreshold) +{ + d->mRotationThreshold = rotationThreshold; +} + +qreal Touch::getRotationFromPinchGesture(QGestureEvent* event) +{ + const QPinchGesture* pinch = static_cast(event->gesture(Qt::PinchGesture)); + static qreal lastRotationAngel; + if (pinch) { + if (pinch->state() == Qt::GestureStarted) { + lastRotationAngel = 0; + return 0.0; + } + if (pinch->state() == Qt::GestureUpdated) { + const qreal rotationDelta = pinch->rotationAngle() - pinch->lastRotationAngle(); + //very low and high changes in the rotation are suspect, so we ignore them + if (abs(rotationDelta) <= 1.5 || abs(rotationDelta) >= 30) { + return 0.0; + } + lastRotationAngel += rotationDelta; + const qreal ret = lastRotationAngel; + if (abs(lastRotationAngel) > d->mRotationThreshold) { + lastRotationAngel = 0; + return ret; + } else { + return 0.0; + } + } + } + return 0.0; +} + +qreal Touch::getZoomFromPinchGesture(QGestureEvent* event) +{ + static qreal lastZoom; + const QPinchGesture* pinch = static_cast(event->gesture(Qt::PinchGesture)); + if (pinch) { + if (pinch->state() == Qt::GestureStarted) { + lastZoom = d->mStartZoom; + return -1; + } + if (pinch->state() == Qt::GestureUpdated) { + lastZoom = calculateZoom(pinch->scaleFactor(), d->mZoomModifier) * lastZoom; + return lastZoom; + } + } + return -1; +} + +qreal Touch::calculateZoom(qreal scale, qreal modifier) +{ + return ((scale - 1.0) * modifier) + 1.0; +} + +QPoint Touch::positionGesture(QGestureEvent* event) +{ + //return the position or the center point for follow gestures: QTapGesture, TabHoldAndMovingGesture and PinchGesture; + QPoint position = QPoint(-1, -1); + if (QTapGesture* tap = static_cast(event->gesture(Qt::TapGesture))) { + position = tap->position().toPoint(); + } else if (QGesture* gesture = event->gesture(getTapHoldandMovingGesture())) { + position = gesture->property("pos").toPoint(); + } else if (QPinchGesture* pinch = static_cast(event->gesture(Qt::PinchGesture))) { + if (qobject_cast(d->mTarget)) { + QGraphicsWidget* widget = qobject_cast(d->mTarget); + position = widget->mapFromScene(event->mapToGraphicsScene(pinch->centerPoint())).toPoint(); + } else { + position = pinch->centerPoint().toPoint(); + } + } + return position; +} + +bool Touch::checkTwoFingerPanGesture(QGestureEvent* event) +{ + if (QGesture* gesture = event->gesture(getTwoFingerPanGesture())) { + event->accept(); + setPanGestureState(event); + if (gesture->state() == Qt::GestureUpdated) { + const QPoint diff = gesture->property("delta").toPoint(); + emit PanTriggered(diff); + return true; + } + } + return false; +} + +bool Touch::checkOneAndTwoFingerSwipeGesture(QGestureEvent* event) +{ + if (QGesture* gesture = event->gesture(getOneAndTwoFingerSwipeGesture())) { + event->accept(); + if (gesture->state() == Qt::GestureFinished) { + if (gesture->property("right").toBool()) { + emit swipeRightTriggered(); + return true; + } else if (gesture->property("left").toBool()) { + emit swipeLeftTriggered(); + return true; + } + } + } + return false; +} + + +bool Touch::checkTapGesture(QGestureEvent* event) +{ + const QTapGesture* tap = static_cast(event->gesture(Qt::TapGesture)); + if (tap) { + event->accept(); + if (tap->state() == Qt::GestureFinished) return true; + } + return false; +} + +bool Touch::checkDoubleTapGesture(QGestureEvent* event) +{ + if (QGesture* gesture = event->gesture(getDoubleTapGesture())) { + event->accept(); + if (gesture->state() == Qt::GestureFinished) { + emit doubleTapTriggered(); + return true; + } + } + return false; +} + +bool Touch::checkTwoFingerTapGesture(QGestureEvent* event) +{ + if (QGesture* twoFingerTap = event->gesture(getTwoFingerTapGesture())) { + event->accept(); + if (twoFingerTap->state() == Qt::GestureFinished) { + emit twoFingerTapTriggered(); + return true; + } + } + return false; +} + +bool Touch::checkTapHoldAndMovingGesture(QGestureEvent* event, QObject* target) +{ + if (QGesture* tapHoldAndMoving = event->gesture(getTapHoldandMovingGesture())) { + event->accept(); + const QPoint pos = tapHoldAndMoving->property("pos").toPoint(); + switch (tapHoldAndMoving->state()) { + case Qt::GestureStarted: { + setTapHoldandMovingGestureActive(true); + return true; + } + case Qt::GestureUpdated: { + touchToMouseMove(pos, target, Qt::LeftButton); + break; + } + case Qt::GestureCanceled: + case Qt::GestureFinished: { + touchToMouseRelease(pos, target); + setTapHoldandMovingGestureActive(false); + break; + } + default: + break; + } + } + return false; +} + +bool Touch::checkPinchGesture(QGestureEvent* event) +{ + static qreal lastScaleFactor; + const QPinchGesture* pinch = static_cast(event->gesture(Qt::PinchGesture)); + if (pinch) { + //we don't want a pinch gesture, if a pan gesture is active + //only exception is, if the pinch gesture state is Qt::GestureStarted + if (getLastPanGestureState() == Qt::GestureCanceled || pinch->state() == Qt::GestureStarted) { + event->accept(); + if (pinch->state() == Qt::GestureStarted) { + lastScaleFactor = 0; + emit pinchGestureStarted(); + } else if (pinch->state() == Qt::GestureUpdated) { + //Because of a bug in Qt in a gesture event in a graphicsview, all gestures are trigger twice + //https://bugreports.qt.io/browse/QTBUG-13103 + //the duplicate events have the same scaleFactor, so I ignore them + if (lastScaleFactor == pinch->scaleFactor()) { + return false; + } else { + lastScaleFactor = pinch->scaleFactor(); + } + } + return true; + } + } + return false; +} + +void Touch::touchToMouseRelease(QPoint pos, QObject* receiver) +{ + touchToMouseEvent(pos, receiver, QEvent::MouseButtonRelease, Qt::LeftButton, Qt::LeftButton); +} + +void Touch::touchToMouseMove(QPoint pos, QEvent* event, Qt::MouseButton button) +{ + if (QTouchEvent* touchEvent = static_cast(event)) { + touchToMouseEvent(pos, touchEvent->target(), QEvent::MouseMove, button, button); + } +} + +void Touch::touchToMouseMove(QPoint pos, QObject* receiver, Qt::MouseButton button) +{ + touchToMouseEvent(pos, receiver, QEvent::MouseMove, button, button); +} + + +void Touch::touchToMouseClick (QPoint pos, QObject* receiver) +{ + touchToMouseEvent(pos, receiver, QEvent::MouseButtonPress, Qt::LeftButton, Qt::LeftButton); + touchToMouseEvent(pos, receiver, QEvent::MouseButtonRelease, Qt::LeftButton, Qt::LeftButton); +} + +void Touch::touchToMouseEvent (QPoint pos, QObject* receiver, QEvent::Type type, Qt::MouseButton button, Qt::MouseButtons buttons) +{ + QMouseEvent* evt = new QMouseEvent(type, pos, button, buttons, Qt::NoModifier); + QCoreApplication::postEvent(receiver, evt); +} + +Qt::GestureState Touch::getLastPanGestureState() +{ + return d->mLastPanGestureState;; +} + +void Touch::setPanGestureState(QGestureEvent* event) +{ + if (QGesture* panGesture = event->gesture(getTwoFingerPanGesture())) { + d->mLastPanGestureState = panGesture->state(); + } + return; +} + +QPointF Touch::getLastTapPos() +{ + return d->mLastTapPos; +} + +void Touch::setLastTapPos(QPointF pos) +{ + d->mLastTapPos = pos; +} + +Qt::GestureType Touch::getTapHoldandMovingGesture() +{ + return d->mTapHoldAndMoving; +} + +Qt::GestureType Touch::getTwoFingerPanGesture() +{ + return d->mTwoFingerPan; +} + +Qt::GestureType Touch::getOneAndTwoFingerSwipeGesture() +{ + return d->mOneAndTwoFingerSwipe; +} + +Qt::GestureType Touch::getDoubleTapGesture() +{ + return d->mDoubleTap; +} + +Qt::GestureType Touch::getTwoFingerTapGesture() +{ + return d->mTwoFingerTap; +} + +bool Touch::getTapHoldandMovingGestureActive() +{ + return d->mTabHoldandMovingGestureActive; +} + +void Touch::setTapHoldandMovingGestureActive(bool active) +{ + d->mTabHoldandMovingGestureActive = active; +} + +} // namespace diff --git a/lib/touch/touch_helper.h b/lib/touch/touch_helper.h new file mode 100644 --- /dev/null +++ b/lib/touch/touch_helper.h @@ -0,0 +1,67 @@ +/* +Gwenview: an image viewer +Copyright 2019 Steffen Hartleib + +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 detai +ls. + +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, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef TOUCH_HELPER_H +#define TOUCH_HELPER_H + +// Qt +#include +// KDE + +// Local +#include + +class QPoint; +class QPointF; +class QEvent; + +namespace Gwenview +{ + +namespace Touch_Helper +{ + +//constant variables that define touch behavior +struct Touch +{ + //a little delay in the begin of the gesture, to get more data of the touch points moving so the recognizing + //of the pan gesture is better + static const int gestureDelay = 110; + //this defines how much a touch point can move, for a single tap gesture + static const int wiggleRoomForTap = 10; + //how long must a touch point be stationary, before he can move for a TabHoldAndMoving gesture + static const int durationForTapHold = 400; + //in what time and how far must the touch point moving to trigger a swipe gesture + static const int maxTimeFrameForSwipe = 100; + static const int minDistanceForSwipe = 70; + //How long is the duration for a simple tap gesture + static const int maxTimeForTap = 100; + //max interval for a double tap gesture + static const int doubleTapInterval = 400; +}; + +GWENVIEWLIB_EXPORT QPoint simpleTapPosition(QEvent*); +GWENVIEWLIB_EXPORT QPoint simpleTouchPosition(QEvent*,int = 0); +GWENVIEWLIB_EXPORT bool touchStationary(QEvent*); + +} // namespace +} // namespace + +#endif /* TOUCH_HELPER_H */ diff --git a/lib/touch/touch_helper.cpp b/lib/touch/touch_helper.cpp new file mode 100644 --- /dev/null +++ b/lib/touch/touch_helper.cpp @@ -0,0 +1,69 @@ +/* +Gwenview: an image viewer +Copyright 2019 Steffen Hartleib + +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 "touch_helper.h" + +// Qt +#include +#include +#include +#include +#include +#include + + +namespace Gwenview +{ +namespace Touch_Helper +{ + +QPoint simpleTapPosition (QEvent* event) +{ + if (event->type() == QEvent::TouchEnd) { + event->accept(); + if (touchStationary(event)) { + return simpleTouchPosition(event); + } + } + return QPoint(-1, -1); +} + +QPoint simpleTouchPosition(QEvent* event, int at) +{ + if (QTouchEvent* touchEvent = static_cast(event)) { + if (touchEvent->touchPoints().size() > at) { + return touchEvent->touchPoints().at(at).pos().toPoint(); + } + } + return QPoint(-1, -1); +} + +bool touchStationary(QEvent* event) +{ + if (QTouchEvent* touchEvent = static_cast(event)) { + const QPointF distance = touchEvent->touchPoints().first().startPos() - touchEvent->touchPoints().first().pos(); + if (distance.manhattanLength() <= Touch::wiggleRoomForTap) { + return true; + } + } + return false; +} + +} // namespace +} // namespace diff --git a/lib/touch/twofingerpan.h b/lib/touch/twofingerpan.h new file mode 100644 --- /dev/null +++ b/lib/touch/twofingerpan.h @@ -0,0 +1,66 @@ +/* +Gwenview: an image viewer +Copyright 2019 Steffen Hartleib + +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, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef TWOFINGERPAN_H +#define TWOFINGERPAN_H + +#include +// Qt +#include +#include + +// KDE + +// Local + +namespace Gwenview +{ +struct TwoFingerPanRecognizerPrivate; + +class GWENVIEWLIB_EXPORT TwoFingerPan : public QGesture +{ + Q_PROPERTY(QPointF delta READ getDelta WRITE setDelta) + Q_PROPERTY(bool delayActive READ getDelayActive WRITE setDelayActive) + +public: + explicit TwoFingerPan(QObject* parent = 0); + QPointF getDelta() {return delta;}; + void setDelta(QPointF _delta) {delta = _delta;}; + bool getDelayActive() {return delayActive;}; + void setDelayActive (bool _delay) {delayActive = _delay;}; +private: + QPointF delta; + bool delayActive; +}; + +class GWENVIEWLIB_EXPORT TwoFingerPanRecognizer : public QGestureRecognizer +{ +public: + explicit TwoFingerPanRecognizer(); + ~TwoFingerPanRecognizer(); +private: + TwoFingerPanRecognizerPrivate* d; + + virtual QGesture* create(QObject*) override; + virtual Result recognize(QGesture*, QObject*, QEvent*) override; + +}; + +} // namespace +#endif /* TWOFINGERPAN_H */ diff --git a/lib/touch/twofingerpan.cpp b/lib/touch/twofingerpan.cpp new file mode 100644 --- /dev/null +++ b/lib/touch/twofingerpan.cpp @@ -0,0 +1,165 @@ +/* +Gwenview: an image viewer +Copyright 2019 Steffen Hartleib + +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, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "twofingerpan.h" + +// Qt +#include +#include +#include +#include + +// KDE + +// Local +#include "lib/touch/touch_helper.h" + +namespace Gwenview +{ + +struct TwoFingerPanRecognizerPrivate +{ + TwoFingerPanRecognizer* q; + bool mTargetIsGrapicsWidget = false; + qint64 mTouchBeginnTimestamp; + bool mGestureTriggered; + qint64 mLastTouchTimestamp; +}; + +TwoFingerPanRecognizer::TwoFingerPanRecognizer() : QGestureRecognizer() + , d (new TwoFingerPanRecognizerPrivate) +{ + d->q = this; +} + +TwoFingerPanRecognizer::~TwoFingerPanRecognizer() +{ + delete d; +} + +QGesture* TwoFingerPanRecognizer::create(QObject*) +{ + return static_cast(new TwoFingerPan()); +} + +QGestureRecognizer::Result TwoFingerPanRecognizer::recognize(QGesture* state, QObject* watched, QEvent* event) +{ + //Because of a bug in Qt in a gesture event in a graphicsview, all gestures are trigger twice + //https://bugreports.qt.io/browse/QTBUG-13103 + if (qobject_cast(watched)) d->mTargetIsGrapicsWidget = true; + if (d->mTargetIsGrapicsWidget && watched->isWidgetType()) return Ignore; + + switch (event->type()) { + case QEvent::TouchBegin: { + QTouchEvent* touchEvent = static_cast(event); + d->mTouchBeginnTimestamp = touchEvent->timestamp(); + d->mLastTouchTimestamp = touchEvent->timestamp(); + d->mGestureTriggered = false; + state->setProperty("delayActive", true); + state->setHotSpot(touchEvent->touchPoints().first().screenPos()); + return TriggerGesture; + } + + case QEvent::TouchUpdate: { + QTouchEvent* touchEvent = static_cast(event); + const qint64 now = touchEvent->timestamp(); + const QPointF pos = touchEvent->touchPoints().first().pos(); + state->setHotSpot(touchEvent->touchPoints().first().screenPos()); + + if (touchEvent->touchPoints().size() >> 2) { + if (d->mGestureTriggered) { + d->mGestureTriggered = false; + } + return CancelGesture; + } + + if (touchEvent->touchPoints().size() == 2) { + if (touchEvent->touchPointStates() & Qt::TouchPointReleased) { + if (d->mGestureTriggered) { + d->mGestureTriggered = false; + return FinishGesture; + } + } + + if (now - d->mTouchBeginnTimestamp >= Touch_Helper::Touch::gestureDelay) { + state ->setProperty("delayActive", false); + + //Check if both touch points moving in the same direction + const QVector2D vectorTouchPoint1 = QVector2D (touchEvent->touchPoints().at(0).startPos() - touchEvent->touchPoints().at(0).pos()); + const QVector2D vectorTouchPoint2 = QVector2D (touchEvent->touchPoints().at(1).startPos() - touchEvent->touchPoints().at(1).pos()); + QVector2D dotProduct = vectorTouchPoint1 * vectorTouchPoint2; + + //The dot product is greater than zero if both touch points moving in the same direction + //special case if the touch point moving exact or almost exact in x or y axis + //one value of the dot product is zero or a little bit less than zero and + //the other value is bigger than zero + if (dotProduct.x() >= -500 && dotProduct.x() <= 0 && dotProduct.y() > 0) dotProduct.setX(1); + if (dotProduct.y() >= -500 && dotProduct.y() <= 0 && dotProduct.x() > 0) dotProduct.setY(1); + + if (dotProduct.x() > 0 && dotProduct.y() > 0) { + const QPointF diff = (touchEvent->touchPoints().first().lastPos() - pos); + state->setProperty("delta", diff); + d->mGestureTriggered = true; + return TriggerGesture; + } else { + //special case if the user makes a very slow pan gesture, the vectors a very short. Sometimes the + //dot product is then zero, so we only want to cancel the gesture if the user makes a bigger wrong gesture + if (vectorTouchPoint1.toPoint().manhattanLength() > 50 || vectorTouchPoint2.toPoint().manhattanLength() > 50 ) { + d->mGestureTriggered = false; + return CancelGesture; + } else { + const QPointF diff = (touchEvent->touchPoints().first().lastPos() - pos); + state->setProperty("delta", diff); + d->mGestureTriggered = true; + return TriggerGesture; + } + } + } else { + state->setProperty("delta", QPointF (0, 0)); + state->setProperty("delayActive", true); + d->mGestureTriggered = false; + return TriggerGesture; + } + } + break; + } + + case QEvent::TouchEnd: { + QTouchEvent* touchEvent = static_cast(event); + if (d->mGestureTriggered) { + d->mGestureTriggered = false; + state->setHotSpot(touchEvent->touchPoints().first().screenPos()); + return FinishGesture; + } + break; + } + + default: + return Ignore; + } + return Ignore; +} + +TwoFingerPan::TwoFingerPan(QObject* parent) + : QGesture(parent) +{ +} + +} // namespace diff --git a/lib/touch/twofingertap.h b/lib/touch/twofingertap.h new file mode 100644 --- /dev/null +++ b/lib/touch/twofingertap.h @@ -0,0 +1,59 @@ +/* +Gwenview: an image viewer +Copyright 2019 Steffen Hartleib + +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, Cambridge, MA 02110-1301, USA. + +*/ +#ifndef TWOFINGERTAP_H +#define TWOFINGERTAP_H + +#include +// Qt +#include +#include + +// KDE + +// Local + +namespace Gwenview +{ +struct TwoFingerTapRecognizerPrivate; + +class GWENVIEWLIB_EXPORT TwoFingerTap : public QGesture +{ + +public: + explicit TwoFingerTap(QObject* parent = 0); + +private: +}; + +class GWENVIEWLIB_EXPORT TwoFingerTapRecognizer : public QGestureRecognizer +{ +public: + explicit TwoFingerTapRecognizer(); + ~TwoFingerTapRecognizer(); +private: + TwoFingerTapRecognizerPrivate* d; + + virtual QGesture* create(QObject*) override; + virtual Result recognize(QGesture*, QObject*, QEvent*) override; + +}; + +} // namespace +#endif /* TWOFINGERTAP_H */ diff --git a/lib/touch/twofingertap.cpp b/lib/touch/twofingertap.cpp new file mode 100644 --- /dev/null +++ b/lib/touch/twofingertap.cpp @@ -0,0 +1,117 @@ +/* +Gwenview: an image viewer +Copyright 2019 Steffen Hartleib + +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, Cambridge, MA 02110-1301, USA. + +*/ +// Self +#include "twofingertap.h" + +// Qt +#include +#include +#include + +// KDE + +// Local +#include "lib/touch/touch_helper.h" + +namespace Gwenview +{ + +struct TwoFingerTapRecognizerPrivate +{ + TwoFingerTapRecognizer* q; + bool mTargetIsGrapicsWidget = false; + qint64 mTouchBeginnTimestamp; + bool mGestureTriggered; +}; + +TwoFingerTapRecognizer::TwoFingerTapRecognizer() : QGestureRecognizer() +, d (new TwoFingerTapRecognizerPrivate) +{ + d->q = this; +} + +TwoFingerTapRecognizer::~TwoFingerTapRecognizer() +{ + delete d; +} + +QGesture* TwoFingerTapRecognizer::create(QObject*) +{ + return static_cast(new TwoFingerTap()); +} + +QGestureRecognizer::Result TwoFingerTapRecognizer::recognize(QGesture* state, QObject* watched, QEvent* event) +{ + //Because of a bug in Qt in a gesture event in a graphicsview, all gestures are trigger twice + //https://bugreports.qt.io/browse/QTBUG-13103 + if (qobject_cast(watched)) d->mTargetIsGrapicsWidget = true; + if (d->mTargetIsGrapicsWidget && watched->isWidgetType()) return Ignore; + + + switch (event->type()) { + case QEvent::TouchBegin: { + QTouchEvent* touchEvent = static_cast(event); + d->mTouchBeginnTimestamp = touchEvent->timestamp(); + state->setHotSpot(touchEvent->touchPoints().first().screenPos()); + d->mGestureTriggered = false; + return MayBeGesture; + } + + case QEvent::TouchUpdate: { + QTouchEvent* touchEvent = static_cast(event); + state->setHotSpot(touchEvent->touchPoints().first().screenPos()); + + if (touchEvent->touchPoints().size() >> 2) { + d->mGestureTriggered = false; + return CancelGesture; + } + + if (touchEvent->touchPoints().size() == 2) { + if ((touchEvent->touchPoints().first().startPos() - touchEvent->touchPoints().first().pos()).manhattanLength() > Touch_Helper::Touch::wiggleRoomForTap) { + d->mGestureTriggered = false; + return CancelGesture; + } + if ((touchEvent->touchPoints().at(1).startPos() - touchEvent->touchPoints().at(1).pos()).manhattanLength() > Touch_Helper::Touch::wiggleRoomForTap) { + d->mGestureTriggered = false; + return CancelGesture; + } + if (touchEvent->touchPointStates() & Qt::TouchPointPressed) { + d->mGestureTriggered = true; + } + if (touchEvent->touchPointStates() & Qt::TouchPointReleased && d->mGestureTriggered) { + d->mGestureTriggered = false; + return FinishGesture; + } + } + break; + } + + default: + return Ignore; + } + return Ignore; +} + +TwoFingerTap::TwoFingerTap(QObject* parent) +: QGesture(parent) +{ +} + +} // namespace