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.
https://github.com/wxWidgets/wxWidgets/pull/25753
(9 files)
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
So far only tested on GTK+. Will test on OSX later the week. I would appreciate testing on MSW.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
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.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
@vadz commented on this pull request.
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:
GetEffectiveMinSizeFirstPass()
and changing wxWrapSizer
to use it.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.
> + // Can be overidden for two step process, e.g. by wxWrapSizer + virtual wxSize CalcMinFirstPass() { return CalcMin(); }
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.
> @@ -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(); }
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.
> @@ -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;
Is this different from wxControl::m_labelOrig
?
> @@ -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; + int m_currentWrap = 0;
What is the value of this? Wrap width? With 0
meaning "none"? Please comment it too.
In samples/wrapsizer/wrapsizer.cpp:
> @@ -118,10 +118,13 @@ WrapSizerFrame::WrapSizerFrame() sizerMidWrap->Add(chk, wxSizerFlags().Centre().Border()); } -
Please avoid mixing whitespace-only changes with significant ones if possible.
In samples/wrapsizer/wrapsizer.cpp:
> sizerMid->Add(sizerMidWrap, wxSizerFlags(100).Expand()); sizerRoot->Add(sizerMid, wxSizerFlags(100).Expand().Border()); + // A long wxStaticText that wraps like a wxWrapSizer
Really not sure this belongs to this sample anyhow. I'd rather add a button setting a long label to the "widgets" sample.
In src/common/stattextcmn.cpp:
> @@ -213,8 +213,46 @@ class wxLabelWrapper : public wxTextWrapper void wxStaticTextBase::Wrap(int width) { + if (width == m_currentWrap) return;
Please don't do this:
⬇️ Suggested change- if (width == m_currentWrap) return; + if (width == m_currentWrap) + return;
In src/common/stattextcmn.cpp:
> @@ -213,8 +213,46 @@ class wxLabelWrapper : public wxTextWrapper 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.IsNull())
Please use standard functions instead of wx 1.x ones
⬇️ Suggested change- if (m_unwrappedLabel.IsNull()) + if (m_unwrappedLabel.empty())
In src/common/stattextcmn.cpp:
> wxLabelWrapper wrapper; wrapper.WrapLabel(this, width); + InvalidateBestSize(); +} + +wxSize wxStaticTextBase::GetEffectiveMinSizeFirstPass() const +{ + // 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 );
Could we avoid hardcoding magic numbers please? It would make sense to use GetCharWidth()
or something else based it.
In src/common/stattextcmn.cpp:
> + +wxSize wxStaticTextBase::GetEffectiveMinSizeFirstPass() const +{ + // 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 ); +} + +bool wxStaticTextBase::InformFirstDirection(int direction, int size, int WXUNUSED(availableOtherDir)) +{ + // In the second pass, this control has been given "size" amount of + // space in the horizontal direction. Wrap there and report a new + // GetEffectiveMinSize() from then on. + + if (direction != wxHORIZONTAL) return false;⬇️ Suggested change
- if (direction != wxHORIZONTAL) return false; + if (direction != wxHORIZONTAL) + return false;
In src/common/stattextcmn.cpp:
> + // 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 ); +} + +bool wxStaticTextBase::InformFirstDirection(int direction, int size, int WXUNUSED(availableOtherDir)) +{ + // In the second pass, this control has been given "size" amount of + // space in the horizontal direction. Wrap there and report a new + // GetEffectiveMinSize() from then on. + + if (direction != wxHORIZONTAL) return false; + + int style = GetWindowStyleFlag(); + SetWindowStyleFlag( style | wxST_NO_AUTORESIZE );
It looks like we could avoid calling it unnecessarily (and also below):
⬇️ Suggested change- SetWindowStyleFlag( style | wxST_NO_AUTORESIZE ); + if ( !(style & wxST_NO_AUTORESIZE) ) + SetWindowStyleFlag( style | wxST_NO_AUTORESIZE );
> @@ -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);
Please remove this and all the other changes to this file.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
@RobertRoeb commented on this pull request.
> + // Can be overidden for two step process, e.g. by wxWrapSizer + virtual wxSize CalcMinFirstPass() { return CalcMin(); }
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()
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
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.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
@RobertRoeb pushed 1 commit.
—
View it on GitHub or unsubscribe.
You are receiving this because you are subscribed to this thread.
@RobertRoeb commented on this pull request.
> @@ -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(); }
Right, will do
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
@RobertRoeb commented on this pull request.
> @@ -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;
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.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
> @@ -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; + int m_currentWrap = 0;
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).
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
@RobertRoeb pushed 1 commit.
—
View it on GitHub or unsubscribe.
You are receiving this because you are subscribed to this thread.
@RobertRoeb pushed 1 commit.
—
View it on GitHub or unsubscribe.
You are receiving this because you are subscribed to this thread.
@vadz commented on this pull request.
> + // Can be overidden for two step process, e.g. by wxWrapSizer + virtual wxSize CalcMinFirstPass() { return CalcMin(); }
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.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
@vadz commented on this pull request.
> @@ -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;
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.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
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?
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
Wait, does this mean that wrapping still won't work
in awxFlexGridSizer
?
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.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
@RobertRoeb pushed 2 commits.
—
View it on GitHub or unsubscribe.
You are receiving this because you are subscribed to this thread.
@RobertRoeb pushed 1 commit.
—
View it on GitHub or unsubscribe.
You are receiving this because you are subscribed to this thread.
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.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
Could you please check the CI failures? There are some trivial to fix compile errors, but this also broke wxWrapSizer
unit tests.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
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.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
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.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
@RobertRoeb pushed 1 commit.
—
View it on GitHub or unsubscribe.
You are receiving this because you are subscribed to this thread.
@RobertRoeb pushed 1 commit.
—
View it on GitHub or unsubscribe.
You are receiving this because you are subscribed to this thread.
@RobertRoeb pushed 1 commit.
—
View it on GitHub or unsubscribe.
You are receiving this because you are subscribed to this thread.
@RobertRoeb pushed 1 commit.
—
View it on GitHub or unsubscribe.
You are receiving this because you are subscribed to this thread.
@RobertRoeb pushed 1 commit.
You are receiving this because you are subscribed to this thread.
Added wxST_WRAP, fixed tests and spelling mistakes
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
@RobertRoeb pushed 1 commit.
—
View it on GitHub or unsubscribe.
You are receiving this because you are subscribed to this thread.
@RobertRoeb pushed 1 commit.
You are receiving this because you are subscribed to this thread.
@vadz pushed 17 commits.
You are receiving this because you are subscribed to this thread.
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:
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.wxStaticText
wrapping there).wxTextWrapper
.I'm still unhappy about several things but I don't have time to work on them myself:
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...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.Wrap()
, i.e. change just the label used by the underlying control (of course, the generic one would have to store it then).—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
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).
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
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.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.