From 7fc567eda8a187e365f4c29c6e8f08440bf31218 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sat, 30 Jan 2016 17:21:55 +0100 Subject: Make popups work without ApplicationWindow, to some degree Using ApplicationWindow is highly recommended. First of all, with a plain Window, QQuickPopup attempts to set a high z-value, but cannot guarantee correct stacking order. Secondly, we cannot provide style- specific background dimming for modal popups, because it is styled as part of ApplicationWindow. Last but not least, QQuickPopup has to install a window-level event filter, which is far less efficient than how event handling done in QQuickOverlay. Change-Id: I08915abce7a1764177b92f7539eef77c054a405a Task-number: QTBUG-49921 Reviewed-by: J-P Nurmi --- src/imports/controls/Drawer.qml | 3 +- src/imports/controls/material/Drawer.qml | 3 +- src/templates/qquickcombobox.cpp | 4 + src/templates/qquickmenu.cpp | 9 +-- src/templates/qquickoverlay.cpp | 59 ++++---------- src/templates/qquickpopup.cpp | 129 +++++++++++++++++++++---------- src/templates/qquickpopup_p.h | 1 + src/templates/qquickpopup_p_p.h | 4 +- 8 files changed, 118 insertions(+), 94 deletions(-) diff --git a/src/imports/controls/Drawer.qml b/src/imports/controls/Drawer.qml index eb4e3e57..60abeb0c 100644 --- a/src/imports/controls/Drawer.qml +++ b/src/imports/controls/Drawer.qml @@ -35,12 +35,13 @@ ****************************************************************************/ import QtQuick 2.6 +import QtQuick.Window 2.2 import Qt.labs.templates 1.0 as T T.Drawer { id: control - parent: T.ApplicationWindow.overlay + parent: T.ApplicationWindow.overlay || Window.contentItem width: parent ? parent.width : 0 // TODO: Window.width height: parent ? parent.height : 0 // TODO: Window.height diff --git a/src/imports/controls/material/Drawer.qml b/src/imports/controls/material/Drawer.qml index cb00096d..133ea9c8 100644 --- a/src/imports/controls/material/Drawer.qml +++ b/src/imports/controls/material/Drawer.qml @@ -35,13 +35,14 @@ ****************************************************************************/ import QtQuick 2.6 +import QtQuick.Window 2.2 import Qt.labs.templates 1.0 as T import Qt.labs.controls.material 1.0 T.Drawer { id: control - parent: T.ApplicationWindow.overlay + parent: T.ApplicationWindow.overlay || Window.contentItem width: parent ? parent.width : 0 // TODO: Window.width height: parent ? parent.height : 0 // TODO: Window.height diff --git a/src/templates/qquickcombobox.cpp b/src/templates/qquickcombobox.cpp index f4cb17af..dfb01e83 100644 --- a/src/templates/qquickcombobox.cpp +++ b/src/templates/qquickcombobox.cpp @@ -711,6 +711,10 @@ void QQuickComboBox::keyPressEvent(QKeyEvent *event) return; switch (event->key()) { + case Qt::Key_Escape: + if (d->isPopupVisible()) + event->accept(); + break; case Qt::Key_Space: if (!event->isAutoRepeat()) setPressed(true); diff --git a/src/templates/qquickmenu.cpp b/src/templates/qquickmenu.cpp index 692395ff..d3fc88d9 100644 --- a/src/templates/qquickmenu.cpp +++ b/src/templates/qquickmenu.cpp @@ -489,11 +489,8 @@ void QQuickMenu::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) bool QQuickMenu::eventFilter(QObject *object, QEvent *event) { Q_D(QQuickMenu); - if (d->contentModel->count() == 0) - return false; - - if (object != d->contentItem || event->type() != QEvent::KeyRelease) - return false; + if (object != d->contentItem || event->type() != QEvent::KeyRelease || d->contentModel->count() == 0) + return QQuickPopup::eventFilter(object, event); // QTBUG-17051 // Work around the fact that ListView has no way of distinguishing between @@ -517,7 +514,7 @@ bool QQuickMenu::eventFilter(QObject *object, QEvent *event) break; } - return false; + return QQuickPopup::eventFilter(object, event); } QT_END_NAMESPACE diff --git a/src/templates/qquickoverlay.cpp b/src/templates/qquickoverlay.cpp index 589cafdf..dd9d2c5d 100644 --- a/src/templates/qquickoverlay.cpp +++ b/src/templates/qquickoverlay.cpp @@ -35,7 +35,7 @@ ****************************************************************************/ #include "qquickoverlay_p.h" -#include "qquickpopup_p.h" +#include "qquickpopup_p_p.h" #include "qquickdrawer_p.h" #include #include @@ -52,13 +52,12 @@ public: void popupAboutToShow(); void popupAboutToHide(); - void closePopup(QQuickPopup *popup, QMouseEvent *event); void drawerPositionChange(); void resizeBackground(); QQuickItem *background; QVector drawers; - QHash popups; + QVector popups; int modalPopups; }; @@ -90,30 +89,6 @@ void QQuickOverlayPrivate::popupAboutToHide() QQmlProperty::write(background, QStringLiteral("opacity"), 0.0); } -void QQuickOverlayPrivate::closePopup(QQuickPopup *popup, QMouseEvent *event) -{ - Q_Q(QQuickOverlay); - const bool isPress = event->type() == QEvent::MouseButtonPress; - const bool onOutside = popup->closePolicy().testFlag(isPress ? QQuickPopup::OnPressOutside : QQuickPopup::OnReleaseOutside); - const bool onOutsideParent = popup->closePolicy().testFlag(isPress ? QQuickPopup::OnPressOutsideParent : QQuickPopup::OnReleaseOutsideParent); - if (onOutside || onOutsideParent) { - QQuickItem *popupItem = popup->popupItem(); - QQuickItem *parentItem = popup->parentItem(); - - if (onOutside && onOutsideParent) { - if (!popupItem->contains(q->mapToItem(popupItem, event->pos())) && - (!parentItem || !parentItem->contains(q->mapToItem(parentItem, event->pos())))) - popup->close(); - } else if (onOutside) { - if (!popupItem->contains(q->mapToItem(popupItem, event->pos()))) - popup->close(); - } else if (onOutsideParent) { - if (!parentItem || !parentItem->contains(q->mapToItem(parentItem, event->pos()))) - popup->close(); - } - } -} - void QQuickOverlayPrivate::drawerPositionChange() { Q_Q(QQuickOverlay); @@ -197,27 +172,19 @@ void QQuickOverlay::itemChange(ItemChange change, const ItemChangeData &data) return; if (change == ItemChildAddedChange) { - if (QQuickPopup *prevPopup = d->popups.value(data.item)) { - qmlInfo(popup).nospace() << "Popup is sharing item " << data.item << " with " << prevPopup - << ". This is not supported and strange things are about to happen."; - return; - } - - d->popups.insert(data.item, popup); + d->popups.append(popup); if (popup->isModal()) ++d->modalPopups; QObjectPrivate::connect(popup, &QQuickPopup::aboutToShow, d, &QQuickOverlayPrivate::popupAboutToShow); QObjectPrivate::connect(popup, &QQuickPopup::aboutToHide, d, &QQuickOverlayPrivate::popupAboutToHide); } else if (change == ItemChildRemovedChange) { - Q_ASSERT(popup == d->popups.value(data.item)); + d->popups.removeOne(popup); + if (popup->isModal()) + --d->modalPopups; QObjectPrivate::disconnect(popup, &QQuickPopup::aboutToShow, d, &QQuickOverlayPrivate::popupAboutToShow); QObjectPrivate::disconnect(popup, &QQuickPopup::aboutToHide, d, &QQuickOverlayPrivate::popupAboutToHide); - - if (popup->isModal()) - --d->modalPopups; - d->popups.remove(data.item); } } @@ -247,8 +214,10 @@ void QQuickOverlay::mousePressEvent(QMouseEvent *event) event->setAccepted(d->modalPopups > 0); emit pressed(); - foreach (QQuickPopup *popup, d->popups) - d->closePopup(popup, event); + for (int i = d->popups.count() - 1; i >= 0; --i) { + if (QQuickPopupPrivate::get(d->popups.at(i))->tryClose(this, event)) + break; + } } void QQuickOverlay::mouseMoveEvent(QMouseEvent *event) @@ -263,8 +232,10 @@ void QQuickOverlay::mouseReleaseEvent(QMouseEvent *event) event->setAccepted(d->modalPopups > 0); emit released(); - foreach (QQuickPopup *popup, d->popups) - d->closePopup(popup, event); + for (int i = d->popups.count() - 1; i >= 0; --i) { + if (QQuickPopupPrivate::get(d->popups.at(i))->tryClose(this, event)) + break; + } } void QQuickOverlay::wheelEvent(QWheelEvent *event) @@ -292,7 +263,7 @@ bool QQuickOverlay::childMouseEventFilter(QQuickItem *item, QEvent *event) if (popupItem == item) break; - QQuickPopup *popup = d->popups.value(popupItem); + QQuickPopup *popup = qobject_cast(popupItem->parent()); if (popup) { QQuickPopup::ClosePolicy policy = popup->closePolicy(); if (policy.testFlag(QQuickPopup::OnPressOutside) || policy.testFlag(QQuickPopup::OnPressOutsideParent)) diff --git a/src/templates/qquickpopup.cpp b/src/templates/qquickpopup.cpp index 8937eb1f..99525986 100644 --- a/src/templates/qquickpopup.cpp +++ b/src/templates/qquickpopup.cpp @@ -95,7 +95,6 @@ QQuickPopupPrivate::QQuickPopupPrivate() , parentItem(Q_NULLPTR) , background(Q_NULLPTR) , contentItem(Q_NULLPTR) - , overlay(Q_NULLPTR) , enter(Q_NULLPTR) , exit(Q_NULLPTR) , popupItem(Q_NULLPTR) @@ -108,10 +107,32 @@ void QQuickPopupPrivate::init() { Q_Q(QQuickPopup); popupItem = new QQuickPopupItem(q); - popupItem->setParent(q); q->setParentItem(qobject_cast(parent)); } +bool QQuickPopupPrivate::tryClose(QQuickItem *item, QMouseEvent *event) +{ + Q_Q(QQuickPopup); + const bool isPress = event->type() == QEvent::MouseButtonPress; + const bool onOutside = closePolicy.testFlag(isPress ? QQuickPopup::OnPressOutside : QQuickPopup::OnReleaseOutside); + const bool onOutsideParent = closePolicy.testFlag(isPress ? QQuickPopup::OnPressOutsideParent : QQuickPopup::OnReleaseOutsideParent); + if (onOutside || onOutsideParent) { + if (onOutsideParent) { + if (!popupItem->contains(item->mapToItem(popupItem, event->pos())) && + (!parentItem || !parentItem->contains(item->mapToItem(parentItem, event->pos())))) { + q->close(); + return true; + } + } else if (onOutside) { + if (!popupItem->contains(item->mapToItem(popupItem, event->pos()))) { + q->close(); + return true; + } + } + } + return false; +} + void QQuickPopupPrivate::finalizeEnterTransition() { if (focus) @@ -120,11 +141,9 @@ void QQuickPopupPrivate::finalizeEnterTransition() void QQuickPopupPrivate::finalizeExitTransition() { - Q_Q(QQuickPopup); - overlay = Q_NULLPTR; positioner.setParentItem(Q_NULLPTR); popupItem->setParentItem(Q_NULLPTR); - emit q->visibleChanged(); + popupItem->setVisible(false); } void QQuickPopupPrivate::resizeBackground() @@ -281,6 +300,7 @@ public: QQuickPopupItemPrivate::QQuickPopupItemPrivate(QQuickPopup *popup) : popup(popup) { + isTabFence = true; } void QQuickPopupItemPrivate::implicitWidthChanged() @@ -296,6 +316,9 @@ void QQuickPopupItemPrivate::implicitHeightChanged() QQuickPopupItem::QQuickPopupItem(QQuickPopup *popup) : QQuickItem(*(new QQuickPopupItemPrivate(popup))) { + setParent(popup); + setVisible(false); + setFlag(ItemIsFocusScope); setAcceptedMouseButtons(Qt::AllButtons); } @@ -365,6 +388,19 @@ void QQuickPopupItem::geometryChanged(const QRectF &newGeometry, const QRectF &o d->popup->geometryChanged(newGeometry, oldGeometry); } +void QQuickPopupItem::itemChange(ItemChange change, const ItemChangeData &data) +{ + Q_D(QQuickPopupItem); + QQuickItem::itemChange(change, data); + switch (change) { + case ItemVisibleHasChanged: + emit d->popup->visibleChanged(); + break; + default: + break; + } +} + QQuickPopupPositioner::QQuickPopupPositioner(QQuickPopupPrivate *popup) : m_x(0), m_y(0), @@ -390,7 +426,7 @@ void QQuickPopupPositioner::setX(qreal x) { if (m_x != x) { m_x = x; - if (m_popup->overlay) // isVisible + if (m_popup->popupItem->isVisible()) repositionPopup(); } } @@ -404,7 +440,7 @@ void QQuickPopupPositioner::setY(qreal y) { if (m_y != y) { m_y = y; - if (m_popup->overlay) // isVisible + if (m_popup->popupItem->isVisible()) repositionPopup(); } } @@ -432,13 +468,13 @@ void QQuickPopupPositioner::setParentItem(QQuickItem *parent) QQuickItemPrivate::get(parent)->addItemChangeListener(this, ItemChangeTypes); addAncestorListeners(parent->parentItem()); - if (m_popup->overlay) // isVisible + if (m_popup->popupItem->isVisible()) repositionPopup(); } void QQuickPopupPositioner::itemGeometryChanged(QQuickItem *, const QRectF &, const QRectF &) { - if (m_popup->overlay) // isVisible + if (m_popup->popupItem->isVisible()) repositionPopup(); } @@ -607,24 +643,12 @@ QQuickPopup::~QQuickPopup() void QQuickPopup::open() { Q_D(QQuickPopup); - if (d->overlay) { - // popup already open + if (d->popupItem->isVisible()) return; - } QQuickWindow *window = Q_NULLPTR; - QObject *p = parent(); - while (p && !window) { - if (QQuickItem *item = qobject_cast(p)) { - window = item->window(); - if (!window) - p = item->parentItem(); - } else { - window = qobject_cast(p); - if (!window) - p = p->parent(); - } - } + if (d->parentItem) + window = d->parentItem->window(); if (!window) { qmlInfo(this) << "cannot find any window to open popup in."; return; @@ -632,17 +656,17 @@ void QQuickPopup::open() QQuickApplicationWindow *applicationWindow = qobject_cast(window); if (!applicationWindow) { - // FIXME Maybe try to open it in that window somehow - qmlInfo(this) << "is not in an ApplicationWindow."; - return; + window->installEventFilter(this); + d->popupItem->setZ(10001); // DefaultWindowDecoration+1 + d->popupItem->setParentItem(window->contentItem()); + } else { + d->popupItem->setParentItem(applicationWindow->overlay()); } - d->overlay = static_cast(applicationWindow->overlay()); - d->popupItem->setParentItem(d->overlay); - d->positioner.setParentItem(d->parentItem); emit aboutToShow(); + d->popupItem->setVisible(true); + d->positioner.setParentItem(d->parentItem); d->transitionManager.transitionEnter(); - emit visibleChanged(); } /*! @@ -653,9 +677,14 @@ void QQuickPopup::open() void QQuickPopup::close() { Q_D(QQuickPopup); - if (!d->overlay) { - // popup already closed + if (!d->popupItem->isVisible()) return; + + if (d->parentItem) { + QQuickWindow *window = d->parentItem->window(); + if (!qobject_cast(window)) { + window->removeEventFilter(this); + } } d->popupItem->setFocus(false); @@ -1224,18 +1253,12 @@ QQuickItem *QQuickPopup::contentItem() const void QQuickPopup::setContentItem(QQuickItem *item) { Q_D(QQuickPopup); - if (d->overlay) { - // FIXME qmlInfo needs to know about QQuickItem and/or QObject - static_cast(qmlInfo(this) << "cannot set content item") << item << "while Popup is visible."; - return; - } if (d->contentItem != item) { contentItemChange(item, d->contentItem); delete d->contentItem; d->contentItem = item; if (item) { item->setParentItem(d->popupItem); - QQuickItemPrivate::get(item)->isTabFence = true; if (isComponentComplete()) d->resizeContent(); } @@ -1326,7 +1349,7 @@ void QQuickPopup::setModal(bool modal) bool QQuickPopup::isVisible() const { Q_D(const QQuickPopup); - return d->overlay != Q_NULLPTR /*&& !d->transitionManager.isRunning()*/; + return d->popupItem->isVisible(); } void QQuickPopup::setVisible(bool visible) @@ -1467,6 +1490,32 @@ bool QQuickPopup::isComponentComplete() const return d->complete; } +bool QQuickPopup::eventFilter(QObject *object, QEvent *event) +{ + Q_D(QQuickPopup); + Q_UNUSED(object); + switch (event->type()) { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + if (d->modal) + event->setAccepted(true); + if (QQuickWindow *window = qobject_cast(object)) { + if (d->tryClose(window->contentItem(), static_cast(event))) + return true; + } + return false; + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::MouseMove: + case QEvent::Wheel: + if (d->modal) + event->setAccepted(true); + return false; + default: + return false; + } +} + void QQuickPopup::focusInEvent(QFocusEvent *event) { event->accept(); diff --git a/src/templates/qquickpopup_p.h b/src/templates/qquickpopup_p.h index e2d89c66..ac9a238a 100644 --- a/src/templates/qquickpopup_p.h +++ b/src/templates/qquickpopup_p.h @@ -276,6 +276,7 @@ protected: void componentComplete() Q_DECL_OVERRIDE; bool isComponentComplete() const; + bool eventFilter(QObject *object, QEvent *event) Q_DECL_OVERRIDE; virtual void focusInEvent(QFocusEvent *event); virtual void focusOutEvent(QFocusEvent *event); virtual void keyPressEvent(QKeyEvent *event); diff --git a/src/templates/qquickpopup_p_p.h b/src/templates/qquickpopup_p_p.h index f27ed762..29faa94e 100644 --- a/src/templates/qquickpopup_p_p.h +++ b/src/templates/qquickpopup_p_p.h @@ -60,7 +60,6 @@ QT_BEGIN_NAMESPACE class QQuickTransition; class QQuickTransitionManager; class QQuickPopup; -class QQuickOverlay; class QQuickPopupPrivate; class QQuickPopupItemPrivate; @@ -104,6 +103,7 @@ protected: void wheelEvent(QWheelEvent *event) Q_DECL_OVERRIDE; void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE; + void itemChange(ItemChange change, const ItemChangeData &data) Q_DECL_OVERRIDE; private: Q_DECLARE_PRIVATE(QQuickPopupItem) @@ -157,6 +157,7 @@ public: } void init(); + bool tryClose(QQuickItem *item, QMouseEvent *event); void finalizeEnterTransition(); void finalizeExitTransition(); @@ -203,7 +204,6 @@ public: QQuickItem *parentItem; QQuickItem *background; QQuickItem *contentItem; - QQuickOverlay *overlay; QQuickTransition *enter; QQuickTransition *exit; QQuickPopupItem *popupItem; -- cgit v1.2.3