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