summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBłażej Szczygieł <spaz16@wp.pl>2016-05-04 00:23:16 +0200
committerBłażej Szczygieł <spaz16@wp.pl>2016-09-15 10:16:44 +0000
commit25c9a6c9b46e6ae58dcccdc3ba158d14945cbf33 (patch)
tree1376a1853a14a42332fdf2e8dc7560745f80ae49
parent2cf3dee10be1d2163eff0893f51f9ece643c2540 (diff)
QtWidgets: Fix enter/leave events on popup menus
If the sloppy menu popups - send the leave event to the last active menu (except Cocoa), because only currect active menu gets enter/leave events (currently Cocoa is an exception). Check that the menu really has a mouse before hiding the sloppy menu - don't rely on enter events. This patch removes some unnecessary synthetic mouse enter/leave events from QMenu which causes event duplications with different mouse cursor position. Refactor sloppy menu timer handling - start or restart timers on mouse move events. Enter/leave events are not reliable. Fixes: - better enter/leave events handling for native widget actions, - reduce duplicated enter/leave events for menu actions, - better handle torn off sloppy menus. Partially reverts: 0ed68f3f58c63bd1496cb268bd83881da180051f Amends: 57ecd5aeeb1f609206933be66b92fcdf703703d7 Task-number: QTBUG-53068 Change-Id: I7ad56ac1619db124915d373fab82d0512d44c90e Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
-rw-r--r--src/widgets/kernel/qwidgetwindow.cpp34
-rw-r--r--src/widgets/widgets/qmenu.cpp86
-rw-r--r--src/widgets/widgets/qmenu_p.h23
-rw-r--r--tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp5
4 files changed, 74 insertions, 74 deletions
diff --git a/src/widgets/kernel/qwidgetwindow.cpp b/src/widgets/kernel/qwidgetwindow.cpp
index aa7bcc2ef8..95f63025fb 100644
--- a/src/widgets/kernel/qwidgetwindow.cpp
+++ b/src/widgets/kernel/qwidgetwindow.cpp
@@ -381,7 +381,14 @@ void QWidgetWindow::handleEnterLeaveEvent(QEvent *event)
const QEnterEvent *ee = static_cast<QEnterEvent *>(event);
QWidget *child = m_widget->childAt(ee->pos());
QWidget *receiver = child ? child : m_widget.data();
- QApplicationPrivate::dispatchEnterLeave(receiver, 0, ee->screenPos());
+ QWidget *leave = Q_NULLPTR;
+ if (QApplicationPrivate::inPopupMode() && receiver == m_widget
+ && qt_last_mouse_receiver != m_widget) {
+ // This allows to deliver the leave event to the native widget
+ // action on first-level menu.
+ leave = qt_last_mouse_receiver;
+ }
+ QApplicationPrivate::dispatchEnterLeave(receiver, leave, ee->screenPos());
qt_last_mouse_receiver = receiver;
}
}
@@ -471,34 +478,31 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event)
receiver = popupChild;
if (receiver != activePopupWidget)
widgetPos = receiver->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) {
+ if (underMouse != reallyUnderMouse) {
+ if (reallyUnderMouse) {
+ const QPoint receiverMapped = receiver->mapFromGlobal(event->screenPos().toPoint());
+ // Prevent negative mouse position on enter event - this event
+ // should be properly handled in "handleEnterLeaveEvent()".
+ if (receiverMapped.x() >= 0 && receiverMapped.y() >= 0) {
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 {
+ 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());
- QApplicationPrivate::sendMouseEvent(receiver, &e, alien, receiver->window(), &qt_button_down, qt_last_mouse_receiver);
+ QApplicationPrivate::sendMouseEvent(receiver, &e, receiver, receiver->window(), &qt_button_down, qt_last_mouse_receiver);
qt_last_mouse_receiver = receiver;
} else {
// close disabled popups when a mouse button is pressed or released
diff --git a/src/widgets/widgets/qmenu.cpp b/src/widgets/widgets/qmenu.cpp
index c194547f2d..2fd5340f3b 100644
--- a/src/widgets/widgets/qmenu.cpp
+++ b/src/widgets/widgets/qmenu.cpp
@@ -70,22 +70,6 @@
QT_BEGIN_NAMESPACE
QMenu *QMenuPrivate::mouseDown = 0;
-QPointer<QMenu> QMenuPrivate::previousMouseMenu(Q_NULLPTR);
-static void handleEnterLeaveEvents(QPointer<QMenu> *previous_ptr, QMenu *next)
-{
- QWidget *previous = previous_ptr->data();
- if (previous != next) {
- if (previous) {
- QEvent leaveEvent(QEvent::Leave);
- QApplication::sendEvent(previous, &leaveEvent);
- }
- if (next) {
- QEvent enterEvent(QEvent::Enter);
- QApplication::sendEvent(next, &enterEvent);
- }
- }
- *previous_ptr = next;
-}
/* QMenu code */
// internal class used for the torn off popup
@@ -504,8 +488,6 @@ void QMenuPrivate::hideMenu(QMenu *menu)
menu->d_func()->causedPopup.action = 0;
menu->close();
menu->d_func()->causedPopup.widget = 0;
- if (previousMouseMenu.data() == menu)
- handleEnterLeaveEvents(&previousMouseMenu, Q_NULLPTR);
}
void QMenuPrivate::popupAction(QAction *action, int delay, bool activateFirst)
@@ -671,10 +653,26 @@ void QMenuSloppyState::enter()
m_parent->childEnter();
}
+void QMenuSloppyState::childEnter()
+{
+ stopTimer();
+ if (m_parent)
+ m_parent->childEnter();
+}
+
+void QMenuSloppyState::leave()
+{
+ if (!m_dont_start_time_on_leave) {
+ if (m_parent)
+ m_parent->childLeave();
+ startTimerIfNotRunning();
+ }
+}
+
void QMenuSloppyState::childLeave()
{
if (m_enabled && !QMenuPrivate::get(m_menu)->hasReceievedEnter) {
- startTimer();
+ startTimerIfNotRunning();
if (m_parent)
m_parent->childLeave();
}
@@ -720,8 +718,17 @@ public:
void QMenuSloppyState::timeout()
{
QMenuPrivate *menu_priv = QMenuPrivate::get(m_menu);
+
+ bool reallyHasMouse = menu_priv->hasReceievedEnter;
+ if (!reallyHasMouse) {
+ // Check whether the menu really has a mouse, because only active popup
+ // menu gets the enter/leave events. Currently Cocoa is an exception.
+ const QPoint lastCursorPos = QGuiApplicationPrivate::lastCursorPosition.toPoint();
+ reallyHasMouse = m_menu->frameGeometry().contains(lastCursorPos);
+ }
+
if (menu_priv->currentAction == m_reset_action
- && menu_priv->hasReceievedEnter
+ && reallyHasMouse
&& (menu_priv->currentAction
&& menu_priv->currentAction->menu() == menu_priv->activeMenu)) {
return;
@@ -729,13 +736,13 @@ void QMenuSloppyState::timeout()
ResetOnDestroy resetState(this, &m_init_guard);
- if (hasParentActiveDelayTimer() || !m_menu || !m_menu->isVisible())
+ if (hasParentActiveDelayTimer() || !m_menu->isVisible())
return;
if (m_sub_menu)
menu_priv->hideMenu(m_sub_menu);
- if (menu_priv->hasReceievedEnter)
+ if (reallyHasMouse)
menu_priv->setCurrentAction(m_reset_action,0);
else
menu_priv->setCurrentAction(Q_NULLPTR, 0);
@@ -1089,10 +1096,8 @@ bool QMenuPrivate::mouseEventTaken(QMouseEvent *e)
tearoffHighlighted = 0;
}
- if (q->frameGeometry().contains(e->globalPos())) { //otherwise if the event is in our rect we want it..
- handleEnterLeaveEvents(&previousMouseMenu, q);
- return false;
- }
+ if (q->frameGeometry().contains(e->globalPos()))
+ return false; //otherwise if the event is in our rect we want it..
for(QWidget *caused = causedPopup.widget; caused;) {
bool passOnEvent = false;
@@ -1108,17 +1113,16 @@ bool QMenuPrivate::mouseEventTaken(QMouseEvent *e)
next_widget = m->d_func()->causedPopup.widget;
}
if (passOnEvent) {
- handleEnterLeaveEvents(&previousMouseMenu,qobject_cast<QMenu *>(caused));
- if(e->type() != QEvent::MouseButtonRelease || mouseDown == caused) {
- QMouseEvent new_e(e->type(), cpos, caused->mapTo(caused->topLevelWidget(), cpos), e->screenPos(),
- e->button(), e->buttons(), e->modifiers(), e->source());
- QApplication::sendEvent(caused, &new_e);
- return true;
+ if (e->type() != QEvent::MouseButtonRelease || mouseDown == caused) {
+ QMouseEvent new_e(e->type(), cpos, caused->mapTo(caused->topLevelWidget(), cpos), e->screenPos(),
+ e->button(), e->buttons(), e->modifiers(), e->source());
+ QApplication::sendEvent(caused, &new_e);
+ return true;
}
}
caused = next_widget;
if (!caused)
- handleEnterLeaveEvents(&previousMouseMenu, Q_NULLPTR);
+ sloppyState.leave(); // Start timers
}
return false;
}
@@ -3169,7 +3173,6 @@ void QMenu::enterEvent(QEvent *)
Q_D(QMenu);
d->hasReceievedEnter = true;
d->sloppyState.enter();
- d->sloppyState.startTimer();
d->motions = -1; // force us to ignore the generate mouse move in mouseMoveEvent()
}
@@ -3180,7 +3183,6 @@ void QMenu::leaveEvent(QEvent *)
{
Q_D(QMenu);
d->hasReceievedEnter = false;
- d->sloppyState.leave();
if (!d->activeMenu && d->currentAction)
setActiveAction(0);
}
@@ -3352,10 +3354,18 @@ void QMenu::internalDelayedPopup()
const QRect actionRect(d->actionRect(d->currentAction));
const QPoint rightPos(mapToGlobal(QPoint(actionRect.right() + subMenuOffset + 1, actionRect.top())));
- QPoint pos(rightPos);
-
- d->activeMenu->popup(pos);
+ d->activeMenu->popup(rightPos);
d->sloppyState.setSubMenuPopup(actionRect, d->currentAction, d->activeMenu);
+
+#if !defined(Q_OS_DARWIN)
+ // Send the leave event to the current menu - only active popup menu gets
+ // mouse enter/leave events. Currently Cocoa is an exception, so disable
+ // it there to avoid event duplication.
+ if (underMouse()) {
+ QEvent leaveEvent(QEvent::Leave);
+ QCoreApplication::sendEvent(this, &leaveEvent);
+ }
+#endif
}
/*!
diff --git a/src/widgets/widgets/qmenu_p.h b/src/widgets/widgets/qmenu_p.h
index e717d923ae..c78abf8aed 100644
--- a/src/widgets/widgets/qmenu_p.h
+++ b/src/widgets/widgets/qmenu_p.h
@@ -122,8 +122,6 @@ public:
void reset();
bool enabled() const { return m_enabled; }
- void setResetAction(QAction *action) { m_reset_action = action; }
-
enum MouseEventResult {
EventIsProcessed,
EventShouldBePropagated,
@@ -148,22 +146,9 @@ public:
}
void enter();
+ void childEnter();
- void childEnter()
- {
- stopTimer();
- if (m_parent)
- m_parent->childEnter();
- }
-
- void leave()
- {
- if (m_dont_start_time_on_leave)
- return;
- if (m_parent)
- m_parent->childLeave();
- startTimer();
- }
+ void leave();
void childLeave();
static float slope(const QPointF &p1, const QPointF &p2)
@@ -189,8 +174,7 @@ public:
if (!m_enabled)
return EventShouldBePropagated;
- if (!m_time.isActive())
- startTimer();
+ startTimerIfNotRunning();
if (!m_sub_menu) {
reset();
@@ -493,7 +477,6 @@ public:
QAction* wceCommands(uint command);
#endif
QPointer<QWidget> noReplayFor;
- static QPointer<QMenu> previousMouseMenu;
};
#endif // QT_NO_MENU
diff --git a/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp b/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp
index 34a1835413..50d7b258bc 100644
--- a/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp
+++ b/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp
@@ -10358,8 +10358,11 @@ void tst_QWidget::underMouse()
QCOMPARE(childWidget2.leaves, 0);
// Mouse leaves popup and enters topLevelWidget, should cause leave for popup
- // but no enter to topLevelWidget. Again, artificial leave event needed.
+ // but no enter to topLevelWidget.
+#ifdef Q_OS_DARWIN
+ // Artificial leave event needed for Cocoa.
QWindowSystemInterface::handleLeaveEvent(popupWindow);
+#endif
QTest::mouseMove(popupWindow, popupWindow->mapFromGlobal(window->mapToGlobal(inWindowPoint)));
QApplication::processEvents();
QVERIFY(!topLevelWidget.underMouse());