diff --git a/include/wx/sizer.h b/include/wx/sizer.h index f8676f4df0..90d07ae3e7 100644 --- a/include/wx/sizer.h +++ b/include/wx/sizer.h @@ -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,9 +660,12 @@ public: // this size to really update all the sizer items. virtual wxSize CalcMin() = 0; - // Can be overridden to compute minimal size if it depends on the layout - // direction set by InformFirstDirection(). - virtual wxSize CalcMinUsingLayoutDirection() { return CalcMin(); } + // 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. @@ -709,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)); } @@ -751,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; @@ -993,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; diff --git a/include/wx/stattext.h b/include/wx/stattext.h index b36dc4cd4a..d9bf529ef6 100644 --- a/include/wx/stattext.h +++ b/include/wx/stattext.h @@ -40,15 +40,17 @@ public: // This function will modify the value returned by GetLabel()! void Wrap(int width); - virtual wxSize GetMinSizeUsingLayoutDirection() const override; - - virtual bool - InformFirstDirection(int direction, int size, int availableOtherDir) override; - // 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; diff --git a/include/wx/window.h b/include/wx/window.h index 389616fdfa..389d2010d4 100644 --- a/include/wx/window.h +++ b/include/wx/window.h @@ -480,9 +480,22 @@ public: int GetMaxWidth() const { return GetMaxSize().x; } int GetMaxHeight() const { return GetMaxSize().y; } - // Can be overridden to compute minimal size if it depends on the - // layout direction set by InformFirstDirection(). - virtual wxSize GetMinSizeUsingLayoutDirection() const { return GetMinSize(); } + // 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 diff --git a/include/wx/wrapsizer.h b/include/wx/wrapsizer.h index d99eb0375d..8adb6b14c2 100644 --- a/include/wx/wrapsizer.h +++ b/include/wx/wrapsizer.h @@ -36,13 +36,12 @@ public: // override base class virtual methods virtual wxSize CalcMin() override; - virtual wxSize CalcMinUsingLayoutDirection() 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 diff --git a/interface/wx/sizer.h b/interface/wx/sizer.h index 5955103e1e..2f5b3664ad 100644 --- a/interface/wx/sizer.h +++ b/interface/wx/sizer.h @@ -337,25 +337,44 @@ public: virtual wxSize CalcMin() = 0; /** - This method may be overridden by sizers whose minimal size depends on - the layout direction. + May be overridden by sizers whose minimal size depends on the layout + direction. - It is called after InformFirstDirection(), so the implementation may - use the direction provided to this function to decide which size to - return. + 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. - The typical and almost only example of a sizer needing to override this - function is wxWrapSizer which can stretch in either direction. + 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 method must return the smallest possible size that this sizer can be - shrunk to, e.g. the width of a single control when the major layout - direction is vertical. + The default implementation simply returns wxDefaultSize (after calling + InformFirstDirection() for backward compatibility). - By default, this method simply returns the result of CalcMin(). + @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 CalcMinUsingLayoutDirection() const; + virtual wxSize + CalcMinSizeFromKnownDirection(int direction, + int size, + int availableOtherDir); /** Detaches all children from the sizer. @@ -454,11 +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(). - @see wxWindow::GetMinSizeUsingLayoutDirection(), CalcMinUsingLayoutDirection() + This function shouldn't be used in the new code, please override + CalcMinSizeFromKnownDirection() instead. */ virtual bool InformFirstDirection(int direction, int size, int availableOtherDir); diff --git a/interface/wx/window.h b/interface/wx/window.h index d5d115cf2d..edeb7a2561 100644 --- a/interface/wx/window.h +++ b/interface/wx/window.h @@ -1405,19 +1405,43 @@ public: May be overridden if the control minimal size depends on the layout direction. - This method is called after InformFirstDirection() and so its - implementation can rely on the values passed to that method. + 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. - For example, a multi-line wxStaticText returns the minimum size at - which it can be wrapped when the major layout direction is vertical. + 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 just calls GetMinSize(). + 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 InformFirstDirection(), wxSizer::CalcMinUsingLayoutDirection() + @see wxSizer::CalcMinSizeFromKnownDirection() */ - virtual wxSize GetMinSizeUsingLayoutDirection() const; + virtual wxSize + GetMinSizeFromKnownDirection(int direction, + int size, + int availableOtherDir) const; /** Returns the maximum size of window's client area. @@ -1608,14 +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(). - @see GetMinSizeUsingLayoutDirection(), wxSizer::CalcMinUsingLayoutDirection() + This function shouldn't be used in the new code, please override + GetMinSizeFromKnownDirection() instead. */ virtual bool InformFirstDirection(int direction, diff --git a/src/common/sizer.cpp b/src/common/sizer.cpp index 02be5eef9b..f06688de8f 100644 --- a/src/common/sizer.cpp +++ b/src/common/sizer.cpp @@ -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()->CalcMinUsingLayoutDirection(); + 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->GetMinSizeUsingLayoutDirection(); + 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 @@ -594,10 +612,7 @@ wxSize wxSizerItem::CalcMin() { // Since the size of the window may change during runtime, we // should use the current minimal/best size. - m_minSize = m_window->GetMinSizeUsingLayoutDirection(); - - if ( !m_minSize.IsFullySpecified() ) - m_minSize.SetDefaults(m_window->GetBestSize()); + m_minSize = m_window->GetEffectiveMinSize(); } return GetMinSizeWithBorder(); @@ -1232,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; @@ -2666,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 ) @@ -2684,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(); } //--------------------------------------------------------------------------- diff --git a/src/common/stattextcmn.cpp b/src/common/stattextcmn.cpp index 54c22f48d4..e96af8998c 100644 --- a/src/common/stattextcmn.cpp +++ b/src/common/stattextcmn.cpp @@ -277,11 +277,25 @@ void wxStaticTextBase::Wrap(int width) InvalidateBestSize(); } -wxSize wxStaticTextBase::GetMinSizeUsingLayoutDirection() const +wxSize +wxStaticTextBase::GetMinSizeFromKnownDirection(int direction, + int size, + int WXUNUSED(availableOtherDir)) { - if ( !m_currentWrap ) - return GetMinSize(); + 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') ) @@ -296,34 +310,21 @@ wxSize wxStaticTextBase::GetMinSizeUsingLayoutDirection() const return wxSize( maxLineWidth, numLines*GetCharHeight() ); } -bool wxStaticTextBase::InformFirstDirection(int direction, int size, int WXUNUSED(availableOtherDir)) +void wxStaticTextBase::SetWindowStyleFlag(long style) { - if ( !HasFlag(wxST_WRAP) || direction != wxHORIZONTAL ) + // Check if wxST_WRAP is being cleared. + if ( HasFlag(wxST_WRAP) && !(style & wxST_WRAP) ) { - // If we had wrapped the control before, don't do it any longer. + // And unwrap the label in this case. if ( m_currentWrap ) { SetLabel(m_unwrappedLabel); + m_unwrappedLabel.clear(); m_currentWrap = 0; } - - return false; } - // 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. - - const int style = GetWindowStyleFlag(); - if ( !(style & wxST_NO_AUTORESIZE) ) - SetWindowStyleFlag( style | wxST_NO_AUTORESIZE ); - - Wrap( size ); - - if ( !(style & wxST_NO_AUTORESIZE) ) - SetWindowStyleFlag( style ); - - return true; + wxControl::SetWindowStyleFlag(style); } void wxStaticTextBase::AutoResizeIfNecessary() diff --git a/src/common/wincmn.cpp b/src/common/wincmn.cpp index e24252209c..d90474d164 100644 --- a/src/common/wincmn.cpp +++ b/src/common/wincmn.cpp @@ -854,6 +854,17 @@ 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 diff --git a/src/common/wrapsizer.cpp b/src/common/wrapsizer.cpp index 0e0451a753..378e5667e8 100644 --- a/src/common/wrapsizer.cpp +++ b/src/common/wrapsizer.cpp @@ -127,23 +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; - return true; -} - - void wxWrapSizer::AdjustLastRowItemProp(size_t n, wxSizerItem *itemLast) { if ( !itemLast || !(m_flags & wxEXTEND_LAST_ON_EACH_LINE) ) @@ -159,13 +142,31 @@ void wxWrapSizer::AdjustLastRowItemProp(size_t n, wxSizerItem *itemLast) item->SetUserData(new wxPropChanger(*this, *itemLast)); } -wxSize wxWrapSizer::CalcMinUsingLayoutDirection() +wxSize +wxWrapSizer::CalcMinSizeFromKnownDirection(int direction, + int size, + int availableOtherDir) { if ( m_children.empty() ) - return wxSize(); + return wxDefaultSize; - // We're called after InformFirstDirection() to find a min size that - // uses one dimension maximally and the other direction minimally. + // 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 diff --git a/tests/sizers/wrapsizer.cpp b/tests/sizers/wrapsizer.cpp index 303791306d..2cb74eb5b3 100644 --- a/tests/sizers/wrapsizer.cpp +++ b/tests/sizers/wrapsizer.cpp @@ -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->CalcMinUsingLayoutDirection() ); + 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->CalcMinUsingLayoutDirection() ); + 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->CalcMinUsingLayoutDirection() ); + checkSizeIsExpected("Three children in two rows"); } TEST_CASE("wxWrapSizer::CalcMinFromMinor", "[wxWrapSizer]")