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.

@@ -411,6 +411,7 @@ class WXDLLIMPEXP_CORE wxWindowBase : public wxEvtHandler
// minimum size, giving priority to the min size components, and
// returns the results.
virtual wxSize GetEffectiveMinSize() const;
virtual wxSize GetEffectiveMinSizeFirstPass() const { return GetEffectiveMinSize(); }
Copy link
Contributor

Choose a reason for hiding this comment

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

This should really be documented (in a comment here and in interface/wx/window.h) because I just have no idea how to use it right now.

Copy link
Author

Choose a reason for hiding this comment

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

Right, will do

@@ -72,6 +77,12 @@ class WXDLLIMPEXP_CORE wxStaticTextBase : public wxControl
// display.
void UpdateLabel();

// save unwrapped label to allow to call Wrap() several times and
// always starting from the original, unwrapped label
wxString m_unwrappedLabel;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this different from wxControl::m_labelOrig?

Copy link
Author

Choose a reason for hiding this comment

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

I could not figure out of the stripping of markup and/or mnemonics and/or the ellipsis code would alter m_labelOrig. I think m_labelOrig is the before any change, and m_unwrappedLabel is after the other changes, but before wrapping.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think you're right, but is it really worth storing it separately instead of just stripping the other stuff from m_labelOrig?

It's not just about optimizing memory usage but also, in more places we store redundant information, more places we need to remember to update later.

// save unwrapped label to allow to call Wrap() several times and
// always starting from the original, unwrapped label
wxString m_unwrappedLabel;
int m_currentWrap = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the value of this? Wrap width? With 0 meaning "none"? Please comment it too.

Copy link
Author

Choose a reason for hiding this comment

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

0 means no wrapping (will add comment). The current value needs to be saved to prevent uncountable recalculations of the wrapping code during the wxSizer Layout() process (as you probably guessed).

@@ -118,10 +118,13 @@ WrapSizerFrame::WrapSizerFrame()

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.

@@ -859,21 +859,39 @@ wxSize wxWindowBase::GetEffectiveMinSize() const
// 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

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