471 lines
15 KiB
C++
Raw Normal View History

2024-06-29 11:52:32 +06:00
/*
Gwenview: an image viewer
Copyright 2019 Steffen Hartleib <steffenhartleib@t-online.de>
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 <cmath>
// Qt
#include <QCoreApplication>
#include <QDateTime>
#include <QGraphicsWidget>
#include <QMouseEvent>
#include <QWidget>
// 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<QGraphicsWidget *>(target)) {
auto widgetTarget = qobject_cast<QGraphicsWidget *>(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<QWidget *>(target)) {
QWidget *widgetTarget = qobject_cast<QWidget *>(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<QTouchEvent *>(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<QGestureEvent *>(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<QPinchGesture *>(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<QPinchGesture *>(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<QTapGesture *>(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<QPinchGesture *>(event->gesture(Qt::PinchGesture))) {
if (qobject_cast<QGraphicsWidget *>(d->mTarget)) {
auto widget = qobject_cast<QGraphicsWidget *>(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<QTapGesture *>(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<QPinchGesture *>(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<QTouchEvent *>(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"