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