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,26 @@ FileOperations::showMenuForDroppedUrls(this, urlList, destUrl); } + bool viewportEvent(QEvent* event) override + { + if (event->type() == QEvent::TouchBegin) { + event->accept(); + return true; + } + // I want only catch a tap, when using the TapGesture I need a lot more code + if (event->type() == QEvent::TouchEnd) { + event->accept(); + QTouchEvent* touchEvent = static_cast(event); + const QPoint pos = touchEvent->touchPoints().first().pos().toPoint(); + const QPoint startPos = touchEvent->touchPoints().first().startPos().toPoint(); + if ((pos - startPos).manhattanLength() < Touch::wiggelRoomForTap) { + emit expand(indexAt(pos)); + emit activated(indexAt(pos)); + } + return true; + } + return QTreeView::viewportEvent(event); + } private: QRect mDropRect; }; @@ -209,6 +233,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,13 @@ 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 ${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 { @@ -202,7 +206,7 @@ protected: void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override; - + bool event(QEvent*) override; void resizeEvent(QGraphicsSceneResizeEvent* event) override; void mousePressEvent(QGraphicsSceneMouseEvent* event) override; void wheelEvent(QGraphicsSceneWheelEvent* event) override; @@ -235,6 +239,11 @@ void dragThumbnailLoadingFailed(const KFileItem&); private: + void tapGestureTriggered(QGestureEvent*); + void pinchGestureTriggered(QGestureEvent*); + void tapHoldAndMovingGestureTriggered(QGesture*); + void oneAndTwoFingerSwipeGestureTriggered(QGesture*); + void gestureEvent(QGestureEvent*); friend struct DocumentViewPrivate; DocumentViewPrivate* const d; 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,9 @@ d->mDragStartPosition = QPointF(0, 0); d->mDrag = nullptr; + d->mTouch = new Touch(this); + setAcceptTouchEvents (true); + // 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 +479,7 @@ DocumentView::~DocumentView() { + delete d->mTouch; delete d->mDragThumbnailProvider; delete d->mDrag; delete d; @@ -710,6 +719,215 @@ return d->mAdapter->zoom(); } +bool DocumentView::event(QEvent* event) +{ + //we need to catch all touch events, otherwise, Qt makes mouse clicks from touch events + switch (event->type()) { + case QEvent::TouchBegin: { + QTouchEvent* touchEvent = static_cast(event); + //Move mouse cursor to touch point, needed to fade in thumbnailbar on the top screen + QObject* receiver = touchEvent->target(); + const QPoint pos = touchEvent->touchPoints().at(0).pos().toPoint(); + QMouseEvent* evt = new QMouseEvent(QEvent::MouseMove, pos, Qt::NoButton, Qt::NoButton, Qt::NoModifier); + QCoreApplication::postEvent(receiver, evt); + return true; + } + case QEvent::TouchEnd: + return true; + case 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 && !d->mTouch->getTapHoldandMovingGestureActive()) { + const QPointF delta = touchEvent->touchPoints().first().lastPos() - touchEvent->touchPoints().first().pos(); + d->mAdapter->setScrollPos(d->mAdapter->scrollPos() + delta); + } + return true; + } + case QEvent::Gesture: { + gestureEvent(static_cast(event)); + return true; + } + default: + break; + } + + return QGraphicsWidget::event(event); +} + +void DocumentView::gestureEvent(QGestureEvent* event) +{ + if (QGesture* twoFingerTap = event->gesture(d->mTouch->getTwoFingerTapGesture())) { + event->accept(); + if (twoFingerTap->state() == Qt::GestureFinished) { + contextMenuRequested(); + } + } + + if (event->gesture(Qt::TapGesture)) { + event->accept(); + tapGestureTriggered(event); + } + + if (QGesture* gesture = event->gesture(d->mTouch->getDoubleTapGesture())) { + event->accept(); + if (gesture->state() == Qt::GestureFinished) { + d->mAdapter->toggleFullScreenRequested(); + } + } + + if (QGesture* gesture = event->gesture(d->mTouch->getTapHoldandMovingGesture())) { + event->accept(); + tapHoldAndMovingGestureTriggered(gesture); + } + + if (QGesture* gesture = event->gesture(d->mTouch->getOneAndTwoFingerSwipeGesture())) { + event->accept(); + oneAndTwoFingerSwipeGestureTriggered(gesture); + } + + if (QGesture* gesture = event->gesture(d->mTouch->getTwoFingerPanGesture())) { + event->accept(); + d->mTouch->setLastPanGestureState(event); + if (gesture->state() == Qt::GestureUpdated) { + const QPoint diff = gesture->property("delta").toPoint(); + d->mAdapter->setScrollPos(d->mAdapter->scrollPos() + diff); + } + } + + if (QGesture* pinchGesture = event->gesture(Qt::PinchGesture)) { + //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 (d->mTouch->getLastPanGestureState() == Qt::GestureCanceled || pinchGesture->state() == Qt::GestureStarted) { + event->accept(); + pinchGestureTriggered(event); + } + } + +} + +void DocumentView::tapGestureTriggered (QGestureEvent* event) +{ + const QTapGesture* tap = static_cast(event->gesture(Qt::TapGesture)); + + if (tap->state() != Qt::GestureFinished) return; + QPoint pos = tap->position().toPoint(); + d->mTouch->touchToMouseClick(pos, event->widget()); +} + +void DocumentView::pinchGestureTriggered(QGestureEvent* event) +{ + const QPinchGesture* pinch = static_cast(event->gesture(Qt::PinchGesture)); + const qreal rotationThreshold = 40; + const qreal sensitivModifier = 0.85; + static qreal lastRotation; + static qreal lastScaleFactor; + + if (pinch->state() == Qt::GestureStarted) { + lastRotation = 0; + lastScaleFactor = 0; + return; + } + + if (pinch->state() == Qt::GestureUpdated) { + const qreal scaleFactor = pinch->scaleFactor(); + const qreal rotationAngle = pinch->rotationAngle(); + //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 == scaleFactor) { + return; + } else { + lastScaleFactor = scaleFactor; + } + + if (pinch->changeFlags() & QPinchGesture::ScaleFactorChanged) { + if (d->mAdapter->canZoom()) { + const qreal currentZoom = d->mAdapter->zoom(); + qreal zoom; + if (scaleFactor >= 1.0) { + zoom = ((scaleFactor - 1.0) * sensitivModifier + 1.0) * currentZoom; + } else { + zoom = (1.0 - (1.0 - scaleFactor) * sensitivModifier) * currentZoom; + } + const QPointF zoomCenter = mapFromScene(event->mapToGraphicsScene(pinch->centerPoint())); + d->setZoom(zoom, zoomCenter); + } + } + + if (pinch->changeFlags() & QPinchGesture::RotationAngleChanged) { + const qreal rotationDelta = rotationAngle - pinch->lastRotationAngle(); + //very low and high changes in the rotation are suspect, so we ignore them + if (abs(rotationDelta) <= 1.5 || abs(rotationDelta) >= 50) { + return; + } + lastRotation += rotationDelta; + if (lastRotation > rotationThreshold) { + TransformImageOperation* op = new TransformImageOperation(ROT_90); + op->applyToDocument(d->mDocument); + lastRotation = 0; + } + else if (-(lastRotation) > rotationThreshold) { + TransformImageOperation* op = new TransformImageOperation(ROT_270); + op->applyToDocument(d->mDocument); + lastRotation = 0; + } + } + } +} + +void DocumentView::tapHoldAndMovingGestureTriggered (QGesture* gesture) +{ + const QPoint pos = gesture->property("pos").toPoint(); + switch (gesture->state()) { + case Qt::GestureStarted: + d->mTouch->setTapHoldandMovingGestureActive(true); + d->startDragIfSensible(); + break; + + case Qt::GestureUpdated: { + QGraphicsWidget* receiver = this; + QMouseEvent* mouseEvent = new QMouseEvent(QEvent::MouseMove, pos, Qt::NoButton, Qt::LeftButton, Qt::NoModifier); + QCoreApplication::postEvent(receiver, mouseEvent); + break; + } + + case Qt::GestureCanceled: + case Qt::GestureFinished: { + QGraphicsWidget* receiver = this; + QMouseEvent* mouseEvent = new QMouseEvent(QEvent::MouseButtonRelease, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + QCoreApplication::postEvent(receiver, mouseEvent); + d->mTouch->setTapHoldandMovingGestureActive(false); + break; + } + + default: + break; + } +} + +void DocumentView::oneAndTwoFingerSwipeGestureTriggered(QGesture* gesture) +{ + //only change image if the viewport on the right or left border of the zoomed-in image + if (gesture->state() == Qt::GestureFinished) { + if (gesture->property("right").toBool()) { + const QPoint scrollPos = d->mAdapter->scrollPos().toPoint(); + if (scrollPos.x() <= 1) { + d->mAdapter->previousImageRequested(); + } + } + if (gesture->property("left").toBool()) { + 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)) { + d->mAdapter->nextImageRequested(); + } + } + } +} + void DocumentView::resizeEvent(QGraphicsSceneResizeEvent *event) { d->resizeAdapterWidget(); @@ -942,6 +1160,11 @@ } } 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. + 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,39 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +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* viewport); + +} // 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,46 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2007 Aurélien Gâteau + +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 +#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 { @@ -158,6 +162,8 @@ void generateThumbnailsForItems(); protected: + bool viewportEvent(QEvent*) override; + void dragEnterEvent(QDragEnterEvent*) override; void dragMoveEvent(QDragMoveEvent*) override; @@ -207,6 +213,10 @@ void smoothNextThumbnail(); private: + void pinchGestureTriggered(QPinchGesture*); + void tapHoldAndMovingGestureTriggered(QGesture*); + void tapGestureTriggered(QTapGesture*); + void gestureEvent(QGestureEvent*); friend struct ThumbnailViewPrivate; ThumbnailViewPrivate * const d; }; 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,14 @@ connect(this, &ThumbnailView::customContextMenuRequested, this, &ThumbnailView::showContextMenu); connect(this, &ThumbnailView::activated, this, &ThumbnailView::emitIndexActivatedIfNoModifiers); + + d->mScroller = ScrollerUtils::setQScroller(this->viewport()); + d->mTouch = new Touch(this, viewport()); } ThumbnailView::~ThumbnailView() { + delete d->mTouch; delete d; } @@ -693,6 +704,132 @@ drag->exec(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction, Qt::CopyAction); } +bool ThumbnailView::viewportEvent(QEvent* event) +{ + //necessary, otherwise, Qt makes mouse clicks from touch events + if (event->type() == QEvent::TouchBegin) { + return true; + } + + if (event->type() == QEvent::Gesture) { + gestureEvent(static_cast(event)); + } + + return QListView::viewportEvent(event); +} + +void ThumbnailView::gestureEvent(QGestureEvent* event) +{ + if (event->gesture(d->mTouch->getTwoFingerPanGesture())) { + d->mTouch->setLastPanGestureState(event); + } + + if (QGesture* twoFingerTap = event->gesture(d->mTouch->getTwoFingerTapGesture())) { + event->accept(); + if (twoFingerTap->state() == Qt::GestureFinished) { + showContextMenu(); + return; + } + } + + if (QGesture* pinchGesture = event->gesture(Qt::PinchGesture)) { + + //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 (d->mTouch->getLastPanGestureState() == Qt::GestureCanceled || pinchGesture->state() == Qt::GestureStarted) { + event->accept(); + pinchGestureTriggered(static_cast(pinchGesture)); + //if pinch gesture is active, we don't want a other gesture + return; + } + } + + if (QGesture* tapGesture = event->gesture(Qt::TapGesture)) { + event->accept(); + tapGestureTriggered(static_cast(tapGesture)); + } + + if (QGesture* tapHoldAndMovingGesture = event->gesture(d->mTouch->getTapHoldandMovingGesture())) { + event->accept(); + tapHoldAndMovingGestureTriggered(tapHoldAndMovingGesture); + } + return; +} + +void ThumbnailView::pinchGestureTriggered(QPinchGesture* pinch) +{ + static int startThumbnailSizeWidth; + static bool firstGestureUpdatedCompleted; + if (pinch->state() == Qt::GestureStarted) { + startThumbnailSizeWidth = d->mThumbnailSize.width(); + firstGestureUpdatedCompleted = false; + return; + } + + if (pinch->state() == Qt::GestureUpdated) { + if (!firstGestureUpdatedCompleted) { + firstGestureUpdatedCompleted = true; + return; + } + + int width; + float sensitivModifier = 0.25; + if (pinch->totalScaleFactor() >= 1.0) { + width = ((pinch->totalScaleFactor() - 1.0) * sensitivModifier + 1.0) * startThumbnailSizeWidth; + } else { + width = (1.0 - (1.0 - pinch->totalScaleFactor()) * sensitivModifier) * startThumbnailSizeWidth; + } + width = qBound (int(MinThumbnailSize), width, int(MaxThumbnailSize)); + setThumbnailWidth(width); + return; + } +} + +void ThumbnailView::tapHoldAndMovingGestureTriggered(QGesture* gesture) +{ + const QPoint pos = gesture->property("pos").toPoint(); + + switch (gesture->state()) { + case Qt::GestureStarted: { + QModelIndex index = indexAt(pos); + if (index.isValid()) { + setCurrentIndex(index); + d->mScroller->stop(); + startDrag(Qt::CopyAction); + } + break; + } + + case Qt::GestureUpdated: { + QWidget* receiver = this->viewport(); + QMouseEvent* mouseEvent = new QMouseEvent(QEvent::MouseMove, pos, Qt::NoButton, Qt::LeftButton, Qt::NoModifier); + QCoreApplication::postEvent(receiver, mouseEvent); + break; + } + + case Qt::GestureCanceled: + case Qt::GestureFinished: { + QWidget* receiver = this->viewport(); + QMouseEvent* mouseEvent = new QMouseEvent(QEvent::MouseButtonRelease, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + QCoreApplication::postEvent(receiver, mouseEvent); + break; + } + + default: + break; + } +} + +void ThumbnailView::tapGestureTriggered(QTapGesture* gesture) +{ + if (gesture->state() == Qt::GestureFinished) { + const QPoint pos = gesture->position().toPoint(); + const QRect rect = QRect(pos, QSize(1, 1)); + setSelection(rect, QItemSelectionModel::ClearAndSelect); + emit activated(indexAt(pos)); + } +} + 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,63 @@ +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +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 + +class QGraphicsWidget; + +namespace Gwenview +{ +struct DoubleTapRecognizerPrivate; + +class GWENVIEWLIB_EXPORT DoubleTap : public QGesture +{ + Q_PROPERTY(QPointF pos READ pos WRITE pos) +public: + QPointF test; + explicit DoubleTap(QObject* parent = 0); +private: + QPointF pos; +}; + +class GWENVIEWLIB_EXPORT DoubleTapRecognizer : public QGestureRecognizer +{ +public: + explicit DoubleTapRecognizer(QGraphicsWidget* target = 0); + ~DoubleTapRecognizer(); +private: + DoubleTapRecognizerPrivate* d; + + virtual QGesture* create(QObject*); + virtual Result recognize(QGesture*, QObject*, QEvent*); + +}; + +} // 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,136 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +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; + QGraphicsWidget* mTargetGrapicsWidget; + qint64 mTouchBeginnTimestamp; + bool mIsOnlyTap; + qint64 mLastTapTimestamp = 0; + qint64 mLastDoupleTapTimestamp = 0; +}; + +DoubleTapRecognizer::DoubleTapRecognizer(QGraphicsWidget* target) : QGestureRecognizer() +, d (new DoubleTapRecognizerPrivate) +{ + d->q = this; + if (target) { + d->mTargetGrapicsWidget = target; + } else { + d->mTargetGrapicsWidget = nullptr; + } +} + +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 (d->mTargetGrapicsWidget) { + if (watched != d->mTargetGrapicsWidget) 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::doubleTapInterval; + state->setHotSpot(touchEvent->touchPoints().first().screenPos()); + return MayBeGesture; + } + + case QEvent::TouchUpdate: { + QTouchEvent* touchEvent = static_cast(event); + const qint64 now = touchEvent->timestamp(); + const QPointF pos = touchEvent->touchPoints().first().pos(); + const QPointF distance = touchEvent->touchPoints().first().startPos() - pos; + state->setHotSpot(touchEvent->touchPoints().first().screenPos()); + + if (d->mIsOnlyTap && now - d->mTouchBeginnTimestamp < Touch::maxTimeForTap && distance.manhattanLength() < Touch::wiggelRoomForTap) { + 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::doubleTapInterval && d->mIsOnlyTap) { + //Interval between two double tap gesture need to be bigger than Touch::doupleTapIntervall, + //to suppress fast successively double tap gestures + if (now - d->mLastDoupleTapTimestamp > 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,64 @@ +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +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 + +class QGraphicsWidget; + +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(QGraphicsWidget* target = 0); + ~OneAndTwoFingerSwipeRecognizer(); +private: + OneAndTwoFingerSwipeRecognizerPrivate* d; + + virtual QGesture* create(QObject*); + virtual Result recognize(QGesture*, QObject*, QEvent*); +}; + +} // 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,130 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +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; + QGraphicsWidget* mTargetGrapicsWidget; + qint64 mTouchBeginnTimestamp; + bool mGestureAlreadyTriggered; +}; + +OneAndTwoFingerSwipeRecognizer::OneAndTwoFingerSwipeRecognizer(QGraphicsWidget* target) : QGestureRecognizer() +, d (new OneAndTwoFingerSwipeRecognizerPrivate) +{ + d->q = this; + if (target) { + d->mTargetGrapicsWidget = target; + } else { + d->mTargetGrapicsWidget = nullptr; + } +} + +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 (d->mTargetGrapicsWidget) { + if (watched != d->mTargetGrapicsWidget) 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::minDistanceForSwipe && + (now - d->mTouchBeginnTimestamp) <= 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::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,66 @@ +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +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 + +class QGraphicsWidget; + +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(QGraphicsWidget* target = 0); + ~TapHoldAndMovingRecognizer(); +private: + TapHoldAndMovingRecognizerPrivate* d; + + virtual QGesture* create(QObject* target); + virtual Result recognize(QGesture* state, QObject* watched, QEvent* event); + +}; + +} // 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,137 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +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; + QGraphicsWidget* mTargetGrapicsWidget; + qint64 mTouchBeginnTimestamp; + bool mTouchPointStationary; + bool mGestureTriggered; + Qt::GestureState mLastGestureState = Qt::NoGesture; +}; + +TapHoldAndMovingRecognizer::TapHoldAndMovingRecognizer(QGraphicsWidget* target) : QGestureRecognizer() +, d (new TapHoldAndMovingRecognizerPrivate) +{ + d->q = this; + if (target) { + d->mTargetGrapicsWidget = target; + } else { + d->mTargetGrapicsWidget = nullptr; + } +} + +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 (d->mTargetGrapicsWidget) { + if (watched != d->mTargetGrapicsWidget) return Ignore; + } + + switch (event->type()) { + case QEvent::TouchBegin: { + QTouchEvent* touchEvent = static_cast(event); + d->mTouchBeginnTimestamp = touchEvent->timestamp(); + d->mTouchPointStationary = true; + d->mGestureTriggered = false; + 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 QPointF distance = touchEvent->touchPoints().first().startPos() - touchEvent->touchPoints().first().pos(); + 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::durationForTapHold) { + d->mGestureTriggered = true; + } + } + + if (distance.manhattanLength() >= Touch::wiggelRoomForTap) { + d->mTouchPointStationary = false; + } + + 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,70 @@ +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +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" + +class QGraphicsWidget; + +namespace Gwenview +{ + +struct TouchPrivate; +class GWENVIEWLIB_EXPORT Touch : public QObject +{ + Q_OBJECT +public: + Touch(QWidget* parent, QWidget* target); + Touch(QGraphicsWidget* target); + + ~Touch() override; + void touchToMouseClick(QPoint, QWidget*); + void setLastPanGestureState(QGestureEvent*); + 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: + +private: + 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,186 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +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 + +// KDE + +// Local + +namespace Gwenview +{ + +struct TouchPrivate +{ + Touch* q; + QWidget* mParent; + Qt::GestureState mLastPanGestureState; + QPointF mLastTapPos; + bool mTabHoldandMovingGestureActive; + + + 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(QWidget* parent, QWidget* target) +: QObject () +, d(new TouchPrivate) +{ + d->q = this; + d->mParent = parent; + + d->mTapHoldAndMovingRecognizer = new TapHoldAndMovingRecognizer; + d->mTapHoldAndMoving = QGestureRecognizer::registerRecognizer(d->mTapHoldAndMovingRecognizer); + target->grabGesture(d->mTapHoldAndMoving); + + d->mTwoFingerPanRecognizer = new TwoFingerPanRecognizer; + d->mTwoFingerPan = QGestureRecognizer::registerRecognizer(d->mTwoFingerPanRecognizer); + target->grabGesture(d->mTwoFingerPan); + + d->mTwoFingerTapRecognizer = new TwoFingerTapRecognizer(); + d->mTwoFingerTap = QGestureRecognizer::registerRecognizer(d->mTwoFingerTapRecognizer); + target->grabGesture(d->mTwoFingerTap); + + target->grabGesture(Qt::TapGesture); + target->grabGesture(Qt::PinchGesture); +} + +Touch::Touch(QGraphicsWidget* target) +: QObject () +, d(new TouchPrivate) +{ + d->q = this; + + d->mTapHoldAndMovingRecognizer = new TapHoldAndMovingRecognizer(target); + d->mTapHoldAndMoving = QGestureRecognizer::registerRecognizer(d->mTapHoldAndMovingRecognizer); + target->grabGesture(d->mTapHoldAndMoving); + d->mTabHoldandMovingGestureActive = false; + + d->mTwoFingerPanRecognizer = new TwoFingerPanRecognizer(target); + d->mTwoFingerPan = QGestureRecognizer::registerRecognizer(d->mTwoFingerPanRecognizer); + target->grabGesture(d->mTwoFingerPan); + + d->mOneAndTwoFingerSwipeRecognizer = new OneAndTwoFingerSwipeRecognizer(target); + d->mOneAndTwoFingerSwipe = QGestureRecognizer::registerRecognizer(d->mOneAndTwoFingerSwipeRecognizer); + target->grabGesture(d->mOneAndTwoFingerSwipe); + + d->mDoubleTapRecognizer = new DoubleTapRecognizer(target); + d->mDoubleTap = QGestureRecognizer::registerRecognizer(d->mDoubleTapRecognizer); + target->grabGesture(d->mDoubleTap); + + d->mTwoFingerTapRecognizer = new TwoFingerTapRecognizer(target); + d->mTwoFingerTap = QGestureRecognizer::registerRecognizer(d->mTwoFingerTapRecognizer); + target->grabGesture(d->mTwoFingerTap); + + target->grabGesture(Qt::TapGesture); + target->grabGesture(Qt::PinchGesture); +} + +Touch::~Touch() +{ + delete d; +} + +void Touch::touchToMouseClick (QPoint pos, QWidget* receiver) +{ + QMouseEvent* evt = new QMouseEvent(QEvent::MouseButtonPress, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + QCoreApplication::postEvent(receiver, evt); + QMouseEvent* evtr = new QMouseEvent(QEvent::MouseButtonRelease, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + QCoreApplication::postEvent(receiver, evtr); +} + +Qt::GestureState Touch::getLastPanGestureState() +{ + return d->mLastPanGestureState;; +} + +void Touch::setLastPanGestureState(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,53 @@ +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +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 + +// KDE + +// Local + +namespace Gwenview +{ + +//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 wiggelRoomForTap = 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; +}; + +} // namespace +#endif /* TOUCH_HELPER_H */ 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,68 @@ +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +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 +class QGraphicsWidget; + +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(QGraphicsWidget* target = 0); + ~TwoFingerPanRecognizer(); +private: + TwoFingerPanRecognizerPrivate* d; + + virtual QGesture* create(QObject*); + virtual Result recognize(QGesture*, QObject*, QEvent*); + +}; + +} // 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,171 @@ +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +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; + QGraphicsWidget* mTargetGrapicsWidget; + qint64 mTouchBeginnTimestamp; + bool mGestureTriggered; + qint64 mLastTouchTimestamp; +}; + +TwoFingerPanRecognizer::TwoFingerPanRecognizer(QGraphicsWidget* target) : QGestureRecognizer() +, d (new TwoFingerPanRecognizerPrivate) +{ + d->q = this; + if (target) { + d->mTargetGrapicsWidget = target; + } else { + d->mTargetGrapicsWidget = nullptr; + } +} + +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 (d->mTargetGrapicsWidget) { + if (watched != d->mTargetGrapicsWidget) 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::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,65 @@ +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +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 + +class QGraphicsWidget; +namespace Gwenview +{ +struct TwoFingerTapRecognizerPrivate; + +class GWENVIEWLIB_EXPORT TwoFingerTap : public QGesture +{ + Q_PROPERTY(bool left READ left WRITE left) + Q_PROPERTY(bool right READ right WRITE right) + +public: + explicit TwoFingerTap(QObject* parent = 0); + +private: + bool left; + bool right; +}; + +class GWENVIEWLIB_EXPORT TwoFingerTapRecognizer : public QGestureRecognizer +{ +public: + explicit TwoFingerTapRecognizer(QGraphicsWidget* parent = 0); + ~TwoFingerTapRecognizer(); +private: + TwoFingerTapRecognizerPrivate* d; + + virtual QGesture* create(QObject*); + virtual Result recognize(QGesture*, QObject*, QEvent*); + +}; + +} // 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,122 @@ +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau + +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; + QGraphicsWidget* mTargetGrapicsWidget; + qint64 mTouchBeginnTimestamp; + bool mGestureTriggered; +}; + +TwoFingerTapRecognizer::TwoFingerTapRecognizer(QGraphicsWidget* target) : QGestureRecognizer() +, d (new TwoFingerTapRecognizerPrivate) +{ + d->q = this; + if (target) { + d->mTargetGrapicsWidget = target; + } else { + d->mTargetGrapicsWidget = nullptr; + } +} + +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 (d->mTargetGrapicsWidget) { + if (watched != d->mTargetGrapicsWidget) 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::wiggelRoomForTap) { + d->mGestureTriggered = false; + return CancelGesture; + } + if ((touchEvent->touchPoints().at(1).startPos() - touchEvent->touchPoints().at(1).pos()).manhattanLength() > Touch::wiggelRoomForTap) { + 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