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 if (ev->touchPointStates() & QEventPoint::State::Pressed) {
352 result = QGestureRecognizer::CancelGesture;
353 } else if (d->verticalDirection != QSwipeGesture::NoDirection ||
354 d->horizontalDirection != QSwipeGesture::NoDirection) {
355 result = QGestureRecognizer::TriggerGesture;
356 } else {
357 result = QGestureRecognizer::Ignore;
358 }
359 break;
360 }
361 }
362 break;
363 }
364 default:
365 break;
366 }
367 return result;
368}
369
370void QSwipeGestureRecognizer::reset(QGesture *state)
371{
372 QSwipeGesture *q = static_cast<QSwipeGesture *>(state);
373 QSwipeGesturePrivate *d = q->d_func();
374
375 d->verticalDirection = d->horizontalDirection = QSwipeGesture::NoDirection;
376 d->swipeAngle = 0;
377
378 d->lastPositions[0] = d->lastPositions[1] = d->lastPositions[2] = QPoint();
379 d->state = QSwipeGesturePrivate::NoGesture;
380 d->velocityValue = 0;
381 d->time.invalidate();
382
383 QGestureRecognizer::reset(state);
384}
385
386//
387// QTapGestureRecognizer
388//
389
393
394QGesture *QTapGestureRecognizer::create(QObject *target)
395{
396 if (target && target->isWidgetType()) {
397 static_cast<QWidget *>(target)->setAttribute(Qt::WA_AcceptTouchEvents);
398 }
399 return new QTapGesture;
400}
401
402QGestureRecognizer::Result QTapGestureRecognizer::recognize(QGesture *state,
403 QObject *,
404 QEvent *event)
405{
406 QTapGesture *q = static_cast<QTapGesture *>(state);
407 QTapGesturePrivate *d = q->d_func();
408
409 QGestureRecognizer::Result result = QGestureRecognizer::CancelGesture;
410
411 switch (event->type()) {
412 case QEvent::TouchBegin: {
413 const auto ev = static_cast<const QTouchEvent *>(event);
414 d->position = ev->points().at(0).position();
415 q->setHotSpot(ev->points().at(0).globalPosition());
416 result = QGestureRecognizer::TriggerGesture;
417 break;
418 }
419 case QEvent::TouchUpdate:
420 case QEvent::TouchEnd: {
421 const auto ev = static_cast<const QTouchEvent *>(event);
422 if (q->state() != Qt::NoGesture && ev->points().size() == 1) {
423 const QEventPoint &p = ev->points().at(0);
424 QPoint delta = p.position().toPoint() - p.pressPosition().toPoint();
425 enum { TapRadius = 40 };
426 if (delta.manhattanLength() <= TapRadius) {
427 if (event->type() == QEvent::TouchEnd)
428 result = QGestureRecognizer::FinishGesture;
429 else
430 result = QGestureRecognizer::TriggerGesture;
431 }
432 }
433 break;
434 }
435 case QEvent::MouseButtonPress:
436 case QEvent::MouseMove:
437 case QEvent::MouseButtonRelease:
438 result = QGestureRecognizer::Ignore;
439 break;
440 default:
441 result = QGestureRecognizer::Ignore;
442 break;
443 }
444 return result;
445}
446
447void QTapGestureRecognizer::reset(QGesture *state)
448{
449 QTapGesture *q = static_cast<QTapGesture *>(state);
450 QTapGesturePrivate *d = q->d_func();
451
452 d->position = QPointF();
453
454 QGestureRecognizer::reset(state);
455}
456
457//
458// QTapAndHoldGestureRecognizer
459//
460
464
465QGesture *QTapAndHoldGestureRecognizer::create(QObject *target)
466{
467 if (target && target->isWidgetType()) {
468 static_cast<QWidget *>(target)->setAttribute(Qt::WA_AcceptTouchEvents);
469 }
470 return new QTapAndHoldGesture;
471}
472
473QGestureRecognizer::Result
474QTapAndHoldGestureRecognizer::recognize(QGesture *state, QObject *object,
475 QEvent *event)
476{
477 QTapAndHoldGesture *q = static_cast<QTapAndHoldGesture *>(state);
478 QTapAndHoldGesturePrivate *d = q->d_func();
479
480 if (object == state && event->type() == QEvent::Timer) {
481 d->tapAndHoldTimer.stop();
482 return QGestureRecognizer::FinishGesture | QGestureRecognizer::ConsumeEventHint;
483 }
484
485 enum { TapRadius = 40 };
486
487 switch (event->type()) {
488#if QT_CONFIG(graphicsview)
489 case QEvent::GraphicsSceneMousePress: {
490 const QGraphicsSceneMouseEvent *gsme = static_cast<const QGraphicsSceneMouseEvent *>(event);
491 d->position = gsme->screenPos();
492 q->setHotSpot(d->position);
493 d->tapAndHoldTimer.start(QTapAndHoldGesturePrivate::Timeout * 1ms, q);
494 return QGestureRecognizer::MayBeGesture; // we don't show a sign of life until the timeout
495 }
496#endif
497 case QEvent::MouseButtonPress: {
498 const QMouseEvent *me = static_cast<const QMouseEvent *>(event);
499 d->position = me->globalPosition().toPoint();
500 q->setHotSpot(d->position);
501 d->tapAndHoldTimer.start(QTapAndHoldGesturePrivate::Timeout * 1ms, q);
502 return QGestureRecognizer::MayBeGesture; // we don't show a sign of life until the timeout
503 }
504 case QEvent::TouchBegin: {
505 const QTouchEvent *ev = static_cast<const QTouchEvent *>(event);
506 d->position = ev->points().at(0).globalPressPosition();
507 q->setHotSpot(d->position);
508 d->tapAndHoldTimer.start(QTapAndHoldGesturePrivate::Timeout * 1ms, q);
509 return QGestureRecognizer::MayBeGesture; // we don't show a sign of life until the timeout
510 }
511#if QT_CONFIG(graphicsview)
512 case QEvent::GraphicsSceneMouseRelease:
513#endif
514 case QEvent::MouseButtonRelease:
515 case QEvent::TouchEnd:
516 return QGestureRecognizer::CancelGesture; // get out of the MayBeGesture state
517 case QEvent::TouchUpdate: {
518 const QTouchEvent *ev = static_cast<const QTouchEvent *>(event);
519 if (d->tapAndHoldTimer.isActive() && ev->points().size() == 1) {
520 const QEventPoint &p = ev->points().at(0);
521 QPoint delta = p.position().toPoint() - p.pressPosition().toPoint();
522 if (delta.manhattanLength() <= TapRadius)
523 return QGestureRecognizer::MayBeGesture;
524 }
525 return QGestureRecognizer::CancelGesture;
526 }
527 case QEvent::MouseMove: {
528 const QMouseEvent *me = static_cast<const QMouseEvent *>(event);
529 QPoint delta = me->globalPosition().toPoint() - d->position.toPoint();
530 if (d->tapAndHoldTimer.isActive() && delta.manhattanLength() <= TapRadius)
531 return QGestureRecognizer::MayBeGesture;
532 return QGestureRecognizer::CancelGesture;
533 }
534#if QT_CONFIG(graphicsview)
535 case QEvent::GraphicsSceneMouseMove: {
536 const QGraphicsSceneMouseEvent *gsme = static_cast<const QGraphicsSceneMouseEvent *>(event);
537 QPoint delta = gsme->screenPos() - d->position.toPoint();
538 if (d->tapAndHoldTimer.isActive() && delta.manhattanLength() <= TapRadius)
539 return QGestureRecognizer::MayBeGesture;
540 return QGestureRecognizer::CancelGesture;
541 }
542#endif
543 default:
544 return QGestureRecognizer::Ignore;
545 }
546}
547
549{
550 QTapAndHoldGesture *q = static_cast<QTapAndHoldGesture *>(state);
551 QTapAndHoldGesturePrivate *d = q->d_func();
552
553 d->position = QPointF();
554 d->tapAndHoldTimer.stop();
555
556 QGestureRecognizer::reset(state);
557}
558
559QT_END_NAMESPACE
560
561#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:232
constexpr QPointF() noexcept
Constructs a null point, i.e.
Definition qpoint.h:339
\inmodule QtCore\reentrant
Definition qpoint.h:30
constexpr int manhattanLength() const
Returns the sum of the absolute values of x() and y(), traditionally known as the "Manhattan length" ...
Definition qpoint.h:164
constexpr QPoint() noexcept
Constructs a null point, i.e.
Definition qpoint.h:135
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