File indexing completed on 2024-05-12 04:19:52

0001 /*
0002 Gwenview: an image viewer
0003 Copyright 2019 Steffen Hartleib <steffenhartleib@t-online.de>
0004 
0005 This program is free software; you can redistribute it and/or
0006 modify it under the terms of the GNU General Public License
0007 as published by the Free Software Foundation; either version 2
0008 of the License, or (at your option) any later version.
0009 
0010 This program is distributed in the hope that it will be useful,
0011 but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 GNU General Public License for more details.
0014 
0015 You should have received a copy of the GNU General Public License
0016 along with this program; if not, write to the Free Software
0017 Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA.
0018 
0019 */
0020 // Self
0021 #include "twofingerpan.h"
0022 
0023 // Qt
0024 #include <QGraphicsWidget>
0025 #include <QTouchEvent>
0026 #include <QVector2D>
0027 
0028 // KF
0029 
0030 // Local
0031 #include "gwenview_lib_debug.h"
0032 #include "lib/touch/touch_helper.h"
0033 
0034 namespace Gwenview
0035 {
0036 struct TwoFingerPanRecognizerPrivate {
0037     TwoFingerPanRecognizer *q = nullptr;
0038     bool mTargetIsGrapicsWidget = false;
0039     qint64 mTouchBeginnTimestamp;
0040     bool mGestureTriggered;
0041     qint64 mLastTouchTimestamp;
0042 };
0043 
0044 TwoFingerPanRecognizer::TwoFingerPanRecognizer()
0045     : QGestureRecognizer()
0046     , d(new TwoFingerPanRecognizerPrivate)
0047 {
0048     d->q = this;
0049 }
0050 
0051 TwoFingerPanRecognizer::~TwoFingerPanRecognizer()
0052 {
0053     delete d;
0054 }
0055 
0056 QGesture *TwoFingerPanRecognizer::create(QObject *)
0057 {
0058     return static_cast<QGesture *>(new TwoFingerPan());
0059 }
0060 
0061 QGestureRecognizer::Result TwoFingerPanRecognizer::recognize(QGesture *state, QObject *watched, QEvent *event)
0062 {
0063     // Because of a bug in Qt in a gesture event in a graphicsview, all gestures are trigger twice
0064     // https://bugreports.qt.io/browse/QTBUG-13103
0065     if (qobject_cast<QGraphicsWidget *>(watched))
0066         d->mTargetIsGrapicsWidget = true;
0067     if (d->mTargetIsGrapicsWidget && watched->isWidgetType())
0068         return Ignore;
0069 
0070     switch (event->type()) {
0071     case QEvent::TouchBegin: {
0072         auto touchEvent = static_cast<QTouchEvent *>(event);
0073         d->mTouchBeginnTimestamp = touchEvent->timestamp();
0074         d->mLastTouchTimestamp = touchEvent->timestamp();
0075         d->mGestureTriggered = false;
0076         state->setProperty("delayActive", true);
0077         state->setHotSpot(touchEvent->touchPoints().first().screenPos());
0078         return TriggerGesture;
0079     }
0080 
0081     case QEvent::TouchUpdate: {
0082         auto touchEvent = static_cast<QTouchEvent *>(event);
0083         const qint64 now = touchEvent->timestamp();
0084         const QPointF pos = touchEvent->touchPoints().first().pos();
0085         state->setHotSpot(touchEvent->touchPoints().first().screenPos());
0086 
0087         if (touchEvent->touchPoints().size() >> 2) {
0088             if (d->mGestureTriggered) {
0089                 d->mGestureTriggered = false;
0090             }
0091             return CancelGesture;
0092         }
0093 
0094         if (touchEvent->touchPoints().size() == 2) {
0095             if (touchEvent->touchPointStates() & Qt::TouchPointReleased) {
0096                 if (d->mGestureTriggered) {
0097                     d->mGestureTriggered = false;
0098                     return FinishGesture;
0099                 }
0100             }
0101 
0102             if (now - d->mTouchBeginnTimestamp >= Touch_Helper::Touch::gestureDelay) {
0103                 state->setProperty("delayActive", false);
0104 
0105                 // Check if both touch points moving in the same direction
0106                 const QVector2D vectorTouchPoint1 = QVector2D(touchEvent->touchPoints().at(0).startPos() - touchEvent->touchPoints().at(0).pos());
0107                 const QVector2D vectorTouchPoint2 = QVector2D(touchEvent->touchPoints().at(1).startPos() - touchEvent->touchPoints().at(1).pos());
0108                 QVector2D dotProduct = vectorTouchPoint1 * vectorTouchPoint2;
0109 
0110                 // The dot product is greater than zero if both touch points moving in the same direction
0111                 // special case if the touch point moving exact or almost exact in x or y axis
0112                 // one value of the dot product is zero or a little bit less than zero and
0113                 // the other value is bigger than zero
0114                 if (dotProduct.x() >= -500 && dotProduct.x() <= 0 && dotProduct.y() > 0)
0115                     dotProduct.setX(1);
0116                 if (dotProduct.y() >= -500 && dotProduct.y() <= 0 && dotProduct.x() > 0)
0117                     dotProduct.setY(1);
0118 
0119                 if (dotProduct.x() > 0 && dotProduct.y() > 0) {
0120                     const QPointF diff = (touchEvent->touchPoints().first().lastPos() - pos);
0121                     state->setProperty("delta", diff);
0122                     d->mGestureTriggered = true;
0123                     return TriggerGesture;
0124                 } else {
0125                     // special case if the user makes a very slow pan gesture, the vectors a very short. Sometimes the
0126                     // dot product is then zero, so we only want to cancel the gesture if the user makes a bigger wrong gesture
0127                     if (vectorTouchPoint1.toPoint().manhattanLength() > 50 || vectorTouchPoint2.toPoint().manhattanLength() > 50) {
0128                         d->mGestureTriggered = false;
0129                         return CancelGesture;
0130                     } else {
0131                         const QPointF diff = (touchEvent->touchPoints().first().lastPos() - pos);
0132                         state->setProperty("delta", diff);
0133                         d->mGestureTriggered = true;
0134                         return TriggerGesture;
0135                     }
0136                 }
0137             } else {
0138                 state->setProperty("delta", QPointF(0, 0));
0139                 state->setProperty("delayActive", true);
0140                 d->mGestureTriggered = false;
0141                 return TriggerGesture;
0142             }
0143         }
0144         break;
0145     }
0146 
0147     case QEvent::TouchEnd: {
0148         auto touchEvent = static_cast<QTouchEvent *>(event);
0149         if (d->mGestureTriggered) {
0150             d->mGestureTriggered = false;
0151             state->setHotSpot(touchEvent->touchPoints().first().screenPos());
0152             return FinishGesture;
0153         }
0154         break;
0155     }
0156 
0157     default:
0158         return Ignore;
0159     }
0160     return Ignore;
0161 }
0162 
0163 TwoFingerPan::TwoFingerPan(QObject *parent)
0164     : QGesture(parent)
0165 {
0166 }
0167 
0168 } // namespace