Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickoverlay.cpp
Go to the documentation of this file.
1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
10#include "qquickdrawer_p.h"
13#include <QtGui/qpainterpath.h>
14#include <QtQml/qqmlinfo.h>
15#include <QtQml/qqmlproperty.h>
16#include <QtQml/qqmlcomponent.h>
17#if QT_CONFIG(quick_shadereffect)
18#include <QtQuick/private/qquickshadereffectsource_p.h>
19#endif
20#include <algorithm>
21
22QT_BEGIN_NAMESPACE
23
24/*!
25 \qmltype Overlay
26 \inherits Item
27//! \nativetype QQuickOverlay
28 \inqmlmodule QtQuick.Controls
29 \since 5.10
30 \brief A window overlay for popups.
31
32 Overlay provides a layer for popups, ensuring that popups are displayed above
33 other content and that the background is dimmed when a \l {Popup::}{modal} or
34 \l {Popup::dim}{dimmed} popup is visible.
35
36 The overlay is an ordinary Item that covers the entire window. It can be used
37 as a visual parent to position a popup in scene coordinates.
38
39 \include qquickoverlay-popup-parent.qdocinc
40
41 \sa ApplicationWindow
42*/
43
44QList<QQuickPopup *> QQuickOverlayPrivate::stackingOrderPopups() const
45{
46 const QList<QQuickItem *> children = paintOrderChildItems();
47
48 QList<QQuickPopup *> popups;
49 popups.reserve(children.size());
50
51 for (auto it = children.crbegin(), end = children.crend(); it != end; ++it) {
52 QQuickPopup *popup = qobject_cast<QQuickPopup *>((*it)->parent());
53 if (popup)
54 popups += popup;
55 }
56
57 return popups;
58}
59
60QList<QQuickPopup *> QQuickOverlayPrivate::stackingOrderDrawers() const
61{
62 QList<QQuickPopup *> sorted(allDrawers);
63 std::sort(sorted.begin(), sorted.end(), [](const QQuickPopup *one, const QQuickPopup *another) {
64 return one->z() > another->z();
65 });
66 return sorted;
67}
68
69void QQuickOverlayPrivate::itemGeometryChanged(QQuickItem *, QQuickGeometryChange, const QRectF &)
70{
71 updateGeometry();
72}
73
74void QQuickOverlayPrivate::itemRotationChanged(QQuickItem *)
75{
76 updateGeometry();
77}
78
79// Wheel and tablet events are not subject to modal popup blocking in delivery agent,
80// so modal popup blocking must be handled explicitly here.
81// Returns true if event is consumed by a modal popup blocking its top-most target.
82#if QT_CONFIG(tabletevent) || QT_CONFIG(wheelevent)
83bool QQuickOverlayPrivate::eatEventIfBlockedByModal(QPointerEvent *event)
84{
85 Q_Q(QQuickOverlay);
86 const QList<QQuickItem *> targetItems = deliveryAgentPrivate()->pointerTargets(
87 window->contentItem(), event, event->point(0), false, false);
88 if (targetItems.isEmpty())
89 return false;
90
91 QQuickItem *const topItem = targetItems.first();
92 QQuickItem *const dimmerItem = q->property("_q_dimmerItem").value<QQuickItem *>();
93 // Find the QQuickPopupItem that contains topItem, if any.
94 QQuickItem *item = qobject_cast<QQuickPopupItem *>(topItem) ? topItem : nullptr;
95
96 if (!item) {
97 item = topItem;
98 while ((item = item->parentItem())) {
99 if (qobject_cast<QQuickPopupItem *>(item))
100 break;
101 }
102 }
103
104#if QT_CONFIG(quick_shadereffect)
105 // topItem may be a QQuickShaderEffectSource created for a popup's drop shadow layer.
106 // It is a direct child of the overlay (not of the popup item), so the parent walk above
107 // misses it. Resolve it to the popup item it renders so the loop below handles it correctly.
108 if (!item && dimmerItem != topItem && q->isAncestorOf(topItem)) {
109 if (auto *shaderEffect = qobject_cast<QQuickShaderEffectSource *>(topItem)) {
110 if (auto *pi = qobject_cast<QQuickPopupItem *>(shaderEffect->sourceItem()))
111 item = pi;
112 }
113 if (!item)
114 return false;
115 }
116#endif
117
118 // Iterate popups front-to-back. Block the event if topItem is not inside a popup
119 // that sits above the first modal popup in stacking order.
120 for (const auto &popup : stackingOrderPopups()) {
121 const QQuickItem *popupItem = popup->popupItem();
122 if (!popupItem)
123 continue;
124 if (popupItem == item) {
125 if (topItem != item && popup->isModal()
126 && !item->contains(item->mapFromScene(event->point(0).scenePosition()))) {
127 // topItem is the popup's shader effect source (drop shadow), not the popup item
128 // itself — the event landed in the shadow area outside the popup's bounds.
129 // Eat it for modal popups to prevent it reaching items behind the shadow.
130 event->accept();
131 return true;
132 }
133 // The event landed inside the popup. Tablet events must be filtered here
134 // because, unlike wheel and mouse events, no standard item
135 // accepts tablet events — so the delivery agent would otherwise continue
136 // past the popup to a TapHandler behind it.
137 // Intentionally not calling event->accept() so Qt synthesizes a mouse event,
138 // which the overlay's mouse press handler delivers to popup content normally.
139#if QT_CONFIG(tabletevent)
140 if (QQuickDeliveryAgentPrivate::isTabletEvent(event))
141 return true;
142#endif
143 break;
144 }
145 // topItem is outside this popup — let the popup decide whether to block.
146 if (popup->overlayEvent(topItem, event))
147 return true;
148 }
149 return false;
150}
151#endif
152
153bool QQuickOverlayPrivate::startDrag(QEvent *event, const QPointF &pos)
154{
155 Q_Q(QQuickOverlay);
156 if (allDrawers.isEmpty())
157 return false;
158
159 // don't start dragging a drawer if a modal popup overlay is blocking (QTBUG-60602)
160 QQuickItem *item = q->childAt(pos.x(), pos.y());
161 if (item) {
162 const auto popups = stackingOrderPopups();
163 for (QQuickPopup *popup : popups) {
164 QQuickPopupPrivate *p = QQuickPopupPrivate::get(popup);
165 if (p->dimmer == item && popup->isVisible() && popup->isModal())
166 return false;
167 }
168 }
169
170 const QList<QQuickPopup *> drawers = stackingOrderDrawers();
171 for (QQuickPopup *popup : drawers) {
172 QQuickDrawer *drawer = qobject_cast<QQuickDrawer *>(popup);
173 Q_ASSERT(drawer);
174 QQuickDrawerPrivate *p = QQuickDrawerPrivate::get(drawer);
175 if (p->startDrag(event)) {
176 setMouseGrabberPopup(drawer);
177 return true;
178 }
179 }
180
181 return false;
182}
183
184// A popup that isn't eligible to auto-close on an outside press/release at all
185// (e.g. ClosePolicy::NoAutoClose, or only CloseOnEscape) never participates in
186// the close cascade to begin with, and must not be treated as if it stopped a
187// cascade that never included it in the first place. Otherwise a non-modal,
188// non-closing popup sitting above a modal one (e.g. a tooltip) would wrongly
189// prevent the modal popup below from being asked to block the event.
190static bool canCascadeCloseOnOutsidePress(const QQuickPopup *popup)
191{
192 static const QQuickPopup::ClosePolicy outsideFlags = QQuickPopup::CloseOnPressOutside
193 | QQuickPopup::CloseOnReleaseOutside
194 | QQuickPopup::CloseOnPressOutsideParent
195 | QQuickPopup::CloseOnReleaseOutsideParent;
196 return popup->closePolicy().testAnyFlags(outsideFlags);
197}
198
199// A modal popup's overlayEvent()/blockInput() reports an outside press/release as
200// "handled" simply because its dimmer blocks input from reaching whatever is behind
201// it - regardless of whether the popup also closed itself as a result of the very
202// same event. Without this check, the cascade would stop at a modal popup that just
203// closed itself, instead of continuing on to the next popup below when CloseMultiple
204// is set, since a closed popup can no longer visually block anything.
205// Note: isVisible() doesn't flip to false until the exit transition finishes, so we
206// check isOpened() instead, which (via its transitionState check) turns false the
207// instant the exit transition starts.
208static bool closedItselfViaCloseMultiple(const QQuickPopup *popup, bool wasOpened)
209{
210 return wasOpened && !popup->isOpened() && canCascadeCloseOnOutsidePress(popup)
211 && popup->closePolicy().testFlag(QQuickPopup::CloseMultiple);
212}
213
214bool QQuickOverlayPrivate::handlePress(QQuickItem *source, QEvent *event, QQuickPopup *target)
215{
216 if (target) {
217 const bool wasOpened = target->isOpened();
218 if (target->overlayEvent(source, event)) {
219 if (closedItselfViaCloseMultiple(target, wasOpened))
220 return false;
221 setMouseGrabberPopup(target);
222 return true;
223 }
224 return false;
225 }
226
227 switch (event->type()) {
228 default: {
229 if (mouseGrabberPopup)
230 break;
231#if QT_CONFIG(quicktemplates2_multitouch)
232 Q_FALLTHROUGH();
233 case QEvent::TouchBegin:
234 case QEvent::TouchUpdate:
235 case QEvent::TouchEnd:
236#endif
237 // allow non-modal popups to close themselves,
238 // and non-dimming modal popups to block the event
239 if (closeCascadeStopped)
240 break;
241 const auto popups = stackingOrderPopups();
242 bool passedWithCloseMultiple = false;
243 for (QQuickPopup *popup : popups) {
244 if (popup->overlayEvent(source, event)) {
245 // Don't grab a deeper popup as the mouse grabber when we've
246 // already iterated past a higher popup via CloseMultiple. The
247 // release loop's CloseMultiple logic will handle closing the
248 // higher popup correctly without a grabbed target.
249 if (!passedWithCloseMultiple)
250 setMouseGrabberPopup(popup);
251 return true;
252 }
253 if (!canCascadeCloseOnOutsidePress(popup))
254 continue;
255 if (!popup->closePolicy().testFlag(QQuickPopup::CloseMultiple)) {
256 closeCascadeStopped = true;
257 break;
258 }
259 passedWithCloseMultiple = true;
260 }
261 break;
262 }
263 }
264
265 event->ignore();
266 return false;
267}
268
269bool QQuickOverlayPrivate::handleMove(QQuickItem *source, QEvent *event, QQuickPopup *target)
270{
271 if (target)
272 return target->overlayEvent(source, event);
273 return false;
274}
275
276bool QQuickOverlayPrivate::handleRelease(QQuickItem *source, QEvent *event, QQuickPopup *target)
277{
278 if (target) {
279 const bool wasOpened = target->isOpened();
280 setMouseGrabberPopup(nullptr);
281 if (target->overlayEvent(source, event)) {
282 setMouseGrabberPopup(nullptr);
283 if (!closedItselfViaCloseMultiple(target, wasOpened))
284 return true;
285 }
286 } else if (!closeCascadeStopped) {
287 const auto popups = stackingOrderPopups();
288 for (QQuickPopup *popup : popups) {
289 if (popup->overlayEvent(source, event))
290 return true;
291 if (!canCascadeCloseOnOutsidePress(popup))
292 continue;
293 if (!popup->closePolicy().testFlag(QQuickPopup::CloseMultiple)) {
294 closeCascadeStopped = true;
295 break;
296 }
297 }
298 }
299 return false;
300}
301
302bool QQuickOverlayPrivate::handleMouseEvent(QQuickItem *source, QMouseEvent *event, QQuickPopup *target)
303{
304 switch (event->type()) {
305 case QEvent::MouseButtonPress:
306 if (!target && startDrag(event, event->scenePosition()))
307 return true;
308 return handlePress(source, event, target);
309 case QEvent::MouseMove:
310 return handleMove(source, event, target ? target : mouseGrabberPopup.data());
311 case QEvent::MouseButtonRelease:
312 return handleRelease(source, event, target ? target : mouseGrabberPopup.data());
313 default:
314 break;
315 }
316 return false;
317}
318
319bool QQuickOverlayPrivate::handleHoverEvent(QQuickItem *source, QHoverEvent *event, QQuickPopup *target)
320{
321 switch (event->type()) {
322 case QEvent::HoverEnter:
323 case QEvent::HoverMove:
324 case QEvent::HoverLeave:
325 if (target)
326 return target->overlayEvent(source, event);
327 return false;
328 default:
329 Q_UNREACHABLE(); // function must only be called on hover events
330 break;
331 }
332 return false;
333}
334
335#if QT_CONFIG(quicktemplates2_multitouch)
336bool QQuickOverlayPrivate::handleTouchEvent(QQuickItem *source, QTouchEvent *event, QQuickPopup *target)
337{
338 bool handled = false;
339 switch (event->type()) {
340 case QEvent::TouchBegin:
341 case QEvent::TouchUpdate:
342 case QEvent::TouchEnd:
343 for (const QTouchEvent::TouchPoint &point : event->points()) {
344 switch (point.state()) {
345 case QEventPoint::Pressed:
346 if (!target && startDrag(event, point.scenePosition()))
347 handled = true;
348 else
349 handled |= handlePress(source, event, target);
350 break;
351 case QEventPoint::Updated:
352 handled |= handleMove(source, event, target ? target : mouseGrabberPopup.data());
353 break;
354 case QEventPoint::Released:
355 handled |= handleRelease(source, event, target ? target : mouseGrabberPopup.data());
356 break;
357 default:
358 break;
359 }
360 }
361 break;
362
363 default:
364 break;
365 }
366
367 return handled;
368}
369#endif
370
371void QQuickOverlayPrivate::addPopup(QQuickPopup *popup)
372{
373 Q_Q(QQuickOverlay);
374 allPopups += popup;
375 if (QQuickDrawer *drawer = qobject_cast<QQuickDrawer *>(popup)) {
376 allDrawers += drawer;
377 q->setVisible(!allDrawers.isEmpty() || !q->childItems().isEmpty());
378 }
379}
380
381void QQuickOverlayPrivate::removePopup(QQuickPopup *popup)
382{
383 Q_Q(QQuickOverlay);
384 allPopups.removeOne(popup);
385 if (allDrawers.removeOne(popup))
386 q->setVisible(!allDrawers.isEmpty() || !q->childItems().isEmpty());
387}
388
389void QQuickOverlayPrivate::setMouseGrabberPopup(QQuickPopup *popup)
390{
391 if (popup && !popup->isVisible())
392 popup = nullptr;
393 mouseGrabberPopup = popup;
394}
395
396void QQuickOverlayPrivate::updateGeometry()
397{
398 Q_Q(QQuickOverlay);
399 if (!window || !window->contentItem())
400 return;
401
402 const QSizeF size = window->contentItem()->size();
403 const QPointF pos(-(size.width() - window->size().width()) / 2,
404 -(size.height() - window->size().height()) / 2);
405
406 q->setSize(size);
407 q->setPosition(pos);
408}
409
410QQuickOverlay::QQuickOverlay(QQuickItem *parent)
411 : QQuickItem(*(new QQuickOverlayPrivate), parent)
412{
413 Q_D(QQuickOverlay);
414 setZ(1000001); // DefaultWindowDecoration+1
415 setAcceptedMouseButtons(Qt::AllButtons);
416#if QT_CONFIG(quicktemplates2_multitouch)
417 setAcceptTouchEvents(true);
418#endif
419 setFiltersChildMouseEvents(true);
420 setVisible(false);
421
422 if (parent) {
423 d->updateGeometry();
424 QQuickItemPrivate::get(parent)->addItemChangeListener(d, QQuickItemPrivate::Geometry);
425 if (QQuickWindow *window = parent->window()) {
426 window->installEventFilter(this);
427 if (QQuickItem *contentItem = window->contentItem())
428 QQuickItemPrivate::get(contentItem)->addItemChangeListener(d, QQuickItemPrivate::Rotation);
429 }
430 }
431}
432
433QQuickOverlay::~QQuickOverlay()
434{
435 Q_D(QQuickOverlay);
436 if (QQuickItem *parent = parentItem()) {
437 QQuickItemPrivate::get(parent)->removeItemChangeListener(d, QQuickItemPrivate::Geometry);
438 if (QQuickWindow *window = parent->window()) {
439 if (QQuickItem *contentItem = window->contentItem())
440 QQuickItemPrivate::get(contentItem)->removeItemChangeListener(d, QQuickItemPrivate::Rotation);
441 }
442 }
443}
444
445QQmlComponent *QQuickOverlay::modal() const
446{
447 Q_D(const QQuickOverlay);
448 return d->modal;
449}
450
451void QQuickOverlay::setModal(QQmlComponent *modal)
452{
453 Q_D(QQuickOverlay);
454 if (d->modal == modal)
455 return;
456
457 d->modal = modal;
458 emit modalChanged();
459}
460
461QQmlComponent *QQuickOverlay::modeless() const
462{
463 Q_D(const QQuickOverlay);
464 return d->modeless;
465}
466
467void QQuickOverlay::setModeless(QQmlComponent *modeless)
468{
469 Q_D(QQuickOverlay);
470 if (d->modeless == modeless)
471 return;
472
473 d->modeless = modeless;
474 emit modelessChanged();
475}
476
477QQuickOverlay *QQuickOverlay::overlay(QQuickWindow *window, QQuickItem *parent)
478{
479 if (!window)
480 return nullptr;
481
482 const char *name = "_q_QQuickOverlay";
483
484 if (QQuickItemPrivate::customOverlayRequested) {
485 while (parent) {
486 if (QQuickItemPrivate::get(parent)->customOverlay) {
487 QQuickOverlay *overlay = parent->property(name).value<QQuickOverlay *>();
488 if (!overlay) {
489 overlay = new QQuickOverlay(parent);
490 parent->setProperty(name, QVariant::fromValue(overlay));
491 }
492 return overlay;
493 }
494 parent = parent->parentItem();
495 }
496 }
497 QQuickOverlay *overlay = window->property(name).value<QQuickOverlay *>();
498 if (!overlay) {
499 QQuickItem *content = window->contentItem();
500 // Do not re-create the overlay if the window is being destroyed
501 // and thus, its content item no longer has a window associated.
502 if (content && content->window()) {
503 overlay = new QQuickOverlay(window->contentItem());
504 window->setProperty(name, QVariant::fromValue(overlay));
505 }
506 }
507 return overlay;
508}
509
510QQuickOverlayAttached *QQuickOverlay::qmlAttachedProperties(QObject *object)
511{
512 return new QQuickOverlayAttached(object);
513}
514
515void QQuickOverlay::itemChange(ItemChange change, const ItemChangeData &data)
516{
517 Q_D(QQuickOverlay);
518 QQuickItem::itemChange(change, data);
519
520 if (change == ItemChildAddedChange || change == ItemChildRemovedChange) {
521 setVisible(!d->allDrawers.isEmpty() || !childItems().isEmpty());
522 if (data.item->parent() == d->mouseGrabberPopup)
523 d->setMouseGrabberPopup(nullptr);
524 }
525}
526
527void QQuickOverlay::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
528{
529 Q_D(QQuickOverlay);
530 QQuickItem::geometryChange(newGeometry, oldGeometry);
531 for (QQuickPopup *popup : std::as_const(d->allPopups))
532 QQuickPopupPrivate::get(popup)->resizeDimmer();
533}
534
535void QQuickOverlay::mousePressEvent(QMouseEvent *event)
536{
537 Q_D(QQuickOverlay);
538 d->handleMouseEvent(this, event);
539}
540
541void QQuickOverlay::mouseMoveEvent(QMouseEvent *event)
542{
543 Q_D(QQuickOverlay);
544 d->handleMouseEvent(this, event);
545}
546
547void QQuickOverlay::mouseReleaseEvent(QMouseEvent *event)
548{
549 Q_D(QQuickOverlay);
550 d->handleMouseEvent(this, event);
551}
552
553#if QT_CONFIG(quicktemplates2_multitouch)
554void QQuickOverlay::touchEvent(QTouchEvent *event)
555{
556 Q_D(QQuickOverlay);
557 d->handleTouchEvent(this, event);
558}
559#endif
560
561void QQuickOverlay::handleWheelAndDnDEvents(QEvent *event)
562{
563 Q_D(QQuickOverlay);
564 if (d->mouseGrabberPopup) {
565 d->mouseGrabberPopup->overlayEvent(this, event);
566 return;
567 } else {
568 const auto popups = d->stackingOrderPopups();
569 for (QQuickPopup *popup : popups) {
570 if (popup->overlayEvent(this, event))
571 return;
572 }
573 }
574 event->ignore();
575}
576
577#if QT_CONFIG(wheelevent)
578void QQuickOverlay::wheelEvent(QWheelEvent *event)
579{
580 handleWheelAndDnDEvents(event);
581}
582#endif
583
584#if QT_CONFIG(quick_draganddrop)
585void QQuickOverlay::dragEnterEvent(QDragEnterEvent *event)
586{
587 handleWheelAndDnDEvents(event);
588}
589
590void QQuickOverlay::dragMoveEvent(QDragMoveEvent *event)
591{
592 handleWheelAndDnDEvents(event);
593}
594
595void QQuickOverlay::dragLeaveEvent(QDragLeaveEvent *event)
596{
597 handleWheelAndDnDEvents(event);
598}
599
600void QQuickOverlay::dropEvent(QDropEvent *event)
601{
602 handleWheelAndDnDEvents(event);
603}
604#endif
605
606/*!
607 \internal
608
609 When clicking inside a scene with a single active popup, a few things can happen.
610 The click can be inside the popup item, in which case, we stop filtering to allow
611 normal event handling. Or the click can be outside the popup, in which case,
612 it will normally be inside the overlay, or the popup's dimmer item.
613
614 When dealing with nested popups, the second popup's dimmer (or popupItem if the dimmer is absent)
615 will become an exclusive grabber to the pointerEvent, during childMouseEventFilter.
616 (sometimes the overlay becomes the exclusive grabber instead, why?).
617*/
618bool QQuickOverlay::childMouseEventFilter(QQuickItem *item, QEvent *event)
619{
620 Q_D(QQuickOverlay);
621
622 // Qt retries delivery of a single press/release/touch event against each
623 // overlapping sibling popupItem underneath, from top to bottom, as long as
624 // the previous one doesn't accept it - which means this function can be
625 // called multiple times for what is really the same physical event. Once a
626 // popup without ClosePolicy::CloseMultiple has had its one chance to close
627 // below, don't let a later retry give another popup a chance too.
628 const bool isCloseCascadeEvent = event->type() == QEvent::MouseButtonPress
629 || event->type() == QEvent::MouseButtonRelease
630#if QT_CONFIG(quicktemplates2_multitouch)
631 || event->type() == QEvent::TouchBegin
632 || event->type() == QEvent::TouchUpdate
633 || event->type() == QEvent::TouchEnd
634#endif
635 ;
636 if (isCloseCascadeEvent && d->closeCascadeStopped)
637 return false;
638
639 const auto popups = d->stackingOrderPopups();
640 for (qsizetype i = 0; i < popups.size(); ++i) {
641 QQuickPopup *popup = popups.at(i);
642 QQuickPopupPrivate *p = QQuickPopupPrivate::get(popup);
643
644 // Stop filtering overlay events when reaching a popup item or an item
645 // that is inside the popup. Let the popup content handle its events.
646 if (item == p->popupItem || p->popupItem->isAncestorOf(item))
647 break;
648
649 // Let the popup try closing itself when pressing or releasing over its
650 // background dimming OR over another popup underneath, in case the popup
651 // does not have background dimming.
652 if (item == p->dimmer || !p->popupItem->isAncestorOf(item)) {
653 bool handled = false;
654 switch (event->type()) {
655#if QT_CONFIG(quicktemplates2_multitouch)
656 case QEvent::TouchBegin:
657 case QEvent::TouchUpdate:
658 case QEvent::TouchEnd:
659 handled = d->handleTouchEvent(item, static_cast<QTouchEvent *>(event), popup);
660 break;
661#endif
662 case QEvent::HoverEnter:
663 case QEvent::HoverMove:
664 case QEvent::HoverLeave:
665 // If the control item has already been hovered, allow the hover leave event
666 // to be processed by the same item for resetting its internal hovered state
667 // instead of filtering it here.
668 if (auto *control = qobject_cast<QQuickControl *>(item)) {
669 if (control->isHovered() && event->type() == QEvent::HoverLeave)
670 return false;
671 }
672 handled = d->handleHoverEvent(item, static_cast<QHoverEvent *>(event), popup);
673 break;
674
675 case QEvent::MouseButtonPress:
676 case QEvent::MouseButtonRelease:
677 case QEvent::MouseMove:
678 handled = d->handleMouseEvent(item, static_cast<QMouseEvent *>(event), popup);
679 break;
680
681 default:
682 break;
683 }
684 if (handled) {
685 // A modal popup's dimmer legitimately blocks a press from reaching
686 // whatever is behind it without closing itself (e.g. the click landed
687 // inside it, or its closePolicy doesn't close on press) - that's correct
688 // and must keep stopping delivery here. But popups further down the
689 // stack then never get their own QQuickPopupPrivate::handlePress() call,
690 // so their outsidePressed/outsideParentPressed is never recorded. If this
691 // popup participates in a CloseMultiple cascade, those lower popups may
692 // still need to close on the matching release, and tryClose() refuses to
693 // close a popup that never registered pressing outside it. Record that
694 // state now, without disturbing the blocked/handled return value above.
695 if (event->type() == QEvent::MouseButtonPress && isCloseCascadeEvent
696 && canCascadeCloseOnOutsidePress(popup)
697 && popup->closePolicy().testFlag(QQuickPopup::CloseMultiple)) {
698 auto *mouseEvent = static_cast<QMouseEvent *>(event);
699 for (qsizetype j = i + 1; j < popups.size(); ++j) {
700 QQuickPopup *lower = popups.at(j);
701 QQuickPopupPrivate::get(lower)->handlePress(
702 item, mouseEvent->scenePosition(), mouseEvent->timestamp());
703 if (canCascadeCloseOnOutsidePress(lower)
704 && !lower->closePolicy().testFlag(QQuickPopup::CloseMultiple))
705 break;
706 }
707 }
708 return true;
709 }
710
711 // Unless this popup has CloseMultiple set, don't let popups further down
712 // the stack (in this call, or a later retry for the same event) get a
713 // chance to close themselves for this same press/release. Popups that
714 // aren't eligible to auto-close on an outside press/release at all don't
715 // participate in the cascade, so they don't stop it either.
716 if (isCloseCascadeEvent && canCascadeCloseOnOutsidePress(popup)
717 && !popup->closePolicy().testFlag(QQuickPopup::CloseMultiple)) {
718 d->closeCascadeStopped = true;
719 break;
720 }
721 }
722 }
723 return false;
724}
725
726/*!
727 \internal
728
729 The overlay installs itself as an event filter on the window it belongs to.
730 It will filter Touch, Mouse (press and release), Wheel and DnD related events.
731
732 Touch and MousePress events will be passed to the delivery agent for normal event propagation,
733 where they will be filtered by the overlay again in QQuickOverlay::childMouseEventFilter.
734 All opened popups will then be iterated, to check if the event should close the popup or not.
735 Also modality is checked to determine the return type, which will cause the delivery agent to
736 continue normal propagation in this second childMouseEventFilter filtering step.
737
738 The reason for installing the eventFilter is to allow non-modal popups with CloseOnReleaseOutside
739 as the closing policy to close when clicking the overlay. The delivery agent won't send the
740 release event to the overlay when clicking it directly, only the press event.
741*/
742bool QQuickOverlay::eventFilter(QObject *object, QEvent *event)
743{
744 Q_D(QQuickOverlay);
745 if (!isVisible() || object != d->window)
746 return false;
747
748#if QT_CONFIG(wheelevent) || QT_CONFIG(quick_draganddrop)
749 auto targetItemsForEvent = [&](QEvent *){
750#if QT_CONFIG(wheelevent)
751 if (event->type() == QEvent::Wheel) {
752 QWheelEvent *we = static_cast<QWheelEvent *>(event);
753 return d->deliveryAgentPrivate()->pointerTargets(
754 d->window->contentItem(), we, we->point(0), false, false);
755 }
756#endif
757#if QT_CONFIG(quick_draganddrop)
758 bool isDnDEvent = false;
759 switch (event->type()) {
760 case QEvent::DragEnter:
761 case QEvent::DragMove:
762 case QEvent::Drop:
763 isDnDEvent = true;
764 break;
765 default:
766 break;
767 }
768 if (isDnDEvent) {
769 QDropEvent *de = static_cast<QDropEvent *>(event);
770 auto position = mapFromScene(de->position());
771 return d->deliveryAgentPrivate()->eventTargets(
772 d->window->contentItem(), de, -1, position, de->position(),
773 [](QQuickItem *item, const QEvent *) -> std::optional<bool> {
774 // Only consider actual drop targets; still recurse into
775 // non-accepting items so their children can be found.
776 if (!item->flags().testFlag(QQuickItem::ItemAcceptsDrops))
777 return false;
778 return std::nullopt; // use default containment check
779 });
780 }
781#endif
782 // Not needed so far
783 Q_UNREACHABLE_RETURN(QList<QQuickItem *> {});
784 };
785#endif
786
787 switch (event->type()) {
788#if QT_CONFIG(quicktemplates2_multitouch)
789 case QEvent::TouchBegin:
790 case QEvent::TouchUpdate:
791 case QEvent::TouchEnd:
792 if (static_cast<QTouchEvent *>(event)->touchPointStates() & QEventPoint::Pressed)
793 emit pressed();
794 if (static_cast<QTouchEvent *>(event)->touchPointStates() & QEventPoint::Released)
795 emit released();
796
797 // Starting a new touch event; let its cascade of popup closes run fresh.
798 d->closeCascadeStopped = false;
799
800 // allow non-modal popups to close on touch release outside
801 if (!d->mouseGrabberPopup) {
802 QTouchEvent *touchEvent = static_cast<QTouchEvent *>(event);
803 for (const QTouchEvent::TouchPoint &point : touchEvent->points()) {
804 if (point.state() == QEventPoint::Released) {
805 QQuickDeliveryAgentPrivate::translateTouchEvent(touchEvent);
806 if (d->handleRelease(d->window->contentItem(), event, nullptr))
807 break;
808 }
809 }
810 }
811
812 // setup currentEventDeliveryAgent like in QQuickDeliveryAgent::event
813 QQuickDeliveryAgentPrivate::currentEventDeliveryAgent = d->deliveryAgent();
814 d->deliveryAgentPrivate()->handleTouchEvent(static_cast<QTouchEvent *>(event));
815 QQuickDeliveryAgentPrivate::currentEventDeliveryAgent = nullptr;
816
817 // If a touch event hasn't been accepted after being delivered, there
818 // were no items interested in touch events at any of the touch points.
819 // Make sure to accept the touch event in order to receive the consequent
820 // touch events, to be able to close non-modal popups on release outside.
821 event->accept();
822 // Since we eat the event, QQuickWindow::event never sees it to clean up the
823 // grabber states. So we have to do so explicitly.
824 d->deliveryAgentPrivate()->clearGrabbers(static_cast<QPointerEvent *>(event));
825 return true;
826#endif
827
828 case QEvent::MouseButtonPress: {
829 auto *mouseEvent = static_cast<QMouseEvent *>(event);
830 // Don't filter right mouse button clicks, as it prevents ContextMenu from
831 // receiving QContextMenuEvents as long as e.g. a Drawer exists, even if it's not visible.
832 // This does not prevent popups from being closed with the right mouse button,
833 // as mousePressEvent takes care of that.
834 if (mouseEvent->button() == Qt::RightButton)
835 break;
836
837#if QT_CONFIG(quicktemplates2_multitouch)
838 // do not emit pressed() twice when mouse events have been synthesized from touch events
839 if (mouseEvent->source() == Qt::MouseEventNotSynthesized)
840#endif
841 emit pressed();
842
843 // Starting a new press; let its cascade of popup closes run fresh.
844 d->closeCascadeStopped = false;
845
846 // setup currentEventDeliveryAgent like in QQuickDeliveryAgent::event
847 QQuickDeliveryAgentPrivate::currentEventDeliveryAgent = d->deliveryAgent();
848 d->deliveryAgentPrivate()->handleMouseEvent(mouseEvent);
849 QQuickDeliveryAgentPrivate::currentEventDeliveryAgent = nullptr;
850
851 // If a mouse event hasn't been accepted after being delivered, there
852 // was no item interested in mouse events at the mouse point. Make sure
853 // to accept the mouse event in order to receive the consequent mouse
854 // events, to be able to close non-modal popups on release outside.
855 event->accept();
856 return true;
857 }
858 case QEvent::MouseButtonRelease: {
859 auto *mouseEvent = static_cast<QMouseEvent *>(event);
860 if (mouseEvent->button() == Qt::RightButton)
861 break;
862
863#if QT_CONFIG(quicktemplates2_multitouch)
864 // do not emit released() twice when mouse events have been synthesized from touch events
865 if (mouseEvent->source() == Qt::MouseEventNotSynthesized)
866#endif
867 emit released();
868
869 // Starting a new release; let its cascade of popup closes run fresh.
870 d->closeCascadeStopped = false;
871
872 // allow non-modal popups to close on mouse release outside
873 if (!d->mouseGrabberPopup)
874 d->handleRelease(d->window->contentItem(), event, nullptr);
875 break;
876 }
877#if QT_CONFIG(quick_draganddrop)
878 case QEvent::DragEnter:
879 case QEvent::DragMove:
880 case QEvent::Drop: {
881 // Find which item the drag is targeting and block if a modal popup is in front.
882 const QList<QQuickItem *> targetItems = targetItemsForEvent(event);
883 if (targetItems.isEmpty())
884 break;
885 QQuickItem *const dimmerItem = property("_q_dimmerItem").value<QQuickItem *>();
886 QQuickItem *const topItem = targetItems.first();
887 QQuickItem *item = qobject_cast<QQuickPopupItem *>(topItem) ? topItem : nullptr;
888 if (!item) {
889 item = topItem;
890 while ((item = item->parentItem())) {
891 if (qobject_cast<QQuickPopupItem *>(item))
892 break;
893 }
894 }
895 if (!item && dimmerItem != topItem && isAncestorOf(topItem))
896 break;
897 for (const auto &popup : d->stackingOrderPopups()) {
898 const QQuickItem *popupItem = popup->popupItem();
899 if (!popupItem)
900 continue;
901 if (popupItem == item)
902 break;
903 if (popup->overlayEvent(topItem, event))
904 return true;
905 }
906 break;
907 }
908#endif
909#if QT_CONFIG(wheelevent)
910 case QEvent::Wheel:
911 return d->eatEventIfBlockedByModal(static_cast<QWheelEvent *>(event));
912#endif
913#if QT_CONFIG(tabletevent)
914 case QEvent::TabletMove:
915 case QEvent::TabletPress:
916 case QEvent::TabletRelease:
917 return d->eatEventIfBlockedByModal(static_cast<QTabletEvent *>(event));
918#endif
919 default:
920 break;
921 }
922
923 return false;
924}
925
927{
928public:
929 Q_DECLARE_PUBLIC(QQuickOverlayAttached)
930
932 void setWindowAndParent(QQuickWindow *newWindow, QQuickItem *parent);
933
935 QQmlComponent *modal = nullptr;
936 QQmlComponent *modeless = nullptr;
938};
939
940void QQuickOverlayAttachedPrivate::setWindow(QQuickWindow *newWindow)
941{
942 setWindowAndParent(newWindow, parentItem);
943}
944
945void QQuickOverlayAttachedPrivate::setWindowAndParent(QQuickWindow *newWindow, QQuickItem *parent)
946{
947 Q_Q(QQuickOverlayAttached);
948 if (window == newWindow)
949 return;
950
951 if (QQuickOverlay *oldOverlay = QQuickOverlay::overlay(window, parent)) {
952 QObject::disconnect(oldOverlay, &QQuickOverlay::pressed, q, &QQuickOverlayAttached::pressed);
953 QObject::disconnect(oldOverlay, &QQuickOverlay::released, q, &QQuickOverlayAttached::released);
954 }
955
956 if (QQuickOverlay *newOverlay = QQuickOverlay::overlay(newWindow, parent)) {
957 QObject::connect(newOverlay, &QQuickOverlay::pressed, q, &QQuickOverlayAttached::pressed);
958 QObject::connect(newOverlay, &QQuickOverlay::released, q, &QQuickOverlayAttached::released);
959 }
960
961 window = newWindow;
962 if (parent)
963 parentItem = parent;
964 emit q->overlayChanged();
965}
966
967/*!
968 \qmlattachedsignal QtQuick.Controls::Overlay::pressed()
969
970 This attached signal is emitted when the overlay is pressed by the user while
971 a popup is visible.
972
973 The signal can be attached to any item, popup, or window. When attached to an
974 item or a popup, the signal is only emitted if the item or popup is in a window.
975
976 \include qquickoverlay-pressed-released.qdocinc
977*/
978
979/*!
980 \qmlattachedsignal QtQuick.Controls::Overlay::released()
981
982 This attached signal is emitted when the overlay is released by the user while
983 a popup is visible.
984
985 The signal can be attached to any item, popup, or window. When attached to an
986 item or a popup, the signal is only emitted if the item or popup is in a window.
987
988 \include qquickoverlay-pressed-released.qdocinc
989*/
990
991QQuickOverlayAttached::QQuickOverlayAttached(QObject *parent)
992 : QObject(*(new QQuickOverlayAttachedPrivate), parent)
993{
994 Q_D(QQuickOverlayAttached);
995 if (QQuickItem *item = qobject_cast<QQuickItem *>(parent)) {
996 d->setWindowAndParent(item->window(), item);
997 QObjectPrivate::connect(item, &QQuickItem::windowChanged, d, &QQuickOverlayAttachedPrivate::setWindow);
998 } else if (QQuickPopup *popup = qobject_cast<QQuickPopup *>(parent)) {
999 d->setWindowAndParent(popup->window(), popup->parentItem());
1000 QObjectPrivate::connect(popup, &QQuickPopup::windowChanged, d, &QQuickOverlayAttachedPrivate::setWindow);
1001 } else {
1002 d->setWindow(qobject_cast<QQuickWindow *>(parent));
1003 }
1004}
1005
1006/*!
1007 \qmlattachedproperty Overlay QtQuick.Controls::Overlay::overlay
1008 \readonly
1009
1010 This attached property holds the window overlay item.
1011
1012 The property can be attached to any item, popup, or window. When attached to an
1013 item or a popup, the value is \c null if the item or popup is not in a window.
1014*/
1015QQuickOverlay *QQuickOverlayAttached::overlay() const
1016{
1017 Q_D(const QQuickOverlayAttached);
1018 return QQuickOverlay::overlay(d->window, d->parentItem);
1019}
1020
1021/*!
1022 \qmlattachedproperty Component QtQuick.Controls::Overlay::modal
1023
1024 This attached property holds a component to use as a visual item that implements
1025 background dimming for modal popups. It is created for and stacked below visible
1026 modal popups.
1027
1028 The property can be attached to any popup.
1029
1030 For example, to change the color of the background dimming for a modal
1031 popup, the following code can be used:
1032
1033 \snippet qtquickcontrols-overlay-modal.qml 1
1034
1035 \sa Popup::modal
1036*/
1037QQmlComponent *QQuickOverlayAttached::modal() const
1038{
1039 Q_D(const QQuickOverlayAttached);
1040 return d->modal;
1041}
1042
1043void QQuickOverlayAttached::setModal(QQmlComponent *modal)
1044{
1045 Q_D(QQuickOverlayAttached);
1046 if (d->modal == modal)
1047 return;
1048
1049 d->modal = modal;
1050 emit modalChanged();
1051}
1052
1053/*!
1054 \qmlattachedproperty Component QtQuick.Controls::Overlay::modeless
1055
1056 This attached property holds a component to use as a visual item that implements
1057 background dimming for modeless popups. It is created for and stacked below visible
1058 dimming popups.
1059
1060 The property can be attached to any popup.
1061
1062 For example, to change the color of the background dimming for a modeless
1063 popup, the following code can be used:
1064
1065 \snippet qtquickcontrols-overlay-modeless.qml 1
1066
1067 \sa Popup::dim
1068*/
1069QQmlComponent *QQuickOverlayAttached::modeless() const
1070{
1071 Q_D(const QQuickOverlayAttached);
1072 return d->modeless;
1073}
1074
1075void QQuickOverlayAttached::setModeless(QQmlComponent *modeless)
1076{
1077 Q_D(QQuickOverlayAttached);
1078 if (d->modeless == modeless)
1079 return;
1080
1081 d->modeless = modeless;
1082 emit modelessChanged();
1083}
1084
1085QT_END_NAMESPACE
1086
1087#include "moc_qquickoverlay_p.cpp"
void setWindowAndParent(QQuickWindow *newWindow, QQuickItem *parent)
static bool closedItselfViaCloseMultiple(const QQuickPopup *popup, bool wasOpened)
static bool canCascadeCloseOnOutsidePress(const QQuickPopup *popup)