diff options
author | Shawn Rutledge <[email protected]> | 2023-03-22 10:11:44 +0100 |
---|---|---|
committer | Shawn Rutledge <[email protected]> | 2024-05-31 10:40:31 -0700 |
commit | e4ef0f03e6f1fddc397980fd7fbf6f6b829f16d9 (patch) | |
tree | ff85575badfba5fead439054f051284412375052 | |
parent | 1bc78f7739ca4319323de5e7e07f88ef5cbd52bc (diff) |
Move popup management from QApplication to QGuiApplication
We need to be able to have true popup windows in Qt Quick and Controls,
including handling the press-drag-release sequence to select one entry
from a menu or combobox. After the mouse press, a new window is created.
On some platforms (such as xcb), the new window gets window system grabs
of both keyboard and mouse (QApplicationPrivate::openPopup() calls
grabForPopup() and it actually works); while on others, the pre-existing
window continues to get the whole sequence of mouse events until the
release. In the latter case, Qt needs to forward events from the
original window to the popup. Until now, the list of popups was
QApplicationPrivate::popupWidgets.
Now we track the open popups as a list of QWindows rather than QWidgets,
in QGuiApplicationPrivate::popup_list, and add a set of static functions
to manage that list. Functions such as QApplication::activePopupWidget()
QApplicationPrivate::openPopup() and closePopup() are rewritten to make
requests to QGuiApplicationPrivate.
276943c8b791ba5897dcdb1ecfda780ac33a090b made
QGuiApplicationPrivate::closeAllPopups() virtual. That is now reverted,
because we're putting QGuiApplication in charge of popup management
and trying to minimize widget-specific behavior. So far,
QApplicationPrivate::closePopup() is still overridden to take care
of focus changes.
So far, QtGui does not take care of closing popups when the user
clicks outside: the active popup window gets those events, and needs
to close itself if the click occurs outside. An attempt to move this
logic raised some issues with legacy widget test cases.
Using a touchscreen to press on QMenuBar and open a QMenu, drag to
a menu item and release, is temporarily broken for now. The plan is
to fix it in a subsequent patch.
Task-number: QTBUG-68080
Task-number: QTBUG-69777
Change-Id: I02b5034987b5ee8909917d305f414c8b0db9c7f5
Reviewed-by: Richard Moe Gustavsen <[email protected]>
-rw-r--r-- | src/gui/kernel/qguiapplication.cpp | 114 | ||||
-rw-r--r-- | src/gui/kernel/qguiapplication_p.h | 11 | ||||
-rw-r--r-- | src/gui/kernel/qwindow.cpp | 62 | ||||
-rw-r--r-- | src/gui/kernel/qwindow_p.h | 2 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoawindow.mm | 2 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qnswindow.mm | 2 | ||||
-rw-r--r-- | src/widgets/kernel/qapplication.cpp | 80 | ||||
-rw-r--r-- | src/widgets/kernel/qapplication_p.h | 2 | ||||
-rw-r--r-- | src/widgets/kernel/qwidgetwindow.cpp | 65 | ||||
-rw-r--r-- | tests/auto/gui/kernel/qwindow/tst_qwindow.cpp | 5 | ||||
-rw-r--r-- | tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp | 3 |
11 files changed, 214 insertions, 134 deletions
diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp index 2595c005e3d..1630103afe0 100644 --- a/src/gui/kernel/qguiapplication.cpp +++ b/src/gui/kernel/qguiapplication.cpp @@ -107,6 +107,8 @@ QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcPopup, "qt.gui.popup"); + using namespace Qt::StringLiterals; using namespace QtMiscUtils; @@ -180,12 +182,15 @@ Q_CONSTINIT QClipboard *QGuiApplicationPrivate::qt_clipboard = nullptr; Q_CONSTINIT QList<QScreen *> QGuiApplicationPrivate::screen_list; Q_CONSTINIT QWindowList QGuiApplicationPrivate::window_list; +Q_CONSTINIT QWindowList QGuiApplicationPrivate::popup_list; +Q_CONSTINIT const QWindow *QGuiApplicationPrivate::active_popup_on_press = nullptr; Q_CONSTINIT QWindow *QGuiApplicationPrivate::focus_window = nullptr; Q_CONSTINIT static QBasicMutex applicationFontMutex; Q_CONSTINIT QFont *QGuiApplicationPrivate::app_font = nullptr; Q_CONSTINIT QStyleHints *QGuiApplicationPrivate::styleHints = nullptr; Q_CONSTINIT bool QGuiApplicationPrivate::obey_desktop_settings = true; +Q_CONSTINIT bool QGuiApplicationPrivate::popup_closed_on_press = false; Q_CONSTINIT QInputDeviceManager *QGuiApplicationPrivate::m_inputDeviceManager = nullptr; @@ -960,6 +965,43 @@ bool QGuiApplicationPrivate::isWindowBlocked(QWindow *window, QWindow **blocking return false; } +QWindow *QGuiApplicationPrivate::activePopupWindow() +{ + // might be the same as focusWindow() if that's a popup + return QGuiApplicationPrivate::popup_list.isEmpty() ? + nullptr : QGuiApplicationPrivate::popup_list.constLast(); +} + +void QGuiApplicationPrivate::activatePopup(QWindow *popup) +{ + if (!popup->isVisible()) + return; + popup_list.removeOne(popup); // ensure that there's only one entry, and it's the last + qCDebug(lcPopup) << "appending popup" << popup << "to existing" << popup_list; + popup_list.append(popup); +} + +bool QGuiApplicationPrivate::closePopup(QWindow *popup) +{ + const auto removed = QGuiApplicationPrivate::popup_list.removeAll(popup); + qCDebug(lcPopup) << "removed?" << removed << "popup" << popup << "; remaining" << popup_list; + return removed; // >= 1 if something was removed +} + +/*! + Returns \c true if there are no more open popups. +*/ +bool QGuiApplicationPrivate::closeAllPopups() +{ + // Close all popups: In case some popup refuses to close, + // we give up after 1024 attempts (to avoid an infinite loop). + int maxiter = 1024; + QWindow *popup; + while ((popup = activePopupWindow()) && maxiter--) + popup->close(); // this will call QApplicationPrivate::closePopup + return QGuiApplicationPrivate::popup_list.isEmpty(); +} + /*! Returns the QWindow that receives events tied to focus, such as key events. @@ -1785,6 +1827,7 @@ QGuiApplicationPrivate::~QGuiApplicationPrivate() platform_integration = nullptr; window_list.clear(); + popup_list.clear(); screen_list.clear(); self = nullptr; @@ -2306,6 +2349,14 @@ void QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::Mo } #endif + const auto *activePopup = activePopupWindow(); + if (type == QEvent::MouseButtonPress) + active_popup_on_press = activePopup; + if (window->d_func()->blockedByModalWindow && !activePopup) { + // a modal window is blocking this window, don't allow mouse events through + return; + } + QMouseEvent ev(type, localPoint, localPoint, globalPoint, button, e->buttons, e->modifiers, e->source, device); Q_ASSERT(devPriv->pointById(0) == persistentEPD); // we don't expect reallocation in QPlatformCursor::pointerEvenmt() // restore globalLastPosition to avoid invalidating the velocity calculations, @@ -2314,9 +2365,15 @@ void QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::Mo persistentEPD = nullptr; // incoming and synth events can cause reallocation during delivery, so don't use this again // ev now contains a detached copy of the QEventPoint from QPointingDevicePrivate::activePoints ev.setTimestamp(e->timestamp); - if (window->d_func()->blockedByModalWindow && !qApp->d_func()->popupActive()) { - // a modal window is blocking this window, don't allow mouse events through - return; + + if (activePopup && activePopup != window && (!popup_closed_on_press || type == QEvent::MouseButtonRelease)) { + // If the popup handles the event, we're done. + auto *handlingPopup = window->d_func()->forwardToPopup(&ev, active_popup_on_press); + if (handlingPopup) { + if (type == QEvent::MouseButtonPress) + active_popup_on_press = handlingPopup; + return; + } } if (doubleClick && (ev.type() == QEvent::MouseButtonPress)) { @@ -2368,6 +2425,7 @@ void QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::Mo } } if (type == QEvent::MouseButtonRelease && e->buttons == Qt::NoButton) { + popup_closed_on_press = false; if (auto *persistentEPD = devPriv->queryPointById(0)) { ev.setExclusiveGrabber(persistentEPD->eventPoint, nullptr); ev.clearPassiveGrabbers(persistentEPD->eventPoint); @@ -2422,6 +2480,11 @@ void QGuiApplicationPrivate::processKeyEvent(QWindowSystemInterfacePrivate::KeyE window = QGuiApplication::focusWindow(); } + if (!window) { + e->eventAccepted = false; + return; + } + #if defined(Q_OS_ANDROID) static bool backKeyPressAccepted = false; static bool menuKeyPressAccepted = false; @@ -2430,7 +2493,7 @@ void QGuiApplicationPrivate::processKeyEvent(QWindowSystemInterfacePrivate::KeyE #if !defined(Q_OS_MACOS) // FIXME: Include OS X in this code path by passing the key event through // QPlatformInputContext::filterEvent(). - if (e->keyType == QEvent::KeyPress && window) { + if (e->keyType == QEvent::KeyPress) { if (QWindowSystemInterface::handleShortcutEvent(window, e->timestamp, e->key, e->modifiers, e->nativeScanCode, e->nativeVirtualKey, e->nativeModifiers, e->unicode, e->repeat, e->repeatCount)) { #if defined(Q_OS_ANDROID) @@ -2447,9 +2510,16 @@ void QGuiApplicationPrivate::processKeyEvent(QWindowSystemInterfacePrivate::KeyE e->unicode, e->repeat, e->repeatCount); ev.setTimestamp(e->timestamp); + const auto *activePopup = activePopupWindow(); + if (activePopup && activePopup != window) { + // If the popup handles the event, we're done. + if (window->d_func()->forwardToPopup(&ev, active_popup_on_press)) + return; + } + // only deliver key events when we have a window, and no modal window is blocking this window - if (window && !window->d_func()->blockedByModalWindow) + if (!window->d_func()->blockedByModalWindow) QGuiApplication::sendSpontaneousEvent(window, &ev); #ifdef Q_OS_ANDROID else @@ -2520,10 +2590,15 @@ void QGuiApplicationPrivate::processFocusWindowEvent(QWindowSystemInterfacePriva if (previous == newFocus) return; - if (newFocus) + bool activatedPopup = false; + if (newFocus) { if (QPlatformWindow *platformWindow = newFocus->handle()) if (platformWindow->isAlertState()) platformWindow->setAlertState(false); + activatedPopup = (newFocus->flags() & Qt::WindowType_Mask) == Qt::Popup; + if (activatedPopup) + activatePopup(newFocus); + } QObject *previousFocusObject = previous ? previous->focusObject() : nullptr; @@ -2538,8 +2613,7 @@ void QGuiApplicationPrivate::processFocusWindowEvent(QWindowSystemInterfacePriva if (previous) { Qt::FocusReason r = e->reason; - if ((r == Qt::OtherFocusReason || r == Qt::ActiveWindowFocusReason) && - newFocus && (newFocus->flags() & Qt::Popup) == Qt::Popup) + if ((r == Qt::OtherFocusReason || r == Qt::ActiveWindowFocusReason) && activatedPopup) r = Qt::PopupFocusReason; QFocusEvent focusOut(QEvent::FocusOut, r); QCoreApplication::sendSpontaneousEvent(previous, &focusOut); @@ -2782,6 +2856,7 @@ void QGuiApplicationPrivate::processTabletEvent(QWindowSystemInterfacePrivate::T } if (!window) return; + active_popup_on_press = activePopupWindow(); pointData.target = window; } else { if (e->nullWindow()) { @@ -2809,12 +2884,25 @@ void QGuiApplicationPrivate::processTabletEvent(QWindowSystemInterfacePrivate::T } } + const auto *activePopup = activePopupWindow(); + if (window->d_func()->blockedByModalWindow && !activePopup) { + // a modal window is blocking this window, don't allow events through + return; + } + QTabletEvent tabletEvent(type, device, local, e->global, e->pressure, e->xTilt, e->yTilt, e->tangentialPressure, e->rotation, e->z, e->modifiers, button, e->buttons); tabletEvent.setAccepted(false); tabletEvent.setTimestamp(e->timestamp); + + if (activePopup && activePopup != window) { + // If the popup handles the event, we're done. + if (window->d_func()->forwardToPopup(&tabletEvent, active_popup_on_press)) + return; + } + QGuiApplication::sendSpontaneousEvent(window, &tabletEvent); pointData.state = e->buttons; if (!tabletEvent.isAccepted() @@ -2987,6 +3075,7 @@ void QGuiApplicationPrivate::processTouchEvent(QWindowSystemInterfacePrivate::To if (!window) window = QGuiApplication::topLevelAt(tempPt.globalPosition().toPoint()); QMutableEventPoint::setWindow(ep, window); + active_popup_on_press = activePopupWindow(); break; case QEventPoint::State::Released: @@ -3057,7 +3146,8 @@ void QGuiApplicationPrivate::processTouchEvent(QWindowSystemInterfacePrivate::To break; } - if (window->d_func()->blockedByModalWindow && !qApp->d_func()->popupActive()) { + const auto *activePopup = activePopupWindow(); + if (window->d_func()->blockedByModalWindow && !activePopup) { // a modal window is blocking this window, don't allow touch events through // QTBUG-37371 temporary fix; TODO: revisit when we have a forwarding solution @@ -3071,6 +3161,12 @@ void QGuiApplicationPrivate::processTouchEvent(QWindowSystemInterfacePrivate::To continue; } + if (activePopup && activePopup != window) { + // If the popup handles the event, we're done. + if (window->d_func()->forwardToPopup(&touchEvent, active_popup_on_press)) + return; + } + // Note: after the call to sendSpontaneousEvent, touchEvent.position() will have // changed to reflect the local position inside the last (random) widget it tried // to deliver the touch event to, and will therefore be invalid afterwards. diff --git a/src/gui/kernel/qguiapplication_p.h b/src/gui/kernel/qguiapplication_p.h index 39c490c5813..81f616e773c 100644 --- a/src/gui/kernel/qguiapplication_p.h +++ b/src/gui/kernel/qguiapplication_p.h @@ -196,8 +196,11 @@ public: virtual Qt::WindowModality defaultModality() const; virtual bool windowNeverBlocked(QWindow *window) const; bool isWindowBlocked(QWindow *window, QWindow **blockingWindow = nullptr) const; - virtual bool popupActive() { return false; } - virtual bool closeAllPopups() { return false; } + static qsizetype popupCount() { return QGuiApplicationPrivate::popup_list.size(); } + static QWindow *activePopupWindow(); + static void activatePopup(QWindow *popup); + static bool closePopup(QWindow *popup); + static bool closeAllPopups(); static Qt::MouseButton mousePressButton; static struct QLastCursorPosition { @@ -258,6 +261,8 @@ public: static QPalette *app_pal; static QWindowList window_list; + static QWindowList popup_list; + static const QWindow *active_popup_on_press; static QWindow *focus_window; #ifndef QT_NO_CURSOR @@ -270,6 +275,7 @@ public: static QString styleOverride; static QStyleHints *styleHints; static bool obey_desktop_settings; + static bool popup_closed_on_press; QInputMethod *inputMethod; QString firstWindowTitle; @@ -340,6 +346,7 @@ private: static void clearPalette(); friend class QDragManager; + friend class QWindowPrivate; static QGuiApplicationPrivate *self; static int m_fakeMouseSourcePointId; diff --git a/src/gui/kernel/qwindow.cpp b/src/gui/kernel/qwindow.cpp index 7c885032c76..229fba954d6 100644 --- a/src/gui/kernel/qwindow.cpp +++ b/src/gui/kernel/qwindow.cpp @@ -27,6 +27,8 @@ #endif // QT_CONFIG(draganddrop) #include <private/qevent_p.h> +#include <private/qeventpoint_p.h> +#include <private/qguiapplication_p.h> #include <QtCore/QTimer> #include <QtCore/QDebug> @@ -37,6 +39,8 @@ QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcPopup) + /*! \class QWindow \inmodule QtGui @@ -186,6 +190,7 @@ QWindow::~QWindow() // Decouple from parent before window goes under setParent(nullptr); QGuiApplicationPrivate::window_list.removeAll(this); + QGuiApplicationPrivate::popup_list.removeAll(this); if (!QGuiApplicationPrivate::is_app_closing) QGuiApplicationPrivate::instance()->modalWindowList.removeOne(this); @@ -411,6 +416,13 @@ void QWindowPrivate::setVisible(bool visible) QGuiApplicationPrivate::updateBlockedStatus(q); } + if (q->type() == Qt::Popup) { + if (visible) + QGuiApplicationPrivate::activatePopup(q); + else + QGuiApplicationPrivate::closePopup(q); + } + #ifndef QT_NO_CURSOR if (visible && (hasCursor || QGuiApplication::overrideCursor())) applyCursor(); @@ -2361,8 +2373,13 @@ bool QWindow::close() if (!isTopLevel()) return false; - if (!d->platformWindow) + if (!d->platformWindow) { + // dock widgets can transition back and forth to being popups; + // avoid getting stuck + if (QGuiApplicationPrivate::activePopupWindow() == this) + QGuiApplicationPrivate::closePopup(this); return true; + } // The window might be deleted during close, // as a result of delivering the close event. @@ -2402,6 +2419,49 @@ bool QWindowPrivate::treatAsVisible() const return q->isVisible(); } +/*! \internal + Returns the popup window that has consumed \a event, if any. + \a activePopupOnPress is the window that we have observed previously handling the press. +*/ +const QWindow *QWindowPrivate::forwardToPopup(QEvent *event, const QWindow */*activePopupOnPress*/) +{ + Q_Q(const QWindow); + qCDebug(lcPopup) << "checking for popup alternative to" << q << "for" << event + << "active popup?" << QGuiApplicationPrivate::activePopupWindow(); + QWindow *ret = nullptr; + if (QWindow *popupWindow = QGuiApplicationPrivate::activePopupWindow()) { + if (q == popupWindow) + return nullptr; // avoid infinite recursion: we're already handling it + if (event->isPointerEvent()) { + // detach eventPoints before modifying them + QScopedPointer<QPointerEvent> pointerEvent(static_cast<QPointerEvent *>(event)->clone()); + for (int i = 0; i < pointerEvent->pointCount(); ++i) { + QEventPoint &eventPoint = pointerEvent->point(i); + const QPoint globalPos = eventPoint.globalPosition().toPoint(); + const QPointF mapped = popupWindow->mapFromGlobal(globalPos); + QMutableEventPoint::setPosition(eventPoint, mapped); + QMutableEventPoint::setScenePosition(eventPoint, mapped); + } + + /* Popups are expected to be able to directly handle the + drag-release sequence after pressing to open, as well as + any other mouse events that occur within the popup's bounds. */ + if (QCoreApplication::sendEvent(popupWindow, pointerEvent.get())) + ret = popupWindow; + qCDebug(lcPopup) << q << "forwarded" << event->type() << "to popup" << popupWindow + << "handled?" << (ret != nullptr) << event->isAccepted(); + return ret; + } else if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { + if (QCoreApplication::sendEvent(popupWindow, event)) + ret = popupWindow; + qCDebug(lcPopup) << q << "forwarded" << event->type() << "to popup" << popupWindow + << "handled?" << (ret != nullptr) << event->isAccepted(); + return ret; + } + } + return ret; +} + /*! The expose event (\a ev) is sent by the window system when a window moves between the un-exposed and exposed states. diff --git a/src/gui/kernel/qwindow_p.h b/src/gui/kernel/qwindow_p.h index 40ab06af8bf..b3722a6ed8b 100644 --- a/src/gui/kernel/qwindow_p.h +++ b/src/gui/kernel/qwindow_p.h @@ -97,6 +97,8 @@ public: virtual bool participatesInLastWindowClosed() const; virtual bool treatAsVisible() const; + const QWindow *forwardToPopup(QEvent *event, const QWindow *activePopupOnPress); + bool isPopup() const { return (windowFlags & Qt::WindowType_Mask) == Qt::Popup; } void setAutomaticPositionAndResizeEnabled(bool a) { positionAutomatic = resizeAutomatic = a; } diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index baae97fc6f3..37df66d7bdd 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -1717,7 +1717,7 @@ void QCocoaWindow::setupPopupMonitor() | NSEventMaskMouseMoved; s_globalMouseMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:mouseButtonMask handler:^(NSEvent *e){ - if (!QGuiApplicationPrivate::instance()->popupActive()) { + if (!QGuiApplicationPrivate::instance()->activePopupWindow()) { removePopupMonitor(); return; } diff --git a/src/plugins/platforms/cocoa/qnswindow.mm b/src/plugins/platforms/cocoa/qnswindow.mm index 74ba6f65ac9..78ad1a7e998 100644 --- a/src/plugins/platforms/cocoa/qnswindow.mm +++ b/src/plugins/platforms/cocoa/qnswindow.mm @@ -351,7 +351,7 @@ NSWindow<QNSWindowProtocol> *qnswindow_cast(NSWindow *window) // not Qt). However, an active popup is expected to grab any mouse event within the // application, so we need to handle those explicitly and trust Qt's isWindowBlocked // implementation to eat events that shouldn't be delivered anyway. - if (isMouseEvent(theEvent) && QGuiApplicationPrivate::instance()->popupActive() + if (isMouseEvent(theEvent) && QGuiApplicationPrivate::instance()->activePopupWindow() && QGuiApplicationPrivate::instance()->isWindowBlocked(m_platformWindow->window(), nullptr)) { qCDebug(lcQpaWindow) << "Mouse event over modally blocked window" << m_platformWindow->window() << "while popup is open - redirecting"; diff --git a/src/widgets/kernel/qapplication.cpp b/src/widgets/kernel/qapplication.cpp index a1392e10dcc..bf6b41cbcd8 100644 --- a/src/widgets/kernel/qapplication.cpp +++ b/src/widgets/kernel/qapplication.cpp @@ -98,6 +98,8 @@ static void initResources() QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcWidgetPopup, "qt.widgets.popup"); + using namespace Qt::StringLiterals; Q_TRACE_PREFIX(qtwidgets, @@ -352,8 +354,6 @@ Q_GLOBAL_STATIC(FontHash, app_fonts) // Exported accessor for use outside of this file FontHash *qt_app_fonts_hash() { return app_fonts(); } -QWidgetList *QApplicationPrivate::popupWidgets = nullptr; // has keyboard input focus - QWidget *qt_desktopWidget = nullptr; // root window widgets /*! @@ -627,8 +627,8 @@ void QApplicationPrivate::initializeWidgetFontHash() QWidget *QApplication::activePopupWidget() { - return QApplicationPrivate::popupWidgets && !QApplicationPrivate::popupWidgets->isEmpty() ? - QApplicationPrivate::popupWidgets->constLast() : nullptr; + auto *win = qobject_cast<QWidgetWindow *>(QGuiApplicationPrivate::activePopupWindow()); + return win ? win->widget() : nullptr; } @@ -1875,7 +1875,7 @@ void QApplicationPrivate::setActiveWindow(QWidget* act) QApplication::sendSpontaneousEvent(w, &activationChange); } - if (QApplicationPrivate::popupWidgets == nullptr) { // !inPopupMode() + if (!inPopupMode()) { // then focus events if (!QApplicationPrivate::active_window && QApplicationPrivate::focus_widget) { QApplicationPrivate::setFocusWidget(nullptr, Qt::ActiveWindowFocusReason); @@ -3299,11 +3299,12 @@ bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e) bool QApplicationPrivate::inPopupMode() { - return QApplicationPrivate::popupWidgets != nullptr; + return QGuiApplicationPrivate::activePopupWindow() != nullptr; } static void ungrabKeyboardForPopup(QWidget *popup) { + qCDebug(lcWidgetPopup) << "ungrab keyboard for" << popup; if (QWidget::keyboardGrabber()) qt_widget_private(QWidget::keyboardGrabber())->stealKeyboardGrab(true); else @@ -3312,6 +3313,7 @@ static void ungrabKeyboardForPopup(QWidget *popup) static void ungrabMouseForPopup(QWidget *popup) { + qCDebug(lcWidgetPopup) << "ungrab mouse for" << popup; if (QWidget::mouseGrabber()) qt_widget_private(QWidget::mouseGrabber())->stealMouseGrab(true); else @@ -3331,54 +3333,23 @@ static void grabForPopup(QWidget *popup) ungrabKeyboardForPopup(popup); } } -} - -extern QWidget *qt_popup_down; -extern bool qt_replay_popup_mouse_event; -extern bool qt_popup_down_closed; - -bool QApplicationPrivate::closeAllPopups() -{ - // Close all popups: In case some popup refuses to close, - // we give up after 1024 attempts (to avoid an infinite loop). - int maxiter = 1024; - QWidget *popup; - while ((popup = QApplication::activePopupWidget()) && maxiter--) - popup->close(); // this will call QApplicationPrivate::closePopup - return true; + qCDebug(lcWidgetPopup) << "grabbed mouse and keyboard?" << popupGrabOk << "for popup" << popup; } void QApplicationPrivate::closePopup(QWidget *popup) { - if (!popupWidgets) + QWindow *win = popup->windowHandle(); + if (!win) + return; + if (!QGuiApplicationPrivate::closePopup(win)) return; - popupWidgets->removeAll(popup); - - if (popup == qt_popup_down) { - qt_button_down = nullptr; - qt_popup_down_closed = true; - qt_popup_down = nullptr; - } - if (QApplicationPrivate::popupWidgets->size() == 0) { // this was the last popup - delete QApplicationPrivate::popupWidgets; - QApplicationPrivate::popupWidgets = nullptr; - qt_popup_down_closed = false; + const QWindow *nextRemainingPopup = QGuiApplicationPrivate::activePopupWindow(); + if (!nextRemainingPopup) { // this was the last popup if (popupGrabOk) { popupGrabOk = false; - // TODO on multi-seat window systems, we have to know which mouse - auto devPriv = QPointingDevicePrivate::get(QPointingDevice::primaryPointingDevice()); - auto mousePressPos = devPriv->pointById(0)->eventPoint.globalPressPosition(); - if (popup->geometry().contains(mousePressPos.toPoint()) - || popup->testAttribute(Qt::WA_NoMouseReplay)) { - // mouse release event or inside - qt_replay_popup_mouse_event = false; - } else { // mouse press event - qt_replay_popup_mouse_event = true; - } - // transfer grab back to mouse grabber if any, otherwise release the grab ungrabMouseForPopup(popup); @@ -3397,30 +3368,23 @@ void QApplicationPrivate::closePopup(QWidget *popup) } } - } else { + } else if (const auto *popupWin = qobject_cast<const QWidgetWindow *>(nextRemainingPopup)) { // A popup was closed, so the previous popup gets the focus. - QWidget* aw = QApplicationPrivate::popupWidgets->constLast(); - if (QWidget *fw = aw->focusWidget()) + if (QWidget *fw = popupWin->widget()->focusWidget()) fw->setFocus(Qt::PopupFocusReason); // can become nullptr due to setFocus() above - if (QApplicationPrivate::popupWidgets && - QApplicationPrivate::popupWidgets->size() == 1) // grab mouse/keyboard - grabForPopup(aw); + if (QGuiApplicationPrivate::popupCount() == 1) // grab mouse/keyboard + grabForPopup(popupWin->widget()); } } -int openPopupCount = 0; - void QApplicationPrivate::openPopup(QWidget *popup) { - openPopupCount++; - if (!popupWidgets) // create list - popupWidgets = new QWidgetList; - popupWidgets->append(popup); // add to end of list + QGuiApplicationPrivate::activatePopup(popup->windowHandle()); - if (QApplicationPrivate::popupWidgets->size() == 1) // grab mouse/keyboard + if (QGuiApplicationPrivate::popupCount() == 1) // grab mouse/keyboard grabForPopup(popup); // popups are not focus-handled by the window system (the first @@ -3428,7 +3392,7 @@ void QApplicationPrivate::openPopup(QWidget *popup) // new popup gets the focus if (popup->focusWidget()) { popup->focusWidget()->setFocus(Qt::PopupFocusReason); - } else if (popupWidgets->size() == 1) { // this was the first popup + } else if (QGuiApplicationPrivate::popupCount() == 1) { // this was the first popup if (QWidget *fw = QApplication::focusWidget()) { QFocusEvent e(QEvent::FocusOut, Qt::PopupFocusReason); QCoreApplication::sendEvent(fw, &e); diff --git a/src/widgets/kernel/qapplication_p.h b/src/widgets/kernel/qapplication_p.h index 21b1605dfc2..7de9f54b587 100644 --- a/src/widgets/kernel/qapplication_p.h +++ b/src/widgets/kernel/qapplication_p.h @@ -107,8 +107,6 @@ public: static void setActiveWindow(QWidget* act); static bool inPopupMode(); - bool popupActive() override { return inPopupMode(); } - bool closeAllPopups() override; void closePopup(QWidget *popup); void openPopup(QWidget *popup); static void setFocusWidget(QWidget *focus, Qt::FocusReason reason); diff --git a/src/widgets/kernel/qwidgetwindow.cpp b/src/widgets/kernel/qwidgetwindow.cpp index 03dde9ca69e..33e376460f8 100644 --- a/src/widgets/kernel/qwidgetwindow.cpp +++ b/src/widgets/kernel/qwidgetwindow.cpp @@ -27,9 +27,8 @@ Q_WIDGETS_EXPORT QWidget *qt_button_down = nullptr; // widget got last button-do // popup control QWidget *qt_popup_down = nullptr; // popup that contains the pressed widget -extern int openPopupCount; bool qt_popup_down_closed = false; // qt_popup_down has been closed -bool qt_replay_popup_mouse_event = false; + extern bool qt_try_modal(QWidget *widget, QEvent::Type type); class QWidgetWindowPrivate : public QWindowPrivate @@ -532,11 +531,8 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event) break; // nothing for mouse move } - int oldOpenPopupCount = openPopupCount; - if (activePopupWidget->isEnabled()) { // deliver event - qt_replay_popup_mouse_event = false; QPointer<QWidget> receiver = activePopupWidget; QPointF widgetPos = mapped; if (qt_button_down) @@ -588,56 +584,6 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event) } } - if (QApplication::activePopupWidget() != activePopupWidget - && qt_replay_popup_mouse_event - && QGuiApplicationPrivate::platformIntegration()->styleHint(QPlatformIntegration::ReplayMousePressOutsidePopup).toBool()) { - if (m_widget->windowType() != Qt::Popup) - qt_button_down = nullptr; - if (event->type() == QEvent::MouseButtonPress) { - // the popup disappeared, replay the mouse press event - QWidget *w = QApplication::widgetAt(event->globalPosition().toPoint()); - if (w && !QApplicationPrivate::isBlockedByModal(w)) { - // activate window of the widget under mouse pointer - if (!w->isActiveWindow()) { - w->activateWindow(); - w->window()->raise(); - } - - if (auto win = qt_widget_private(w)->windowHandle(QWidgetPrivate::WindowHandleMode::Closest)) { - const QRect globalGeometry = win->isTopLevel() - ? win->geometry() - : QRect(win->mapToGlobal(QPoint(0, 0)), win->size()); - if (globalGeometry.contains(event->globalPosition().toPoint())) { - // Use postEvent() to ensure the local QEventLoop terminates when called from QMenu::exec() - const QPoint localPos = win->mapFromGlobal(event->globalPosition().toPoint()); - QMouseEvent *e = new QMouseEvent(QEvent::MouseButtonPress, localPos, localPos, event->globalPosition().toPoint(), - event->button(), event->buttons(), event->modifiers(), event->source()); - QCoreApplicationPrivate::setEventSpontaneous(e, true); - e->setTimestamp(event->timestamp()); - QCoreApplication::postEvent(win, e); - } - } - } - } - qt_replay_popup_mouse_event = false; -#ifndef QT_NO_CONTEXTMENU - } else if (event->type() == QGuiApplicationPrivate::contextMenuEventType() - && event->button() == Qt::RightButton - && (openPopupCount == oldOpenPopupCount)) { - QWidget *receiver = activePopupWidget; - if (qt_button_down) - receiver = qt_button_down; - else if (popupChild) - receiver = popupChild; - const QPoint localPos = receiver->mapFromGlobal(event->globalPosition().toPoint()); - QContextMenuEvent e(QContextMenuEvent::Mouse, localPos, event->globalPosition().toPoint(), event->modifiers()); - QApplication::forwardEvent(receiver, &e, event); - } -#else - Q_UNUSED(oldOpenPopupCount); - } -#endif - if (releaseAfter) { qt_button_down = nullptr; qt_popup_down_closed = false; @@ -667,6 +613,11 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event) if (!receiver) return; + if (d_func()->isPopup() && receiver->window()->windowHandle() != this) { + receiver = widget; + mapped = event->position().toPoint(); + } + if ((event->type() != QEvent::MouseButtonPress) || !QMutableSinglePointEvent::from(event)->isDoubleClick()) { // The preceding statement excludes MouseButtonPress events which caused @@ -859,6 +810,10 @@ void QWidgetWindow::handleResizeEvent(QResizeEvent *event) void QWidgetWindow::closeEvent(QCloseEvent *event) { Q_D(QWidgetWindow); + if (qt_popup_down == m_widget) { + qt_popup_down = nullptr; + qt_popup_down_closed = true; + } bool accepted = m_widget->d_func()->handleClose(d->inClose ? QWidgetPrivate::CloseWithEvent : QWidgetPrivate::CloseWithSpontaneousEvent); event->setAccepted(accepted); diff --git a/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp b/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp index a9e2c5f882f..227fd77e1a4 100644 --- a/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp +++ b/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp @@ -1509,6 +1509,7 @@ void tst_QWindow::touchCancelWithTouchToMouse() void tst_QWindow::touchInterruptedByPopup() { InputTestWindow window; + window.setObjectName("main"); window.setTitle(QLatin1String(QTest::currentTestFunction())); window.setGeometry(QRect(m_availableTopLeft + QPoint(80, 80), m_testWindowSize)); window.show(); @@ -1529,6 +1530,7 @@ void tst_QWindow::touchInterruptedByPopup() // Launch a popup window InputTestWindow popup; + window.setObjectName("popup"); popup.setFlags(Qt::Popup); popup.setModality(Qt::WindowModal); popup.resize(m_testWindowSize / 2); @@ -1551,9 +1553,6 @@ void tst_QWindow::touchInterruptedByPopup() QWindowSystemInterface::handleTouchEvent(&window, touchDevice, points); QCoreApplication::processEvents(); QTRY_COMPARE(window.touchReleasedCount, 0); - - // Due to temporary fix for QTBUG-37371: the original window should receive a TouchCancel - QTRY_COMPARE(window.touchEventType, QEvent::TouchCancel); } void tst_QWindow::orientation() diff --git a/tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp b/tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp index e771737ae05..1fab69fdcc1 100644 --- a/tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp +++ b/tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp @@ -1634,8 +1634,7 @@ void tst_QWidget_window::mouseMoveWithPopup() // but the release event will still be delivered to the first popup - dialogs might not get it QCOMPARE(mouseAction(Qt::LeftButton), QEvent::MouseButtonRelease); - if (topLevel.popup->mouseReleaseCount != 1 - && !QGuiApplication::platformName().startsWith(QLatin1String("windows"), Qt::CaseInsensitive)) + if (topLevel.popup->mouseReleaseCount != 1) QEXPECT_FAIL("Dialog", "Platform specific behavior", Continue); QCOMPARE(topLevel.popup->mouseReleaseCount, 1); } |