Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

RobertRoeb
Copy link

Changed the wxWrapSizer code from using a variable "m_usedLast" to two different functions
CalcMinFirstPass() and CalcMin() for the two step min size calculation.
Added the same logic and naming to controls using GetEffectiveMinSizeFirstPass()
Implemented this for the generic code in wxStaticTextBase.
Added test to wrapsizer sample.

…to wxWrapSizer

Changed the wxWrapSizer code from using a variable "m_usedLast" to two different functions
  CalcMinFirstPass() and CalcMin() for two step min size calculation.
Added the same logic and naming to controls using GetEffectiveMinSizeFirstPass()
Implemented this for the generic code in wxStaticText
Added test to wrapsizer sample
@RobertRoeb
Copy link
Author

So far only tested on GTK+. Will test on OSX later the week. I would appreciate testing on MSW.

@vadz
Copy link
Contributor

vadz commented Sep 2, 2025

Before looking at it, could you please explain the relationship between this and wxWrapSizer? I mean, they do conceptually similar things, but other than that, there is none, right?

Also, I don't know if we want to do this unconditionally or if some flag is specified. While people probably don't want their labels being truncated, they may not want them to take more than one line too... And the interaction of this with ellipsize flags is not clear at all.

Copy link
Contributor

@vadz vadz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, there are a number of things to fix here, but more importantly I see 2 changes here that should ideally correspond to 2 commits:

  1. Addition of GetEffectiveMinSizeFirstPass() and changing wxWrapSizer to use it.
  2. Changes to wxStaticText.

(1) is probably fine, but it really needs to be documented.

For (2) I'm still not sure if we want to do it unconditionally or depending on a flag. If nothing else, this almost certainly shouldn't override explicit calls to Wrap() in the user code as it, I believe, does now.

Comment on lines 672 to 673
// Can be overidden for two step process, e.g. by wxWrapSizer
virtual wxSize CalcMinFirstPass() { return CalcMin(); }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to explain what the two step process is, because this is really not obvious.

Also, wxWrapSizer doesn't really need it, as it worked before adding it, so this makes things even more confusing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wxWrapSizer called CalcMin() several times, but with different behaviour, but instead of using either a flag or two different calls, it set a flag in wxSizer::InformFirstDirection() which altered the behaviour of CalcMin(). I tried to use CalcMinFirstPass() or CalcMin( bool firstpass = false ); for both wrapsizer and a wrapped control, but there sizers and controls are handled differently in wxSizer::Layout()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wxWrapSizer was, AFAIR, somewhat of a hack and it's great to replace it with something better but this something should be documented/explained.

Maybe documenting it would also give some idea for a better name because, frankly, "first pass" is not great: it doesn't mean anything per se and implies the existence of "second pass" which doesn't actually exist.


sizerMidWrap->Add(chk, wxSizerFlags().Centre().Border());
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please avoid mixing whitespace-only changes with significant ones if possible.

// While wxWrapSizer can only wrap entire controls, a text paragraph
// could theoretically wrap at a few letters, so we start with
// requesting very little space in the first pass
return wxSize( 10, 10 );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we avoid hardcoding magic numbers please? It would make sense to use GetCharWidth() or something else based it.

// merge the best size with the min size, giving priority to the min size
wxSize min = GetMinSize();

