| 1 | /**************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2019 The Qt Company Ltd. | 
| 4 | ** Contact: https://www.qt.io/licensing/ | 
| 5 | ** | 
| 6 | ** This file is part of the QtQuick 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 "qquicktaphandler_p.h" | 
| 41 | #include "qquicksinglepointhandler_p_p.h" | 
| 42 | #include <qpa/qplatformtheme.h> | 
| 43 | #include <private/qguiapplication_p.h> | 
| 44 | #include <QtGui/qstylehints.h> | 
| 45 |  | 
| 46 | QT_BEGIN_NAMESPACE | 
| 47 |  | 
| 48 | Q_LOGGING_CATEGORY(lcTapHandler, "qt.quick.handler.tap" ) | 
| 49 |  | 
| 50 | qreal QQuickTapHandler::m_multiTapInterval(0.0); | 
| 51 | // single tap distance is the same as the drag threshold | 
| 52 | int QQuickTapHandler::m_mouseMultiClickDistanceSquared(-1); | 
| 53 | int QQuickTapHandler::m_touchMultiTapDistanceSquared(-1); | 
| 54 |  | 
| 55 | /*! | 
| 56 |     \qmltype TapHandler | 
| 57 |     \instantiates QQuickTapHandler | 
| 58 |     \inherits SinglePointHandler | 
| 59 |     \inqmlmodule QtQuick | 
| 60 |     \ingroup qtquick-input-handlers | 
| 61 |     \brief Handler for taps and clicks. | 
| 62 |  | 
| 63 |     TapHandler is a handler for taps on a touchscreen or clicks on a mouse. | 
| 64 |  | 
| 65 |     Detection of a valid tap gesture depends on \l gesturePolicy.  The default | 
| 66 |     value is DragThreshold, which requires the press and release to be close | 
| 67 |     together in both space and time.  In this case, DragHandler is able to | 
| 68 |     function using only a passive grab, and therefore does not interfere with | 
| 69 |     event delivery to any other Items or Input Handlers.  So the default | 
| 70 |     gesturePolicy is useful when you want to modify behavior of an existing | 
| 71 |     control or Item by adding a TapHandler with bindings and/or JavaScript | 
| 72 |     callbacks. | 
| 73 |  | 
| 74 |     Note that buttons (such as QPushButton) are often implemented not to care | 
| 75 |     whether the press and release occur close together: if you press the button | 
| 76 |     and then change your mind, you need to drag all the way off the edge of the | 
| 77 |     button in order to cancel the click.  For this use case, set the | 
| 78 |     \l gesturePolicy to \c TapHandler.ReleaseWithinBounds. | 
| 79 |  | 
| 80 |     For multi-tap gestures (double-tap, triple-tap etc.), the distance moved | 
| 81 |     must not exceed QStyleHints::mouseDoubleClickDistance() with mouse and | 
| 82 |     QStyleHints::touchDoubleTapDistance() with touch, and the time between | 
| 83 |     taps must not exceed QStyleHints::mouseDoubleClickInterval(). | 
| 84 |  | 
| 85 |     \sa MouseArea | 
| 86 | */ | 
| 87 |  | 
| 88 | QQuickTapHandler::QQuickTapHandler(QQuickItem *parent) | 
| 89 |     : QQuickSinglePointHandler(parent) | 
| 90 | { | 
| 91 |     if (m_mouseMultiClickDistanceSquared < 0) { | 
| 92 |         m_multiTapInterval = qApp->styleHints()->mouseDoubleClickInterval() / 1000.0; | 
| 93 |         m_mouseMultiClickDistanceSquared = qApp->styleHints()->mouseDoubleClickDistance(); | 
| 94 |         m_mouseMultiClickDistanceSquared *= m_mouseMultiClickDistanceSquared; | 
| 95 |         m_touchMultiTapDistanceSquared = qApp->styleHints()->touchDoubleTapDistance(); | 
| 96 |         m_touchMultiTapDistanceSquared *= m_touchMultiTapDistanceSquared; | 
| 97 |     } | 
| 98 | } | 
| 99 |  | 
| 100 | bool QQuickTapHandler::wantsEventPoint(QQuickEventPoint *point) | 
| 101 | { | 
| 102 |     if (!point->pointerEvent()->asPointerMouseEvent() && | 
| 103 |             !point->pointerEvent()->asPointerTouchEvent() && | 
| 104 |             !point->pointerEvent()->asPointerTabletEvent() ) | 
| 105 |         return false; | 
| 106 |     // If the user has not violated any constraint, it could be a tap. | 
| 107 |     // Otherwise we want to give up the grab so that a competing handler | 
| 108 |     // (e.g. DragHandler) gets a chance to take over. | 
| 109 |     // Don't forget to emit released in case of a cancel. | 
| 110 |     bool ret = false; | 
| 111 |     bool overThreshold = d_func()->dragOverThreshold(point); | 
| 112 |     if (overThreshold) { | 
| 113 |         m_longPressTimer.stop(); | 
| 114 |         m_holdTimer.invalidate(); | 
| 115 |     } | 
| 116 |     switch (point->state()) { | 
| 117 |     case QQuickEventPoint::Pressed: | 
| 118 |     case QQuickEventPoint::Released: | 
| 119 |         ret = parentContains(point); | 
| 120 |         break; | 
| 121 |     case QQuickEventPoint::Updated: | 
| 122 |         switch (m_gesturePolicy) { | 
| 123 |         case DragThreshold: | 
| 124 |             ret = !overThreshold && parentContains(point); | 
| 125 |             break; | 
| 126 |         case WithinBounds: | 
| 127 |             ret = parentContains(point); | 
| 128 |             break; | 
| 129 |         case ReleaseWithinBounds: | 
| 130 |             ret = point->pointId() == this->point().id(); | 
| 131 |             break; | 
| 132 |         } | 
| 133 |         break; | 
| 134 |     case QQuickEventPoint::Stationary: | 
| 135 |         // If the point hasn't moved since last time, the return value should be the same as last time. | 
| 136 |         // If we return false here, QQuickPointerHandler::handlePointerEvent() will call setActive(false). | 
| 137 |         ret = point->pointId() == this->point().id(); | 
| 138 |         break; | 
| 139 |     } | 
| 140 |     // If this is the grabber, returning false from this function will cancel the grab, | 
| 141 |     // so onGrabChanged(this, CancelGrabExclusive, point) and setPressed(false) will be called. | 
| 142 |     // But when m_gesturePolicy is DragThreshold, we don't get an exclusive grab, but | 
| 143 |     // we still don't want to be pressed anymore. | 
| 144 |     if (!ret && point->pointId() == this->point().id()) | 
| 145 |         setPressed(press: false, cancel: true, point); | 
| 146 |     return ret; | 
| 147 | } | 
| 148 |  | 
| 149 | void QQuickTapHandler::handleEventPoint(QQuickEventPoint *point) | 
| 150 | { | 
| 151 |     switch (point->state()) { | 
| 152 |     case QQuickEventPoint::Pressed: | 
| 153 |         setPressed(press: true, cancel: false, point); | 
| 154 |         break; | 
| 155 |     case QQuickEventPoint::Released: | 
| 156 |         if ((point->pointerEvent()->buttons() & acceptedButtons()) == Qt::NoButton) | 
| 157 |             setPressed(press: false, cancel: false, point); | 
| 158 |         break; | 
| 159 |     default: | 
| 160 |         break; | 
| 161 |     } | 
| 162 | } | 
| 163 |  | 
| 164 | /*! | 
| 165 |     \qmlproperty real QtQuick::TapHandler::longPressThreshold | 
| 166 |  | 
| 167 |     The time in seconds that an event point must be pressed in order to | 
| 168 |     trigger a long press gesture and emit the \l longPressed() signal. | 
| 169 |     If the point is released before this time limit, a tap can be detected | 
| 170 |     if the \l gesturePolicy constraint is satisfied. The default value is | 
| 171 |     QStyleHints::mousePressAndHoldInterval() converted to seconds. | 
| 172 | */ | 
| 173 | qreal QQuickTapHandler::longPressThreshold() const | 
| 174 | { | 
| 175 |     return longPressThresholdMilliseconds() / 1000.0; | 
| 176 | } | 
| 177 |  | 
| 178 | void QQuickTapHandler::setLongPressThreshold(qreal longPressThreshold) | 
| 179 | { | 
| 180 |     int ms = qRound(d: longPressThreshold * 1000); | 
| 181 |     if (m_longPressThreshold == ms) | 
| 182 |         return; | 
| 183 |  | 
| 184 |     m_longPressThreshold = ms; | 
| 185 |     emit longPressThresholdChanged(); | 
| 186 | } | 
| 187 |  | 
| 188 | int QQuickTapHandler::longPressThresholdMilliseconds() const | 
| 189 | { | 
| 190 |     return (m_longPressThreshold < 0 ? QGuiApplication::styleHints()->mousePressAndHoldInterval() : m_longPressThreshold); | 
| 191 | } | 
| 192 |  | 
| 193 | void QQuickTapHandler::timerEvent(QTimerEvent *event) | 
| 194 | { | 
| 195 |     if (event->timerId() == m_longPressTimer.timerId()) { | 
| 196 |         m_longPressTimer.stop(); | 
| 197 |         qCDebug(lcTapHandler) << objectName() << "longPressed" ; | 
| 198 |         emit longPressed(); | 
| 199 |     } | 
| 200 | } | 
| 201 |  | 
| 202 | /*! | 
| 203 |     \qmlproperty enumeration QtQuick::TapHandler::gesturePolicy | 
| 204 |  | 
| 205 |     The spatial constraint for a tap or long press gesture to be recognized, | 
| 206 |     in addition to the constraint that the release must occur before | 
| 207 |     \l longPressThreshold has elapsed. If these constraints are not satisfied, | 
| 208 |     the \l tapped signal is not emitted, and \l tapCount is not incremented. | 
| 209 |     If the spatial constraint is violated, \l pressed transitions immediately | 
| 210 |     from true to false, regardless of the time held. | 
| 211 |  | 
| 212 |     The \c gesturePolicy also affects grab behavior as described below. | 
| 213 |  | 
| 214 |     \value TapHandler.DragThreshold | 
| 215 |            (the default value) The event point must not move significantly. | 
| 216 |            If the mouse, finger or stylus moves past the system-wide drag | 
| 217 |            threshold (QStyleHints::startDragDistance), the tap gesture is | 
| 218 |            canceled, even if the button or finger is still pressed. This policy | 
| 219 |            can be useful whenever TapHandler needs to cooperate with other | 
| 220 |            input handlers (for example \l DragHandler) or event-handling Items | 
| 221 |            (for example QtQuick Controls), because in this case TapHandler | 
| 222 |            will not take the exclusive grab, but merely a | 
| 223 |            \l {QPointerEvent::addPassiveGrabber()}{passive grab}. | 
| 224 |  | 
| 225 |     \value TapHandler.WithinBounds | 
| 226 |            If the event point leaves the bounds of the \c parent Item, the tap | 
| 227 |            gesture is canceled. The TapHandler will take the | 
| 228 |            \l {QPointerEvent::setExclusiveGrabber}{exclusive grab} on | 
| 229 |            press, but will release the grab as soon as the boundary constraint | 
| 230 |            is no longer satisfied. | 
| 231 |  | 
| 232 |     \value TapHandler.ReleaseWithinBounds | 
| 233 |            At the time of release (the mouse button is released or the finger | 
| 234 |            is lifted), if the event point is outside the bounds of the | 
| 235 |            \c parent Item, a tap gesture is not recognized. This corresponds to | 
| 236 |            typical behavior for button widgets: you can cancel a click by | 
| 237 |            dragging outside the button, and you can also change your mind by | 
| 238 |            dragging back inside the button before release. Note that it's | 
| 239 |            necessary for TapHandler to take the | 
| 240 |            \l {QPointerEvent::setExclusiveGrabber}{exclusive grab} on press | 
| 241 |            and retain it until release in order to detect this gesture. | 
| 242 | */ | 
| 243 | void QQuickTapHandler::setGesturePolicy(QQuickTapHandler::GesturePolicy gesturePolicy) | 
| 244 | { | 
| 245 |     if (m_gesturePolicy == gesturePolicy) | 
| 246 |         return; | 
| 247 |  | 
| 248 |     m_gesturePolicy = gesturePolicy; | 
| 249 |     emit gesturePolicyChanged(); | 
| 250 | } | 
| 251 |  | 
| 252 | /*! | 
| 253 |     \qmlproperty bool QtQuick::TapHandler::pressed | 
| 254 |     \readonly | 
| 255 |  | 
| 256 |     Holds true whenever the mouse or touch point is pressed, | 
| 257 |     and any movement since the press is compliant with the current | 
| 258 |     \l gesturePolicy. When the event point is released or the policy is | 
| 259 |     violated, \e pressed will change to false. | 
| 260 | */ | 
| 261 | void QQuickTapHandler::setPressed(bool press, bool cancel, QQuickEventPoint *point) | 
| 262 | { | 
| 263 |     if (m_pressed != press) { | 
| 264 |         qCDebug(lcTapHandler) << objectName() << "pressed"  << m_pressed << "->"  << press << (cancel ? "CANCEL"  : "" ) << point; | 
| 265 |         m_pressed = press; | 
| 266 |         connectPreRenderSignal(conn: press); | 
| 267 |         updateTimeHeld(); | 
| 268 |         if (press) { | 
| 269 |             m_longPressTimer.start(msec: longPressThresholdMilliseconds(), obj: this); | 
| 270 |             m_holdTimer.start(); | 
| 271 |         } else { | 
| 272 |             m_longPressTimer.stop(); | 
| 273 |             m_holdTimer.invalidate(); | 
| 274 |         } | 
| 275 |         if (press) { | 
| 276 |             // on press, grab before emitting changed signals | 
| 277 |             if (m_gesturePolicy == DragThreshold) | 
| 278 |                 setPassiveGrab(point, grab: press); | 
| 279 |             else | 
| 280 |                 setExclusiveGrab(point, grab: press); | 
| 281 |         } | 
| 282 |         if (!cancel && !press && parentContains(point)) { | 
| 283 |             if (point->timeHeld() < longPressThreshold()) { | 
| 284 |                 // Assuming here that pointerEvent()->timestamp() is in ms. | 
| 285 |                 qreal ts = point->pointerEvent()->timestamp() / 1000.0; | 
| 286 |                 if (ts - m_lastTapTimestamp < m_multiTapInterval && | 
| 287 |                         QVector2D(point->scenePosition() - m_lastTapPos).lengthSquared() < | 
| 288 |                         (point->pointerEvent()->device()->type() == QQuickPointerDevice::Mouse ? | 
| 289 |                          m_mouseMultiClickDistanceSquared : m_touchMultiTapDistanceSquared)) | 
| 290 |                     ++m_tapCount; | 
| 291 |                 else | 
| 292 |                     m_tapCount = 1; | 
| 293 |                 qCDebug(lcTapHandler) << objectName() << "tapped"  << m_tapCount << "times" ; | 
| 294 |                 emit tapped(eventPoint: point); | 
| 295 |                 emit tapCountChanged(); | 
| 296 |                 if (m_tapCount == 1) | 
| 297 |                     emit singleTapped(eventPoint: point); | 
| 298 |                 else if (m_tapCount == 2) | 
| 299 |                     emit doubleTapped(eventPoint: point); | 
| 300 |                 m_lastTapTimestamp = ts; | 
| 301 |                 m_lastTapPos = point->scenePosition(); | 
| 302 |             } else { | 
| 303 |                 qCDebug(lcTapHandler) << objectName() << "tap threshold"  << longPressThreshold() << "exceeded:"  << point->timeHeld(); | 
| 304 |             } | 
| 305 |         } | 
| 306 |         emit pressedChanged(); | 
| 307 |         if (!press && m_gesturePolicy != DragThreshold) { | 
| 308 |             // on release, ungrab after emitting changed signals | 
| 309 |             setExclusiveGrab(point, grab: press); | 
| 310 |         } | 
| 311 |         if (cancel) { | 
| 312 |             emit canceled(point); | 
| 313 |             setExclusiveGrab(point, grab: false); | 
| 314 |             // In case there is a filtering parent (Flickable), we should not give up the passive grab, | 
| 315 |             // so that it can continue to filter future events. | 
| 316 |             d_func()->reset(); | 
| 317 |             emit pointChanged(); | 
| 318 |         } | 
| 319 |     } | 
| 320 | } | 
| 321 |  | 
| 322 | void QQuickTapHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabTransition transition, QQuickEventPoint *point) | 
| 323 | { | 
| 324 |     QQuickSinglePointHandler::onGrabChanged(grabber, transition, point); | 
| 325 |     bool isCanceled = transition == QQuickEventPoint::CancelGrabExclusive || transition == QQuickEventPoint::CancelGrabPassive; | 
| 326 |     if (grabber == this && (isCanceled || point->state() == QQuickEventPoint::Released)) | 
| 327 |         setPressed(press: false, cancel: isCanceled, point); | 
| 328 | } | 
| 329 |  | 
| 330 | void QQuickTapHandler::connectPreRenderSignal(bool conn) | 
| 331 | { | 
| 332 |     if (conn) | 
| 333 |         connect(sender: parentItem()->window(), signal: &QQuickWindow::beforeSynchronizing, receiver: this, slot: &QQuickTapHandler::updateTimeHeld); | 
| 334 |     else | 
| 335 |         disconnect(sender: parentItem()->window(), signal: &QQuickWindow::beforeSynchronizing, receiver: this, slot: &QQuickTapHandler::updateTimeHeld); | 
| 336 | } | 
| 337 |  | 
| 338 | void QQuickTapHandler::updateTimeHeld() | 
| 339 | { | 
| 340 |     emit timeHeldChanged(); | 
| 341 | } | 
| 342 |  | 
| 343 | /*! | 
| 344 |     \qmlproperty int QtQuick::TapHandler::tapCount | 
| 345 |     \readonly | 
| 346 |  | 
| 347 |     The number of taps which have occurred within the time and space | 
| 348 |     constraints to be considered a single gesture.  For example, to detect | 
| 349 |     a triple-tap, you can write: | 
| 350 |  | 
| 351 |     \qml | 
| 352 |     Rectangle { | 
| 353 |         width: 100; height: 30 | 
| 354 |         signal tripleTap | 
| 355 |         TapHandler { | 
| 356 |             acceptedButtons: Qt.AllButtons | 
| 357 |             onTapped: if (tapCount == 3) tripleTap() | 
| 358 |         } | 
| 359 |     } | 
| 360 |     \endqml | 
| 361 | */ | 
| 362 |  | 
| 363 | /*! | 
| 364 |     \qmlproperty real QtQuick::TapHandler::timeHeld | 
| 365 |     \readonly | 
| 366 |  | 
| 367 |     The amount of time in seconds that a pressed point has been held, without | 
| 368 |     moving beyond the drag threshold. It will be updated at least once per | 
| 369 |     frame rendered, which enables rendering an animation showing the progress | 
| 370 |     towards an action which will be triggered by a long-press. It is also | 
| 371 |     possible to trigger one of a series of actions depending on how long the | 
| 372 |     press is held. | 
| 373 |  | 
| 374 |     A value of less than zero means no point is being held within this | 
| 375 |     handler's \l [QML] Item. | 
| 376 | */ | 
| 377 |  | 
| 378 | /*! | 
| 379 |     \qmlsignal QtQuick::TapHandler::tapped(EventPoint eventPoint) | 
| 380 |  | 
| 381 |     This signal is emitted each time the \c parent Item is tapped. | 
| 382 |  | 
| 383 |     That is, if you press and release a touchpoint or button within a time | 
| 384 |     period less than \l longPressThreshold, while any movement does not exceed | 
| 385 |     the drag threshold, then the \c tapped signal will be emitted at the time | 
| 386 |     of release.  The \a eventPoint signal parameter contains information | 
| 387 |     from the release event about the point that was tapped: | 
| 388 |  | 
| 389 |     \snippet pointerHandlers/tapHandlerOnTapped.qml 0 | 
| 390 | */ | 
| 391 |  | 
| 392 | /*! | 
| 393 |     \qmlsignal QtQuick::TapHandler::singleTapped(EventPoint eventPoint) | 
| 394 |     \since 5.11 | 
| 395 |  | 
| 396 |     This signal is emitted when the \c parent Item is tapped once. | 
| 397 |     After an amount of time greater than QStyleHints::mouseDoubleClickInterval, | 
| 398 |     it can be tapped again; but if the time until the next tap is less, | 
| 399 |     \l tapCount will increase. The \a eventPoint signal parameter contains | 
| 400 |     information from the release event about the point that was tapped. | 
| 401 | */ | 
| 402 |  | 
| 403 | /*! | 
| 404 |     \qmlsignal QtQuick::TapHandler::doubleTapped(EventPoint eventPoint) | 
| 405 |     \since 5.11 | 
| 406 |  | 
| 407 |     This signal is emitted when the \c parent Item is tapped twice within a | 
| 408 |     short span of time (QStyleHints::mouseDoubleClickInterval()) and distance | 
| 409 |     (QStyleHints::mouseDoubleClickDistance() or | 
| 410 |     QStyleHints::touchDoubleTapDistance()). This signal always occurs after | 
| 411 |     \l singleTapped, \l tapped, and \l tapCountChanged. The \a eventPoint | 
| 412 |     signal parameter contains information from the release event about the | 
| 413 |     point that was tapped. | 
| 414 | */ | 
| 415 |  | 
| 416 | /*! | 
| 417 |     \qmlsignal QtQuick::TapHandler::longPressed() | 
| 418 |  | 
| 419 |     This signal is emitted when the \c parent Item is pressed and held for a | 
| 420 |     time period greater than \l longPressThreshold. That is, if you press and | 
| 421 |     hold a touchpoint or button, while any movement does not exceed the drag | 
| 422 |     threshold, then the \c longPressed signal will be emitted at the time that | 
| 423 |     \l timeHeld exceeds \l longPressThreshold. | 
| 424 | */ | 
| 425 |  | 
| 426 | /*! | 
| 427 |     \qmlsignal QtQuick::TapHandler::tapCountChanged() | 
| 428 |  | 
| 429 |     This signal is emitted when the \c parent Item is tapped once or more (within | 
| 430 |     a specified time and distance span) and when the present \c tapCount differs | 
| 431 |     from the previous \c tapCount. | 
| 432 | */ | 
| 433 | QT_END_NAMESPACE | 
| 434 |  |