From e04e3260fcb061e119e11c2742482332c97b9f42 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 8 Jul 2026 13:20:51 -0600 Subject: [PATCH] Handle context-help mouse capture loss End wxContextHelp mode when mouse capture is lost and only release the mouse if the help window still owns capture after the modal help loop. Add coverage for the capture-loss path so context help exits without using another input event. Fixes #21534. Closes #26674. --- src/common/cshelp.cpp | 13 +++-- tests/controls/windowtest.cpp | 94 +++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 5 deletions(-) diff --git a/src/common/cshelp.cpp b/src/common/cshelp.cpp index d8961c5fd3..f4c730fd63 100644 --- a/src/common/cshelp.cpp +++ b/src/common/cshelp.cpp @@ -4,6 +4,7 @@ // Author: Julian Smart, Vadim Zeitlin // Created: 08/09/2000 // Copyright: (c) 2000 Julian Smart, Vadim Zeitlin +// (c) 2026 wxWidgets development team // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// @@ -108,7 +109,8 @@ bool wxContextHelp::BeginContextHelp(wxWindow* win) EventLoop(); - win->ReleaseMouse(); + if ( win->HasCapture() ) + win->ReleaseMouse(); win->PopEventHandler(true); @@ -173,10 +175,11 @@ bool wxContextHelpEvtHandler::ProcessEvent(wxEvent& event) return true; } - if ((event.GetEventType() == wxEVT_CHAR) || - (event.GetEventType() == wxEVT_KEY_DOWN) || - (event.GetEventType() == wxEVT_ACTIVATE) || - (event.GetEventType() == wxEVT_MOUSE_CAPTURE_CHANGED)) + if ( (event.GetEventType() == wxEVT_CHAR) || + (event.GetEventType() == wxEVT_KEY_DOWN) || + (event.GetEventType() == wxEVT_ACTIVATE) || + (event.GetEventType() == wxEVT_MOUSE_CAPTURE_CHANGED) || + (event.GetEventType() == wxEVT_MOUSE_CAPTURE_LOST) ) { // May have already been set to true by a left-click //m_contextHelp->SetStatus(false); diff --git a/tests/controls/windowtest.cpp b/tests/controls/windowtest.cpp index a7de17f63c..44f83e0389 100644 --- a/tests/controls/windowtest.cpp +++ b/tests/controls/windowtest.cpp @@ -4,6 +4,7 @@ // Author: Steven Lamerton // Created: 2010-07-10 // Copyright: (c) 2010 Steven Lamerton +// (c) 2026 wxWidgets development team /////////////////////////////////////////////////////////////////////////////// #include "testprec.h" @@ -25,6 +26,7 @@ #include "wx/caret.h" #include "wx/cshelp.h" #include "wx/dcclient.h" +#include "wx/timer.h" #include "wx/tooltip.h" #include "wx/wupdlock.h" @@ -55,6 +57,78 @@ protected: wxDECLARE_NO_COPY_CLASS(WindowTestCase); }; +#if wxUSE_HELP +class ContextHelpCaptureLostTester : public wxWindow +{ +public: + ContextHelpCaptureLostTester(wxWindow* parent) + : wxWindow(parent, wxID_ANY) + { + } + + void SimulateCaptureLost() + { + DoReleaseMouse(); + NotifyCaptureLost(); + } +}; + +class ContextHelpCaptureLostState : public wxEvtHandler +{ +public: + explicit ContextHelpCaptureLostState(ContextHelpCaptureLostTester* win) + : m_win(win), + m_captureLostTimer(this), + m_fallbackTimer(this) + { + Bind(wxEVT_TIMER, &ContextHelpCaptureLostState::OnTimer, this); + } + + void Start() + { + m_captureLostTimer.StartOnce(1); + } + + void Done() + { + m_done = true; + m_captureLostTimer.Stop(); + m_fallbackTimer.Stop(); + } + + bool WasCaptureLostSent() const { return m_captureLostSent; } + bool WasFallbackUsed() const { return m_fallbackUsed; } + +private: + void OnTimer(wxTimerEvent& event) + { + if ( m_done ) + return; + + if ( &event.GetTimer() == &m_captureLostTimer ) + { + m_captureLostSent = true; + m_win->SimulateCaptureLost(); + m_fallbackTimer.StartOnce(100); + return; + } + + m_fallbackUsed = true; + + wxKeyEvent eventKey(wxEVT_KEY_DOWN); + eventKey.SetEventObject(m_win); + m_win->GetEventHandler()->ProcessEvent(eventKey); + } + + ContextHelpCaptureLostTester* const m_win; + wxTimer m_captureLostTimer; + wxTimer m_fallbackTimer; + bool m_done = false; + bool m_captureLostSent = false; + bool m_fallbackUsed = false; +}; +#endif // wxUSE_HELP + static void DoTestShowHideEvent(wxWindow* window) { EventCounter show(window, wxEVT_SHOW); @@ -179,6 +253,26 @@ TEST_CASE_METHOD(WindowTestCase, "Window::Mouse", "[window]") CHECK(!m_window->HasCapture()); } +TEST_CASE_METHOD(WindowTestCase, "Window::ContextHelpCaptureLost", + "[window][help]") +{ +#if wxUSE_HELP + ContextHelpCaptureLostTester* const win = + new ContextHelpCaptureLostTester(wxTheApp->GetTopWindow()); + ContextHelpCaptureLostState state(win); + state.Start(); + + wxContextHelp contextHelp(win, false); + + CHECK(contextHelp.BeginContextHelp(win)); + + state.Done(); + CHECK(state.WasCaptureLostSent()); + CHECK(!state.WasFallbackUsed()); + CHECK(!win->HasCapture()); +#endif // wxUSE_HELP +} + TEST_CASE_METHOD(WindowTestCase, "Window::Properties", "[window]") { m_window->SetLabel("label");