Merge branch 'stattext-wrap'
No Response / no-response (push) Has been cancelled
Unix builds / Ubuntu 18.04 wxGTK 3 compatible 3.0 (push) Has been cancelled
Unix builds / Ubuntu 24.04 wxGTK ASAN not compatible (push) Has been cancelled
Unix builds / Ubuntu 18.04 wxGTK UTF-8 (push) Has been cancelled
Unix builds / Ubuntu 18.04 wxQt (push) Has been cancelled
Unix builds / Ubuntu 18.04 wxX11 (push) Has been cancelled
Unix builds / Ubuntu 20.04 wxGTK 3 with clang (push) Has been cancelled
Unix builds / Ubuntu 22.04 wxGTK with wx containers (push) Has been cancelled
Unix builds / Ubuntu 24.04 wxGTK UBSAN (push) Has been cancelled
Unix builds / Ubuntu 18.04 wxDFB (push) Has been cancelled
Unix builds / Ubuntu 18.04 wxGTK 3 static with gcc 4.8 (push) Has been cancelled
Unix builds / Ubuntu 18.04 wxGTK 2 (push) Has been cancelled
CMake builds / Ubuntu 22.04 wxGTK 3 (push) Has been cancelled
CMake builds / MSW/MSVC wxMSW (push) Has been cancelled
CMake builds / MSW/Clang wxMSW (push) Has been cancelled
CMake builds / macOS latest wxOSX Ninja (push) Has been cancelled
CMake builds / macOS 14 wxOSX Xcode (push) Has been cancelled
CMake builds / macOS 14 wxIOS (push) Has been cancelled
CMake builds / MSW/MSVC wxQt 5.15 (push) Has been cancelled
CMake builds / MSW/MSVC wxQt 6.8 (push) Has been cancelled
Mac builds / wxMac ARM ASAN not compatible (push) Has been cancelled
Mac builds / wxMac Universal C++14 (push) Has been cancelled
Mac builds / wxiOS Simulator on Silicon Mac (push) Has been cancelled
Mac builds / wxiOS (push) Has been cancelled
Mac builds / wxMac Intel C++17 (push) Has been cancelled
Mac Xcode builds / iOS Simulator static (push) Has been cancelled
Mac Xcode builds / macOS dynamic Release (push) Has been cancelled
Mac Xcode builds / iOS static Debug (push) Has been cancelled
MSW builds / wxMSW vs2022 DLL Debug x64 (push) Has been cancelled
MSW builds / wxMSW vs2022 DLL Release x64 (push) Has been cancelled
MSW builds / wxMSW vs2022 Debug Win32 (push) Has been cancelled
MSW builds / wxMSW vs2022 Release arm64 (push) Has been cancelled
MSW cross-builds / wxMSW 64 bits not compatible (push) Has been cancelled
MSW cross-builds / wxMSW/Univ (push) Has been cancelled
MSW cross-builds / wxMSW 32 bits (push) Has been cancelled
Code Checks / Check Spelling (push) Has been cancelled
Code Checks / Check Whitespace (push) Has been cancelled
Code Checks / Check Mixed EOL (push) Has been cancelled
Code Checks / Check C++ Style (push) Has been cancelled
Code Checks / Check All Headers In allheaders.h (push) Has been cancelled
Update Documentation / Update Online Documentation (push) Has been cancelled

Implement automatic wxStaticText wrapping.

See #25925.

Closes #25753.
This commit is contained in:
Vadim Zeitlin
2025-10-25 17:40:16 +02:00
16 changed files with 461 additions and 138 deletions
+16 -17
View File
@@ -624,15 +624,6 @@ public:
virtual void Clear( bool delete_windows = false );
virtual void DeleteWindows();
// Inform sizer about the first direction that has been decided (by parent item)
// Returns true if it made use of the information (and recalculated min size)
//
// Note that while this method doesn't do anything by default, it should
// almost always be overridden in the derived classes and should have been
// pure virtual if not for backwards compatibility constraints.
virtual bool InformFirstDirection( int WXUNUSED(direction), int WXUNUSED(size), int WXUNUSED(availableOtherDir) )
{ return false; }
void SetMinSize( int width, int height )
{ DoSetMinSize( width, height ); }
void SetMinSize( const wxSize& size )
@@ -669,6 +660,13 @@ public:
// this size to really update all the sizer items.
virtual wxSize CalcMin() = 0;
// Can be overridden to return adjusted minimal size when the size in the
// given direction is already known.
virtual wxSize
CalcMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir);
// This method should be overridden but isn't pure virtual for backwards
// compatibility.
virtual void RepositionChildren(const wxSize& WXUNUSED(minSize))
@@ -705,10 +703,6 @@ public:
m_position = pos;
m_size = size;
Layout();
// This call is required for wxWrapSizer to be able to calculate its
// minimal size correctly.
InformFirstDirection(wxHORIZONTAL, size.x, size.y);
}
void SetDimension(int x, int y, int width, int height)
{ SetDimension(wxPoint(x, y), wxSize(width, height)); }
@@ -747,6 +741,11 @@ public:
// items are shown.
virtual bool AreAnyItemsShown() const;
// Don't use in the new code, override CalcMinSizeFromKnownDirection()
// instead.
virtual bool InformFirstDirection( int WXUNUSED(direction), int WXUNUSED(size), int WXUNUSED(availableOtherDir) )
{ return false; }
protected:
wxSize m_size;
wxSize m_minSize;
@@ -989,12 +988,12 @@ public:
// implementation of our resizing logic
virtual wxSize CalcMin() override;
virtual wxSize
CalcMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir) override;
virtual void RepositionChildren(const wxSize& minSize) override;
virtual bool InformFirstDirection(int direction,
int size,
int availableOtherDir) override;
protected:
// Only overridden to perform extra debugging checks.
virtual wxSizerItem *DoInsert(size_t index, wxSizerItem *item) override;
+18 -1
View File
@@ -20,7 +20,7 @@
* wxStaticText flags
*/
#define wxST_NO_AUTORESIZE 0x0001
// free 0x0002 bit
#define wxST_WRAP 0x0002
#define wxST_ELLIPSIZE_START 0x0004
#define wxST_ELLIPSIZE_MIDDLE 0x0008
#define wxST_ELLIPSIZE_END 0x0010
@@ -41,9 +41,16 @@ public:
void Wrap(int width);
// overridden base virtuals
virtual wxSize
GetMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir) override;
virtual bool AcceptsFocus() const override { return false; }
virtual bool HasTransparentBackground() override { return true; }
virtual void SetWindowStyleFlag(long style) override;
bool IsEllipsized() const
{
return (GetWindowStyle() & wxST_ELLIPSIZE_MASK) != 0;
@@ -72,6 +79,16 @@ protected: // functions required for wxST_ELLIPSIZE_* support
// display.
void UpdateLabel();
// If m_currentWrap is non-zero, contains the label value before wrapping it.
// This is used to allow re-wrapping it at different widths. Note that
// wxControlBase::m_labelOrig is changed when Wrap() is called and so can't
// be used.
wxString m_unwrappedLabel;
// The width at which the label is currently wrapped or 0 if not wrapped.
int m_currentWrap = 0;
// These functions are platform-specific and must be implemented in the
// platform-specific code. They must not use or update m_labelOrig.
+16
View File
@@ -480,6 +480,22 @@ public:
int GetMaxWidth() const { return GetMaxSize().x; }
int GetMaxHeight() const { return GetMaxSize().y; }
// This function may be overridden to return the minimal size if it
// depends on the size known to be available in some direction, e.g.
// for text wrapping controls to compute the number of lines needed to
// fit all their text in the given width.
//
// It must return wxDefaultSize if the size of this window doesn't
// depend on the size in the given direction to avoid unnecessary
// relayouts.
//
// Default implementation calls InformFirstDirection() for
// compatibility and then returns either GetEffectiveMinSize() or
// wxDefaultSize depending on InformFirstDirection() return value.
virtual wxSize
GetMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir);
// Methods for accessing the virtual size of a window. For most
// windows this is just the client area of the window, but for
+4 -6
View File
@@ -36,12 +36,12 @@ public:
// override base class virtual methods
virtual wxSize CalcMin() override;
virtual wxSize
CalcMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir) override;
virtual void RepositionChildren(const wxSize& minSize) override;
virtual bool InformFirstDirection(int direction,
int size,
int availableOtherDir) override;
protected:
// This method is called to decide if an item represents empty space or
// not. We do this to avoid having space-only items first or last on a
@@ -81,8 +81,6 @@ protected:
int m_dirInform; // Direction for size information
int m_availSize; // Size available in m_dirInform direction
int m_availableOtherDir; // Size available in the other direction
bool m_lastUsed; // Indicates whether value from InformFirst... has
// been used yet
// The sizes below are computed by RepositionChildren(), i.e. they don't have
// valid values during the initial call to CalcMin() and they are only
+44 -3
View File
@@ -336,6 +336,46 @@ public:
*/
virtual wxSize CalcMin() = 0;
/**
May be overridden by sizers whose minimal size depends on the layout
direction.
It may be useful to override it if the sizer minimal size varies
depending on its size in some direction. For example, wxWrapSizer uses
it to determine the smallest size it can use and still show all of its
items when the size in some direction is fixed, e.g. it returns the
width of the widest control when @a direction is wxVERTICAL or the
total height of all controls wrapped at the given width when @a
direction is wxHORIZONTAL.
If the sizer minimal size doesn't depend on the size known in the given
direction, this function should return wxDefaultSize to avoid
unnecessary re-layouts.
The default implementation simply returns wxDefaultSize (after calling
InformFirstDirection() for backward compatibility).
@param direction
The direction in which the size is fixed, either ::wxHORIZONTAL or
::wxVERTICAL.
@param size
The size in the direction given by the @a direction parameter,
always valid, i.e. positive.
@param availableOtherDir
The size available in the other direction, may be -1 if the
available size is not known.
@return
The minimal size of the sizer when its size in the given
@a direction is fixed to @a size or ::wxDefaultSize if the minimum
size doesn't depend on the layout direction and is always the same.
@since 3.3.2
*/
virtual wxSize
CalcMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir);
/**
Detaches all children from the sizer.
@@ -433,9 +473,10 @@ public:
void FitInside(wxWindow* window);
/**
Inform sizer about the first direction that has been decided (by
parent item). Returns true if it made use of the information (and
recalculated min size).
Compatibility function called by CalcMinSizeFromKnownDirection().
This function shouldn't be used in the new code, please override
CalcMinSizeFromKnownDirection() instead.
*/
virtual bool InformFirstDirection(int direction, int size, int availableOtherDir);
+5
View File
@@ -6,6 +6,7 @@
/////////////////////////////////////////////////////////////////////////////
#define wxST_NO_AUTORESIZE 0x0001
#define wxST_WRAP 0x0002
#define wxST_ELLIPSIZE_START 0x0004
#define wxST_ELLIPSIZE_MIDDLE 0x0008
#define wxST_ELLIPSIZE_END 0x0010
@@ -42,6 +43,10 @@
@style{wxST_ELLIPSIZE_END}
If the label text width exceeds the control width, replace the end
of the label with an ellipsis; uses wxControl::Ellipsize.
@style{wxST_WRAP}
Wrap label text on multiple lines if necessary, using the available
horizontal space. This style only works when the control is used
inside a sizer. It is available since wxWidgets 3.3.2.
@endStyleTable
@library{wxcore}
+46 -6
View File
@@ -1401,6 +1401,48 @@ public:
*/
virtual wxSize GetEffectiveMinSize() const;
/**
May be overridden if the control minimal size depends on the layout
direction.
This function is called when using sizers for the layout to request
minimum controls size once its size in the specified @a direction is
fixed by the layout algorithm and known to be equal to @a size.
It may be useful to override it if the control minimal size varies
depending on its size in some direction. For example, controls showing
multi-line text may return the size needed to show their text after
wrapping the contents to fit the given width when @a direction is
wxHORIZONTAL and @a size is the available width.
The default implementation of this method returns wxDefaultSize
(to be precise, it may return GetEffectiveMinSize() if the deprecated
InformFirstDirection() is overridden and returns @true, but this
shouldn't be done in the new code).
@param direction
The direction in which the size is fixed, either ::wxHORIZONTAL or
::wxVERTICAL.
@param size
The size in the direction given by the @a direction parameter,
always valid, i.e. positive.
@param availableOtherDir
The size available in the other direction, may be -1 if the
available size is not known.
@return
The minimal size of the window when its size in the given
@a direction is fixed to @a size or ::wxDefaultSize if the minimum
size doesn't depend on the layout direction and is always the same.
@since 3.3.2
@see wxSizer::CalcMinSizeFromKnownDirection()
*/
virtual wxSize
GetMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir) const;
/**
Returns the maximum size of window's client area.
@@ -1590,12 +1632,10 @@ public:
virtual wxSize GetWindowBorderSize() const;
/**
wxSizer and friends use this to give a chance to a component to recalc
its min size once one of the final size components is known. Override
this function when that is useful (such as for wxStaticText which can
stretch over several lines). Parameter availableOtherDir
tells the item how much more space there is available in the opposite
direction (-1 if unknown).
Compatibility function called by GetMinSizeFromKnownDirection().
This function shouldn't be used in the new code, please override
GetMinSizeFromKnownDirection() instead.
*/
virtual bool
InformFirstDirection(int direction,
+29 -16
View File
@@ -136,6 +136,7 @@ protected:
*m_chkBoxWithCheck,
#endif // wxHAS_WINDOW_LABEL_IN_STATIC_BOX
*m_chkAutoResize,
*m_chkWrap,
*m_chkEllipsize;
#if wxUSE_MARKUP
@@ -187,6 +188,7 @@ StaticWidgetsPage::StaticWidgetsPage(WidgetsBookCtrl *book,
// init everything
m_chkVert =
m_chkAutoResize =
m_chkWrap =
m_chkGeneric =
#ifdef wxHAS_WINDOW_LABEL_IN_STATIC_BOX
m_chkBoxWithCheck =
@@ -242,6 +244,9 @@ void StaticWidgetsPage::CreateContent()
m_chkAutoResize = CreateCheckBoxAndAddToSizer(sizerLeft, "&Fit to text", wxID_ANY, sizerLeftBox);
m_chkAutoResize->Bind(wxEVT_CHECKBOX, &StaticWidgetsPage::OnRecreate, this);
m_chkWrap = CreateCheckBoxAndAddToSizer(sizerLeft, "&Wrap", wxID_ANY, sizerLeftBox);
m_chkWrap->Bind(wxEVT_CHECKBOX, &StaticWidgetsPage::OnRecreate, this);
sizerLeft->Add(5, 5, 0, wxGROW | wxALL, 5); // spacer
static const wxString halign[] =
@@ -340,10 +345,10 @@ void StaticWidgetsPage::CreateContent()
m_textLabel->SetValue("And this is a\n\tlabel inside the box with a &mnemonic.\n"
"Only this text is affected by the ellipsize settings.");
#if wxUSE_MARKUP
m_textLabelWithMarkup->SetValue("Another label, this time <b>decorated</b> "
"with <u>markup</u>; here you need entities "
"for the symbols: &lt; &gt; &amp;&amp; &apos; &quot; "
" but you can still use &mnemonics too");
m_textLabelWithMarkup->SetValue("Another label, this time <b>decorated</b>\n"
"with <u>markup</u>; here you need entities\n"
"for the symbols: &lt; &gt; &amp;&amp; &apos; &quot;\n"
"but you can still use &mnemonics too");
#endif // wxUSE_MARKUP
// right pane
@@ -373,7 +378,9 @@ void StaticWidgetsPage::Reset()
#endif // wxHAS_WINDOW_LABEL_IN_STATIC_BOX
m_chkVert->SetValue(false);
m_chkAutoResize->SetValue(true);
m_chkEllipsize->SetValue(true);
m_chkWrap->SetValue(false);
m_chkEllipsize->SetValue(false);
m_radioEllipsize->Disable();
m_radioHAlign->SetSelection(StaticHAlign_Left);
m_radioVAlign->SetSelection(StaticVAlign_Top);
@@ -400,12 +407,18 @@ void StaticWidgetsPage::CreateStatic()
int flagsBox = 0,
flagsText = GetAttrs().m_defaultFlags,
flagsDummyText = GetAttrs().m_defaultFlags;
flagsMarkupText = GetAttrs().m_defaultFlags;
if ( !m_chkAutoResize->GetValue() )
{
flagsText |= wxST_NO_AUTORESIZE;
flagsDummyText |= wxST_NO_AUTORESIZE;
flagsMarkupText |= wxST_NO_AUTORESIZE;
}
if ( m_chkWrap->GetValue() )
{
flagsText |= wxST_WRAP;
flagsMarkupText |= wxST_WRAP;
}
int align = 0;
@@ -456,21 +469,21 @@ void StaticWidgetsPage::CreateStatic()
wxFALLTHROUGH;
case StaticEllipsize_Start:
flagsDummyText |= wxST_ELLIPSIZE_START;
flagsText |= wxST_ELLIPSIZE_START;
break;
case StaticEllipsize_Middle:
flagsDummyText |= wxST_ELLIPSIZE_MIDDLE;
flagsText |= wxST_ELLIPSIZE_MIDDLE;
break;
case StaticEllipsize_End:
flagsDummyText |= wxST_ELLIPSIZE_END;
flagsText |= wxST_ELLIPSIZE_END;
break;
}
}
flagsDummyText |= align;
flagsText |= align;
flagsMarkupText |= align;
flagsBox |= align;
wxStaticBox *staticBox;
@@ -503,12 +516,12 @@ void StaticWidgetsPage::CreateStatic()
m_statText = new wxGenericStaticText(staticBox, wxID_ANY,
m_textLabel->GetValue(),
wxDefaultPosition, wxDefaultSize,
flagsDummyText);
flagsText);
#if wxUSE_MARKUP
m_statMarkup = new wxGenericStaticText(staticBox, wxID_ANY,
wxString(),
wxDefaultPosition, wxDefaultSize,
flagsText);
flagsMarkupText);
#endif // wxUSE_MARKUP
}
else // use native versions
@@ -516,12 +529,12 @@ void StaticWidgetsPage::CreateStatic()
m_statText = new wxStaticText(staticBox, wxID_ANY,
m_textLabel->GetValue(),
wxDefaultPosition, wxDefaultSize,
flagsDummyText);
flagsText);
#if wxUSE_MARKUP
m_statMarkup = new wxStaticText(staticBox, wxID_ANY,
wxString(),
wxDefaultPosition, wxDefaultSize,
flagsText);
flagsMarkupText);
#endif // wxUSE_MARKUP
}
@@ -557,7 +570,7 @@ void StaticWidgetsPage::CreateStatic()
NotifyWidgetRecreation(m_statLine);
#endif
m_sizerStatic->Add(m_sizerStatBox, 0, wxGROW);
m_sizerStatic->Add(m_sizerStatBox, 1, wxGROW);
m_sizerStatic->Layout();
+21
View File
@@ -122,6 +122,27 @@ WrapSizerFrame::WrapSizerFrame()
sizerMid->Add(sizerMidWrap, wxSizerFlags(100).Expand());
sizerRoot->Add(sizerMid, wxSizerFlags(100).Expand().Border());
// A long wxStaticText that wraps like a wxWrapSizer
sizerRoot->Add( new wxStaticText( m_panel, -1,
"This is very long text that will wrap. This is very long text that will wrap. This is very long text that will wrap.",
wxDefaultPosition, wxDefaultSize, wxST_WRAP
));
// A long wxStaticText that does not wrap
sizerRoot->Add( new wxStaticText( m_panel, -1,
"This is long text that will not wrap. This is long text that will not wrap."
));
// A window of 150px width for comparison.
auto* const ruler = new wxStaticText(m_panel, wxID_ANY, "150px",
wxDefaultPosition, wxSize(150, -1),
wxALIGN_CENTER);
ruler->SetBackgroundColour(*wxYELLOW);
sizerRoot->Add(ruler);
// A long wxStaticText that wraps at 150px
wxStaticText *stattext = new wxStaticText( m_panel, -1, "This is very long text that will wrap at 150px. This is very long text that will wrap at 150px." );
stattext->Wrap( 150 );
sizerRoot->Add( stattext );
// A shaped item inside a box sizer
wxStaticBoxSizer *sizerBottom = new wxStaticBoxSizer(wxVERTICAL, m_panel,
+50 -18
View File
@@ -538,15 +538,33 @@ bool wxSizerItem::InformFirstDirection(int direction, int size, int availableOth
// Pass the information along to the held object
if (IsSizer())
{
didUse = GetSizer()->InformFirstDirection(direction,size,availableOtherDir);
if (didUse)
m_minSize = GetSizer()->CalcMin();
const wxSize minSize = GetSizer()->CalcMinSizeFromKnownDirection
(
direction,
size,
availableOtherDir
);
if (minSize != wxDefaultSize)
{
m_minSize = minSize;
didUse = true;
}
}
else if (IsWindow())
{
didUse = GetWindow()->InformFirstDirection(direction,size,availableOtherDir);
if (didUse)
m_minSize = m_window->GetEffectiveMinSize();
const wxSize minSize = GetWindow()->GetMinSizeFromKnownDirection
(
direction,
size,
availableOtherDir
);
if (minSize != wxDefaultSize)
{
m_minSize = minSize;
didUse = true;
}
// This information is useful for items with wxSHAPED flag, since
// we can request an optimal min size for such an item. Even if
@@ -556,7 +574,7 @@ bool wxSizerItem::InformFirstDirection(int direction, int size, int availableOth
{
if ( m_ratio != 0 )
{
wxCHECK_MSG( m_proportion==0, false, wxT("Shaped item, non-zero proportion in wxSizerItem::InformFirstDirection()") );
wxCHECK_MSG( m_proportion==0, false, wxT("Shaped item, non-zero proportion in wxSizerItem::CalcMinSizeFromKnownDirection()") );
if ( direction == wxHORIZONTAL )
{
// Clip size so that we don't take too much
@@ -1229,6 +1247,21 @@ wxSize wxSizer::GetMinSize()
return ret;
}
wxSize
wxSizer::CalcMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir)
{
// For compatibility, call InformFirstDirection().
if ( !InformFirstDirection(direction, size, availableOtherDir) )
return wxDefaultSize;
// Old code overriding InformFirstDirection() must have stored the values
// passed to it internally, so call its CalcMin() again to recalculate the
// minimal size using them.
return CalcMin();
}
void wxSizer::DoSetMinSize( int width, int height )
{
m_minSize.x = width;
@@ -2663,17 +2696,12 @@ wxSize wxBoxSizer::CalcMin()
return minSize;
}
bool
wxBoxSizer::InformFirstDirection(int direction, int size, int availableOtherDir)
{
// In principle, we could propagate the information about the size in the
// sizer major direction too, but this would require refactoring CalcMin()
// to determine the actual sizes all our items would have with the given
// size and we don't do this yet, so for now handle only the simpler case
// of informing all our items about their size in the orthogonal direction.
if ( direction == GetOrientation() )
return false;
wxSize
wxBoxSizer::CalcMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir)
{
bool didUse = false;
for ( wxSizerItem* item: m_children )
@@ -2681,7 +2709,11 @@ wxBoxSizer::InformFirstDirection(int direction, int size, int availableOtherDir)
didUse |= item->InformFirstDirection(direction, size, availableOtherDir);
}
return didUse;
if ( !didUse )
return wxDefaultSize;
// Recalculate the min size now that items had a chance to adjust.
return CalcMin();
}
//---------------------------------------------------------------------------
+121 -11
View File
@@ -99,6 +99,36 @@ wxCONSTRUCTOR_6( wxStaticText, wxWindow*, Parent, wxWindowID, Id, \
// wxTextWrapper
// ----------------------------------------------------------------------------
namespace
{
bool IsBreakableWhiteSpace(wxUniChar ch)
{
// We don't take "\r" into account here as it's not supposed to be present
// in the labels and "\n" is not present because Wrap() splits text on it.
switch ( ch.GetValue() )
{
case ' ':
case '\t':
case 0x2000: // en quad
case 0x2001: // em quad
case 0x2002: // en space
case 0x2003: // em space
case 0x2004: // three-per-em space
case 0x2005: // four-per-em space
case 0x2006: // six-per-em space
case 0x2008: // punctuation space
case 0x2009: // thin space
case 0x200A: // hair space
case 0x200B: // zero width space
return true;
}
return false;
}
} // anonymous namespace
void wxTextWrapper::Wrap(wxWindow *win, const wxString& text, int widthMax)
{
const wxInfoDC dc(win);
@@ -143,7 +173,7 @@ void wxTextWrapper::Wrap(wxWindow *win, const wxString& text, int widthMax)
}
// If the overflowing character is a space, we can break right here.
if ( line[posEnd] == ' ' )
if ( IsBreakableWhiteSpace(line[posEnd]) )
{
DoOutputLine(line.substr(0, posEnd));
line = line.substr(posEnd + 1);
@@ -151,20 +181,35 @@ void wxTextWrapper::Wrap(wxWindow *win, const wxString& text, int widthMax)
}
// Find the last word to chop off.
size_t posSpace = line.rfind(' ', posEnd);
if ( posSpace == wxString::npos )
//
// "Word" is defined here as just a sequence of non-space chars.
//
// TODO: Implement real Unicode word break algorithm.
size_t posSpace = posEnd;
for ( ;; posSpace-- )
{
// No spaces, so can't wrap, output until the end of the word
// which is defined here as just a sequence of non-space chars.
//
// TODO: Implement real Unicode word break algorithm.
posSpace = line.find(' ', posEnd);
if ( posSpace == wxString::npos )
if ( posSpace == 0 )
{
// No more spaces at all, output the rest of the line.
DoOutputLine(line);
// No spaces, so can't wrap, output until the end of the word.
posSpace = posEnd;
for ( ;; )
{
if ( ++posSpace == line.length() )
{
// No more spaces at all, output the rest of the line.
DoOutputLine(line);
return;
}
if ( IsBreakableWhiteSpace(line[posSpace]) )
break;
}
break;
}
if ( IsBreakableWhiteSpace(line[posSpace]) )
break;
}
// Output the part that fits.
@@ -213,8 +258,73 @@ private:
void wxStaticTextBase::Wrap(int width)
{
if (width == m_currentWrap)
return;
m_currentWrap = width;
// Allow for repeated calls to Wrap with different values
if (m_unwrappedLabel.empty())
{
m_unwrappedLabel = GetLabel();
}
else
{
SetLabel( m_unwrappedLabel );
}
wxLabelWrapper wrapper;
wrapper.WrapLabel(this, width);
InvalidateBestSize();
}
wxSize
wxStaticTextBase::GetMinSizeFromKnownDirection(int direction,
int size,
int WXUNUSED(availableOtherDir))
{
if ( !HasFlag(wxST_WRAP) || direction != wxHORIZONTAL )
return wxDefaultSize;
// Wrap at the given width to compute the required size.
const int style = GetWindowStyleFlag();
if ( !(style & wxST_NO_AUTORESIZE) )
SetWindowStyleFlag( style | wxST_NO_AUTORESIZE );
Wrap( size );
if ( !(style & wxST_NO_AUTORESIZE) )
SetWindowStyleFlag( style );
// Now compute the best size for the wrapped label.
int numLines = 0;
int maxLineWidth = 0;
for ( auto line : wxSplit(GetLabel(), '\n', '\0') )
{
const int w = GetTextExtent(line).x;
if ( w > maxLineWidth )
maxLineWidth = w;
++numLines;
}
return wxSize( maxLineWidth, numLines*GetCharHeight() );
}
void wxStaticTextBase::SetWindowStyleFlag(long style)
{
// Check if wxST_WRAP is being cleared.
if ( HasFlag(wxST_WRAP) && !(style & wxST_WRAP) )
{
// And unwrap the label in this case.
if ( m_currentWrap )
{
SetLabel(m_unwrappedLabel);
m_unwrappedLabel.clear();
m_currentWrap = 0;
}
}
wxControl::SetWindowStyleFlag(style);
}
void wxStaticTextBase::AutoResizeIfNecessary()
+13 -6
View File
@@ -854,17 +854,24 @@ wxWindowBase::InformFirstDirection(int direction,
availableOtherDir);
}
wxSize
wxWindowBase::GetMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir)
{
if ( !InformFirstDirection(direction, size, availableOtherDir) )
return wxDefaultSize;
return GetEffectiveMinSize();
}
wxSize wxWindowBase::GetEffectiveMinSize() const
{
// merge the best size with the min size, giving priority to the min size
wxSize min = GetMinSize();
if (min.x == wxDefaultCoord || min.y == wxDefaultCoord)
{
wxSize best = GetBestSize();
if (min.x == wxDefaultCoord) min.x = best.x;
if (min.y == wxDefaultCoord) min.y = best.y;
}
if ( !min.IsFullySpecified() )
min.SetDefaults(GetBestSize());
return min;
}
+48 -51
View File
@@ -72,7 +72,6 @@ wxWrapSizer::wxWrapSizer(int orient, int flags)
m_dirInform(0),
m_availSize(-1),
m_availableOtherDir(0),
m_lastUsed(true),
m_minSizeMinor(0),
m_maxSizeMajor(0),
m_minItemMajor(INT_MAX),
@@ -128,24 +127,6 @@ wxSizer *wxWrapSizer::GetRowSizer(size_t n)
return sizer;
}
bool wxWrapSizer::InformFirstDirection(int direction,
int size,
int availableOtherDir)
{
if ( !direction )
return false;
// Store the values for later use
m_availSize = size;
m_availableOtherDir = availableOtherDir +
(direction == wxHORIZONTAL ? m_calculatedMinSize.y
: m_calculatedMinSize.x);
m_dirInform = direction;
m_lastUsed = false;
return true;
}
void wxWrapSizer::AdjustLastRowItemProp(size_t n, wxSizerItem *itemLast)
{
if ( !itemLast || !(m_flags & wxEXTEND_LAST_ON_EACH_LINE) )
@@ -161,48 +142,64 @@ void wxWrapSizer::AdjustLastRowItemProp(size_t n, wxSizerItem *itemLast)
item->SetUserData(new wxPropChanger(*this, *itemLast));
}
wxSize
wxWrapSizer::CalcMinSizeFromKnownDirection(int direction,
int size,
int availableOtherDir)
{
if ( m_children.empty() )
return wxDefaultSize;
// Store the parameters for use in CalcMin() later.
m_availSize = size;
if ( availableOtherDir == -1 )
{
m_availableOtherDir = -1;
}
else
{
m_availableOtherDir = availableOtherDir +
(direction == wxHORIZONTAL ? m_calculatedMinSize.y
: m_calculatedMinSize.x);
}
m_dirInform = direction;
// We're called to find a min size that uses one dimension maximally and
// the other direction minimally.
//
// There are two different algorithms for doing it, depending on whether
// the first reported size component is the opposite as our own orientation
// (the simpler case) or the same one (more complicated).
if ( m_dirInform == m_orient )
CalcMinFromMajor(m_availSize);
else
CalcMinFromMinor(m_availSize);
return m_calculatedMinSize;
}
wxSize wxWrapSizer::CalcMin()
{
if ( m_children.empty() )
return wxSize();
// We come here to calculate min size in two different situations:
// 1 - Immediately after InformFirstDirection, then we find a min size that
// uses one dimension maximally and the other direction minimally.
// 2 - Ordinary case, get a sensible min size value using the current line
// layout, trying to maintain the possibility to re-arrange lines by
// sizing
// We're called to get a sensible min size value using the current line
// layout, trying to maintain the possibility to re-arrange lines by sizing
if ( !m_lastUsed )
if ( m_availSize > 0 )
{
// Case 1 above: InformFirstDirection() has just been called
m_lastUsed = true;
// There are two different algorithms for finding a useful min size for
// a wrap sizer, depending on whether the first reported size component
// is the opposite as our own orientation (the simpler case) or the same
// one (more complicated).
wxSize szAvail; // Keep track of boundary so we don't overflow
if ( m_dirInform == m_orient )
CalcMinFromMajor(m_availSize);
szAvail = SizeFromMajorMinor(m_availSize, m_availableOtherDir);
else
CalcMinFromMinor(m_availSize);
}
else // Case 2 above: not immediately after InformFirstDirection()
{
if ( m_availSize > 0 )
{
wxSize szAvail; // Keep track of boundary so we don't overflow
if ( m_dirInform == m_orient )
szAvail = SizeFromMajorMinor(m_availSize, m_availableOtherDir);
else
szAvail = SizeFromMajorMinor(m_availableOtherDir, m_availSize);
szAvail = SizeFromMajorMinor(m_availableOtherDir, m_availSize);
CalcMinFittingSize(szAvail);
}
else // Initial calculation, before we have size available to us
{
CalcMaxSingleItemSize();
}
CalcMinFittingSize(szAvail);
}
else // Initial calculation, before we have size available to us
{
CalcMaxSingleItemSize();
}
return m_calculatedMinSize;
+1
View File
@@ -25,6 +25,7 @@ wxStaticTextXmlHandler::wxStaticTextXmlHandler()
: wxXmlResourceHandler()
{
XRC_ADD_STYLE(wxST_NO_AUTORESIZE);
XRC_ADD_STYLE(wxST_WRAP);
XRC_ADD_STYLE(wxALIGN_LEFT);
XRC_ADD_STYLE(wxALIGN_RIGHT);
XRC_ADD_STYLE(wxALIGN_CENTER);
+15
View File
@@ -148,6 +148,21 @@ TEST_CASE("wxTextWrapper::Wrap", "[text]")
}
}
TEST_CASE("wxTextWrapper::WrapUnicodeSpaces", "[text][unicode]")
{
// Check that ZWSP (U+200B) is treated as a breakable space, but NBSP
// (U+00A0) and NNBSP (U+202F) are not.
const wxString text{L"Un\u00a0break\u200bable\u202fspace"};
HardBreakWrapper w;
const auto n = w.Do(text, 1);
INFO("Wrapped text:\n" << w.GetResult() << "\n");
REQUIRE( n == 2 );
CHECK( w.GetLine(0) == wxString{L"Un\u00a0break"} );
CHECK( w.GetLine(1) == wxString{L"able\u202fspace"} );
}
// This pseudo test is disabled by default as it requires the environment
// variables WX_TEST_TEXT_WRAP WX_TEST_TEXT_WIDTH to be defined to test how the
// given text is wrapped.
+14 -3
View File
@@ -33,6 +33,17 @@ TEST_CASE("wxWrapSizer::CalcMin", "[wxWrapSizer]")
wxSize sizeMinExpected;
auto const checkSizeIsExpected = [&](const char* desc)
{
INFO(desc);
CHECK( sizer->CalcMinSizeFromKnownDirection
(
wxHORIZONTAL,
win->GetClientSize().x,
win->GetClientSize().y
) == sizeMinExpected );
};
// With a single child the min size must be the same as child size.
const wxSize sizeChild1 = wxSize(80, 60);
sizeMinExpected = sizeChild1;
@@ -43,7 +54,7 @@ TEST_CASE("wxWrapSizer::CalcMin", "[wxWrapSizer]")
sizer->Add(child1);
win->Layout();
CHECK( sizeMinExpected == sizer->CalcMin() );
checkSizeIsExpected("Single child");
// If both children can fit in the same row, the minimal size of the sizer
// is determined by the sum of their minimal horizontal dimensions and
@@ -58,7 +69,7 @@ TEST_CASE("wxWrapSizer::CalcMin", "[wxWrapSizer]")
sizer->Add(child2);
win->Layout();
CHECK( sizeMinExpected == sizer->CalcMin() );
checkSizeIsExpected("Two children in one row");
// Three children will take at least two rows so the minimal size in
// vertical direction must increase.
@@ -71,7 +82,7 @@ TEST_CASE("wxWrapSizer::CalcMin", "[wxWrapSizer]")
sizer->Add(child3);
win->Layout();
CHECK( sizeMinExpected == sizer->CalcMin() );
checkSizeIsExpected("Three children in two rows");
}
TEST_CASE("wxWrapSizer::CalcMinFromMinor", "[wxWrapSizer]")