1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qstandardgestures_p.h"
5#include "qgesture.h"
6#include "qgesture_p.h"
7#include "qevent.h"
8#include "qwidget.h"
9#if QT_CONFIG(scrollarea)
10#include "qabstractscrollarea.h"
11#endif
12#if QT_CONFIG(graphicsview)
13#include <qgraphicssceneevent.h>
14#endif
15#include "qdebug.h"
16
17#ifndef QT_NO_GESTURES
18
19using namespace std::chrono_literals;
20
21QT_BEGIN_NAMESPACE
22
23// If the change in scale for a single touch event is out of this range,
24// we consider it to be spurious.
25static const qreal kSingleStepScaleMax = 2.0;
26static const qreal kSingleStepScaleMin = 0.1;
27
28QGesture *QPanGestureRecognizer::create(QObject *target)
29{
30 if (target && target->isWidgetType()) {
31#if (defined(Q_OS_MACOS) || defined(Q_OS_WIN)) && !defined(QT_NO_NATIVE_GESTURES) && QT_CONFIG(scrollarea)
32 // for scroll areas on Windows and OS X we want to use native gestures instead
33 if (!qobject_cast<QAbstractScrollArea *>(target->parent()))
34 static_cast<QWidget *>(target)->setAttribute(Qt::WA_AcceptTouchEvents);
35#else
36 static_cast<QWidget *>(target)->setAttribute(Qt::WA_AcceptTouchEvents);
37#endif
38 }
39 return new QPanGesture;
40}
41
42static QPointF panOffset(const QList<QEventPoint> &touchPoints, int maxCount)
43{
44 QPointF result;
45 const int count = qMin(a: touchPoints.size(), b: maxCount);
46 for (int p = 0; p < count; ++p)
47 result += touchPoints.at(i: p).position() - touchPoints.at(i: p).pressPosition();
48 return result / qreal(count);
49}
50
51QGestureRecognizer::Result QPanGestureRecognizer::recognize(QGesture *state,
52 QObject *,
53 QEvent *event)
54{
55 QPanGesture *q = static_cast<QPanGesture *>(state);
56 QPanGesturePrivate *d = q->d_func();
57
58 QGestureRecognizer::Result result = QGestureRecognizer::Ignore;
59 switch (event->type()) {
60 case QEvent::TouchBegin: {
61 result = QGestureRecognizer::MayBeGesture;
62 d->lastOffset = d->offset = QPointF();
63 d->pointCount = m_pointCount;
64 break;
65 }
66 case QEvent::TouchEnd: {
67 if (q->state() != Qt::NoGesture) {
68 const QTouchEvent *ev = static_cast<const QTouchEvent *>(event);
69 if (ev->points().size() == d->pointCount) {
70 d->lastOffset = d->offset;
71 d->offset = panOffset(touchPoints: ev->points(), maxCount: d->pointCount);
72 }
73 result = QGestureRecognizer::FinishGesture;
74 } else {
75 result = QGestureRecognizer::CancelGesture;
76 }
77 break;
78 }
79 case QEvent::TouchUpdate: {
80 const QTouchEvent *ev = static_cast<const QTouchEvent *>(event);
81 if (ev->points().size() >= d->pointCount) {
82 d->lastOffset = d->offset;
83 d->offset = panOffset(touchPoints: ev->points(), maxCount: d->pointCount);
84 if (d->offset.x() > 10 || d->offset.y() > 10 ||
85 d->offset.x() < -10 || d->offset.y() < -10) {
86 q->setHotSpot(ev->points().first().globalPressPosition());
87 result = QGestureRecognizer::TriggerGesture;
88 } else {
89 result = QGestureRecognizer::MayBeGesture;
90 }
91 }
92 break;
93 }
94 default:
95 break;
96 }
97 return result;
98}
99
100void QPanGestureRecognizer::reset(QGesture *state)
101{
102 QPanGesture *pan = static_cast<QPanGesture*>(state);
103 QPanGesturePrivate *d = pan->d_func();
104
105 d->lastOffset = d->offset = QPointF();
106 d->acceleration = 0;
107
108 QGestureRecognizer::reset(state);
109}
110
111
112//
113// QPinchGestureRecognizer
114//
115
116QPinchGestureRecognizer::QPinchGestureRecognizer()
117{
118}
119
120QGesture *QPinchGestureRecognizer::create(QObject *target)
121{
122 if (target && target->isWidgetType()) {
123 static_cast<QWidget *>(target)->setAttribute(Qt::WA_AcceptTouchEvents);
124 }
125 return new QPinchGesture;
126}
127
128QGestureRecognizer::Result QPinchGestureRecognizer::recognize(QGesture *state,
129 QObject *,
130 QEvent *event)
131{
132 QPinchGesture *q = static_cast<QPinchGesture *>(state);
133 QPinchGesturePrivate *d = q->d_func();
134
135 QGestureRecognizer::Result result = QGestureRecognizer::Ignore;
136
137 switch (event->type()) {
138 case QEvent::TouchBegin: {
139 result = QGestureRecognizer::MayBeGesture;
140 break;
141 }
142 case QEvent::TouchEnd: {
143 if (q->state() != Qt::NoGesture) {
144 result = QGestureRecognizer::FinishGesture;
145 } else {
146 result = QGestureRecognizer::CancelGesture;
147 }
148 break;
149 }
150 case QEvent::TouchUpdate: {
151 const QTouchEvent *ev = static_cast<const QTouchEvent *>(event);
152 d->changeFlags = { };
153 if (ev->points().size() == 2) {
154 const QEventPoint &p1 = ev->points().at(i: 0);
155 const QEventPoint &p2 = ev->points().at(i: 1);
156
157 d->hotSpot = p1.globalPosition();
158 d->isHotSpotSet = true;
159
160 QPointF centerPoint = (p1.globalPosition() + p2.globalPosition()) / 2.0;
161 if (d->isNewSequence) {
162 d->startPosition[0] = p1.globalPosition();
163 d->startPosition[1] = p2.globalPosition();
164 d->lastCenterPoint = centerPoint;
165 } else {
166 d->lastCenterPoint = d->centerPoint;
167 }
168 d->centerPoint = centerPoint;
169
170 d->changeFlags |= QPinchGesture::CenterPointChanged;
171
172 if (d->isNewSequence) {
173 d->scaleFactor = 1.0;
174 d->lastScaleFactor = 1.0;
175 } else {
176 d->lastScaleFactor = d->scaleFactor;
177 QLineF line(p1.globalPosition(), p2.globalPosition());
178 QLineF lastLine(p1.globalLastPosition(), p2.globalLastPosition());
179 qreal newScaleFactor = line.length() / lastLine.length();
180 if (newScaleFactor > kSingleStepScaleMax || newScaleFactor < kSingleStepScaleMin)
181 return QGestureRecognizer::Ignore;
182 d->scaleFactor = newScaleFactor;
183 }
184 d->totalScaleFactor = d->totalScaleFactor * d->scaleFactor;
185 d->changeFlags |= QPinchGesture::ScaleFactorChanged;
186
187 qreal angle = QLineF(p1.globalPosition(), p2.globalPosition()).angle();
188 if (angle > 180)
189 angle -= 360;
190 qreal startAngle = QLineF(p1.globalPressPosition(), p2.globalPressPosition()).angle();
191 if (startAngle > 180)
192 startAngle -= 360;
193 const qreal rotationAngle = startAngle - angle;
194 if (d->isNewSequence)
195 d->lastRotationAngle = 0.0;
196 else
197 d->lastRotationAngle = d->rotationAngle;
198 d->rotationAngle = rotationAngle;
199 d->totalRotationAngle += d->rotationAngle - d->lastRotationAngle;
200 d->changeFlags |= QPinchGesture::RotationAngleChanged;
201
202 d->totalChangeFlags |= d->changeFlags;
203 d->isNewSequence = false;
204 result = QGestureRecognizer::TriggerGesture;
205 } else {
206 d->isNewSequence = true;
207 if (q->state() == Qt::NoGesture)
208 result = QGestureRecognizer::Ignore;
209 else
210 result = QGestureRecognizer::FinishGesture;
211 }
212 break;
213 }
214 default:
215 break;
216 }
217 return result;
218}
219
220void QPinchGestureRecognizer::reset(QGesture *state)
221{
222 QPinchGesture *pinch = static_cast<QPinchGesture *>(state);
223 QPinchGesturePrivate *d = pinch->d_func();
224
225 d->totalChangeFlags = d->changeFlags = { };
226
227 d->startCenterPoint = d->lastCenterPoint = d->centerPoint = QPointF();
228 d->totalScaleFactor = d->lastScaleFactor = d->scaleFactor = 1;
229 d->totalRotationAngle = d->lastRotationAngle = d->rotationAngle = 0;
230
231 d->isNewSequence = true;
232 d->startPosition[0] = d->startPosition[1] = QPointF();
233
234 QGestureRecognizer::reset(state);
235}
236
237//
238// QSwipeGestureRecognizer
239//
240
241QSwipeGestureRecognizer::QSwipeGestureRecognizer()
242{
243}
244
245QGesture *QSwipeGestureRecognizer::create(QObject *target)
246{
247 if (target && target->isWidgetType()) {
248 static_cast<QWidget *>(target)->setAttribute(Qt::WA_AcceptTouchEvents);
249 }
250 return new QSwipeGesture;
251}
252
253QGestureRecognizer::Result QSwipeGestureRecognizer::recognize(QGesture *state,
254 QObject *,
255 QEvent *event)
256{
257 QSwipeGesture *q = static_cast<QSwipeGesture *>(state);
258 QSwipeGesturePrivate *d = q->d_func();
259
260 QGestureRecognizer::Result result = QGestureRecognizer::Ignore;
261
262 switch (event->type()) {
263 case QEvent::TouchBegin: {
264 d->velocityValue = 1;
265 d->time.start();
266 d->state = QSwipeGesturePrivate::Started;
267 result = QGestureRecognizer::MayBeGesture;
268 break;
269 }
270 case QEvent::TouchEnd: {
271 if (q->state() != Qt::NoGesture) {
272 result = QGestureRecognizer::FinishGesture;
273 } else {
274 result = QGestureRecognizer::CancelGesture;
275 }
276 break;
277 }
278 case QEvent::TouchUpdate: {
279 const QTouchEvent *ev = static_cast<const QTouchEvent *>(event);
280 if (d->state == QSwipeGesturePrivate::NoGesture)
281 result = QGestureRecognizer::CancelGesture;
282 else if (ev->points().size() == 3) {
283 d->state = QSwipeGesturePrivate::ThreePointsReached;
284 const QEventPoint &p1 = ev->points().at(i: 0);
285 const QEventPoint &p2 = ev->points().at(i: 1);
286 const QEventPoint &p3 = ev->points().at(i: 2);
287
288 if (d->lastPositions[0].isNull()) {
289 d->lastPositions[0] = p1.globalPressPosition().toPoint();
290 d->lastPositions[1] = p2.globalPressPosition().toPoint();
291 d->lastPositions[2] = p3.globalPressPosition().toPoint();
292 }
293 d->hotSpot = p1.globalPosition();
294 d->isHotSpotSet = true;
295
296 int xDistance = (p1.globalPosition().x() - d->lastPositions[0].x() +
297 p2.globalPosition().x() - d->lastPositions[1].x() +
298 p3.globalPosition().x() - d->lastPositions[2].x()) / 3;
299 int yDistance = (p1.globalPosition().y() - d->lastPositions[0].y() +
300 p2.globalPosition().y() - d->lastPositions[1].y() +
301 p3.globalPosition().y() - d->lastPositions[2].y()) / 3;
302
303 const int distance = xDistance >= yDistance ? xDistance : yDistance;
304 int elapsedTime = d->time.restart();
305 if (!elapsedTime)
306 elapsedTime = 1;
307 d->velocityValue = 0.9 * d->velocityValue + (qreal) distance / elapsedTime;
308 d->swipeAngle = QLineF(p1.globalPressPosition(), p1.globalPosition()).angle();
309
310 static const int MoveThreshold = 50;
311 static const int directionChangeThreshold = MoveThreshold / 8;
312 if (qAbs(t: xDistance) > MoveThreshold || qAbs(t: yDistance) > MoveThreshold) {
313 // measure the distance to check if the direction changed
314 d->lastPositions[0] = p1.globalPosition().toPoint();
315 d->lastPositions[1] = p2.globalPosition().toPoint();
316 d->lastPositions[2] = p3.globalPosition().toPoint();
317 result = QGestureRecognizer::TriggerGesture;
318 // QTBUG-46195, small changes in direction should not cause the gesture to be canceled.
319 if (d->verticalDirection == QSwipeGesture::NoDirection || qAbs(t: yDistance) > directionChangeThreshold) {
320 const QSwipeGesture::SwipeDirection vertical = yDistance > 0
321 ? QSwipeGesture::Down : QSwipeGesture::Up;
322 if (d->verticalDirection != QSwipeGesture::NoDirection && d->verticalDirection != vertical)
323 result = QGestureRecognizer::CancelGesture;
324 d->verticalDirection = vertical;
325 }
326 if (d->horizontalDirection == QSwipeGesture::NoDirection || qAbs(t: xDistance) > directionChangeThreshold) {
327 const QSwipeGesture::SwipeDirection horizontal = xDistance > 0
328 ? QSwipeGesture::Right : QSwipeGesture::Left;
329 if (d->horizontalDirection != QSwipeGesture::NoDirection && d->horizontalDirection != horizontal)
330 result = QGestureRecognizer::CancelGesture;
331 d->horizontalDirection = horizontal;
332 }
333 } else {
334 if (q->state() != Qt::NoGesture)
335 result = QGestureRecognizer::TriggerGesture;
336 else
337 result = QGestureRecognizer::MayBeGesture;
338 }
339 } else if (ev->points().size() > 3) {
340 result = QGestureRecognizer::CancelGesture;
341 } else { // less than 3 touch points
342 switch (d->state) {
343 case QSwipeGesturePrivate::NoGesture:
344 result = QGestureRecognizer::MayBeGesture;
345 break;
346 case QSwipeGesturePrivate::Started:
347 result = QGestureRecognizer::Ignore;
348 break;
349 case QSwipeGesturePrivate::ThreePointsReached:
350 result = (ev->touchPointStates() & QEventPoint::State::Pressed)
351 ? QGestureRecognizer::CancelGesture : QGestureRecognizer::Ignore;
352 break;
353 }
354 }
355 break;
356 }
357 default:
358 break;
359 }
360 return result;
361}
362
363void QSwipeGestureRecognizer::reset(QGesture *state)
364{
365 QSwipeGesture *q = static_cast<QSwipeGesture *>(state);
366 QSwipeGesturePrivate *d = q->d_func();
367
368 d->verticalDirection = d->horizontalDirection = QSwipeGesture::NoDirection;
369 d->swipeAngle = 0;
370
371 d->lastPositions[0] = d->lastPositions[1] = d->lastPositions[2] = QPoint();
372 d->state = QSwipeGesturePrivate::NoGesture;
373 d->velocityValue = 0;
374 d->time.invalidate();
375
376 QGestureRecognizer::reset(state);
377}
378
379//
380// QTapGestureRecognizer
381//
382
383QTapGestureRecognizer::QTapGestureRecognizer()
384{
385}
386
387QGesture *QTapGestureRecognizer::create(QObject *target)
388{
389 if (target && target->isWidgetType()) {
390 static_cast<QWidget *>(target)->setAttribute(Qt::WA_AcceptTouchEvents);
391 }
392 return new QTapGesture;
393}
394
395QGestureRecognizer::Result QTapGestureRecognizer::recognize(QGesture *state,
396 QObject *,
397 QEvent *event)
398{
399 QTapGesture *q = static_cast<QTapGesture *>(state);
400 QTapGesturePrivate *d = q->d_func();
401
402 QGestureRecognizer::Result result = QGestureRecognizer::CancelGesture;
403
404 switch (event->type()) {
405 case QEvent::TouchBegin: {
406 const auto ev = static_cast<const QTouchEvent *>(event);
407 d->position = ev->points().at(i: 0).position();
408 q->setHotSpot(ev->points().at(i: 0).globalPosition());
409 result = QGestureRecognizer::TriggerGesture;
410 break;
411 }
412 case QEvent::TouchUpdate:
413 case QEvent::TouchEnd: {
414 const auto ev = static_cast<const QTouchEvent *>(event);
415 if (q->state() != Qt::NoGesture && ev->points().size() == 1) {
416 const QEventPoint &p = ev->points().at(i: 0);
417 QPoint delta = p.position().toPoint() - p.pressPosition().toPoint();
418 enum { TapRadius = 40 };
419 if (delta.manhattanLength() <= TapRadius) {
420 if (event->type() == QEvent::TouchEnd)
421 result = QGestureRecognizer::FinishGesture;
422 else
423 result = QGestureRecognizer::TriggerGesture;
424 }
425 }
426 break;
427 }
428 case QEvent::MouseButtonPress:
429 case QEvent::MouseMove:
430 case QEvent::MouseButtonRelease:
431 result = QGestureRecognizer::Ignore;
432 break;
433 default:
434 result = QGestureRecognizer::Ignore;
435 break;
436 }
437 return result;
438}
439
440void QTapGestureRecognizer::reset(QGesture *state)
441{
442 QTapGesture *q = static_cast<QTapGesture *>(state);
443 QTapGesturePrivate *d = q->d_func();
444
445 d->position = QPointF();
446
447 QGestureRecognizer::reset(state);
448}
449
450//
451// QTapAndHoldGestureRecognizer
452//
453
454QTapAndHoldGestureRecognizer::QTapAndHoldGestureRecognizer()
455{
456}
457
458QGesture *QTapAndHoldGestureRecognizer::create(QObject *target)
459{
460 if (target && target->isWidgetType()) {
461 static_cast<QWidget *>(target)->setAttribute(Qt::WA_AcceptTouchEvents);
462 }
463 return new QTapAndHoldGesture;
464}
465
466QGestureRecognizer::Result
467QTapAndHoldGestureRecognizer::recognize(QGesture *state, QObject *object,
468 QEvent *event)
469{
470 QTapAndHoldGesture *q = static_cast<QTapAndHoldGesture *>(state);
471 QTapAndHoldGesturePrivate *d = q->d_func();
472
473 if (object == state && event->type() == QEvent::Timer) {
474 d->tapAndHoldTimer.stop();
475 return QGestureRecognizer::FinishGesture | QGestureRecognizer::ConsumeEventHint;
476 }
477
478 enum { TapRadius = 40 };
479
480 switch (event->type()) {
481#if QT_CONFIG(graphicsview)
482 case QEvent::GraphicsSceneMousePress: {
483 const QGraphicsSceneMouseEvent *gsme = static_cast<const QGraphicsSceneMouseEvent *>(event);
484 d->position = gsme->screenPos();
485 q->setHotSpot(d->position);
486 d->tapAndHoldTimer.start(duration: QTapAndHoldGesturePrivate::Timeout * 1ms, obj: q);
487 return QGestureRecognizer::MayBeGesture; // we don't show a sign of life until the timeout
488 }
489#endif
490 case QEvent::MouseButtonPress: {
491 const QMouseEvent *me = static_cast<const QMouseEvent *>(event);
492 d->position = me->globalPosition().toPoint();
493 q->setHotSpot(d->position);
494 d->tapAndHoldTimer.start(duration: QTapAndHoldGesturePrivate::Timeout * 1ms, obj: q);
495 return QGestureRecognizer::MayBeGesture; // we don't show a sign of life until the timeout
496 }
497 case QEvent::TouchBegin: {
498 const QTouchEvent *ev = static_cast<const QTouchEvent *>(event);
499 d->position = ev->points().at(i: 0).globalPressPosition();
500 q->setHotSpot(d->position);
501 d->tapAndHoldTimer.start(duration: QTapAndHoldGesturePrivate::Timeout * 1ms, obj: q);
502 return QGestureRecognizer::MayBeGesture; // we don't show a sign of life until the timeout
503 }
504#if QT_CONFIG(graphicsview)
505 case QEvent::GraphicsSceneMouseRelease:
506#endif
507 case QEvent::MouseButtonRelease:
508 case QEvent::TouchEnd:
509 return QGestureRecognizer::CancelGesture; // get out of the MayBeGesture state
510 case QEvent::TouchUpdate: {
511 const QTouchEvent *ev = static_cast<const QTouchEvent *>(event);
512 if (d->tapAndHoldTimer.isActive() && ev->points().size() == 1) {
513 const QEventPoint &p = ev->points().at(i: 0);
514 QPoint delta = p.position().toPoint() - p.pressPosition().toPoint();
515 if (delta.manhattanLength() <= TapRadius)
516 return QGestureRecognizer::MayBeGesture;
517 }
518 return QGestureRecognizer::CancelGesture;
519 }
520 case QEvent::MouseMove: {
521 const QMouseEvent *me = static_cast<const QMouseEvent *>(event);
522 QPoint delta = me->globalPosition().toPoint() - d->position.toPoint();
523 if (d->tapAndHoldTimer.isActive() && delta.manhattanLength() <= TapRadius)
524 return QGestureRecognizer::MayBeGesture;
525 return QGestureRecognizer::CancelGesture;
526 }
527#if QT_CONFIG(graphicsview)
528 case QEvent::GraphicsSceneMouseMove: {
529 const QGraphicsSceneMouseEvent *gsme = static_cast<const QGraphicsSceneMouseEvent *>(event);
530 QPoint delta = gsme->screenPos() - d->position.toPoint();
531 if (d->tapAndHoldTimer.isActive() && delta.manhattanLength() <= TapRadius)
532 return QGestureRecognizer::MayBeGesture;
533 return QGestureRecognizer::CancelGesture;
534 }
535#endif
536 default:
537 return QGestureRecognizer::Ignore;
538 }
539}
540
541void QTapAndHoldGestureRecognizer::reset(QGesture *state)
542{
543 QTapAndHoldGesture *q = static_cast<QTapAndHoldGesture *>(state);
544 QTapAndHoldGesturePrivate *d = q->d_func();
545
546 d->position = QPointF();
547 d->tapAndHoldTimer.stop();
548
549 QGestureRecognizer::reset(state);
550}
551
552QT_END_NAMESPACE
553
554#endif // QT_NO_GESTURES
555

source code of qtbase/src/widgets/kernel/qstandardgestures.cpp