Provide safe API for detecting closing wxTipWindow

Previous API was ugly and suffered from a race condition as the window
could be closed before the constructor returned.

Provide a safer API using weak-pointer-like wxTipWindow::Ref class that
is guaranteed to stop being valid when the tip window is closed.

Also implement two-stage constructions for this class, as usual.

Closes #26070.

Closes #26071.
This commit is contained in:
Bill Su
2026-01-07 15:29:06 +01:00
committed by Vadim Zeitlin
parent 8bfadeeb76
commit 96eca6412e
7 changed files with 253 additions and 20 deletions
+50 -2
View File
@@ -26,7 +26,55 @@ class WXDLLIMPEXP_FWD_CORE wxTipWindowView;
class WXDLLIMPEXP_CORE wxTipWindow : public wxPopupTransientWindow
{
public:
// the mandatory ctor parameters are: the parent window and the text to
// wxTipWindow may close itself, so provide a smart pointer that acts as a
// weak reference to wxTipWindow.
//
// Note that this is a move-only type (because wxTipWindow::Close() only
// sets a single wxTipWindow* to nullptr).
//
// Note that this is not a wxWeakRef<> because this is set to nullptr when
// wxTipWindow is closed, which may be "long" before wxTipWindow is
// destroyed, bug wxWeakRef<> is set to nullptr on object destruction
class WXDLLIMPEXP_CORE Ref
{
public:
Ref() = default;
~Ref();
Ref(const Ref&) = delete;
Ref(Ref&& other);
Ref& operator=(const Ref&) = delete;
Ref& operator=(Ref&& other);
Ref& operator=(std::nullptr_t);
bool operator!=(std::nullptr_t) const { return m_ptr != nullptr; }
explicit operator bool() const { return m_ptr; }
wxTipWindow* operator->() const { return m_ptr; }
private:
wxTipWindow* m_ptr = nullptr;
friend wxTipWindow;
};
// replace the deprecated single-step constructor
// see Create() for parameters
static Ref New(wxWindow *parent,
const wxString& text,
wxCoord maxLength = 100,
wxRect *rectBound = nullptr);
wxTipWindow();
wxDEPRECATED_MSG("Using this has a race condition; use New() instead")
wxTipWindow(wxWindow *parent,
const wxString& text,
wxCoord maxLength = 100,
wxTipWindow** windowPtr = nullptr,
wxRect *rectBound = nullptr)
{ (void)Create(parent, text, maxLength, windowPtr, rectBound); }
// the mandatory parameters are: the parent window and the text to
// show
//
// optionally you may also specify the length at which the lines are going
@@ -34,7 +82,7 @@ public:
//
// windowPtr and rectBound are just passed to SetTipWindowPtr() and
// SetBoundingRect() - see below
wxTipWindow(wxWindow *parent,
bool Create(wxWindow *parent,
const wxString& text,
wxCoord maxLength = 100,
wxTipWindow** windowPtr = nullptr,
+113 -2
View File
@@ -25,7 +25,116 @@ class wxTipWindow : public wxWindow
{
public:
/**
Constructor. The tip is shown immediately after the window is constructed.
The (weak) reference to wxTipWindow.
wxTipWindow may close itself at any moment, so creating it as usual,
with `new` and using a raw pointer to it is dangerous. Instead, use
New() to create it and use the returned Ref which is guaranteed to
become invalid when the tip window is closed.
Note that the objects of this type can't be copied, but can be moved.
To test if this object is still valid, it can be compared to @NULL or
its explicit bool conversion operator can be used, i.e. either this:
@code
wxTipWindow::Ref tip = wxTipWindow::New(...);
if ( tip != nullptr )
tip->DoSomething();
@endcode
or this:
@code
wxTipWindow::Ref tip = wxTipWindow::New(...);
if ( tip )
tip->DoSomething();
@endcode
works fine.
@since 3.3.2
*/
class Ref
{
public:
/**
Default constructor.
The object is created in invalid state.
*/
Ref();
/**
Put object in invalid state.
*/
Ref& operator=(std::nullptr_t);
/**
Returns whether the object is in a valid state.
Object is in valid state if it is associated with a still open
wxTipWindow.
*/
bool operator!=(std::nullptr_t) const;
/**
Returns whether the tracked object is valid.
This is equivalent to comparing the object to @NULL.
*/
explicit operator bool() const;
/**
Returns a pointer to the tracked object.
May only be called if the object is valid.
*/
wxTipWindow* operator->() const;
};
/*
Replace the deprecated single-step constructor. See
Create() for parameters
@see Create()
@since 3.3.2
*/
static Ref New(wxWindow *parent,
const wxString& text,
wxCoord maxLength = 100,
wxRect *rectBound = nullptr);
/**
Default constructor.
If this constructor is used, Create() must be called later to actually
create the window.
Prefer using New() instead.
@since 3.3.2
*/
wxTipWindow();
/**
Use New() instead instead of the new code.
This constructor is preserved only for backward compatibility, don't
use it in the new code.
@deprecated
*/
wxTipWindow(wxWindow* parent, const wxString& text,
wxCoord maxLength = 100,
wxTipWindow** windowPtr = nullptr,
wxRect* rectBounds = nullptr);
/**
Construct the actual window object.
The tip is shown immediately.
@param parent
The parent window, must be non-null
@@ -40,8 +149,10 @@ public:
@param rectBounds
If non-null, passed to SetBoundingRect() below, please see its
documentation for the description of this parameter
@since 3.3.2
*/
wxTipWindow(wxWindow* parent, const wxString& text,
bool Create(wxWindow* parent, const wxString& text,
wxCoord maxLength = 100,
wxTipWindow** windowPtr = nullptr,
wxRect* rectBounds = nullptr);
+2 -7
View File
@@ -763,10 +763,6 @@ MyFrame::MyFrame(const wxString& title)
SetOwnBackgroundColour(m_canvas->GetBackgroundColour());
#endif // wxUSE_INFOBAR
#if wxUSE_TIPWINDOW
m_tipWindow = nullptr;
#endif // wxUSE_TIPWINDOW
#ifdef __WXMSW__
// Test MSW-specific function allowing to access the "system" menu.
wxMenu * const menu = MSWGetSystemMenu();
@@ -2858,14 +2854,13 @@ void MyFrame::OnShowTip(wxCommandEvent& WXUNUSED(event))
}
else
{
m_tipWindow = new wxTipWindow
m_tipWindow = wxTipWindow::New
(
this,
"This is just some text to be shown in the tip "
"window, broken into multiple lines, each less "
"than 60 logical pixels wide.",
FromDIP(60),
&m_tipWindow
FromDIP(60)
);
}
}
+1 -1
View File
@@ -563,7 +563,7 @@ private:
void OnShowTip(wxCommandEvent& event);
void OnUpdateShowTipUI(wxUpdateUIEvent& event);
wxTipWindow *m_tipWindow;
wxTipWindow::Ref m_tipWindow;
#endif // wxUSE_TIPWINDOW
bool m_confirmExit;
+3 -3
View File
@@ -360,7 +360,7 @@ bool wxSimpleHelpProvider::ShowHelp(wxWindowBase *window)
#endif // wxUSE_MS_HTML_HELP
{
#if wxUSE_TIPWINDOW
static wxTipWindow* s_tipWindow = nullptr;
static wxTipWindow::Ref s_tipWindow;
if ( s_tipWindow )
{
@@ -370,8 +370,8 @@ bool wxSimpleHelpProvider::ShowHelp(wxWindowBase *window)
s_tipWindow->Close();
}
s_tipWindow = new wxTipWindow((wxWindow *)window, text,
100, &s_tipWindow);
s_tipWindow = wxTipWindow::New((wxWindow *)window, text,
100);
#else // !wxUSE_TIPWINDOW
// we tried wxCHMHelpController but it failed and we don't have
// wxTipWindow to fall back on, so
+82 -3
View File
@@ -96,13 +96,90 @@ wxEND_EVENT_TABLE()
// wxTipWindow
// ----------------------------------------------------------------------------
wxTipWindow::wxTipWindow(wxWindow *parent,
wxTipWindow::Ref::~Ref()
{
if (m_ptr)
{
m_ptr->SetTipWindowPtr(nullptr);
}
}
wxTipWindow::Ref::Ref(Ref&& other)
{
m_ptr = other.m_ptr;
if (m_ptr)
{
other.m_ptr = nullptr;
m_ptr->SetTipWindowPtr(&m_ptr);
}
}
wxTipWindow::Ref& wxTipWindow::Ref::operator=(Ref&& other)
{
if (m_ptr != other.m_ptr)
{
if (m_ptr)
{
m_ptr->SetTipWindowPtr(nullptr);
}
m_ptr = other.m_ptr;
if (m_ptr)
{
other.m_ptr = nullptr;
m_ptr->SetTipWindowPtr(&m_ptr);
}
}
return *this;
}
wxTipWindow::Ref& wxTipWindow::Ref::operator=(std::nullptr_t)
{
if (m_ptr)
{
m_ptr->SetTipWindowPtr(nullptr);
m_ptr = nullptr;
}
return *this;
}
wxTipWindow::Ref wxTipWindow::New(wxWindow *parent,
const wxString& text,
wxCoord maxLength /*= 100*/,
wxRect *rectBound /*= nullptr*/)
{
std::unique_ptr<wxTipWindow> temp(new wxTipWindow);
wxTipWindow::Ref retval;
retval.m_ptr = temp.get();
if (!temp->Create(parent, text, maxLength, &retval.m_ptr, rectBound))
{
return Ref();
}
temp.release();
return retval;
}
wxTipWindow::wxTipWindow()
{
SetTipWindowPtr(nullptr);
m_view = nullptr;
}
bool wxTipWindow::Create(wxWindow *parent,
const wxString& text,
wxCoord maxLength,
wxTipWindow** windowPtr,
wxRect *rectBounds)
: wxPopupTransientWindow(parent)
{
if (!wxPopupTransientWindow::Create(parent))
{
return false;
}
SetTipWindowPtr(windowPtr);
if ( rectBounds )
{
@@ -158,6 +235,8 @@ wxTipWindow::wxTipWindow(wxWindow *parent,
#ifdef __WXGTK__
m_view->CaptureMouse();
#endif
return true;
}
wxTipWindow::~wxTipWindow()
@@ -167,7 +246,7 @@ wxTipWindow::~wxTipWindow()
*m_windowPtr = nullptr;
}
#ifdef __WXGTK__
if ( m_view->HasCapture() )
if ( m_view && m_view->HasCapture() )
m_view->ReleaseMouse();
#endif
}
+2 -2
View File
@@ -305,7 +305,7 @@ bool wxHtmlHelpController::DisplaySection(int sectionNo)
bool wxHtmlHelpController::DisplayTextPopup(const wxString& text, const wxPoint& WXUNUSED(pos))
{
#if wxUSE_TIPWINDOW
static wxTipWindow* s_tipWindow = nullptr;
static wxTipWindow::Ref s_tipWindow;
if (s_tipWindow)
{
@@ -318,7 +318,7 @@ bool wxHtmlHelpController::DisplayTextPopup(const wxString& text, const wxPoint&
if ( !text.empty() )
{
s_tipWindow = new wxTipWindow(wxTheApp->GetTopWindow(), text, 100, & s_tipWindow);
s_tipWindow = wxTipWindow::New(wxTheApp->GetTopWindow(), text, 100);
return true;
}