diff options
-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); } |