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