Commit 57ecd5ae authored by Błażej Szczygieł's avatar Błażej Szczygieł Committed by Shawn Rutledge
Browse files

QtWidgets: Proper delivery of enter/leave event to context menus


First-level context menu grabs the mouse, so all mouse events are
delivered to it. This menu passes the mouse events to submenus. Any
platform delivers mouse enter/leave event differently when window is
grabbed. This patch unifies event delivery to context menus - it can
block some unwanted events and it emulates fake events if necessary.

This patch can reduce duplicated events and can provide proper enter
or leave event to additional widgets in the context menu. It can also
prevent submenu from unwanted close on Windows and X11.

Added autotest.

Task-number: QTBUG-45565
Task-number: QTBUG-45893
Task-number: QTBUG-47515
Change-Id: I7dd476d0be23afa34e947e54aef235012d173dcf
Reviewed-by: default avatarShawn Rutledge <shawn.rutledge@theqtcompany.com>
Showing with 151 additions and 12 deletions
......@@ -313,6 +313,14 @@ QPointer<QWidget> qt_last_mouse_receiver = 0;
void QWidgetWindow::handleEnterLeaveEvent(QEvent *event)
{
#if !defined(Q_OS_OSX) && !defined(Q_OS_IOS) // Cocoa tracks popups
// Ignore all enter/leave events from QPA if we are not on the first-level context menu.
// This prevents duplicated events on most platforms. Fake events will be delivered in
// QWidgetWindow::handleMouseEvent(QMouseEvent *). Make an exception whether the widget
// is already under mouse - let the mouse leave.
if (QApplicationPrivate::inPopupMode() && m_widget != QApplication::activePopupWidget() && !m_widget->underMouse())
return;
#endif
if (event->type() == QEvent::Leave) {
QWidget *enter = 0;
// Check from window system event queue if the next queued enter targets a window
......@@ -407,14 +415,13 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event)
QEvent::MouseButtonRelease : QEvent::MouseButtonPress;
if (qApp->d_func()->inPopupMode()) {
QWidget *activePopupWidget = qApp->activePopupWidget();
QWidget *popup = activePopupWidget;
QPoint mapped = event->pos();
if (popup != m_widget)
mapped = popup->mapFromGlobal(event->globalPos());
if (activePopupWidget != m_widget)
mapped = activePopupWidget->mapFromGlobal(event->globalPos());
bool releaseAfter = false;
QWidget *popupChild = popup->childAt(mapped);
QWidget *popupChild = activePopupWidget->childAt(mapped);
if (popup != qt_popup_down) {
if (activePopupWidget != qt_popup_down) {
qt_button_down = 0;
qt_popup_down = 0;
}
......@@ -423,7 +430,7 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event)
case QEvent::MouseButtonPress:
case QEvent::MouseButtonDblClick:
qt_button_down = popupChild;
qt_popup_down = popup;
qt_popup_down = activePopupWidget;
break;
case QEvent::MouseButtonRelease:
releaseAfter = true;
......@@ -434,18 +441,41 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event)
int oldOpenPopupCount = openPopupCount;
if (popup->isEnabled()) {
if (activePopupWidget->isEnabled()) {
// deliver event
qt_replay_popup_mouse_event = false;
QWidget *receiver = popup;
QWidget *receiver = activePopupWidget;
QPoint widgetPos = mapped;
if (qt_button_down)
receiver = qt_button_down;
else if (popupChild)
receiver = popupChild;
if (receiver != popup)
if (receiver != activePopupWidget)
widgetPos = receiver->mapFromGlobal(event->globalPos());
QWidget *alien = m_widget->childAt(m_widget->mapFromGlobal(event->globalPos()));
QWidget *alien = receiver;
#if !defined(Q_OS_OSX) && !defined(Q_OS_IOS) // Cocoa tracks popups
const bool reallyUnderMouse = activePopupWidget->rect().contains(mapped);
const bool underMouse = activePopupWidget->underMouse();
if (activePopupWidget != m_widget || (!underMouse && qt_button_down)) {
// If active popup menu is not the first-level popup menu then we must emulate enter/leave events,
// because first-level popup menu grabs the mouse and enter/leave events are delivered only to it
// by QPA. Make an exception for first-level popup menu when the mouse button is pressed on widget.
if (underMouse != reallyUnderMouse) {
if (reallyUnderMouse) {
QApplicationPrivate::dispatchEnterLeave(receiver, Q_NULLPTR, event->screenPos());
qt_last_mouse_receiver = receiver;
} else {
QApplicationPrivate::dispatchEnterLeave(Q_NULLPTR, qt_last_mouse_receiver, event->screenPos());
qt_last_mouse_receiver = receiver;
receiver = activePopupWidget;
}
}
} else if (!reallyUnderMouse) {
alien = Q_NULLPTR;
}
#endif
QMouseEvent e(event->type(), widgetPos, event->windowPos(), event->screenPos(),
event->button(), event->buttons(), event->modifiers(), event->source());
e.setTimestamp(event->timestamp());
......@@ -457,7 +487,7 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event)
case QEvent::MouseButtonPress:
case QEvent::MouseButtonDblClick:
case QEvent::MouseButtonRelease:
popup->close();
activePopupWidget->close();
break;
default:
break;
......@@ -503,7 +533,7 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event)
} else if (event->type() == contextMenuTrigger
&& event->button() == Qt::RightButton
&& (openPopupCount == oldOpenPopupCount)) {
QWidget *popupEvent = popup;
QWidget *popupEvent = activePopupWidget;
if (qt_button_down)
popupEvent = qt_button_down;
else if(popupChild)
......
......@@ -110,6 +110,9 @@ private slots:
void QTBUG7411_submenus_activate();
void QTBUG30595_rtl_submenu();
void QTBUG20403_nested_popup_on_shortcut_trigger();
#ifndef QT_NO_CURSOR
void QTBUG47515_widgetActionEnterLeave();
#endif
void QTBUG_10735_crashWithDialog();
#ifdef Q_OS_MAC
void QTBUG_37933_ampersands_data();
......@@ -1070,6 +1073,112 @@ void tst_QMenu::QTBUG20403_nested_popup_on_shortcut_trigger()
QVERIFY(!subsub1.isVisible());
}
class MyWidget : public QWidget
{
public:
MyWidget(QWidget *parent) :
QWidget(parent),
move(0), enter(0), leave(0)
{
setMinimumSize(100, 100);
setMouseTracking(true);
}
bool event(QEvent *e) Q_DECL_OVERRIDE
{
switch (e->type()) {
case QEvent::MouseMove:
++move;
break;
case QEvent::Enter:
++enter;
break;
case QEvent::Leave:
++leave;
break;
default:
break;
}
return QWidget::event(e);
}
int move, enter, leave;
};
#ifndef QT_NO_CURSOR
void tst_QMenu::QTBUG47515_widgetActionEnterLeave()
{
if (QGuiApplication::platformName() == QLatin1String("cocoa"))
QSKIP("This test fails on OS X on CI");
const QPoint center = QGuiApplication::primaryScreen()->availableGeometry().center();
const QPoint cursorPos = center - QPoint(100, 100);
QScopedPointer<QMenu> menu1(new QMenu("Menu1"));
QScopedPointer<QMenu> menu2(new QMenu("Menu2"));
QWidgetAction *wA1 = new QWidgetAction(menu1.data());
MyWidget *w1 = new MyWidget(menu1.data());
wA1->setDefaultWidget(w1);
QWidgetAction *wA2 = new QWidgetAction(menu2.data());
MyWidget *w2 = new MyWidget(menu2.data());
wA2->setDefaultWidget(w2);
QAction *nextMenuAct = menu1->addMenu(menu2.data());
menu1->addAction(wA1);
menu2->addAction(wA2);
// Root menu
{
QCursor::setPos(cursorPos);
QCoreApplication::processEvents();
menu1->popup(center);
QVERIFY(QTest::qWaitForWindowExposed(menu1.data()));
QCursor::setPos(w1->mapToGlobal(w1->rect().center()));
QVERIFY(w1->isVisible());
QTRY_COMPARE(w1->leave, 0);
QTRY_COMPARE(w1->enter, 1);
// Check whether leave event is not delivered on mouse move
w1->move = 0;
QCursor::setPos(w1->mapToGlobal(w1->rect().center()) + QPoint(1, 1));
QTRY_COMPARE(w1->move, 1);
QTRY_COMPARE(w1->leave, 0);
QTRY_COMPARE(w1->enter, 1);
QCursor::setPos(cursorPos);
QTRY_COMPARE(w1->leave, 1);
QTRY_COMPARE(w1->enter, 1);
}
// Submenu
{
menu1->setActiveAction(nextMenuAct);
QVERIFY(QTest::qWaitForWindowExposed(menu2.data()));
QCursor::setPos(w2->mapToGlobal(w2->rect().center()));
QVERIFY(w2->isVisible());
QTRY_COMPARE(w2->leave, 0);
QTRY_COMPARE(w2->enter, 1);
// Check whether leave event is not delivered on mouse move
w2->move = 0;
QCursor::setPos(w2->mapToGlobal(w2->rect().center()) + QPoint(1, 1));
QTRY_COMPARE(w2->move, 1);
QTRY_COMPARE(w2->leave, 0);
QTRY_COMPARE(w2->enter, 1);
QCursor::setPos(cursorPos);
QTRY_COMPARE(w2->leave, 1);
QTRY_COMPARE(w2->enter, 1);
}
}
#endif // !QT_NO_CURSOR
class MyMenu : public QMenu
{
Q_OBJECT
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment