Merge branch 'qt-enter-leave-events' of https://github.com/AliKet/wxWidgets

Fix wxEVT_{ENTER,LEAVE}_WINDOW events generation in wxQt and add a test
for them.

See #24883.
This commit is contained in:
Vadim Zeitlin
2025-01-08 17:32:30 +01:00
6 changed files with 242 additions and 23 deletions
+73 -23
View File
@@ -1728,7 +1728,7 @@ bool wxWindowQt::QtHandleMouseEvent ( QWidget *handler, QMouseEvent *event )
// Use screen position as the event might originate from a different
// Qt window than this one.
wxPoint mousePos = ScreenToClient(wxQtConvertPoint(event->globalPos()));
const wxPoint mousePos = ScreenToClient(wxQtConvertPoint(event->globalPos()));
wxMouseEvent e( wxType );
e.SetEventObject(this);
@@ -1742,42 +1742,92 @@ bool wxWindowQt::QtHandleMouseEvent ( QWidget *handler, QMouseEvent *event )
// Keyboard modifiers
wxQtFillKeyboardModifiers( event->modifiers(), &e );
bool handled = ProcessWindowEvent( e );
bool processed = ProcessWindowEvent( e );
// Determine if mouse is inside the widget
bool mouseInside = true;
if ( mousePos.x < 0 || mousePos.x > handler->width() ||
mousePos.y < 0 || mousePos.y > handler->height() )
mouseInside = false;
if ( e.GetEventType() == wxEVT_MOTION )
if ( wxType == wxEVT_MOTION && QtGetParentWidget() == handler )
{
/* Qt doesn't emit leave/enter events while the mouse is grabbed
* and it automatically grabs the mouse while dragging. In that cases
* we emulate the enter and leave events */
// Mouse enter/leaves
if ( m_mouseInside != mouseInside )
{
if ( mouseInside )
e.SetEventType( wxEVT_ENTER_WINDOW );
else
e.SetEventType( wxEVT_LEAVE_WINDOW );
ProcessWindowEvent( e );
}
static QWidget* s_targetHandler = nullptr;
QtSendSetCursorEvent(this, mousePos);
const auto qtMousePos = wxQtConvertPoint(mousePos);
// Determine if mouse is inside the widget, see below...
bool mouseInside = handler->rect().contains(qtMousePos);
if ( !s_targetHandler && mouseInside )
{
s_targetHandler = handler;
}
if ( QApplication::mouseButtons() != Qt::NoButton )
{
if ( mouseInside )
{
// Quoting wx docs: the mouse is considered to be inside the window if
// it is in the window client area and not inside one of its children.
auto rgn = handler->childrenRegion();
if ( rgn.rectCount() > 1 )
{
rgn = QRegion(handler->rect()) - rgn;
mouseInside = rgn.contains(qtMousePos);
}
}
// Generate mouse enter/leaves for target handler only
if ( m_mouseInside != mouseInside && s_targetHandler == handler )
{
e.SetEventType(mouseInside ? wxEVT_ENTER_WINDOW : wxEVT_LEAVE_WINDOW);
processed = ProcessWindowEvent( e ) && processed;
}
}
else // No mouse button is pressed
{
s_targetHandler = nullptr; // reset
}
m_mouseInside = mouseInside;
}
m_mouseInside = mouseInside;
return handled;
return processed;
}
bool wxWindowQt::QtHandleEnterEvent ( QWidget *handler, QEvent *event )
{
wxMouseEvent e( event->type() == QEvent::Enter ? wxEVT_ENTER_WINDOW : wxEVT_LEAVE_WINDOW );
static QWidget* s_handlerParent = nullptr;
const bool isEnterEvent = event->type() == QEvent::Enter;
// Notice that Qt doesn't generate Enter/Leave events for parent widget when
// the mouse enters/leaves a child widget. And for consistency with the wx
// documentation, we should generate the events manually for s_handlerParent.
if ( s_handlerParent != handler )
{
s_handlerParent = handler->parentWidget();
if ( s_handlerParent )
{
QEvent qtEvent(isEnterEvent ? QEvent::Leave : QEvent::Enter);
QApplication::sendEvent(s_handlerParent, &qtEvent);
}
}
else // s_handlerParent == handler
{
if ( isEnterEvent && !s_handlerParent->underMouse() )
{
return false;
}
}
s_handlerParent = nullptr;
wxMouseEvent e( isEnterEvent ? wxEVT_ENTER_WINDOW : wxEVT_LEAVE_WINDOW );
e.m_clickCount = 0;
e.SetPosition( wxQtConvertPoint( handler->mapFromGlobal( QCursor::pos() ) ) );
e.SetEventObject(this);
+4
View File
@@ -251,6 +251,7 @@ TEST_GUI_OBJECTS = \
test_gui_windowtest.o \
test_gui_dialogtest.o \
test_gui_clone.o \
test_gui_enterleave.o \
test_gui_evtlooptest.o \
test_gui_propagation.o \
test_gui_keyboard.o \
@@ -1165,6 +1166,9 @@ test_gui_dialogtest.o: $(srcdir)/controls/dialogtest.cpp $(TEST_GUI_ODEP)
test_gui_clone.o: $(srcdir)/events/clone.cpp $(TEST_GUI_ODEP)
$(CXXC) -c -o $@ $(TEST_GUI_CXXFLAGS) $(srcdir)/events/clone.cpp
test_gui_enterleave.o: $(srcdir)/events/enterleave.cpp $(TEST_GUI_ODEP)
$(CXXC) -c -o $@ $(TEST_GUI_CXXFLAGS) $(srcdir)/events/enterleave.cpp
test_gui_evtlooptest.o: $(srcdir)/events/evtlooptest.cpp $(TEST_GUI_ODEP)
$(CXXC) -c -o $@ $(TEST_GUI_CXXFLAGS) $(srcdir)/events/evtlooptest.cpp
+156
View File
@@ -0,0 +1,156 @@
///////////////////////////////////////////////////////////////////////////////
// Name: tests/events/enterleave.cpp
// Purpose: Test wxEVT_ENTER_WINDOW and wxEVT_LEAVE_WINDOW events
// Author: Ali Kettab
// Created: 2024-10-16
// Copyright: (c) 2024 wxWidgets team
///////////////////////////////////////////////////////////////////////////////
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
#include "testprec.h"
#ifndef WX_PRECOMP
#include "wx/app.h"
#include "wx/button.h"
#include "wx/panel.h"
#include "wx/textctrl.h"
#include "wx/window.h"
#endif // WX_PRECOMP
#include "wx/uiaction.h"
#include "asserthelper.h"
#include "testableframe.h"
#include "waitfor.h"
// ----------------------------------------------------------------------------
// tests themselves
// ----------------------------------------------------------------------------
#if wxUSE_UIACTIONSIMULATOR
TEST_CASE("EnterLeaveEvents", "[wxEvent][enter-leave]")
{
if ( !EnableUITests() )
{
WARN("Skipping wxEVT_{ENTER,LEAVE}_WINDOW tests: wxUIActionSimulator not available");
return;
}
std::unique_ptr<wxPanel>
panel(new wxPanel(wxTheApp->GetTopWindow(), wxID_ANY));
auto button = new wxButton(panel.get(), wxID_ANY, "button", {50, 50});
auto textctrl = new wxTextCtrl(panel.get(), wxID_ANY, "", {160, 50});
EventCounter enter(panel.get(), wxEVT_ENTER_WINDOW);
EventCounter leave(panel.get(), wxEVT_LEAVE_WINDOW);
// Wait for the first paint event to be sure that panel really
// has its final size.
WaitForPaint waitForPaint(panel.get());
panel->SendSizeEventToParent();
waitForPaint.YieldUntilPainted();
wxUIActionSimulator sim;
SECTION("Without mouse capture")
{
sim.MouseMove(panel->GetScreenPosition() + wxPoint(5, 5));
wxYield();
CHECK( enter.GetCount() == 1 );
CHECK( leave.GetCount() == 0 );
enter.Clear();
sim.MouseMove(button->GetScreenPosition() + wxPoint(5, 5));
wxYield();
// The parent window (panel) should receive wxEVT_LEAVE_WINDOW event
// when mouse enters the child window (button)
CHECK( enter.GetCount() == 0 );
CHECK( leave.GetCount() == 1 );
leave.Clear();
sim.MouseMove(panel->GetScreenPosition() + wxPoint(5, 5));
wxYield();
// Now it (panel) should receive wxEVT_ENTER_WINDOW event when
// the mouse leaves the button and enters the panel again.
CHECK( enter.GetCount() == 1 );
CHECK( leave.GetCount() == 0 );
}
SECTION("With (implicit) mouse capture")
{
// Just to be sure that the button is really shown
EventCounter clicked(button, wxEVT_BUTTON);
sim.MouseMove(button->GetScreenPosition() + wxPoint(5, 5));
wxYield();
sim.MouseClick();
wxYield();
CHECK( clicked.GetCount() == 1 );
enter.Clear();
leave.Clear();
sim.MouseDown();
wxYield();
#if defined(__WXGTK__) && !defined(__WXGTK3__)
if ( IsAutomaticTest() )
{
WARN("Skipping tests known to fail under GitHub Actions");
return;
}
#endif
sim.MouseMove(button->GetScreenPosition() + wxPoint(10, 5));
wxYield();
// Holding the mouse button down (initiated on the button) and then
// hovering over the panel should not generate any events (enter/leave)
// Additionally, entering and leaving another child (textctrl) while the
// mouse is still held down should also not generate any events.
sim.MouseMove(panel->GetScreenPosition() + wxPoint(5, 5));
wxYield();
CHECK( enter.GetCount() == 0 );
CHECK( leave.GetCount() == 0 );
sim.MouseMove(textctrl->GetScreenPosition() + wxPoint(5, 5));
wxYield();
CHECK( enter.GetCount() == 0 );
CHECK( leave.GetCount() == 0 );
sim.MouseMove(panel->GetScreenPosition() + wxPoint(5, 5));
wxYield();
CHECK( enter.GetCount() == 0 );
CHECK( leave.GetCount() == 0 );
sim.MouseUp();
wxYield();
// wxGTK behaves differently here, as it does not generate a
// wxEVT_ENTER_WINDOW event when we release the mouse button.
#ifndef __WXGTK__
CHECK( enter.GetCount() == 1 );
#else
CHECK( enter.GetCount() == 0 );
#endif
CHECK( leave.GetCount() == 0 );
}
}
#endif // wxUSE_UIACTIONSIMULATOR
+4
View File
@@ -224,6 +224,7 @@ TEST_GUI_OBJECTS = \
$(OBJS)\test_gui_windowtest.o \
$(OBJS)\test_gui_dialogtest.o \
$(OBJS)\test_gui_clone.o \
$(OBJS)\test_gui_enterleave.o \
$(OBJS)\test_gui_evtlooptest.o \
$(OBJS)\test_gui_propagation.o \
$(OBJS)\test_gui_keyboard.o \
@@ -1109,6 +1110,9 @@ $(OBJS)\test_gui_dialogtest.o: ./controls/dialogtest.cpp
$(OBJS)\test_gui_clone.o: ./events/clone.cpp
$(CXX) -c -o $@ $(TEST_GUI_CXXFLAGS) $(CPPDEPS) $<
$(OBJS)\test_gui_enterleave.o: ./events/enterleave.cpp
$(CXX) -c -o $@ $(TEST_GUI_CXXFLAGS) $(CPPDEPS) $<
$(OBJS)\test_gui_evtlooptest.o: ./events/evtlooptest.cpp
$(CXX) -c -o $@ $(TEST_GUI_CXXFLAGS) $(CPPDEPS) $<
+4
View File
@@ -239,6 +239,7 @@ TEST_GUI_OBJECTS = \
$(OBJS)\test_gui_windowtest.obj \
$(OBJS)\test_gui_dialogtest.obj \
$(OBJS)\test_gui_clone.obj \
$(OBJS)\test_gui_enterleave.obj \
$(OBJS)\test_gui_evtlooptest.obj \
$(OBJS)\test_gui_propagation.obj \
$(OBJS)\test_gui_keyboard.obj \
@@ -1410,6 +1411,9 @@ $(OBJS)\test_gui_dialogtest.obj: .\controls\dialogtest.cpp
$(OBJS)\test_gui_clone.obj: .\events\clone.cpp
$(CXX) /c /nologo /TP /Fo$@ $(TEST_GUI_CXXFLAGS) .\events\clone.cpp
$(OBJS)\test_gui_enterleave.obj: .\events\enterleave.cpp
$(CXX) /c /nologo /TP /Fo$@ $(TEST_GUI_CXXFLAGS) .\events\enterleave.cpp
$(OBJS)\test_gui_evtlooptest.obj: .\events\evtlooptest.cpp
$(CXX) /c /nologo /TP /Fo$@ $(TEST_GUI_CXXFLAGS) .\events\evtlooptest.cpp
+1
View File
@@ -261,6 +261,7 @@
controls/windowtest.cpp
controls/dialogtest.cpp
events/clone.cpp
events/enterleave.cpp
<!--
Duplicate this file here to test GUI event loops too.
-->