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:bb100ed68f3f58Amends:57ecd5aeebTask-number: QTBUG-53068 Change-Id: I7ad56ac1619db124915d373fab82d0512d44c90e Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
parent
2cf3dee10b
commit
25c9a6c9b4
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
Loading…
Reference in New Issue