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