macOS: send enter/leave when a window opens/closes
Since macOS doesn't give us any event when a modal window opens, we need to do so ourselves explicitly so that the current mouse window gets a leave event when e.g. a popup opens, and an enter event when the popup closes again. The case for modal dialogs is partially handled by QGuiApplication already. Note: We cannot rely on the transientParent of the opening/closing window, as it's nullptr for QMenu windows even if the QMenu has a widget parent. Add a test for enter/leave events when a secondary window opens, covering both the dialog and the popup case. For the dialog case, we sometimes get two Enter events when the dailog closes, which we have to tolerate for now. To make the test pass on b2qt platforms, fix the offscreen plugin to explicitly send enter/leave events in the same way as Cocoa now does. Fixes: QTBUG-78970 Pick-to: 6.2 Change-Id: If45e43e625e8362c3502c740154f6a6a8962b9e9 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
parent
8da42e1af6
commit
a5e5943d8a
@ -1225,12 +1225,32 @@ void QCocoaWindow::windowDidResignKey()
|
||||
|
||||
void QCocoaWindow::windowDidOrderOnScreen()
|
||||
{
|
||||
// The current mouse window needs to get a leave event when a popup window opens.
|
||||
// For modal dialogs, QGuiApplicationPrivate::showModalWindow takes care of this.
|
||||
if (QWindowPrivate::get(window())->isPopup()) {
|
||||
QWindowSystemInterface::handleLeaveEvent<QWindowSystemInterface::SynchronousDelivery>
|
||||
(QGuiApplicationPrivate::currentMouseWindow);
|
||||
}
|
||||
|
||||
[m_view setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
void QCocoaWindow::windowDidOrderOffScreen()
|
||||
{
|
||||
handleExposeEvent(QRegion());
|
||||
// We are closing a window, so the window that is now under the mouse
|
||||
// might need to get an Enter event if it isn't already the mouse window.
|
||||
if (window()->type() & Qt::Window) {
|
||||
const QPointF screenPoint = QCocoaScreen::mapFromNative([NSEvent mouseLocation]);
|
||||
if (QWindow *windowUnderMouse = QGuiApplication::topLevelAt(screenPoint.toPoint())) {
|
||||
if (windowUnderMouse != QGuiApplicationPrivate::instance()->currentMouseWindow) {
|
||||
const auto windowPoint = windowUnderMouse->mapFromGlobal(screenPoint);
|
||||
// asynchronous delivery on purpose
|
||||
QWindowSystemInterface::handleEnterEvent<QWindowSystemInterface::AsynchronousDelivery>
|
||||
(windowUnderMouse, windowPoint, screenPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QCocoaWindow::windowDidChangeOcclusionState()
|
||||
|
@ -44,6 +44,7 @@
|
||||
#include <qpa/qwindowsysteminterface.h>
|
||||
|
||||
#include <private/qwindow_p.h>
|
||||
#include <private/qguiapplication_p.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
@ -129,11 +130,26 @@ void QOffscreenWindow::setVisible(bool visible)
|
||||
}
|
||||
}
|
||||
|
||||
const QPoint cursorPos = QCursor::pos();
|
||||
if (visible) {
|
||||
QRect rect(QPoint(), geometry().size());
|
||||
QWindowSystemInterface::handleExposeEvent(window(), rect);
|
||||
if (QWindowPrivate::get(window())->isPopup() && QGuiApplicationPrivate::currentMouseWindow) {
|
||||
QWindowSystemInterface::handleLeaveEvent<QWindowSystemInterface::SynchronousDelivery>
|
||||
(QGuiApplicationPrivate::currentMouseWindow);
|
||||
}
|
||||
if (geometry().contains(cursorPos))
|
||||
QWindowSystemInterface::handleEnterEvent(window(),
|
||||
window()->mapFromGlobal(cursorPos), cursorPos);
|
||||
} else {
|
||||
QWindowSystemInterface::handleExposeEvent(window(), QRegion());
|
||||
if (window()->type() & Qt::Window) {
|
||||
if (QWindow *windowUnderMouse = QGuiApplication::topLevelAt(cursorPos)) {
|
||||
QWindowSystemInterface::handleEnterEvent(windowUnderMouse,
|
||||
windowUnderMouse->mapFromGlobal(cursorPos),
|
||||
cursorPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_visible = visible;
|
||||
|
@ -358,6 +358,8 @@ private slots:
|
||||
void maskedUpdate();
|
||||
#ifndef QT_NO_CURSOR
|
||||
void syntheticEnterLeave();
|
||||
void enterLeaveOnWindowShowHide_data();
|
||||
void enterLeaveOnWindowShowHide();
|
||||
void taskQTBUG_4055_sendSyntheticEnterLeave();
|
||||
void underMouse();
|
||||
void taskQTBUG_27643_enterEvents();
|
||||
@ -9838,6 +9840,124 @@ void tst_QWidget::syntheticEnterLeave()
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef QT_NO_CURSOR
|
||||
void tst_QWidget::enterLeaveOnWindowShowHide_data()
|
||||
{
|
||||
QTest::addColumn<Qt::WindowType>("windowType");
|
||||
QTest::addRow("dialog") << Qt::Dialog;
|
||||
QTest::addRow("popup") << Qt::Popup;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Verify that a window that has the mouse gets a leave event
|
||||
when a dialog or popup opens (even if that dialog or popup is
|
||||
not under the mouse), and an enter event when the secondary window
|
||||
closes again (while the mouse is still over the original widget.
|
||||
|
||||
Since mouse grabbing might cause some event interaction, simulate
|
||||
the opening of the secondary window from a mouse press, like we would with
|
||||
a button or context menu. See QTBUG-78970.
|
||||
*/
|
||||
void tst_QWidget::enterLeaveOnWindowShowHide()
|
||||
{
|
||||
QFETCH(Qt::WindowType, windowType);
|
||||
class Widget : public QWidget
|
||||
{
|
||||
public:
|
||||
int numEnterEvents = 0;
|
||||
int numLeaveEvents = 0;
|
||||
QPoint enterPosition;
|
||||
Qt::WindowType secondaryWindowType = {};
|
||||
protected:
|
||||
void enterEvent(QEnterEvent *e) override
|
||||
{
|
||||
enterPosition = e->position().toPoint();
|
||||
++numEnterEvents;
|
||||
}
|
||||
void leaveEvent(QEvent *) override
|
||||
{
|
||||
enterPosition = {};
|
||||
++numLeaveEvents;
|
||||
}
|
||||
void mousePressEvent(QMouseEvent *e) override
|
||||
{
|
||||
QWidget *secondary = nullptr;
|
||||
switch (secondaryWindowType) {
|
||||
case Qt::Dialog: {
|
||||
QDialog *dialog = new QDialog(this);
|
||||
dialog->setModal(true);
|
||||
dialog->setWindowModality(Qt::ApplicationModal);
|
||||
secondary = dialog;
|
||||
break;
|
||||
}
|
||||
case Qt::Popup: {
|
||||
QMenu *menu = new QMenu(this);
|
||||
menu->addAction("Action 1");
|
||||
menu->addAction("Action 2");
|
||||
secondary = menu;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
QVERIFY2(false, "Test case not implemented for window type");
|
||||
break;
|
||||
}
|
||||
|
||||
QPoint secondaryPos = e->globalPosition().toPoint();
|
||||
if (e->button() == Qt::LeftButton)
|
||||
secondaryPos += QPoint(10, 10); // cursor outside secondary
|
||||
else
|
||||
secondaryPos -= QPoint(10, 10); // cursor inside secondary
|
||||
secondary->move(secondaryPos);
|
||||
secondary->show();
|
||||
if (!QTest::qWaitForWindowExposed(secondary))
|
||||
QEXPECT_FAIL("", "Secondary window failed to show, test will fail", Abort);
|
||||
}
|
||||
};
|
||||
|
||||
int expectedEnter = 0;
|
||||
int expectedLeave = 0;
|
||||
|
||||
Widget widget;
|
||||
widget.secondaryWindowType = windowType;
|
||||
const QRect screenGeometry = widget.screen()->availableGeometry();
|
||||
const QPoint cursorPos = screenGeometry.topLeft() + QPoint(50, 50);
|
||||
widget.setGeometry(QRect(cursorPos - QPoint(50, 50), screenGeometry.size() / 4));
|
||||
QCursor::setPos(cursorPos);
|
||||
|
||||
if (!QTest::qWaitFor([&]{ return widget.geometry().contains(QCursor::pos()); }))
|
||||
QSKIP("We can't move the cursor");
|
||||
widget.show();
|
||||
QApplication::setActiveWindow(&widget);
|
||||
QVERIFY(QTest::qWaitForWindowActive(&widget));
|
||||
|
||||
++expectedEnter;
|
||||
QTRY_COMPARE_WITH_TIMEOUT(widget.numEnterEvents, expectedEnter, 250);
|
||||
QCOMPARE(widget.enterPosition, widget.mapFromGlobal(cursorPos));
|
||||
QVERIFY(widget.underMouse());
|
||||
|
||||
QTest::mouseClick(&widget, Qt::LeftButton, {}, widget.mapFromGlobal(cursorPos));
|
||||
++expectedLeave;
|
||||
QTRY_COMPARE_WITH_TIMEOUT(widget.numLeaveEvents, expectedLeave, 500);
|
||||
QVERIFY(!widget.underMouse());
|
||||
if (QApplication::activeModalWidget())
|
||||
QApplication::activeModalWidget()->close();
|
||||
else if (QApplication::activePopupWidget())
|
||||
QApplication::activePopupWidget()->close();
|
||||
++expectedEnter;
|
||||
// Use default timeout, the test is flaky on Windows otherwise.
|
||||
QVERIFY(QTest::qWaitFor([&]{ return widget.numEnterEvents >= expectedEnter; }));
|
||||
// When a modal dialog closes we might get more than one enter event on macOS.
|
||||
// This seems to depend on timing, so we tolerate that flakiness for now.
|
||||
if (widget.numEnterEvents > expectedEnter && QGuiApplication::platformName() == "cocoa")
|
||||
QEXPECT_FAIL("dialog", "On macOS, we might get more than one Enter event", Continue);
|
||||
|
||||
QCOMPARE(widget.numEnterEvents, expectedEnter);
|
||||
QCOMPARE(widget.enterPosition, widget.mapFromGlobal(cursorPos));
|
||||
QVERIFY(widget.underMouse());
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef QT_NO_CURSOR
|
||||
void tst_QWidget::taskQTBUG_4055_sendSyntheticEnterLeave()
|
||||
{
|
||||
@ -11281,9 +11401,10 @@ void tst_QWidget::underMouse()
|
||||
QCOMPARE(QApplication::activePopupWidget(), &popupWidget);
|
||||
|
||||
// Send an artificial leave event for window, as it won't get generated automatically
|
||||
// due to cursor not actually being over the window.
|
||||
QWindowSystemInterface::handleLeaveEvent(window);
|
||||
QApplication::processEvents();
|
||||
// due to cursor not actually being over the window. The Cocoa and offscreen plugins
|
||||
// do this for us.
|
||||
if (QGuiApplication::platformName() != "cocoa" && QGuiApplication::platformName() != "offscreen")
|
||||
QWindowSystemInterface::handleLeaveEvent<QWindowSystemInterface::SynchronousDelivery>(window);
|
||||
|
||||
// If there is an active popup, undermouse should not be reported (QTBUG-27478),
|
||||
// but opening a popup causes leave for widgets under mouse.
|
||||
|
Loading…
Reference in New Issue
Block a user