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 "touch.h"
0022 
0023 // STL
0024 #include <cmath>
0025 
0026 // Qt
0027 #include <QCoreApplication>
0028 #include <QDateTime>
0029 #include <QGraphicsWidget>
0030 #include <QMouseEvent>
0031 #include <QWidget>
0032 
0033 // KF
0034 
0035 // Local
0036 #include "gwenview_lib_debug.h"
0037 #include "touch_helper.h"
0038 
0039 namespace Gwenview
0040 {
0041 struct TouchPrivate {
0042     Touch *q = nullptr;
0043     QObject *mTarget = nullptr;
0044     Qt::GestureState mLastPanGestureState;
0045     QPointF mLastTapPos;
0046     bool mTabHoldandMovingGestureActive;
0047     qreal mStartZoom;
0048     qreal mZoomModifier;
0049     qreal mRotationThreshold;
0050     qint64 mLastTouchTimeStamp;
0051 
0052     TapHoldAndMovingRecognizer *mTapHoldAndMovingRecognizer = nullptr;
0053     Qt::GestureType mTapHoldAndMoving;
0054     TwoFingerPanRecognizer *mTwoFingerPanRecognizer = nullptr;
0055     Qt::GestureType mTwoFingerPan;
0056     OneAndTwoFingerSwipeRecognizer *mOneAndTwoFingerSwipeRecognizer = nullptr;
0057     Qt::GestureType mOneAndTwoFingerSwipe;
0058     DoubleTapRecognizer *mDoubleTapRecognizer = nullptr;
0059     Qt::GestureType mDoubleTap;
0060     TwoFingerTapRecognizer *mTwoFingerTapRecognizer = nullptr;
0061     Qt::GestureType mTwoFingerTap;
0062 };
0063 
0064 Touch::Touch(QObject *target)
0065     : QObject()
0066     , d(new TouchPrivate)
0067 {
0068     d->q = this;
0069     d->mTarget = target;
0070 
0071     d->mTapHoldAndMovingRecognizer = new TapHoldAndMovingRecognizer();
0072     d->mTapHoldAndMoving = QGestureRecognizer::registerRecognizer(d->mTapHoldAndMovingRecognizer);
0073 
0074     d->mTwoFingerPanRecognizer = new TwoFingerPanRecognizer();
0075     d->mTwoFingerPan = QGestureRecognizer::registerRecognizer(d->mTwoFingerPanRecognizer);
0076 
0077     d->mTwoFingerTapRecognizer = new TwoFingerTapRecognizer();
0078     d->mTwoFingerTap = QGestureRecognizer::registerRecognizer(d->mTwoFingerTapRecognizer);
0079 
0080     d->mOneAndTwoFingerSwipeRecognizer = new OneAndTwoFingerSwipeRecognizer();
0081     d->mOneAndTwoFingerSwipe = QGestureRecognizer::registerRecognizer(d->mOneAndTwoFingerSwipeRecognizer);
0082 
0083     d->mDoubleTapRecognizer = new DoubleTapRecognizer();
0084     d->mDoubleTap = QGestureRecognizer::registerRecognizer(d->mDoubleTapRecognizer);
0085 
0086     if (qobject_cast<QGraphicsWidget *>(target)) {
0087         auto widgetTarget = qobject_cast<QGraphicsWidget *>(target);
0088         widgetTarget->grabGesture(d->mOneAndTwoFingerSwipe);
0089         widgetTarget->grabGesture(d->mDoubleTap);
0090         widgetTarget->grabGesture(Qt::TapGesture);
0091         widgetTarget->grabGesture(Qt::PinchGesture);
0092         widgetTarget->grabGesture(d->mTwoFingerTap);
0093         widgetTarget->grabGesture(d->mTwoFingerPan);
0094         widgetTarget->grabGesture(d->mTapHoldAndMoving);
0095     } else if (qobject_cast<QWidget *>(target)) {
0096         QWidget *widgetTarget = qobject_cast<QWidget *>(target);
0097         widgetTarget->grabGesture(Qt::TapGesture);
0098         widgetTarget->grabGesture(Qt::PinchGesture);
0099         widgetTarget->grabGesture(d->mTwoFingerTap);
0100         widgetTarget->grabGesture(d->mTwoFingerPan);
0101         widgetTarget->grabGesture(d->mTapHoldAndMoving);
0102     }
0103     target->installEventFilter(this);
0104 }
0105 
0106 Touch::~Touch()
0107 {
0108     delete d;
0109 }
0110 
0111 bool Touch::eventFilter(QObject *, QEvent *event)
0112 {
0113     if (event->type() == QEvent::TouchBegin) {
0114         d->mLastTouchTimeStamp = QDateTime::currentMSecsSinceEpoch();
0115         const QPoint pos = Touch_Helper::simpleTouchPosition(event);
0116         touchToMouseMove(pos, event, Qt::NoButton);
0117         return true;
0118     }
0119     if (event->type() == QEvent::TouchUpdate) {
0120         auto touchEvent = static_cast<QTouchEvent *>(event);
0121         d->mLastTouchTimeStamp = QDateTime::currentMSecsSinceEpoch();
0122         // because we suppress the making of mouse event through Qt, we need to make our own one finger panning
0123         // but only if no TapHoldandMovingGesture is active (Drag and Drop action)
0124         if (touchEvent->touchPoints().size() == 1 && !getTapHoldandMovingGestureActive()) {
0125             const QPointF delta = touchEvent->touchPoints().first().lastPos() - touchEvent->touchPoints().first().pos();
0126             Q_EMIT PanTriggered(delta);
0127         }
0128         return true;
0129     }
0130     if (event->type() == QEvent::TouchEnd) {
0131         d->mLastTouchTimeStamp = QDateTime::currentMSecsSinceEpoch();
0132     }
0133     if (event->type() == QEvent::Gesture) {
0134         gestureEvent(static_cast<QGestureEvent *>(event));
0135     }
0136     return false;
0137 }
0138 
0139 bool Touch::gestureEvent(QGestureEvent *event)
0140 {
0141     bool ret = false;
0142 
0143     if (checkTwoFingerTapGesture(event)) {
0144         ret = true;
0145     }
0146 
0147     if (checkPinchGesture(event)) {
0148         ret = true;
0149         Q_EMIT pinchZoomTriggered(getZoomFromPinchGesture(event), positionGesture(event), d->mLastTouchTimeStamp);
0150         Q_EMIT pinchRotateTriggered(getRotationFromPinchGesture(event));
0151     }
0152 
0153     if (checkTapGesture(event)) {
0154         ret = true;
0155         if (event->widget()) {
0156             touchToMouseClick(positionGesture(event), event->widget());
0157         }
0158         Q_EMIT tapTriggered(positionGesture(event));
0159     }
0160 
0161     if (checkTapHoldAndMovingGesture(event, d->mTarget)) {
0162         ret = true;
0163         Q_EMIT tapHoldAndMovingTriggered(positionGesture(event));
0164     }
0165 
0166     if (checkDoubleTapGesture(event)) {
0167         ret = true;
0168     }
0169 
0170     if (checkOneAndTwoFingerSwipeGesture(event)) {
0171         ret = true;
0172     }
0173 
0174     checkTwoFingerPanGesture(event);
0175 
0176     return ret;
0177 }
0178 
0179 void Touch::setZoomParameter(qreal modifier, qreal startZoom)
0180 {
0181     d->mZoomModifier = modifier;
0182     d->mStartZoom = startZoom;
0183 }
0184 
0185 void Touch::setRotationThreshold(qreal rotationThreshold)
0186 {
0187     d->mRotationThreshold = rotationThreshold;
0188 }
0189 
0190 qreal Touch::getRotationFromPinchGesture(QGestureEvent *event)
0191 {
0192     const QPinchGesture *pinch = static_cast<QPinchGesture *>(event->gesture(Qt::PinchGesture));
0193     static qreal lastRotationAngel;
0194     if (pinch) {
0195         if (pinch->state() == Qt::GestureStarted) {
0196             lastRotationAngel = 0;
0197             return 0.0;
0198         }
0199         if (pinch->state() == Qt::GestureUpdated) {
0200             const qreal rotationDelta = pinch->rotationAngle() - pinch->lastRotationAngle();
0201             // very low and high changes in the rotation are suspect, so we ignore them
0202             if (abs(rotationDelta) <= 1.5 || abs(rotationDelta) >= 30) {
0203                 return 0.0;
0204             }
0205             lastRotationAngel += rotationDelta;
0206             const qreal ret = lastRotationAngel;
0207             if (abs(lastRotationAngel) > d->mRotationThreshold) {
0208                 lastRotationAngel = 0;
0209                 return ret;
0210             } else {
0211                 return 0.0;
0212             }
0213         }
0214     }
0215     return 0.0;
0216 }
0217 
0218 qreal Touch::getZoomFromPinchGesture(QGestureEvent *event)
0219 {
0220     static qreal lastZoom;
0221     const QPinchGesture *pinch = static_cast<QPinchGesture *>(event->gesture(Qt::PinchGesture));
0222     if (pinch) {
0223         if (pinch->state() == Qt::GestureStarted) {
0224             lastZoom = d->mStartZoom;
0225             return -1;
0226         }
0227         if (pinch->state() == Qt::GestureUpdated) {
0228             lastZoom = calculateZoom(pinch->scaleFactor(), d->mZoomModifier) * lastZoom;
0229             return lastZoom;
0230         }
0231     }
0232     return -1;
0233 }
0234 
0235 qreal Touch::calculateZoom(qreal scale, qreal modifier)
0236 {
0237     return ((scale - 1.0) * modifier) + 1.0;
0238 }
0239 
0240 QPoint Touch::positionGesture(QGestureEvent *event)
0241 {
0242     // return the position or the center point for follow gestures: QTapGesture, TabHoldAndMovingGesture and PinchGesture;
0243     QPoint position = QPoint(-1, -1);
0244     if (auto tap = static_cast<QTapGesture *>(event->gesture(Qt::TapGesture))) {
0245         position = tap->position().toPoint();
0246     } else if (QGesture *gesture = event->gesture(getTapHoldandMovingGesture())) {
0247         position = gesture->property("pos").toPoint();
0248     } else if (auto pinch = static_cast<QPinchGesture *>(event->gesture(Qt::PinchGesture))) {
0249         if (qobject_cast<QGraphicsWidget *>(d->mTarget)) {
0250             auto widget = qobject_cast<QGraphicsWidget *>(d->mTarget);
0251             position = widget->mapFromScene(event->mapToGraphicsScene(pinch->centerPoint())).toPoint();
0252         } else {
0253             position = pinch->centerPoint().toPoint();
0254         }
0255     }
0256     return position;
0257 }
0258 
0259 bool Touch::checkTwoFingerPanGesture(QGestureEvent *event)
0260 {
0261     if (QGesture *gesture = event->gesture(getTwoFingerPanGesture())) {
0262         event->accept();
0263         setPanGestureState(event);
0264         if (gesture->state() == Qt::GestureUpdated) {
0265             const QPoint diff = gesture->property("delta").toPoint();
0266             Q_EMIT PanTriggered(diff);
0267             return true;
0268         }
0269     }
0270     return false;
0271 }
0272 
0273 bool Touch::checkOneAndTwoFingerSwipeGesture(QGestureEvent *event)
0274 {
0275     if (QGesture *gesture = event->gesture(getOneAndTwoFingerSwipeGesture())) {
0276         event->accept();
0277         if (gesture->state() == Qt::GestureFinished) {
0278             if (gesture->property("right").toBool()) {
0279                 Q_EMIT swipeRightTriggered();
0280                 return true;
0281             } else if (gesture->property("left").toBool()) {
0282                 Q_EMIT swipeLeftTriggered();
0283                 return true;
0284             }
0285         }
0286     }
0287     return false;
0288 }
0289 
0290 bool Touch::checkTapGesture(QGestureEvent *event)
0291 {
0292     const QTapGesture *tap = static_cast<QTapGesture *>(event->gesture(Qt::TapGesture));
0293     if (tap) {
0294         event->accept();
0295         if (tap->state() == Qt::GestureFinished)
0296             return true;
0297     }
0298     return false;
0299 }
0300 
0301 bool Touch::checkDoubleTapGesture(QGestureEvent *event)
0302 {
0303     if (QGesture *gesture = event->gesture(getDoubleTapGesture())) {
0304         event->accept();
0305         if (gesture->state() == Qt::GestureFinished) {
0306             Q_EMIT doubleTapTriggered();
0307             return true;
0308         }
0309     }
0310     return false;
0311 }
0312 
0313 bool Touch::checkTwoFingerTapGesture(QGestureEvent *event)
0314 {
0315     if (QGesture *twoFingerTap = event->gesture(getTwoFingerTapGesture())) {
0316         event->accept();
0317         if (twoFingerTap->state() == Qt::GestureFinished) {
0318             Q_EMIT twoFingerTapTriggered();
0319             return true;
0320         }
0321     }
0322     return false;
0323 }
0324 
0325 bool Touch::checkTapHoldAndMovingGesture(QGestureEvent *event, QObject *target)
0326 {
0327     if (QGesture *tapHoldAndMoving = event->gesture(getTapHoldandMovingGesture())) {
0328         event->accept();
0329         const QPoint pos = tapHoldAndMoving->property("pos").toPoint();
0330         switch (tapHoldAndMoving->state()) {
0331         case Qt::GestureStarted: {
0332             setTapHoldandMovingGestureActive(true);
0333             return true;
0334         }
0335         case Qt::GestureUpdated: {
0336             touchToMouseMove(pos, target, Qt::LeftButton);
0337             break;
0338         }
0339         case Qt::GestureCanceled:
0340         case Qt::GestureFinished: {
0341             touchToMouseRelease(pos, target);
0342             setTapHoldandMovingGestureActive(false);
0343             break;
0344         }
0345         default:
0346             break;
0347         }
0348     }
0349     return false;
0350 }
0351 
0352 bool Touch::checkPinchGesture(QGestureEvent *event)
0353 {
0354     static qreal lastScaleFactor;
0355     const QPinchGesture *pinch = static_cast<QPinchGesture *>(event->gesture(Qt::PinchGesture));
0356     if (pinch) {
0357         // we don't want a pinch gesture, if a pan gesture is active
0358         // only exception is, if the pinch gesture state is Qt::GestureStarted
0359         if (getLastPanGestureState() == Qt::GestureCanceled || pinch->state() == Qt::GestureStarted) {
0360             event->accept();
0361             if (pinch->state() == Qt::GestureStarted) {
0362                 lastScaleFactor = 0;
0363                 Q_EMIT pinchGestureStarted(d->mLastTouchTimeStamp);
0364             } else if (pinch->state() == Qt::GestureUpdated) {
0365                 // Because of a bug in Qt in a gesture event in a graphicsview, all gestures are trigger twice
0366                 // https://bugreports.qt.io/browse/QTBUG-13103
0367                 // the duplicate events have the same scaleFactor, so I ignore them
0368                 if (lastScaleFactor == pinch->scaleFactor()) {
0369                     return false;
0370                 } else {
0371                     lastScaleFactor = pinch->scaleFactor();
0372                 }
0373             }
0374             return true;
0375         }
0376     }
0377     return false;
0378 }
0379 
0380 void Touch::touchToMouseRelease(QPoint pos, QObject *receiver)
0381 {
0382     touchToMouseEvent(pos, receiver, QEvent::MouseButtonRelease, Qt::LeftButton, Qt::LeftButton);
0383 }
0384 
0385 void Touch::touchToMouseMove(QPoint pos, QEvent *event, Qt::MouseButton button)
0386 {
0387     if (auto touchEvent = static_cast<QTouchEvent *>(event)) {
0388         touchToMouseEvent(pos, touchEvent->target(), QEvent::MouseMove, button, button);
0389     }
0390 }
0391 
0392 void Touch::touchToMouseMove(QPoint pos, QObject *receiver, Qt::MouseButton button)
0393 {
0394     touchToMouseEvent(pos, receiver, QEvent::MouseMove, button, button);
0395 }
0396 
0397 void Touch::touchToMouseClick(QPoint pos, QObject *receiver)
0398 {
0399     touchToMouseEvent(pos, receiver, QEvent::MouseButtonPress, Qt::LeftButton, Qt::LeftButton);
0400     touchToMouseEvent(pos, receiver, QEvent::MouseButtonRelease, Qt::LeftButton, Qt::LeftButton);
0401 }
0402 
0403 void Touch::touchToMouseEvent(QPoint pos, QObject *receiver, QEvent::Type type, Qt::MouseButton button, Qt::MouseButtons buttons)
0404 {
0405     auto evt = new QMouseEvent(type, pos, button, buttons, Qt::NoModifier);
0406     QCoreApplication::postEvent(receiver, evt);
0407 }
0408 
0409 Qt::GestureState Touch::getLastPanGestureState()
0410 {
0411     return d->mLastPanGestureState;
0412     ;
0413 }
0414 
0415 void Touch::setPanGestureState(QGestureEvent *event)
0416 {
0417     if (QGesture *panGesture = event->gesture(getTwoFingerPanGesture())) {
0418         d->mLastPanGestureState = panGesture->state();
0419     }
0420     return;
0421 }
0422 
0423 QPointF Touch::getLastTapPos()
0424 {
0425     return d->mLastTapPos;
0426 }
0427 
0428 void Touch::setLastTapPos(QPointF pos)
0429 {
0430     d->mLastTapPos = pos;
0431 }
0432 
0433 Qt::GestureType Touch::getTapHoldandMovingGesture()
0434 {
0435     return d->mTapHoldAndMoving;
0436 }
0437 
0438 Qt::GestureType Touch::getTwoFingerPanGesture()
0439 {
0440     return d->mTwoFingerPan;
0441 }
0442 
0443 Qt::GestureType Touch::getOneAndTwoFingerSwipeGesture()
0444 {
0445     return d->mOneAndTwoFingerSwipe;
0446 }
0447 
0448 Qt::GestureType Touch::getDoubleTapGesture()
0449 {
0450     return d->mDoubleTap;
0451 }
0452 
0453 Qt::GestureType Touch::getTwoFingerTapGesture()
0454 {
0455     return d->mTwoFingerTap;
0456 }
0457 
0458 bool Touch::getTapHoldandMovingGestureActive()
0459 {
0460     return d->mTabHoldandMovingGestureActive;
0461 }
0462 
0463 void Touch::setTapHoldandMovingGestureActive(bool active)
0464 {
0465     d->mTabHoldandMovingGestureActive = active;
0466 }
0467 
0468 } // namespace
0469 
0470 #include "moc_touch.cpp"