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

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