| 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 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 "qquickmousearea_p.h" |
| 41 | #include "qquickmousearea_p_p.h" |
| 42 | #include "qquickwindow.h" |
| 43 | #if QT_CONFIG(quick_draganddrop) |
| 44 | #include "qquickdrag_p.h" |
| 45 | #endif |
| 46 | |
| 47 | #include <private/qqmldata_p.h> |
| 48 | #include <private/qsgadaptationlayer_p.h> |
| 49 | |
| 50 | #include <QtGui/private/qguiapplication_p.h> |
| 51 | #include <QtGui/qevent.h> |
| 52 | #include <QtGui/qstylehints.h> |
| 53 | |
| 54 | #include <float.h> |
| 55 | |
| 56 | QT_BEGIN_NAMESPACE |
| 57 | |
| 58 | DEFINE_BOOL_CONFIG_OPTION(qmlVisualTouchDebugging, QML_VISUAL_TOUCH_DEBUGGING) |
| 59 | |
| 60 | Q_DECLARE_LOGGING_CATEGORY(DBG_HOVER_TRACE) |
| 61 | |
| 62 | QQuickMouseAreaPrivate::QQuickMouseAreaPrivate() |
| 63 | : enabled(true), scrollGestureEnabled(true), hovered(false), longPress(false), |
| 64 | moved(false), stealMouse(false), doubleClick(false), preventStealing(false), |
| 65 | propagateComposedEvents(false), overThreshold(false), |
| 66 | pressAndHoldInterval(-1) |
| 67 | #if QT_CONFIG(quick_draganddrop) |
| 68 | , drag(nullptr) |
| 69 | #endif |
| 70 | #if QT_CONFIG(cursor) |
| 71 | , cursor(nullptr) |
| 72 | #endif |
| 73 | { |
| 74 | } |
| 75 | |
| 76 | QQuickMouseAreaPrivate::~QQuickMouseAreaPrivate() |
| 77 | { |
| 78 | #if QT_CONFIG(quick_draganddrop) |
| 79 | delete drag; |
| 80 | #endif |
| 81 | #if QT_CONFIG(cursor) |
| 82 | delete cursor; |
| 83 | #endif |
| 84 | } |
| 85 | |
| 86 | void QQuickMouseAreaPrivate::init() |
| 87 | { |
| 88 | Q_Q(QQuickMouseArea); |
| 89 | q->setAcceptedMouseButtons(Qt::LeftButton); |
| 90 | q->setAcceptTouchEvents(false); // rely on mouse events synthesized from touch |
| 91 | q->setFiltersChildMouseEvents(true); |
| 92 | if (qmlVisualTouchDebugging()) { |
| 93 | q->setFlag(flag: QQuickItem::ItemHasContents); |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | void QQuickMouseAreaPrivate::saveEvent(QMouseEvent *event) |
| 98 | { |
| 99 | lastPos = event->localPos(); |
| 100 | lastScenePos = event->windowPos(); |
| 101 | lastButton = event->button(); |
| 102 | lastButtons = event->buttons(); |
| 103 | lastModifiers = event->modifiers(); |
| 104 | lastFlags = event->flags(); |
| 105 | } |
| 106 | |
| 107 | bool QQuickMouseAreaPrivate::isPressAndHoldConnected() |
| 108 | { |
| 109 | Q_Q(QQuickMouseArea); |
| 110 | IS_SIGNAL_CONNECTED(q, QQuickMouseArea, pressAndHold, (QQuickMouseEvent *)); |
| 111 | } |
| 112 | |
| 113 | bool QQuickMouseAreaPrivate::isDoubleClickConnected() |
| 114 | { |
| 115 | Q_Q(QQuickMouseArea); |
| 116 | IS_SIGNAL_CONNECTED(q, QQuickMouseArea, doubleClicked, (QQuickMouseEvent *)); |
| 117 | } |
| 118 | |
| 119 | bool QQuickMouseAreaPrivate::isClickConnected() |
| 120 | { |
| 121 | Q_Q(QQuickMouseArea); |
| 122 | IS_SIGNAL_CONNECTED(q, QQuickMouseArea, clicked, (QQuickMouseEvent *)); |
| 123 | } |
| 124 | |
| 125 | bool QQuickMouseAreaPrivate::isWheelConnected() |
| 126 | { |
| 127 | Q_Q(QQuickMouseArea); |
| 128 | IS_SIGNAL_CONNECTED(q, QQuickMouseArea, wheel, (QQuickWheelEvent *)); |
| 129 | } |
| 130 | |
| 131 | void QQuickMouseAreaPrivate::propagate(QQuickMouseEvent* event, PropagateType t) |
| 132 | { |
| 133 | Q_Q(QQuickMouseArea); |
| 134 | if (!window || !propagateComposedEvents) |
| 135 | return; |
| 136 | QPointF scenePos = q->mapToScene(point: QPointF(event->x(), event->y())); |
| 137 | propagateHelper(event, window->contentItem(), scenePos, t); |
| 138 | } |
| 139 | |
| 140 | bool QQuickMouseAreaPrivate::propagateHelper(QQuickMouseEvent *ev, QQuickItem *item,const QPointF &sp, PropagateType sig) |
| 141 | { |
| 142 | //Based off of QQuickWindow::deliverInitialMousePressEvent |
| 143 | //But specific to MouseArea, so doesn't belong in window |
| 144 | Q_Q(const QQuickMouseArea); |
| 145 | QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); |
| 146 | |
| 147 | if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { |
| 148 | QPointF p = item->mapFromScene(point: sp); |
| 149 | if (!item->contains(point: p)) |
| 150 | return false; |
| 151 | } |
| 152 | |
| 153 | QList<QQuickItem *> children = itemPrivate->paintOrderChildItems(); |
| 154 | for (int ii = children.count() - 1; ii >= 0; --ii) { |
| 155 | QQuickItem *child = children.at(i: ii); |
| 156 | if (!child->isVisible() || !child->isEnabled()) |
| 157 | continue; |
| 158 | if (propagateHelper(ev, item: child, sp, sig)) |
| 159 | return true; |
| 160 | } |
| 161 | |
| 162 | QQuickMouseArea* ma = qobject_cast<QQuickMouseArea*>(object: item); |
| 163 | if (ma && ma != q && ma->isEnabled() && itemPrivate->acceptedMouseButtons() & ev->button()) { |
| 164 | switch (sig) { |
| 165 | case Click: |
| 166 | if (!ma->d_func()->isClickConnected()) |
| 167 | return false; |
| 168 | break; |
| 169 | case DoubleClick: |
| 170 | if (!ma->d_func()->isDoubleClickConnected()) |
| 171 | return false; |
| 172 | break; |
| 173 | case PressAndHold: |
| 174 | if (!ma->d_func()->isPressAndHoldConnected()) |
| 175 | return false; |
| 176 | break; |
| 177 | } |
| 178 | QPointF p = item->mapFromScene(point: sp); |
| 179 | if (item->contains(point: p)) { |
| 180 | ev->setX(p.x()); |
| 181 | ev->setY(p.y()); |
| 182 | ev->setAccepted(true);//It is connected, they have to explicitly ignore to let it slide |
| 183 | switch (sig) { |
| 184 | case Click: emit ma->clicked(mouse: ev); break; |
| 185 | case DoubleClick: emit ma->doubleClicked(mouse: ev); break; |
| 186 | case PressAndHold: emit ma->pressAndHold(mouse: ev); break; |
| 187 | } |
| 188 | if (ev->isAccepted()) |
| 189 | return true; |
| 190 | } |
| 191 | } |
| 192 | return false; |
| 193 | |
| 194 | } |
| 195 | |
| 196 | /*! |
| 197 | \qmltype MouseArea |
| 198 | \instantiates QQuickMouseArea |
| 199 | \inqmlmodule QtQuick |
| 200 | \ingroup qtquick-input |
| 201 | \brief Enables simple mouse handling. |
| 202 | \inherits Item |
| 203 | |
| 204 | A MouseArea is an invisible item that is typically used in conjunction with |
| 205 | a visible item in order to provide mouse handling for that item. |
| 206 | By effectively acting as a proxy, the logic for mouse handling can be |
| 207 | contained within a MouseArea item. |
| 208 | |
| 209 | The \l enabled property is used to enable and disable mouse handling for |
| 210 | the proxied item. When disabled, the mouse area becomes transparent to |
| 211 | mouse events. |
| 212 | |
| 213 | MouseArea is an invisible Item, but it has a visible property. |
| 214 | When set to false, the mouse area becomes transparent to mouse events. |
| 215 | |
| 216 | The \l pressed read-only property indicates whether or not the user is |
| 217 | holding down a mouse button over the mouse area. This property is often |
| 218 | used in bindings between properties in a user interface. The containsMouse |
| 219 | read-only property indicates the presence of the mouse cursor over the |
| 220 | mouse area but, by default, only when a mouse button is held down; see |
| 221 | the containsMouse documentation for details. |
| 222 | |
| 223 | Information about the mouse position and button clicks are provided via |
| 224 | signals for which event handler properties are defined. The most commonly |
| 225 | used involved handling mouse presses and clicks: onClicked, onDoubleClicked, |
| 226 | onPressed, onReleased and onPressAndHold. It's also possible to handle mouse |
| 227 | wheel events via the onWheel signal. |
| 228 | |
| 229 | If a MouseArea overlaps with the area of other MouseArea items, you can choose |
| 230 | to propagate \c clicked, \c doubleClicked and \c pressAndHold events to these |
| 231 | other items by setting propagateComposedEvents to true and rejecting events |
| 232 | that should be propagated. See the propagateComposedEvents documentation for |
| 233 | details. |
| 234 | |
| 235 | By default, MouseArea items only report mouse clicks and not changes to the |
| 236 | position of the mouse cursor. Setting the hoverEnabled property ensures that |
| 237 | handlers defined for onPositionChanged, onEntered and onExited are used and |
| 238 | that the containsMouse property is updated even when no mouse buttons are |
| 239 | pressed. |
| 240 | |
| 241 | \section1 Example Usage |
| 242 | |
| 243 | \div {class="float-right"} |
| 244 | \inlineimage qml-mousearea-snippet.png |
| 245 | \enddiv |
| 246 | |
| 247 | The following example uses a MouseArea in a \l Rectangle that changes |
| 248 | the \l Rectangle color to red when clicked: |
| 249 | |
| 250 | \snippet qml/mousearea/mousearea.qml import |
| 251 | \codeline |
| 252 | \snippet qml/mousearea/mousearea.qml intro |
| 253 | |
| 254 | \clearfloat |
| 255 | Many MouseArea signals pass a \l{MouseEvent}{mouse} parameter that contains |
| 256 | additional information about the mouse event, such as the position, button, |
| 257 | and any key modifiers. |
| 258 | |
| 259 | Here is an extension of the previous example that produces a different |
| 260 | color when the area is right clicked: |
| 261 | |
| 262 | \snippet qml/mousearea/mousearea.qml intro-extended |
| 263 | |
| 264 | \sa MouseEvent, {mousearea}{MouseArea example}, |
| 265 | {Important Concepts In Qt Quick - User Input} |
| 266 | */ |
| 267 | |
| 268 | /*! |
| 269 | \qmlsignal QtQuick::MouseArea::entered() |
| 270 | |
| 271 | This signal is emitted when the mouse enters the mouse area. |
| 272 | |
| 273 | By default this signal is only emitted if a button is currently |
| 274 | pressed. Set \l hoverEnabled to true to emit this signal |
| 275 | even when no mouse button is pressed. |
| 276 | |
| 277 | \sa hoverEnabled |
| 278 | */ |
| 279 | |
| 280 | /*! |
| 281 | \qmlsignal QtQuick::MouseArea::exited() |
| 282 | |
| 283 | This signal is emitted when the mouse exits the mouse area. |
| 284 | |
| 285 | By default this signal is only emitted if a button is currently |
| 286 | pressed. Set \l hoverEnabled to true to emit this signal |
| 287 | even when no mouse button is pressed. |
| 288 | |
| 289 | The example below shows a fairly typical relationship between |
| 290 | two MouseAreas, with \c mouseArea2 on top of \c mouseArea1. Moving the |
| 291 | mouse into \c mouseArea2 from \c mouseArea1 will cause \c mouseArea1 |
| 292 | to emit the \c exited signal. |
| 293 | \qml |
| 294 | Rectangle { |
| 295 | width: 400; height: 400 |
| 296 | MouseArea { |
| 297 | id: mouseArea1 |
| 298 | anchors.fill: parent |
| 299 | hoverEnabled: true |
| 300 | } |
| 301 | MouseArea { |
| 302 | id: mouseArea2 |
| 303 | width: 100; height: 100 |
| 304 | anchors.centerIn: parent |
| 305 | hoverEnabled: true |
| 306 | } |
| 307 | } |
| 308 | \endqml |
| 309 | |
| 310 | If instead you give the two MouseAreas a parent-child relationship, |
| 311 | moving the mouse into \c mouseArea2 from \c mouseArea1 will \b not |
| 312 | cause \c mouseArea1 to emit \c exited. Instead, they will |
| 313 | both be considered to be simultaneously hovered. |
| 314 | |
| 315 | \sa hoverEnabled |
| 316 | */ |
| 317 | |
| 318 | /*! |
| 319 | \qmlsignal QtQuick::MouseArea::positionChanged(MouseEvent mouse) |
| 320 | |
| 321 | This signal is emitted when the mouse position changes. |
| 322 | |
| 323 | The \l {MouseEvent}{mouse} parameter provides information about the mouse, including the x and y |
| 324 | position, and any buttons currently pressed. |
| 325 | |
| 326 | By default this signal is only emitted if a button is currently |
| 327 | pressed. Set \l hoverEnabled to true to emit this signal |
| 328 | even when no mouse button is pressed. |
| 329 | |
| 330 | When handling this signal, changing the \l {MouseEvent::}{accepted} property of the \a mouse |
| 331 | parameter has no effect. |
| 332 | */ |
| 333 | |
| 334 | /*! |
| 335 | \qmlsignal QtQuick::MouseArea::clicked(MouseEvent mouse) |
| 336 | |
| 337 | This signal is emitted when there is a click. A click is defined as a press followed by a release, |
| 338 | both inside the MouseArea (pressing, moving outside the MouseArea, and then moving back inside and |
| 339 | releasing is also considered a click). |
| 340 | |
| 341 | The \l {MouseEvent}{mouse} parameter provides information about the click, including the x and y |
| 342 | position of the release of the click, and whether the click was held. |
| 343 | |
| 344 | When handling this signal, changing the \l {MouseEvent::}{accepted} property of the \a mouse |
| 345 | parameter has no effect, unless the \l propagateComposedEvents property is \c true. |
| 346 | */ |
| 347 | |
| 348 | /*! |
| 349 | \qmlsignal QtQuick::MouseArea::pressed(MouseEvent mouse) |
| 350 | |
| 351 | This signal is emitted when there is a press. |
| 352 | The \l {MouseEvent}{mouse} parameter provides information about the press, including the x and y |
| 353 | position and which button was pressed. |
| 354 | |
| 355 | When handling this signal, use the \l {MouseEvent::}{accepted} property of the \a mouse |
| 356 | parameter to control whether this MouseArea handles the press and all future mouse events until |
| 357 | release. The default is to accept the event and not allow other MouseAreas beneath this one to |
| 358 | handle the event. If \e accepted is set to false, no further events will be sent to this MouseArea |
| 359 | until the button is next pressed. |
| 360 | */ |
| 361 | |
| 362 | /*! |
| 363 | \qmlsignal QtQuick::MouseArea::released(MouseEvent mouse) |
| 364 | |
| 365 | This signal is emitted when there is a release. |
| 366 | The \l {MouseEvent}{mouse} parameter provides information about the click, including the x and y |
| 367 | position of the release of the click, and whether the click was held. |
| 368 | |
| 369 | When handling this signal, changing the \l {MouseEvent::}{accepted} property of the \a mouse |
| 370 | parameter has no effect. |
| 371 | |
| 372 | \sa canceled |
| 373 | */ |
| 374 | |
| 375 | /*! |
| 376 | \qmlsignal QtQuick::MouseArea::pressAndHold(MouseEvent mouse) |
| 377 | |
| 378 | This signal is emitted when there is a long press (currently 800ms). |
| 379 | The \l {MouseEvent}{mouse} parameter provides information about the press, including the x and y |
| 380 | position of the press, and which button is pressed. |
| 381 | |
| 382 | When handling this signal, changing the \l {MouseEvent::}{accepted} property of the \a mouse |
| 383 | parameter has no effect, unless the \l propagateComposedEvents property is \c true. |
| 384 | */ |
| 385 | |
| 386 | /*! |
| 387 | \qmlsignal QtQuick::MouseArea::doubleClicked(MouseEvent mouse) |
| 388 | |
| 389 | This signal is emitted when there is a double-click (a press followed by a release followed by a press). |
| 390 | The \l {MouseEvent}{mouse} parameter provides information about the click, including the x and y |
| 391 | position of the release of the click, and whether the click was held. |
| 392 | |
| 393 | When handling this signal, if the \l {MouseEvent::}{accepted} property of the \a mouse |
| 394 | parameter is set to false, the pressed/released/clicked signals will be emitted for the second |
| 395 | click; otherwise they are suppressed. The \c accepted property defaults to true. |
| 396 | */ |
| 397 | |
| 398 | /*! |
| 399 | \qmlsignal QtQuick::MouseArea::canceled() |
| 400 | |
| 401 | This signal is emitted when mouse events have been canceled, because another item stole the mouse event handling. |
| 402 | |
| 403 | This signal is for advanced use: it is useful when there is more than one MouseArea |
| 404 | that is handling input, or when there is a MouseArea inside a \l Flickable. In the latter |
| 405 | case, if you execute some logic in the \c onPressed signal handler and then start dragging, the |
| 406 | \l Flickable will steal the mouse handling from the MouseArea. In these cases, to reset |
| 407 | the logic when the MouseArea has lost the mouse handling to the \l Flickable, |
| 408 | \c canceled should be handled in addition to \l released. |
| 409 | */ |
| 410 | |
| 411 | /*! |
| 412 | \qmlsignal QtQuick::MouseArea::wheel(WheelEvent wheel) |
| 413 | |
| 414 | This signal is emitted in response to both mouse wheel and trackpad scroll gestures. |
| 415 | |
| 416 | The \a wheel parameter provides information about the event, including the x and y |
| 417 | position, any buttons currently pressed, and information about the wheel movement, including |
| 418 | angleDelta and pixelDelta. |
| 419 | */ |
| 420 | |
| 421 | QQuickMouseArea::QQuickMouseArea(QQuickItem *parent) |
| 422 | : QQuickItem(*(new QQuickMouseAreaPrivate), parent) |
| 423 | { |
| 424 | Q_D(QQuickMouseArea); |
| 425 | d->init(); |
| 426 | #if QT_CONFIG(cursor) |
| 427 | // Explcitly call setCursor on QQuickItem since |
| 428 | // it internally keeps a boolean hasCursor that doesn't |
| 429 | // get set to true unless you call setCursor |
| 430 | setCursor(Qt::ArrowCursor); |
| 431 | #endif |
| 432 | } |
| 433 | |
| 434 | QQuickMouseArea::~QQuickMouseArea() |
| 435 | { |
| 436 | } |
| 437 | |
| 438 | /*! |
| 439 | \qmlproperty real QtQuick::MouseArea::mouseX |
| 440 | \qmlproperty real QtQuick::MouseArea::mouseY |
| 441 | These properties hold the coordinates of the mouse cursor. |
| 442 | |
| 443 | If the hoverEnabled property is false then these properties will only be valid |
| 444 | while a button is pressed, and will remain valid as long as the button is held |
| 445 | down even if the mouse is moved outside the area. |
| 446 | |
| 447 | By default, this property is false. |
| 448 | |
| 449 | If hoverEnabled is true then these properties will be valid when: |
| 450 | \list |
| 451 | \li no button is pressed, but the mouse is within the MouseArea (containsMouse is true). |
| 452 | \li a button is pressed and held, even if it has since moved out of the area. |
| 453 | \endlist |
| 454 | |
| 455 | The coordinates are relative to the MouseArea. |
| 456 | */ |
| 457 | qreal QQuickMouseArea::mouseX() const |
| 458 | { |
| 459 | Q_D(const QQuickMouseArea); |
| 460 | return d->lastPos.x(); |
| 461 | } |
| 462 | |
| 463 | qreal QQuickMouseArea::mouseY() const |
| 464 | { |
| 465 | Q_D(const QQuickMouseArea); |
| 466 | return d->lastPos.y(); |
| 467 | } |
| 468 | |
| 469 | /*! |
| 470 | \qmlproperty bool QtQuick::MouseArea::enabled |
| 471 | This property holds whether the item accepts mouse events. |
| 472 | |
| 473 | \note Due to historical reasons, this property is not equivalent to |
| 474 | Item.enabled. It only affects mouse events, and its effect does not |
| 475 | propagate to child items. |
| 476 | |
| 477 | By default, this property is true. |
| 478 | */ |
| 479 | bool QQuickMouseArea::isEnabled() const |
| 480 | { |
| 481 | Q_D(const QQuickMouseArea); |
| 482 | return d->enabled; |
| 483 | } |
| 484 | |
| 485 | void QQuickMouseArea::setEnabled(bool a) |
| 486 | { |
| 487 | Q_D(QQuickMouseArea); |
| 488 | if (a != d->enabled) { |
| 489 | d->enabled = a; |
| 490 | emit enabledChanged(); |
| 491 | } |
| 492 | } |
| 493 | |
| 494 | /*! |
| 495 | \qmlproperty bool QtQuick::MouseArea::scrollGestureEnabled |
| 496 | \since 5.5 |
| 497 | |
| 498 | This property controls whether this MouseArea responds to scroll gestures |
| 499 | from non-mouse devices, such as the 2-finger flick gesture on a trackpad. |
| 500 | If set to false, the \l wheel signal be emitted only when the wheel event |
| 501 | comes from an actual mouse with a wheel, while scroll gesture events will |
| 502 | pass through to any other Item that will handle them. For example, the user |
| 503 | might perform a flick gesture while the cursor is over an item containing a |
| 504 | MouseArea, intending to interact with a Flickable which is underneath. |
| 505 | Setting this property to false will allow the PinchArea to handle the mouse |
| 506 | wheel or the pinch gesture, while the Flickable handles the flick gesture. |
| 507 | |
| 508 | By default, this property is true. |
| 509 | */ |
| 510 | bool QQuickMouseArea::isScrollGestureEnabled() const |
| 511 | { |
| 512 | Q_D(const QQuickMouseArea); |
| 513 | return d->scrollGestureEnabled; |
| 514 | } |
| 515 | |
| 516 | void QQuickMouseArea::setScrollGestureEnabled(bool e) |
| 517 | { |
| 518 | Q_D(QQuickMouseArea); |
| 519 | if (e != d->scrollGestureEnabled) { |
| 520 | d->scrollGestureEnabled = e; |
| 521 | emit scrollGestureEnabledChanged(); |
| 522 | } |
| 523 | } |
| 524 | |
| 525 | /*! |
| 526 | \qmlproperty bool QtQuick::MouseArea::preventStealing |
| 527 | This property holds whether the mouse events may be stolen from this |
| 528 | MouseArea. |
| 529 | |
| 530 | If a MouseArea is placed within an item that filters child mouse |
| 531 | events, such as Flickable, the mouse |
| 532 | events may be stolen from the MouseArea if a gesture is recognized |
| 533 | by the parent item, e.g. a flick gesture. If preventStealing is |
| 534 | set to true, no item will steal the mouse events. |
| 535 | |
| 536 | Note that setting preventStealing to true once an item has started |
| 537 | stealing events will have no effect until the next press event. |
| 538 | |
| 539 | By default this property is false. |
| 540 | */ |
| 541 | bool QQuickMouseArea::preventStealing() const |
| 542 | { |
| 543 | Q_D(const QQuickMouseArea); |
| 544 | return d->preventStealing; |
| 545 | } |
| 546 | |
| 547 | void QQuickMouseArea::setPreventStealing(bool prevent) |
| 548 | { |
| 549 | Q_D(QQuickMouseArea); |
| 550 | if (prevent != d->preventStealing) { |
| 551 | d->preventStealing = prevent; |
| 552 | setKeepMouseGrab(d->preventStealing && d->enabled); |
| 553 | emit preventStealingChanged(); |
| 554 | } |
| 555 | } |
| 556 | |
| 557 | |
| 558 | /*! |
| 559 | \qmlproperty bool QtQuick::MouseArea::propagateComposedEvents |
| 560 | This property holds whether composed mouse events will automatically propagate to |
| 561 | other MouseAreas that overlap with this MouseArea but are lower in the visual stacking order. |
| 562 | By default, this property is false. |
| 563 | |
| 564 | MouseArea contains several composed events: \c clicked, \c doubleClicked and \c pressAndHold. |
| 565 | These are composed of basic mouse events, like \c pressed, and can be propagated differently |
| 566 | in comparison to basic events. |
| 567 | |
| 568 | If propagateComposedEvents is set to true, then composed events will be automatically |
| 569 | propagated to other MouseAreas in the same location in the scene. Each event is propagated |
| 570 | to the next \l enabled MouseArea beneath it in the stacking order, propagating down this visual |
| 571 | hierarchy until a MouseArea accepts the event. Unlike \c pressed events, composed events will |
| 572 | not be automatically accepted if no handler is present. |
| 573 | |
| 574 | For example, below is a yellow \l Rectangle that contains a blue \l Rectangle. The blue |
| 575 | rectangle is the top-most item in the hierarchy of the visual stacking order; it will |
| 576 | visually rendered above the yellow rectangle. Since the blue rectangle sets |
| 577 | propagateComposedEvents to true, and also sets \l MouseEvent::accepted to false for all |
| 578 | received \c clicked events, any \c clicked events it receives are propagated to the |
| 579 | MouseArea of the yellow rectangle beneath it. |
| 580 | |
| 581 | \qml |
| 582 | import QtQuick 2.0 |
| 583 | |
| 584 | Rectangle { |
| 585 | color: "yellow" |
| 586 | width: 100; height: 100 |
| 587 | |
| 588 | MouseArea { |
| 589 | anchors.fill: parent |
| 590 | onClicked: console.log("clicked yellow") |
| 591 | } |
| 592 | |
| 593 | Rectangle { |
| 594 | color: "blue" |
| 595 | width: 50; height: 50 |
| 596 | |
| 597 | MouseArea { |
| 598 | anchors.fill: parent |
| 599 | propagateComposedEvents: true |
| 600 | onClicked: { |
| 601 | console.log("clicked blue") |
| 602 | mouse.accepted = false |
| 603 | } |
| 604 | } |
| 605 | } |
| 606 | } |
| 607 | \endqml |
| 608 | |
| 609 | Clicking on the blue rectangle will cause the \c onClicked handler of its child MouseArea to |
| 610 | be invoked; the event will then be propagated to the MouseArea of the yellow rectangle, causing |
| 611 | its own \c onClicked handler to be invoked. |
| 612 | |
| 613 | This property greatly simplifies the usecase of when you want to have overlapping MouseAreas |
| 614 | handling the composed events together. For example: if you want one MouseArea to handle \c clicked |
| 615 | signals and the other to handle \c pressAndHold, or if you want one MouseArea to handle \c clicked most |
| 616 | of the time, but pass it through when certain conditions are met. |
| 617 | */ |
| 618 | bool QQuickMouseArea::propagateComposedEvents() const |
| 619 | { |
| 620 | Q_D(const QQuickMouseArea); |
| 621 | return d->propagateComposedEvents; |
| 622 | } |
| 623 | |
| 624 | void QQuickMouseArea::setPropagateComposedEvents(bool prevent) |
| 625 | { |
| 626 | Q_D(QQuickMouseArea); |
| 627 | if (prevent != d->propagateComposedEvents) { |
| 628 | d->propagateComposedEvents = prevent; |
| 629 | setKeepMouseGrab(d->propagateComposedEvents && d->enabled); |
| 630 | emit propagateComposedEventsChanged(); |
| 631 | } |
| 632 | } |
| 633 | |
| 634 | /*! |
| 635 | \qmlproperty MouseButtons QtQuick::MouseArea::pressedButtons |
| 636 | This property holds the mouse buttons currently pressed. |
| 637 | |
| 638 | It contains a bitwise combination of: |
| 639 | \list |
| 640 | \li Qt.LeftButton |
| 641 | \li Qt.RightButton |
| 642 | \li Qt.MiddleButton |
| 643 | \endlist |
| 644 | |
| 645 | The code below displays "right" when the right mouse buttons is pressed: |
| 646 | |
| 647 | \snippet qml/mousearea/mousearea.qml mousebuttons |
| 648 | |
| 649 | \note this property only handles buttons specified in \l acceptedButtons. |
| 650 | |
| 651 | \sa acceptedButtons |
| 652 | */ |
| 653 | Qt::MouseButtons QQuickMouseArea::pressedButtons() const |
| 654 | { |
| 655 | Q_D(const QQuickMouseArea); |
| 656 | return d->pressed; |
| 657 | } |
| 658 | |
| 659 | void QQuickMouseArea::mousePressEvent(QMouseEvent *event) |
| 660 | { |
| 661 | Q_D(QQuickMouseArea); |
| 662 | d->moved = false; |
| 663 | d->stealMouse = d->preventStealing; |
| 664 | d->overThreshold = false; |
| 665 | if (!d->enabled || !(event->button() & acceptedMouseButtons())) { |
| 666 | QQuickItem::mousePressEvent(event); |
| 667 | } else { |
| 668 | d->longPress = false; |
| 669 | d->saveEvent(event); |
| 670 | #if QT_CONFIG(quick_draganddrop) |
| 671 | if (d->drag) |
| 672 | d->drag->setActive(false); |
| 673 | #endif |
| 674 | setHovered(true); |
| 675 | d->startScene = event->windowPos(); |
| 676 | setKeepMouseGrab(d->stealMouse); |
| 677 | event->setAccepted(setPressed(button: event->button(), p: true, source: event->source())); |
| 678 | if (event->isAccepted()) |
| 679 | d->pressAndHoldTimer.start(msec: pressAndHoldInterval(), obj: this); |
| 680 | } |
| 681 | } |
| 682 | |
| 683 | void QQuickMouseArea::mouseMoveEvent(QMouseEvent *event) |
| 684 | { |
| 685 | Q_D(QQuickMouseArea); |
| 686 | if (!d->enabled && !d->pressed) { |
| 687 | QQuickItem::mouseMoveEvent(event); |
| 688 | return; |
| 689 | } |
| 690 | |
| 691 | // ### we should skip this if these signals aren't used |
| 692 | // ### can GV handle this for us? |
| 693 | setHovered(contains(point: event->localPos())); |
| 694 | |
| 695 | if ((event->buttons() & acceptedMouseButtons()) == 0) { |
| 696 | QQuickItem::mouseMoveEvent(event); |
| 697 | return; |
| 698 | } |
| 699 | |
| 700 | d->saveEvent(event); |
| 701 | |
| 702 | |
| 703 | #if QT_CONFIG(quick_draganddrop) |
| 704 | if (d->drag && d->drag->target()) { |
| 705 | if (!d->moved) { |
| 706 | d->targetStartPos = d->drag->target()->parentItem() |
| 707 | ? d->drag->target()->parentItem()->mapToScene(point: d->drag->target()->position()) |
| 708 | : d->drag->target()->position(); |
| 709 | } |
| 710 | |
| 711 | QPointF startLocalPos; |
| 712 | QPointF curLocalPos; |
| 713 | if (drag()->target()->parentItem()) { |
| 714 | startLocalPos = drag()->target()->parentItem()->mapFromScene(point: d->startScene); |
| 715 | curLocalPos = drag()->target()->parentItem()->mapFromScene(point: event->windowPos()); |
| 716 | } else { |
| 717 | startLocalPos = d->startScene; |
| 718 | curLocalPos = event->windowPos(); |
| 719 | } |
| 720 | |
| 721 | if (keepMouseGrab() && d->stealMouse && d->overThreshold && !d->drag->active()) |
| 722 | d->drag->setActive(true); |
| 723 | |
| 724 | QPointF startPos = d->drag->target()->parentItem() |
| 725 | ? d->drag->target()->parentItem()->mapFromScene(point: d->targetStartPos) |
| 726 | : d->targetStartPos; |
| 727 | |
| 728 | bool dragX = drag()->axis() & QQuickDrag::XAxis; |
| 729 | bool dragY = drag()->axis() & QQuickDrag::YAxis; |
| 730 | |
| 731 | QPointF dragPos = d->drag->target()->position(); |
| 732 | QPointF boundedDragPos = dragPos; |
| 733 | if (dragX) { |
| 734 | dragPos.setX(startPos.x() + curLocalPos.x() - startLocalPos.x()); |
| 735 | boundedDragPos.setX(qBound( |
| 736 | min: d->drag->xmin(), |
| 737 | val: dragPos.x(), |
| 738 | max: d->drag->xmax())); |
| 739 | } |
| 740 | if (dragY) { |
| 741 | dragPos.setY(startPos.y() + curLocalPos.y() - startLocalPos.y()); |
| 742 | boundedDragPos.setY(qBound( |
| 743 | min: d->drag->ymin(), |
| 744 | val: dragPos.y(), |
| 745 | max: d->drag->ymax())); |
| 746 | } |
| 747 | |
| 748 | QPointF targetPos = d->drag->target()->position(); |
| 749 | |
| 750 | if (d->drag->active()) { |
| 751 | d->drag->target()->setPosition(boundedDragPos); |
| 752 | d->lastPos = mapFromScene(point: d->lastScenePos); |
| 753 | } |
| 754 | |
| 755 | bool dragOverThresholdX = QQuickWindowPrivate::dragOverThreshold(d: dragPos.x() - startPos.x(), |
| 756 | axis: Qt::XAxis, event, startDragThreshold: d->drag->threshold()); |
| 757 | bool dragOverThresholdY = QQuickWindowPrivate::dragOverThreshold(d: dragPos.y() - startPos.y(), |
| 758 | axis: Qt::YAxis, event, startDragThreshold: d->drag->threshold()); |
| 759 | |
| 760 | if (!d->overThreshold && (((targetPos.x() != boundedDragPos.x()) && dragOverThresholdX) || |
| 761 | ((targetPos.y() != boundedDragPos.y()) && dragOverThresholdY))) |
| 762 | { |
| 763 | d->overThreshold = true; |
| 764 | if (d->drag->smoothed()) |
| 765 | d->startScene = event->windowPos(); |
| 766 | } |
| 767 | |
| 768 | if (!keepMouseGrab() && d->overThreshold) { |
| 769 | setKeepMouseGrab(true); |
| 770 | d->stealMouse = true; |
| 771 | } |
| 772 | |
| 773 | d->moved = true; |
| 774 | } |
| 775 | #endif |
| 776 | |
| 777 | QQuickMouseEvent &me = d->quickMouseEvent; |
| 778 | me.reset(x: d->lastPos.x(), y: d->lastPos.y(), button: d->lastButton, buttons: d->lastButtons, modifiers: d->lastModifiers, isClick: false, wasHeld: d->longPress, flags: event->flags()); |
| 779 | me.setSource(event->source()); |
| 780 | emit mouseXChanged(mouse: &me); |
| 781 | me.setPosition(d->lastPos); |
| 782 | emit mouseYChanged(mouse: &me); |
| 783 | me.setPosition(d->lastPos); |
| 784 | emit positionChanged(mouse: &me); |
| 785 | } |
| 786 | |
| 787 | void QQuickMouseArea::mouseReleaseEvent(QMouseEvent *event) |
| 788 | { |
| 789 | Q_D(QQuickMouseArea); |
| 790 | d->stealMouse = false; |
| 791 | d->overThreshold = false; |
| 792 | if (!d->enabled && !d->pressed) { |
| 793 | QQuickItem::mouseReleaseEvent(event); |
| 794 | } else { |
| 795 | d->saveEvent(event); |
| 796 | setPressed(button: event->button(), p: false, source: event->source()); |
| 797 | if (!d->pressed) { |
| 798 | // no other buttons are pressed |
| 799 | #if QT_CONFIG(quick_draganddrop) |
| 800 | if (d->drag) |
| 801 | d->drag->setActive(false); |
| 802 | #endif |
| 803 | // If we don't accept hover, we need to reset containsMouse. |
| 804 | if (!acceptHoverEvents()) |
| 805 | setHovered(false); |
| 806 | QQuickWindow *w = window(); |
| 807 | if (w && w->mouseGrabberItem() == this) |
| 808 | ungrabMouse(); |
| 809 | setKeepMouseGrab(false); |
| 810 | } |
| 811 | } |
| 812 | d->doubleClick = false; |
| 813 | } |
| 814 | |
| 815 | void QQuickMouseArea::mouseDoubleClickEvent(QMouseEvent *event) |
| 816 | { |
| 817 | Q_D(QQuickMouseArea); |
| 818 | if (d->enabled) { |
| 819 | d->saveEvent(event); |
| 820 | QQuickMouseEvent &me = d->quickMouseEvent; |
| 821 | me.reset(x: d->lastPos.x(), y: d->lastPos.y(), button: d->lastButton, buttons: d->lastButtons, modifiers: d->lastModifiers, isClick: true, |
| 822 | wasHeld: false, flags: event->flags()); |
| 823 | me.setSource(event->source()); |
| 824 | me.setAccepted(d->isDoubleClickConnected()); |
| 825 | emit this->doubleClicked(mouse: &me); |
| 826 | if (!me.isAccepted()) |
| 827 | d->propagate(event: &me, t: QQuickMouseAreaPrivate::DoubleClick); |
| 828 | d->doubleClick = d->isDoubleClickConnected() || me.isAccepted(); |
| 829 | } |
| 830 | QQuickItem::mouseDoubleClickEvent(event); |
| 831 | } |
| 832 | |
| 833 | void QQuickMouseArea::hoverEnterEvent(QHoverEvent *event) |
| 834 | { |
| 835 | Q_D(QQuickMouseArea); |
| 836 | if (!d->enabled && !d->pressed) { |
| 837 | QQuickItem::hoverEnterEvent(event); |
| 838 | } else { |
| 839 | d->lastPos = event->posF(); |
| 840 | d->lastModifiers = event->modifiers(); |
| 841 | setHovered(true); |
| 842 | QQuickMouseEvent &me = d->quickMouseEvent; |
| 843 | me.reset(x: d->lastPos.x(), y: d->lastPos.y(), button: Qt::NoButton, buttons: Qt::NoButton, modifiers: d->lastModifiers, isClick: false, wasHeld: false); |
| 844 | emit mouseXChanged(mouse: &me); |
| 845 | me.setPosition(d->lastPos); |
| 846 | emit mouseYChanged(mouse: &me); |
| 847 | me.setPosition(d->lastPos); |
| 848 | } |
| 849 | } |
| 850 | |
| 851 | void QQuickMouseArea::hoverMoveEvent(QHoverEvent *event) |
| 852 | { |
| 853 | Q_D(QQuickMouseArea); |
| 854 | if (!d->enabled && !d->pressed) { |
| 855 | QQuickItem::hoverMoveEvent(event); |
| 856 | } else if (d->lastPos != event->posF()) { |
| 857 | d->lastPos = event->posF(); |
| 858 | d->lastModifiers = event->modifiers(); |
| 859 | QQuickMouseEvent &me = d->quickMouseEvent; |
| 860 | me.reset(x: d->lastPos.x(), y: d->lastPos.y(), button: Qt::NoButton, buttons: Qt::NoButton, modifiers: d->lastModifiers, isClick: false, wasHeld: false); |
| 861 | emit mouseXChanged(mouse: &me); |
| 862 | me.setPosition(d->lastPos); |
| 863 | emit mouseYChanged(mouse: &me); |
| 864 | me.setPosition(d->lastPos); |
| 865 | emit positionChanged(mouse: &me); |
| 866 | } |
| 867 | } |
| 868 | |
| 869 | void QQuickMouseArea::hoverLeaveEvent(QHoverEvent *event) |
| 870 | { |
| 871 | Q_D(QQuickMouseArea); |
| 872 | if (!d->enabled && !d->pressed) |
| 873 | QQuickItem::hoverLeaveEvent(event); |
| 874 | else |
| 875 | setHovered(false); |
| 876 | } |
| 877 | |
| 878 | #if QT_CONFIG(wheelevent) |
| 879 | void QQuickMouseArea::wheelEvent(QWheelEvent *event) |
| 880 | { |
| 881 | Q_D(QQuickMouseArea); |
| 882 | if (!d->enabled || (!isScrollGestureEnabled() && event->source() != Qt::MouseEventNotSynthesized)) { |
| 883 | QQuickItem::wheelEvent(event); |
| 884 | return; |
| 885 | } |
| 886 | |
| 887 | QQuickWheelEvent &we = d->quickWheelEvent; |
| 888 | we.reset(x: event->position().x(), y: event->position().y(), angleDelta: event->angleDelta(), pixelDelta: event->pixelDelta(), |
| 889 | buttons: event->buttons(), modifiers: event->modifiers(), inverted: event->inverted()); |
| 890 | we.setAccepted(d->isWheelConnected()); |
| 891 | emit wheel(wheel: &we); |
| 892 | if (!we.isAccepted()) |
| 893 | QQuickItem::wheelEvent(event); |
| 894 | } |
| 895 | #endif |
| 896 | |
| 897 | void QQuickMouseArea::ungrabMouse() |
| 898 | { |
| 899 | Q_D(QQuickMouseArea); |
| 900 | if (d->pressed) { |
| 901 | // if our mouse grab has been removed (probably by Flickable), fix our |
| 902 | // state |
| 903 | d->pressed = Qt::NoButton; |
| 904 | d->stealMouse = false; |
| 905 | d->doubleClick = false; |
| 906 | d->overThreshold = false; |
| 907 | setKeepMouseGrab(false); |
| 908 | |
| 909 | #if QT_CONFIG(quick_draganddrop) |
| 910 | if (d->drag) |
| 911 | d->drag->setActive(false); |
| 912 | #endif |
| 913 | |
| 914 | emit canceled(); |
| 915 | emit pressedChanged(); |
| 916 | emit containsPressChanged(); |
| 917 | emit pressedButtonsChanged(); |
| 918 | |
| 919 | if (d->hovered && !isUnderMouse()) { |
| 920 | d->hovered = false; |
| 921 | emit hoveredChanged(); |
| 922 | } |
| 923 | } |
| 924 | } |
| 925 | |
| 926 | void QQuickMouseArea::mouseUngrabEvent() |
| 927 | { |
| 928 | ungrabMouse(); |
| 929 | } |
| 930 | |
| 931 | void QQuickMouseArea::touchUngrabEvent() |
| 932 | { |
| 933 | // allow a Pointer Handler to steal the grab from MouseArea |
| 934 | ungrabMouse(); |
| 935 | } |
| 936 | |
| 937 | bool QQuickMouseArea::sendMouseEvent(QMouseEvent *event) |
| 938 | { |
| 939 | Q_D(QQuickMouseArea); |
| 940 | QPointF localPos = mapFromScene(point: event->windowPos()); |
| 941 | |
| 942 | QQuickWindow *c = window(); |
| 943 | QQuickItem *grabber = c ? c->mouseGrabberItem() : nullptr; |
| 944 | bool stealThisEvent = d->stealMouse; |
| 945 | if ((stealThisEvent || contains(point: localPos)) && (!grabber || !grabber->keepMouseGrab())) { |
| 946 | QMouseEvent mouseEvent(event->type(), localPos, event->windowPos(), event->screenPos(), |
| 947 | event->button(), event->buttons(), event->modifiers()); |
| 948 | mouseEvent.setAccepted(false); |
| 949 | |
| 950 | switch (event->type()) { |
| 951 | case QEvent::MouseMove: |
| 952 | mouseMoveEvent(event: &mouseEvent); |
| 953 | break; |
| 954 | case QEvent::MouseButtonPress: |
| 955 | mousePressEvent(event: &mouseEvent); |
| 956 | break; |
| 957 | case QEvent::MouseButtonRelease: |
| 958 | mouseReleaseEvent(event: &mouseEvent); |
| 959 | stealThisEvent = d->stealMouse; |
| 960 | break; |
| 961 | default: |
| 962 | break; |
| 963 | } |
| 964 | grabber = c ? c->mouseGrabberItem() : nullptr; |
| 965 | if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this) |
| 966 | grabMouse(); |
| 967 | |
| 968 | return stealThisEvent; |
| 969 | } |
| 970 | if (event->type() == QEvent::MouseButtonRelease) { |
| 971 | if (d->pressed) { |
| 972 | d->pressed &= ~event->button(); |
| 973 | emit pressedButtonsChanged(); |
| 974 | if (!d->pressed) { |
| 975 | // no other buttons are pressed |
| 976 | d->stealMouse = false; |
| 977 | d->overThreshold = false; |
| 978 | if (c && c->mouseGrabberItem() == this) |
| 979 | ungrabMouse(); |
| 980 | emit canceled(); |
| 981 | emit pressedChanged(); |
| 982 | emit containsPressChanged(); |
| 983 | if (d->hovered) { |
| 984 | d->hovered = false; |
| 985 | emit hoveredChanged(); |
| 986 | } |
| 987 | } |
| 988 | } |
| 989 | } |
| 990 | return false; |
| 991 | } |
| 992 | |
| 993 | bool QQuickMouseArea::childMouseEventFilter(QQuickItem *i, QEvent *e) |
| 994 | { |
| 995 | Q_D(QQuickMouseArea); |
| 996 | if (!d->pressed && |
| 997 | (!d->enabled || !isVisible() |
| 998 | #if QT_CONFIG(quick_draganddrop) |
| 999 | || !d->drag || !d->drag->filterChildren() |
| 1000 | #endif |
| 1001 | ) |
| 1002 | ) |
| 1003 | return QQuickItem::childMouseEventFilter(i, e); |
| 1004 | switch (e->type()) { |
| 1005 | case QEvent::MouseButtonPress: |
| 1006 | case QEvent::MouseMove: |
| 1007 | case QEvent::MouseButtonRelease: |
| 1008 | return sendMouseEvent(event: static_cast<QMouseEvent *>(e)); |
| 1009 | default: |
| 1010 | break; |
| 1011 | } |
| 1012 | |
| 1013 | return QQuickItem::childMouseEventFilter(i, e); |
| 1014 | } |
| 1015 | |
| 1016 | void QQuickMouseArea::timerEvent(QTimerEvent *event) |
| 1017 | { |
| 1018 | Q_D(QQuickMouseArea); |
| 1019 | if (event->timerId() == d->pressAndHoldTimer.timerId()) { |
| 1020 | d->pressAndHoldTimer.stop(); |
| 1021 | #if QT_CONFIG(quick_draganddrop) |
| 1022 | bool dragged = d->drag && d->drag->active(); |
| 1023 | #else |
| 1024 | bool dragged = false; |
| 1025 | #endif |
| 1026 | if (d->pressed && dragged == false && d->hovered == true) { |
| 1027 | d->longPress = true; |
| 1028 | QQuickMouseEvent &me = d->quickMouseEvent; |
| 1029 | me.reset(x: d->lastPos.x(), y: d->lastPos.y(), button: d->lastButton, buttons: d->lastButtons, modifiers: d->lastModifiers, isClick: false, wasHeld: d->longPress, flags: d->lastFlags); |
| 1030 | me.setSource(Qt::MouseEventSynthesizedByQt); |
| 1031 | me.setAccepted(d->isPressAndHoldConnected()); |
| 1032 | emit pressAndHold(mouse: &me); |
| 1033 | if (!me.isAccepted()) |
| 1034 | d->propagate(event: &me, t: QQuickMouseAreaPrivate::PressAndHold); |
| 1035 | if (!me.isAccepted()) // no one handled the long press - allow click |
| 1036 | d->longPress = false; |
| 1037 | } |
| 1038 | } |
| 1039 | } |
| 1040 | |
| 1041 | void QQuickMouseArea::windowDeactivateEvent() |
| 1042 | { |
| 1043 | ungrabMouse(); |
| 1044 | QQuickItem::windowDeactivateEvent(); |
| 1045 | } |
| 1046 | |
| 1047 | void QQuickMouseArea::geometryChanged(const QRectF &newGeometry, |
| 1048 | const QRectF &oldGeometry) |
| 1049 | { |
| 1050 | Q_D(QQuickMouseArea); |
| 1051 | QQuickItem::geometryChanged(newGeometry, oldGeometry); |
| 1052 | |
| 1053 | if (d->lastScenePos.isNull) |
| 1054 | d->lastScenePos = mapToScene(point: d->lastPos); |
| 1055 | else if (newGeometry.x() != oldGeometry.x() || newGeometry.y() != oldGeometry.y()) |
| 1056 | d->lastPos = mapFromScene(point: d->lastScenePos); |
| 1057 | } |
| 1058 | |
| 1059 | void QQuickMouseArea::itemChange(ItemChange change, const ItemChangeData &value) |
| 1060 | { |
| 1061 | Q_D(QQuickMouseArea); |
| 1062 | switch (change) { |
| 1063 | case ItemVisibleHasChanged: |
| 1064 | if (d->effectiveEnable && d->enabled && acceptHoverEvents() && d->hovered != (isVisible() && isUnderMouse())) { |
| 1065 | if (!d->hovered) { |
| 1066 | QPointF cursorPos = QGuiApplicationPrivate::lastCursorPosition; |
| 1067 | d->lastScenePos = d->window->mapFromGlobal(pos: cursorPos.toPoint()); |
| 1068 | d->lastPos = mapFromScene(point: d->lastScenePos); |
| 1069 | } |
| 1070 | setHovered(!d->hovered); |
| 1071 | } |
| 1072 | if (d->pressed && (!isVisible())) { |
| 1073 | // This happens when the mouse area sets itself disabled or hidden |
| 1074 | // inside the press handler. In that case we should not keep the internal |
| 1075 | // state as pressed, since we never became the mouse grabber. |
| 1076 | ungrabMouse(); |
| 1077 | } |
| 1078 | break; |
| 1079 | default: |
| 1080 | break; |
| 1081 | } |
| 1082 | |
| 1083 | QQuickItem::itemChange(change, value); |
| 1084 | } |
| 1085 | |
| 1086 | /*! |
| 1087 | \qmlproperty bool QtQuick::MouseArea::hoverEnabled |
| 1088 | This property holds whether hover events are handled. |
| 1089 | |
| 1090 | By default, mouse events are only handled in response to a button event, or when a button is |
| 1091 | pressed. Hover enables handling of all mouse events even when no mouse button is |
| 1092 | pressed. |
| 1093 | |
| 1094 | This property affects the containsMouse property and the onEntered, onExited and |
| 1095 | onPositionChanged signals. |
| 1096 | */ |
| 1097 | bool QQuickMouseArea::hoverEnabled() const |
| 1098 | { |
| 1099 | return acceptHoverEvents(); |
| 1100 | } |
| 1101 | |
| 1102 | void QQuickMouseArea::setHoverEnabled(bool h) |
| 1103 | { |
| 1104 | if (h == acceptHoverEvents()) |
| 1105 | return; |
| 1106 | |
| 1107 | setAcceptHoverEvents(h); |
| 1108 | emit hoverEnabledChanged(); |
| 1109 | } |
| 1110 | |
| 1111 | |
| 1112 | /*! |
| 1113 | \qmlproperty bool QtQuick::MouseArea::containsMouse |
| 1114 | This property holds whether the mouse is currently inside the mouse area. |
| 1115 | |
| 1116 | \warning If hoverEnabled is false, containsMouse will only be valid |
| 1117 | when the mouse is pressed while the mouse cursor is inside the MouseArea. |
| 1118 | */ |
| 1119 | bool QQuickMouseArea::hovered() const |
| 1120 | { |
| 1121 | Q_D(const QQuickMouseArea); |
| 1122 | return d->hovered; |
| 1123 | } |
| 1124 | |
| 1125 | /*! |
| 1126 | \qmlproperty bool QtQuick::MouseArea::pressed |
| 1127 | This property holds whether any of the \l acceptedButtons are currently pressed. |
| 1128 | */ |
| 1129 | bool QQuickMouseArea::pressed() const |
| 1130 | { |
| 1131 | Q_D(const QQuickMouseArea); |
| 1132 | return d->pressed; |
| 1133 | } |
| 1134 | |
| 1135 | /*! |
| 1136 | \qmlproperty bool QtQuick::MouseArea::containsPress |
| 1137 | \since 5.4 |
| 1138 | This is a convenience property equivalent to \c {pressed && containsMouse}, |
| 1139 | i.e. it holds whether any of the \l acceptedButtons are currently pressed |
| 1140 | and the mouse is currently within the MouseArea. |
| 1141 | |
| 1142 | This property is particularly useful for highlighting an item while the mouse |
| 1143 | is pressed within its bounds. |
| 1144 | |
| 1145 | \sa pressed, containsMouse |
| 1146 | */ |
| 1147 | bool QQuickMouseArea::containsPress() const |
| 1148 | { |
| 1149 | Q_D(const QQuickMouseArea); |
| 1150 | return d->pressed && d->hovered; |
| 1151 | } |
| 1152 | |
| 1153 | void QQuickMouseArea::setHovered(bool h) |
| 1154 | { |
| 1155 | Q_D(QQuickMouseArea); |
| 1156 | if (d->hovered != h) { |
| 1157 | qCDebug(DBG_HOVER_TRACE) << this << d->hovered << "->" << h; |
| 1158 | d->hovered = h; |
| 1159 | emit hoveredChanged(); |
| 1160 | d->hovered ? emit entered() : emit exited(); |
| 1161 | if (d->pressed) |
| 1162 | emit containsPressChanged(); |
| 1163 | } |
| 1164 | } |
| 1165 | |
| 1166 | /*! |
| 1167 | \qmlproperty Qt::MouseButtons QtQuick::MouseArea::acceptedButtons |
| 1168 | This property holds the mouse buttons that the mouse area reacts to. |
| 1169 | |
| 1170 | To specify that the MouseArea will react to multiple buttons, |
| 1171 | Qt::MouseButtons flag values are combined using the "|" (or) operator: |
| 1172 | |
| 1173 | \code |
| 1174 | MouseArea { acceptedButtons: Qt.LeftButton | Qt.RightButton } |
| 1175 | \endcode |
| 1176 | |
| 1177 | To indicate that all possible mouse buttons are to be accepted, |
| 1178 | the special value 'Qt.AllButtons' may be used: |
| 1179 | |
| 1180 | \code |
| 1181 | MouseArea { acceptedButtons: Qt.AllButtons } |
| 1182 | \endcode |
| 1183 | |
| 1184 | The default value is \c Qt.LeftButton. |
| 1185 | */ |
| 1186 | Qt::MouseButtons QQuickMouseArea::acceptedButtons() const |
| 1187 | { |
| 1188 | return acceptedMouseButtons(); |
| 1189 | } |
| 1190 | |
| 1191 | void QQuickMouseArea::setAcceptedButtons(Qt::MouseButtons buttons) |
| 1192 | { |
| 1193 | if (buttons != acceptedMouseButtons()) { |
| 1194 | setAcceptedMouseButtons(buttons); |
| 1195 | emit acceptedButtonsChanged(); |
| 1196 | } |
| 1197 | } |
| 1198 | |
| 1199 | bool QQuickMouseArea::setPressed(Qt::MouseButton button, bool p, Qt::MouseEventSource source) |
| 1200 | { |
| 1201 | Q_D(QQuickMouseArea); |
| 1202 | |
| 1203 | #if QT_CONFIG(quick_draganddrop) |
| 1204 | bool dragged = d->drag && d->drag->active(); |
| 1205 | #else |
| 1206 | bool dragged = false; |
| 1207 | #endif |
| 1208 | bool wasPressed = d->pressed & button; |
| 1209 | bool isclick = wasPressed && p == false && dragged == false && d->hovered == true; |
| 1210 | Qt::MouseButtons oldPressed = d->pressed; |
| 1211 | |
| 1212 | if (wasPressed != p) { |
| 1213 | QQuickMouseEvent &me = d->quickMouseEvent; |
| 1214 | me.reset(x: d->lastPos.x(), y: d->lastPos.y(), button: d->lastButton, buttons: d->lastButtons, modifiers: d->lastModifiers, isClick: isclick, wasHeld: d->longPress, flags: d->lastFlags); |
| 1215 | me.setSource(source); |
| 1216 | if (p) { |
| 1217 | d->pressed |= button; |
| 1218 | if (!d->doubleClick) |
| 1219 | emit pressed(mouse: &me); |
| 1220 | me.setPosition(d->lastPos); |
| 1221 | emit mouseXChanged(mouse: &me); |
| 1222 | me.setPosition(d->lastPos); |
| 1223 | emit mouseYChanged(mouse: &me); |
| 1224 | |
| 1225 | if (!me.isAccepted()) { |
| 1226 | d->pressed = Qt::NoButton; |
| 1227 | } |
| 1228 | |
| 1229 | if (!oldPressed) { |
| 1230 | emit pressedChanged(); |
| 1231 | emit containsPressChanged(); |
| 1232 | } |
| 1233 | emit pressedButtonsChanged(); |
| 1234 | } else { |
| 1235 | d->pressed &= ~button; |
| 1236 | emit released(mouse: &me); |
| 1237 | me.setPosition(d->lastPos); |
| 1238 | if (!d->pressed) { |
| 1239 | emit pressedChanged(); |
| 1240 | emit containsPressChanged(); |
| 1241 | } |
| 1242 | emit pressedButtonsChanged(); |
| 1243 | if (isclick && !d->longPress && !d->doubleClick){ |
| 1244 | me.setAccepted(d->isClickConnected()); |
| 1245 | emit clicked(mouse: &me); |
| 1246 | if (!me.isAccepted()) |
| 1247 | d->propagate(event: &me, t: QQuickMouseAreaPrivate::Click); |
| 1248 | } |
| 1249 | } |
| 1250 | |
| 1251 | return me.isAccepted(); |
| 1252 | } |
| 1253 | return false; |
| 1254 | } |
| 1255 | |
| 1256 | |
| 1257 | /*! |
| 1258 | \qmlproperty Qt::CursorShape QtQuick::MouseArea::cursorShape |
| 1259 | This property holds the cursor shape for this mouse area. |
| 1260 | Note that on platforms that do not display a mouse cursor this may have |
| 1261 | no effect. |
| 1262 | |
| 1263 | The available cursor shapes are: |
| 1264 | \list |
| 1265 | \li Qt.ArrowCursor |
| 1266 | \li Qt.UpArrowCursor |
| 1267 | \li Qt.CrossCursor |
| 1268 | \li Qt.WaitCursor |
| 1269 | \li Qt.IBeamCursor |
| 1270 | \li Qt.SizeVerCursor |
| 1271 | \li Qt.SizeHorCursor |
| 1272 | \li Qt.SizeBDiagCursor |
| 1273 | \li Qt.SizeFDiagCursor |
| 1274 | \li Qt.SizeAllCursor |
| 1275 | \li Qt.BlankCursor |
| 1276 | \li Qt.SplitVCursor |
| 1277 | \li Qt.SplitHCursor |
| 1278 | \li Qt.PointingHandCursor |
| 1279 | \li Qt.ForbiddenCursor |
| 1280 | \li Qt.WhatsThisCursor |
| 1281 | \li Qt.BusyCursor |
| 1282 | \li Qt.OpenHandCursor |
| 1283 | \li Qt.ClosedHandCursor |
| 1284 | \li Qt.DragCopyCursor |
| 1285 | \li Qt.DragMoveCursor |
| 1286 | \li Qt.DragLinkCursor |
| 1287 | \endlist |
| 1288 | |
| 1289 | In order to only set a mouse cursor shape for a region without reacting |
| 1290 | to mouse events set the acceptedButtons to none: |
| 1291 | |
| 1292 | \code |
| 1293 | MouseArea { cursorShape: Qt.IBeamCursor; acceptedButtons: Qt.NoButton } |
| 1294 | \endcode |
| 1295 | |
| 1296 | The default value is \c Qt.ArrowCursor. |
| 1297 | \sa Qt::CursorShape |
| 1298 | */ |
| 1299 | |
| 1300 | #if QT_CONFIG(cursor) |
| 1301 | Qt::CursorShape QQuickMouseArea::cursorShape() const |
| 1302 | { |
| 1303 | return cursor().shape(); |
| 1304 | } |
| 1305 | |
| 1306 | void QQuickMouseArea::setCursorShape(Qt::CursorShape shape) |
| 1307 | { |
| 1308 | if (cursor().shape() == shape) |
| 1309 | return; |
| 1310 | |
| 1311 | setCursor(shape); |
| 1312 | |
| 1313 | emit cursorShapeChanged(); |
| 1314 | } |
| 1315 | |
| 1316 | #endif |
| 1317 | |
| 1318 | |
| 1319 | /*! |
| 1320 | \qmlproperty int QtQuick::MouseArea::pressAndHoldInterval |
| 1321 | \since 5.9 |
| 1322 | |
| 1323 | This property overrides the elapsed time in milliseconds before |
| 1324 | \c pressAndHold is emitted. |
| 1325 | |
| 1326 | If not explicitly set -- or after reset -- the value follows |
| 1327 | \c QStyleHints::mousePressAndHoldInterval. |
| 1328 | |
| 1329 | Typically it's sufficient to set this property globally using the |
| 1330 | application style hint. This property should be used when varying intervals |
| 1331 | are needed for certain MouseAreas. |
| 1332 | |
| 1333 | \sa pressAndHold |
| 1334 | */ |
| 1335 | int QQuickMouseArea::pressAndHoldInterval() const |
| 1336 | { |
| 1337 | Q_D(const QQuickMouseArea); |
| 1338 | return d->pressAndHoldInterval > -1 ? |
| 1339 | d->pressAndHoldInterval : QGuiApplication::styleHints()->mousePressAndHoldInterval(); |
| 1340 | } |
| 1341 | |
| 1342 | void QQuickMouseArea::setPressAndHoldInterval(int interval) |
| 1343 | { |
| 1344 | Q_D(QQuickMouseArea); |
| 1345 | if (interval != d->pressAndHoldInterval) { |
| 1346 | d->pressAndHoldInterval = interval; |
| 1347 | emit pressAndHoldIntervalChanged(); |
| 1348 | } |
| 1349 | } |
| 1350 | |
| 1351 | void QQuickMouseArea::resetPressAndHoldInterval() |
| 1352 | { |
| 1353 | Q_D(QQuickMouseArea); |
| 1354 | if (d->pressAndHoldInterval > -1) { |
| 1355 | d->pressAndHoldInterval = -1; |
| 1356 | emit pressAndHoldIntervalChanged(); |
| 1357 | } |
| 1358 | } |
| 1359 | |
| 1360 | /*! |
| 1361 | \qmlpropertygroup QtQuick::MouseArea::drag |
| 1362 | \qmlproperty Item QtQuick::MouseArea::drag.target |
| 1363 | \qmlproperty bool QtQuick::MouseArea::drag.active |
| 1364 | \qmlproperty enumeration QtQuick::MouseArea::drag.axis |
| 1365 | \qmlproperty real QtQuick::MouseArea::drag.minimumX |
| 1366 | \qmlproperty real QtQuick::MouseArea::drag.maximumX |
| 1367 | \qmlproperty real QtQuick::MouseArea::drag.minimumY |
| 1368 | \qmlproperty real QtQuick::MouseArea::drag.maximumY |
| 1369 | \qmlproperty bool QtQuick::MouseArea::drag.filterChildren |
| 1370 | \qmlproperty real QtQuick::MouseArea::drag.threshold |
| 1371 | \qmlproperty bool QtQuick::MouseArea::drag.smoothed |
| 1372 | |
| 1373 | \c drag provides a convenient way to make an item draggable. |
| 1374 | |
| 1375 | \list |
| 1376 | \li \c drag.target specifies the id of the item to drag. |
| 1377 | \li \c drag.active specifies if the target item is currently being dragged. |
| 1378 | \li \c drag.axis specifies whether dragging can be done horizontally (\c Drag.XAxis), vertically (\c Drag.YAxis), or both (\c Drag.XAndYAxis) |
| 1379 | \li \c drag.minimum and \c drag.maximum limit how far the target can be dragged along the corresponding axes. |
| 1380 | \endlist |
| 1381 | |
| 1382 | The following example displays a \l Rectangle that can be dragged along the X-axis. The opacity |
| 1383 | of the rectangle is reduced when it is dragged to the right. |
| 1384 | |
| 1385 | \snippet qml/mousearea/mousearea.qml drag |
| 1386 | |
| 1387 | \note Items cannot be dragged if they are anchored for the requested |
| 1388 | \c drag.axis. For example, if \c anchors.left or \c anchors.right was set |
| 1389 | for \c rect in the above example, it cannot be dragged along the X-axis. |
| 1390 | This can be avoided by settng the anchor value to \c undefined in |
| 1391 | an \l {pressed}{onPressed} handler. |
| 1392 | |
| 1393 | If \c drag.filterChildren is set to true, a drag can override descendant MouseAreas. This |
| 1394 | enables a parent MouseArea to handle drags, for example, while descendants handle clicks: |
| 1395 | |
| 1396 | \c drag.threshold determines the threshold in pixels of when the drag operation should |
| 1397 | start. By default this is bound to a platform dependent value. This property was added in |
| 1398 | Qt Quick 2.2. |
| 1399 | |
| 1400 | If \c drag.smoothed is \c true, the target will be moved only after the drag operation has |
| 1401 | started. If set to \c false, the target will be moved straight to the current mouse position. |
| 1402 | By default, this property is \c true. This property was added in Qt Quick 2.4 |
| 1403 | |
| 1404 | See the \l Drag attached property and \l DropArea if you want to make a drop. |
| 1405 | |
| 1406 | \snippet qml/mousearea/mouseareadragfilter.qml dragfilter |
| 1407 | |
| 1408 | */ |
| 1409 | |
| 1410 | #if QT_CONFIG(quick_draganddrop) |
| 1411 | QQuickDrag *QQuickMouseArea::drag() |
| 1412 | { |
| 1413 | Q_D(QQuickMouseArea); |
| 1414 | if (!d->drag) |
| 1415 | d->drag = new QQuickDrag; |
| 1416 | return d->drag; |
| 1417 | } |
| 1418 | #endif |
| 1419 | |
| 1420 | QSGNode *QQuickMouseArea::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) |
| 1421 | { |
| 1422 | Q_UNUSED(data); |
| 1423 | Q_D(QQuickMouseArea); |
| 1424 | |
| 1425 | if (!qmlVisualTouchDebugging()) |
| 1426 | return nullptr; |
| 1427 | |
| 1428 | QSGInternalRectangleNode *rectangle = static_cast<QSGInternalRectangleNode *>(oldNode); |
| 1429 | if (!rectangle) rectangle = d->sceneGraphContext()->createInternalRectangleNode(); |
| 1430 | |
| 1431 | rectangle->setRect(QRectF(0, 0, width(), height())); |
| 1432 | rectangle->setColor(QColor(255, 0, 0, 50)); |
| 1433 | rectangle->update(); |
| 1434 | return rectangle; |
| 1435 | } |
| 1436 | |
| 1437 | QT_END_NAMESPACE |
| 1438 | |
| 1439 | #include "moc_qquickmousearea_p.cpp" |
| 1440 | |