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

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