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

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