const wxStaticText *text = dynamic_cast<const wxStaticText*>(this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this and all the other changes to this file.

@RobertRoeb
Copy link
Author

Before looking at it, could you please explain the relationship between this and wxWrapSizer?
I mean, they do conceptually similar things, but other than that, there is none, right?

The code in wxSizer() always assumed this behaviour for wxWrapSizer and wxStaticText. It just
didn't work until now. This is even documented for probably 10 years:

https://docs.wxwidgets.org/3.2/classwx_window.html#a9fd5b6520c1b30eb8e82bb5d56bc24c0

Also, I don't know if we want to do this unconditionally or if some flag is specified. While
people probably don't want their labels being truncated, they may not want them to take
more than one line too... And the interaction of this with ellipsize flags is not clear at all.

This only happens inside of a vertical sizer, which calls InformsFirstDirection() to for the sole
purpose of wrapping the text. This change makes multiline text usable in sizers and should
not have an effect otherwise.

@vadz
Copy link
Contributor

vadz commented Sep 2, 2025

This only happens inside of a vertical sizer, which calls InformsFirstDirection() to for the sole purpose of wrapping the text.

Wait, does this mean that wrapping still won't work in a wxFlexGridSizer? This makes it significantly less useful and potentially very, very confusing: there are no other examples of controls (AFAIK) that appear differently depending on the sizer they're placed in.

Can we make it work for 2D sizers too?

@RobertRoeb
Copy link
Author

Wait, does this mean that wrapping still won't work
in a wxFlexGridSizer?

It should work there as well. I meant any vertical compartment in any sizer. And with my code it works for controls as well, although I cannot think of any apart from display of text. Well, a text editor or a HTML window, maybe.

Robert Roebling added 3 commits September 7, 2025 14:34
  Use GetCharHeight() and GetCharWidth() as minimal size for wrapping wxStaticText
  Removed accidentally committed debug code
  Added clarifying comments to new wrapping code in wxStaticText
@RobertRoeb
Copy link
Author

I can confirm that the PR works on GTK, OSX and MSW. Thanks for the technical and mental support from VZ for setting git up and making this possible.

@vadz
Copy link
Contributor

vadz commented Sep 7, 2025

Could you please check the CI failures? There are some trivial to fix compile errors, but this also broke wxWrapSizer unit tests.

@RobertRoeb
Copy link
Author

I will look at the compile errors, but doing further test scenarios I encountered a situation that might constitute an unexpected change of behaviour. If you put a vertical wxStaticBoxSizer in a dialog and you add two wxStaticText elements into it, the previous code stretched the wxStaticBoxSizer horizontally until the wxStaticText would fit it using a single line each. Previously, there was no way to make the wxStaticText wrap elegantly if the text was too long - you could just set a hard wrap at a e.g. 200px. But the point is: if your dialog layout relied on an expectation that the text would force the owning sizer to stretch, then that layout will be broken with the new code. Here is my artistic explanation
Before
+--wxStaticBox--------------------------
| This is a long text that stretches the box
| This is short text
| ( ) wxCheckBox
| What ever else here
+----------------------------------------

Now
+--wxStaticBox----------
| This is a long text
| that wraps in the box
| This is short text
| ( ) wxCheckBox
| What ever else here
+------------------------

I need to think if we need a wxST_WRAP_IN_SIZER or a wxWrapStaticText in order not to break anything.

@vadz
Copy link
Contributor

vadz commented Sep 7, 2025

FWIW, I didn't have time to really think about this yet, but since the very beginning I thought we would add a style (wxST_WRAP) or a function (AllowWrap()?) instead of making the new behaviour unconditional.

Robert Roebling added 5 commits September 9, 2025 10:40
  This make it possible to choose between the three wrap modes:
     - Don't wrap at all (and force owning size to stretch out
       to accomodate whole length on a single line)
     - Fixed wrapping when falling wxStaticText::Wrap()
     - Dynamic wrapping, typically with very long text
  Added test for all three modes side by side in the wrapsizer
     sample
  Documented new style flag
@RobertRoeb
Copy link
Author

Added wxST_WRAP, fixed tests and spelling mistakes

Robert Roebling and others added 6 commits September 23, 2025 18:16
 Also added test for this in wrapsizer sample
  std::string::find_last_of does not work with complex
  characters in UTF8 mode AFAIK
They were semantically broken by the recent changes.
Try to make it more clear and correct various typos.
Use wxSize::SetDefaults() instead of reimplementing it to make it more
obvious what happens here.

No real changes.
vadz added 13 commits September 27, 2025 18:56
Rename them to have a name actually meaning something to people not
familiar with the details of the wxSizer layout algorithm who don't even
know that it makes 2 passes.

Also call the new wxWindow function GetMinSizeUsingLayoutDirection(),
without the "Effective" part, as GetEffectiveMinSize() is supposed to be
just a trivial wrapper around GetMinSize() and should have never been
virtual and overridable in the first place, even if it was unfortunately
made so back in a20a357 (Make GetEffectiveMinSize virtual as not
being able to override the method, which the sizer system depends on, is
inflexible, 2009-01-30).
Use multiple lines for the long label to prevent it from making the
contents of the page too wide.

