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 <richard.gustavsen@qt.io>
This commit is contained in:
Shawn Rutledge 2023-03-22 10:11:44 +01:00
parent 1bc78f7739
commit e4ef0f03e6
11 changed files with 214 additions and 134 deletions

View File

@ -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.

View File

@ -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;

View File

@ -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.

View File

@ -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; }

View File

@ -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;
}

View File

@ -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";

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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()

View File

@ -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);
}