/* 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" // STL #include // Qt #include #include #include #include #include // KF // Local #include "gwenview_lib_debug.h" #include "touch_helper.h" namespace Gwenview { struct TouchPrivate { Touch *q = nullptr; QObject *mTarget = nullptr; Qt::GestureState mLastPanGestureState; QPointF mLastTapPos; bool mTabHoldandMovingGestureActive; qreal mStartZoom; qreal mZoomModifier; qreal mRotationThreshold; qint64 mLastTouchTimeStamp; TapHoldAndMovingRecognizer *mTapHoldAndMovingRecognizer = nullptr; Qt::GestureType mTapHoldAndMoving; TwoFingerPanRecognizer *mTwoFingerPanRecognizer = nullptr; Qt::GestureType mTwoFingerPan; OneAndTwoFingerSwipeRecognizer *mOneAndTwoFingerSwipeRecognizer = nullptr; Qt::GestureType mOneAndTwoFingerSwipe; DoubleTapRecognizer *mDoubleTapRecognizer = nullptr; Qt::GestureType mDoubleTap; TwoFingerTapRecognizer *mTwoFingerTapRecognizer = nullptr; 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)) { auto 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) { d->mLastTouchTimeStamp = QDateTime::currentMSecsSinceEpoch(); const QPoint pos = Touch_Helper::simpleTouchPosition(event); touchToMouseMove(pos, event, Qt::NoButton); return true; } if (event->type() == QEvent::TouchUpdate) { auto touchEvent = static_cast(event); d->mLastTouchTimeStamp = QDateTime::currentMSecsSinceEpoch(); // 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(); Q_EMIT PanTriggered(delta); } return true; } if (event->type() == QEvent::TouchEnd) { d->mLastTouchTimeStamp = QDateTime::currentMSecsSinceEpoch(); } 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; Q_EMIT pinchZoomTriggered(getZoomFromPinchGesture(event), positionGesture(event), d->mLastTouchTimeStamp); Q_EMIT pinchRotateTriggered(getRotationFromPinchGesture(event)); } if (checkTapGesture(event)) { ret = true; if (event->widget()) { touchToMouseClick(positionGesture(event), event->widget()); } Q_EMIT tapTriggered(positionGesture(event)); } if (checkTapHoldAndMovingGesture(event, d->mTarget)) { ret = true; Q_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 (auto 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 (auto pinch = static_cast(event->gesture(Qt::PinchGesture))) { if (qobject_cast(d->mTarget)) { auto 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(); Q_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()) { Q_EMIT swipeRightTriggered(); return true; } else if (gesture->property("left").toBool()) { Q_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) { Q_EMIT doubleTapTriggered(); return true; } } return false; } bool Touch::checkTwoFingerTapGesture(QGestureEvent *event) { if (QGesture *twoFingerTap = event->gesture(getTwoFingerTapGesture())) { event->accept(); if (twoFingerTap->state() == Qt::GestureFinished) { Q_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; Q_EMIT pinchGestureStarted(d->mLastTouchTimeStamp); } 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 (auto 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) { auto 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 #include "moc_touch.cpp"