Do make the static box take all available space, if there is any.
The text always fits initially, as the page size is computed using it,
so choosing to ellipsize it has no effect.
Add a checkbox to allow turning on wxST_WRAP.
No real changes, just don't use "dummy text" which doesn't mean anything
even though it has been present since 39bc034 (added support for
ellipsization and markup in wxStaticText (modified patch 1629946),
2007-04-01) and call the flags used for plain text and text with markup
just "flagsText" and "flagsMarkupText" respectively.

This makes the code a bit more understandable.
Don't return ridiculously small width and, even more importantly, don't
return single line height when a wrapped label typically has more than
one line.
This reverts 9d7c647 (Incorporated PR wxWidgets#751 to work with zero width
spaces, 2025-09-23) and 6f03759 (Replaced the code with one that
works in UTF8 mode, 2025-09-23) before reimplementing this in a better
and simpler way.
Break lines on zero width space and other Unicode characters with the
"white space" property, but not non-breaking ones.

Closes wxWidgets#751.
@vadz
Copy link
Contributor

vadz commented Sep 27, 2025

I've done some changes here to make this more or less mergeable. Not counting minor corrections (including those needed to make CI builds pass), they are:

  1. Rename the new functions to make them make at least some sense to wxWidgets users.
  2. Actually made wxStaticText wrapping work in normal use, which wasn't at all the case before because GetMinSizeUsingLayoutDirection() always returned enough vertical space for just one line.
  3. Added demonstration of this to the widgets sample where you can actually see how it works instead of seeing... something? I am still not sure what... in the wrap sizer sample (BTW, I'd like to revert all changes to it, I still don't see what they have to do with wrap sizer and absolutely nobody would think to look for wxStaticText wrapping there).
  4. Provided more reasonable support for Unicode spaces in wxTextWrapper.

I'm still unhappy about several things but I don't have time to work on them myself:

  1. Current API for layout direction-dependent layout is really bad, you have to override 2 functions (InformFirstDirection() and GetMinSizeUsingLayoutDirection()) and store the parameters of the first one in order to use them in the second one. This is clearly suboptinal and we should, ideally, have something superseding them both, i.e. providing them the size available in the minor layout direction and returning the actual size required. This would make things much simpler and make more sense and I think we could make this backwards compatible by just calling InformFirstDirection() and then GetMinSize() from the new function. I'd really like to do this...
  2. There are no tests for the new behaviour, which is really a pity as it makes me hesitate to make more improvements, such as changing the above.
  3. wxST_WRAP should really work even if the object is not inside the sizer, it looks like it could be implemented by simply rewrapping the string with the current width on resize.
  4. Storing "original" string and "unwrapped" string is necessary now (as the comment I added explains), but this is clearly not ideal. Ideal would be to avoid changing the internal label from Wrap(), i.e. change just the label used by the underlying control (of course, the generic one would have to store it then).

@RobertRoeb
Copy link
Author

Hi again, what is the current plan on this PR? I read your comments that not everything is perfect, but I always get nervous if a PR is waiting in queue. Further improvements could also be in later PR and the current PR does solve the issue it is supposed to solve (and the code was actually planning to solve 15 years ago).

@vadz
Copy link
Contributor

vadz commented Oct 5, 2025

I hoped to work on (1) this weekend but I unfortunately couldn't find time to do it for unrelated reasons. Thinking more about this, we really shouldn't build more bad API on the existing bad InformFirstDirection() and adding a new GetMinSizeForKnownSizeInDirection() or maybe, following GTK, GetMinWidthForHeight() and GetMinHeightForWidth(), seems like a much better idea so I'd really like to do it before merging the new function into master.

@RobertRoeb
Copy link
Author

This is the weekly reminder that the API was good enough between 2008 and 2025 - and maybe can be improved after merging?

@vadz
Copy link
Contributor

vadz commented Oct 11, 2025

This is the weekly reminder that the API was good enough between 2008 and 2025 - and maybe can be improved after merging?

Yes, the existing API is poor but let's at least avoid adding a new function which is equally poor or worse. I'll try to get this a.s.a.p., I was busy with other things the last 2 weeks but hope to have more time for wx again soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants