xcb: Deliver mouse enter event to window when closing modal window
When a modal window is closed and the mouse is not under the modal window - find a proper window and send a fake enter event. Added auto test for checking enter event on window when modal window is closed. Task-number: QTBUG-35109 Change-Id: I370b52d386503820ac9de21e6d05fd019ca456ec Reviewed-by: Shawn Rutledge <shawn.rutledge@theqtcompany.com>bb10
parent
63b5f5cb98
commit
7091be1b79
|
|
@ -50,6 +50,7 @@
|
|||
#include "qxcbsystemtraytracker.h"
|
||||
|
||||
#include <qpa/qplatformintegration.h>
|
||||
#include <qpa/qplatformcursor.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
|
|
@ -261,6 +262,26 @@ static inline XTextProperty* qstringToXTP(Display *dpy, const QString& s)
|
|||
}
|
||||
#endif // XCB_USE_XLIB
|
||||
|
||||
// TODO move this into a utility function in QWindow or QGuiApplication
|
||||
static QWindow *childWindowAt(QWindow *win, const QPoint &p)
|
||||
{
|
||||
foreach (QObject *obj, win->children()) {
|
||||
if (obj->isWindowType()) {
|
||||
QWindow *childWin = static_cast<QWindow *>(obj);
|
||||
if (childWin->isVisible()) {
|
||||
if (QWindow *recurse = childWindowAt(childWin, p))
|
||||
return recurse;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!win->isTopLevel()
|
||||
&& !(win->flags() & Qt::WindowTransparentForInput)
|
||||
&& win->geometry().contains(win->parent()->mapFromGlobal(p))) {
|
||||
return win;
|
||||
}
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
static const char *wm_window_type_property_id = "_q_xcb_wm_window_type";
|
||||
|
||||
QXcbWindow::QXcbWindow(QWindow *window)
|
||||
|
|
@ -855,6 +876,33 @@ void QXcbWindow::hide()
|
|||
connection()->setMouseGrabber(Q_NULLPTR);
|
||||
|
||||
m_mapped = false;
|
||||
|
||||
// Hiding a modal window doesn't send an enter event to its transient parent when the
|
||||
// mouse is already over the parent window, so the enter event must be emulated.
|
||||
if (window()->isModal()) {
|
||||
// Get the cursor position at modal window screen
|
||||
const QPoint nativePos = xcbScreen()->cursor()->pos();
|
||||
const QPoint cursorPos = QHighDpi::fromNativePixels(nativePos, xcbScreen()->screenForPosition(nativePos)->screen());
|
||||
|
||||
// Find the top level window at cursor position.
|
||||
// Don't use QGuiApplication::topLevelAt(): search only the virtual siblings of this window's screen
|
||||
QWindow *enterWindow = Q_NULLPTR;
|
||||
foreach (QPlatformScreen *screen, xcbScreen()->virtualSiblings()) {
|
||||
if (screen->geometry().contains(cursorPos)) {
|
||||
const QPoint devicePosition = QHighDpi::toNativePixels(cursorPos, screen->screen());
|
||||
enterWindow = screen->topLevelAt(devicePosition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (enterWindow && enterWindow != window()) {
|
||||
// Find the child window at cursor position, otherwise use the top level window
|
||||
if (QWindow *childWindow = childWindowAt(enterWindow, cursorPos))
|
||||
enterWindow = childWindow;
|
||||
const QPoint localPos = enterWindow->mapFromGlobal(cursorPos);
|
||||
QWindowSystemInterface::handleEnterEvent(enterWindow, localPos, cursorPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static QWindow *tlWindow(QWindow *window)
|
||||
|
|
|
|||
|
|
@ -6,3 +6,5 @@ ubuntu-14.04
|
|||
ubuntu-14.04
|
||||
[modalWithChildWindow]
|
||||
ubuntu-14.04
|
||||
[modalWindowEnterEventOnHide_QTBUG35109]
|
||||
ubuntu-14.04
|
||||
|
|
|
|||
|
|
@ -92,6 +92,9 @@ private slots:
|
|||
void modalWithChildWindow();
|
||||
void modalWindowModallity();
|
||||
void modalWindowPosition();
|
||||
#ifndef QT_NO_CURSOR
|
||||
void modalWindowEnterEventOnHide_QTBUG35109();
|
||||
#endif
|
||||
void windowsTransientChildren();
|
||||
void requestUpdate();
|
||||
void initTestCase();
|
||||
|
|
@ -706,10 +709,24 @@ public:
|
|||
}
|
||||
}
|
||||
}
|
||||
bool event(QEvent *e) {
|
||||
switch (e->type()) {
|
||||
case QEvent::Enter:
|
||||
++enterEventCount;
|
||||
break;
|
||||
case QEvent::Leave:
|
||||
++leaveEventCount;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QWindow::event(e);
|
||||
}
|
||||
void resetCounters() {
|
||||
mousePressedCount = mouseReleasedCount = mouseMovedCount = mouseDoubleClickedCount = 0;
|
||||
mouseSequenceSignature = QString();
|
||||
touchPressedCount = touchReleasedCount = touchMovedCount = 0;
|
||||
enterEventCount = leaveEventCount = 0;
|
||||
}
|
||||
|
||||
InputTestWindow() {
|
||||
|
|
@ -727,6 +744,7 @@ public:
|
|||
QPointF mousePressScreenPos, mouseMoveScreenPos, mousePressLocalPos;
|
||||
int touchPressedCount, touchReleasedCount, touchMovedCount;
|
||||
QEvent::Type touchEventType;
|
||||
int enterEventCount, leaveEventCount;
|
||||
|
||||
bool ignoreMouse, ignoreTouch;
|
||||
|
||||
|
|
@ -1732,6 +1750,181 @@ void tst_QWindow::modalWindowPosition()
|
|||
QCOMPARE(window.geometry(), origGeo);
|
||||
}
|
||||
|
||||
#ifndef QT_NO_CURSOR
|
||||
void tst_QWindow::modalWindowEnterEventOnHide_QTBUG35109()
|
||||
{
|
||||
if (QGuiApplication::platformName() == QLatin1String("cocoa"))
|
||||
QSKIP("This test fails on OS X on CI");
|
||||
|
||||
const QPoint center = QGuiApplication::primaryScreen()->availableGeometry().center();
|
||||
|
||||
const int childOffset = 16;
|
||||
const QPoint rootPos = center - QPoint(m_testWindowSize.width(),
|
||||
m_testWindowSize.height())/2;
|
||||
const QPoint modalPos = rootPos + QPoint(childOffset * 5,
|
||||
childOffset * 5);
|
||||
const QPoint cursorPos = rootPos - QPoint(80, 80);
|
||||
|
||||
// Test whether tlw can receive the enter event
|
||||
{
|
||||
QCursor::setPos(cursorPos);
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
InputTestWindow root;
|
||||
root.setTitle(__FUNCTION__);
|
||||
root.setGeometry(QRect(rootPos, m_testWindowSize));
|
||||
root.show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&root));
|
||||
root.requestActivate();
|
||||
QVERIFY(QTest::qWaitForWindowActive(&root));
|
||||
|
||||
// Move the mouse over the root window, but not over the modal window.
|
||||
QCursor::setPos(rootPos + QPoint(childOffset * 5 / 2,
|
||||
childOffset * 5 / 2));
|
||||
|
||||
// Wait for the enter event. It must be delivered here, otherwise second
|
||||
// compare can PASS because of this event even after "resetCounters()".
|
||||
QTRY_COMPARE(root.enterEventCount, 1);
|
||||
QTRY_COMPARE(root.leaveEventCount, 0);
|
||||
|
||||
QWindow modal;
|
||||
modal.setTitle(QLatin1String("Modal - ") + __FUNCTION__);
|
||||
modal.setTransientParent(&root);
|
||||
modal.resize(m_testWindowSize/2);
|
||||
modal.setFramePosition(modalPos);
|
||||
modal.setModality(Qt::ApplicationModal);
|
||||
modal.show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&modal));
|
||||
modal.requestActivate();
|
||||
QVERIFY(QTest::qWaitForWindowActive(&modal));
|
||||
|
||||
QCoreApplication::processEvents();
|
||||
QTRY_COMPARE(root.leaveEventCount, 1);
|
||||
|
||||
root.resetCounters();
|
||||
modal.close();
|
||||
|
||||
// Check for the enter event
|
||||
QTRY_COMPARE(root.enterEventCount, 1);
|
||||
}
|
||||
|
||||
// Test whether child window can receive the enter event
|
||||
{
|
||||
QCursor::setPos(cursorPos);
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
QWindow root;
|
||||
root.setTitle(__FUNCTION__);
|
||||
root.setGeometry(QRect(rootPos, m_testWindowSize));
|
||||
|
||||
QWindow childLvl1;
|
||||
childLvl1.setParent(&root);
|
||||
childLvl1.setGeometry(childOffset,
|
||||
childOffset,
|
||||
m_testWindowSize.width() - childOffset,
|
||||
m_testWindowSize.height() - childOffset);
|
||||
|
||||
InputTestWindow childLvl2;
|
||||
childLvl2.setParent(&childLvl1);
|
||||
childLvl2.setGeometry(childOffset,
|
||||
childOffset,
|
||||
childLvl1.width() - childOffset,
|
||||
childLvl1.height() - childOffset);
|
||||
|
||||
root.show();
|
||||
childLvl1.show();
|
||||
childLvl2.show();
|
||||
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&root));
|
||||
root.requestActivate();
|
||||
QVERIFY(QTest::qWaitForWindowActive(&root));
|
||||
QVERIFY(childLvl1.isVisible());
|
||||
QVERIFY(childLvl2.isVisible());
|
||||
|
||||
// Move the mouse over the child window, but not over the modal window.
|
||||
// Be sure that the value is almost left-top of second child window for
|
||||
// checking proper position mapping.
|
||||
QCursor::setPos(rootPos + QPoint(childOffset * 5 / 2,
|
||||
childOffset * 5 / 2));
|
||||
|
||||
// Wait for the enter event. It must be delivered here, otherwise second
|
||||
// compare can PASS because of this event even after "resetCounters()".
|
||||
QTRY_COMPARE(childLvl2.enterEventCount, 1);
|
||||
QTRY_COMPARE(childLvl2.leaveEventCount, 0);
|
||||
|
||||
QWindow modal;
|
||||
modal.setTitle(QLatin1String("Modal - ") + __FUNCTION__);
|
||||
modal.setTransientParent(&root);
|
||||
modal.resize(m_testWindowSize/2);
|
||||
modal.setFramePosition(modalPos);
|
||||
modal.setModality(Qt::ApplicationModal);
|
||||
modal.show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&modal));
|
||||
modal.requestActivate();
|
||||
QVERIFY(QTest::qWaitForWindowActive(&modal));
|
||||
|
||||
QCoreApplication::processEvents();
|
||||
QTRY_COMPARE(childLvl2.leaveEventCount, 1);
|
||||
|
||||
childLvl2.resetCounters();
|
||||
modal.close();
|
||||
|
||||
// Check for the enter event
|
||||
QTRY_COMPARE(childLvl2.enterEventCount, 1);
|
||||
}
|
||||
|
||||
// Test whether tlw can receive the enter event if mouse is over the invisible child windnow
|
||||
{
|
||||
QCursor::setPos(cursorPos);
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
InputTestWindow root;
|
||||
root.setTitle(__FUNCTION__);
|
||||
root.setGeometry(QRect(rootPos, m_testWindowSize));
|
||||
|
||||
QWindow child;
|
||||
child.setParent(&root);
|
||||
child.setGeometry(QRect(QPoint(), m_testWindowSize));
|
||||
|
||||
root.show();
|
||||
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&root));
|
||||
root.requestActivate();
|
||||
QVERIFY(QTest::qWaitForWindowActive(&root));
|
||||
QVERIFY(!child.isVisible());
|
||||
|
||||
// Move the mouse over the child window, but not over the modal window.
|
||||
QCursor::setPos(rootPos + QPoint(childOffset * 5 / 2,
|
||||
childOffset * 5 / 2));
|
||||
|
||||
// Wait for the enter event. It must be delivered here, otherwise second
|
||||
// compare can PASS because of this event even after "resetCounters()".
|
||||
QTRY_COMPARE(root.enterEventCount, 1);
|
||||
QTRY_COMPARE(root.leaveEventCount, 0);
|
||||
|
||||
QWindow modal;
|
||||
modal.setTitle(QLatin1String("Modal - ") + __FUNCTION__);
|
||||
modal.setTransientParent(&root);
|
||||
modal.resize(m_testWindowSize/2);
|
||||
modal.setFramePosition(modalPos);
|
||||
modal.setModality(Qt::ApplicationModal);
|
||||
modal.show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&modal));
|
||||
modal.requestActivate();
|
||||
QVERIFY(QTest::qWaitForWindowActive(&modal));
|
||||
|
||||
QCoreApplication::processEvents();
|
||||
QTRY_COMPARE(root.leaveEventCount, 1);
|
||||
|
||||
root.resetCounters();
|
||||
modal.close();
|
||||
|
||||
// Check for the enter event
|
||||
QTRY_COMPARE(root.enterEventCount, 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
class ColoredWindow : public QRasterWindow {
|
||||
public:
|
||||
explicit ColoredWindow(const QColor &color, QWindow *parent = 0) : QRasterWindow(parent), m_color(color) {}
|
||||
|
|
|
|||
Loading…
Reference in New Issue